##// END OF EJS Templates
moved tooltip function from the for loop and from templates to helpers
marcink -
r990:7a1df013 beta
parent child Browse files
Show More
@@ -1,595 +1,605
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 from pygments.formatters import HtmlFormatter
9 from pygments.formatters import HtmlFormatter
10 from pygments import highlight as code_highlight
10 from pygments import highlight as code_highlight
11 from pylons import url, app_globals as g
11 from pylons import url, app_globals as g
12 from pylons.i18n.translation import _, ungettext
12 from pylons.i18n.translation import _, ungettext
13 from vcs.utils.annotate import annotate_highlight
13 from vcs.utils.annotate import annotate_highlight
14 from webhelpers.html import literal, HTML, escape
14 from webhelpers.html import literal, HTML, escape
15 from webhelpers.html.tools import *
15 from webhelpers.html.tools import *
16 from webhelpers.html.builder import make_tag
16 from webhelpers.html.builder import make_tag
17 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
18 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
19 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
20 password, textarea, title, ul, xml_declaration, radio
20 password, textarea, title, ul, xml_declaration, radio
21 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
22 mail_to, strip_links, strip_tags, tag_re
22 mail_to, strip_links, strip_tags, tag_re
23 from webhelpers.number import format_byte_size, format_bit_size
23 from webhelpers.number import format_byte_size, format_bit_size
24 from webhelpers.pylonslib import Flash as _Flash
24 from webhelpers.pylonslib import Flash as _Flash
25 from webhelpers.pylonslib.secure_form import secure_form
25 from webhelpers.pylonslib.secure_form import secure_form
26 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
27 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
28 replace_whitespace, urlify, truncate, wrap_paragraphs
28 replace_whitespace, urlify, truncate, wrap_paragraphs
29 from webhelpers.date import time_ago_in_words
29 from webhelpers.date import time_ago_in_words
30
30
31 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
32 convert_boolean_attrs, NotGiven
32 convert_boolean_attrs, NotGiven
33
33
34 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
35 """Reset button
35 """Reset button
36 """
36 """
37 _set_input_attrs(attrs, type, name, value)
37 _set_input_attrs(attrs, type, name, value)
38 _set_id_attr(attrs, id, name)
38 _set_id_attr(attrs, id, name)
39 convert_boolean_attrs(attrs, ["disabled"])
39 convert_boolean_attrs(attrs, ["disabled"])
40 return HTML.input(**attrs)
40 return HTML.input(**attrs)
41
41
42 reset = _reset
42 reset = _reset
43
43
44
44
45 def get_token():
45 def get_token():
46 """Return the current authentication token, creating one if one doesn't
46 """Return the current authentication token, creating one if one doesn't
47 already exist.
47 already exist.
48 """
48 """
49 token_key = "_authentication_token"
49 token_key = "_authentication_token"
50 from pylons import session
50 from pylons import session
51 if not token_key in session:
51 if not token_key in session:
52 try:
52 try:
53 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
53 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
54 except AttributeError: # Python < 2.4
54 except AttributeError: # Python < 2.4
55 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
55 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
56 session[token_key] = token
56 session[token_key] = token
57 if hasattr(session, 'save'):
57 if hasattr(session, 'save'):
58 session.save()
58 session.save()
59 return session[token_key]
59 return session[token_key]
60
60
61 class _GetError(object):
61 class _GetError(object):
62 """Get error from form_errors, and represent it as span wrapped error
62 """Get error from form_errors, and represent it as span wrapped error
63 message
63 message
64
64
65 :param field_name: field to fetch errors for
65 :param field_name: field to fetch errors for
66 :param form_errors: form errors dict
66 :param form_errors: form errors dict
67 """
67 """
68
68
69 def __call__(self, field_name, form_errors):
69 def __call__(self, field_name, form_errors):
70 tmpl = """<span class="error_msg">%s</span>"""
70 tmpl = """<span class="error_msg">%s</span>"""
71 if form_errors and form_errors.has_key(field_name):
71 if form_errors and form_errors.has_key(field_name):
72 return literal(tmpl % form_errors.get(field_name))
72 return literal(tmpl % form_errors.get(field_name))
73
73
74 get_error = _GetError()
74 get_error = _GetError()
75
75
76 def recursive_replace(str, replace=' '):
76 def recursive_replace(str, replace=' '):
77 """Recursive replace of given sign to just one instance
77 """Recursive replace of given sign to just one instance
78
78
79 :param str: given string
79 :param str: given string
80 :param replace: char to find and replace multiple instances
80 :param replace: char to find and replace multiple instances
81
81
82 Examples::
82 Examples::
83 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
83 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
84 'Mighty-Mighty-Bo-sstones'
84 'Mighty-Mighty-Bo-sstones'
85 """
85 """
86
86
87 if str.find(replace * 2) == -1:
87 if str.find(replace * 2) == -1:
88 return str
88 return str
89 else:
89 else:
90 str = str.replace(replace * 2, replace)
90 str = str.replace(replace * 2, replace)
91 return recursive_replace(str, replace)
91 return recursive_replace(str, replace)
92
92
93 class _ToolTip(object):
93 class _ToolTip(object):
94
94
95 def __call__(self, tooltip_title, trim_at=50):
95 def __call__(self, tooltip_title, trim_at=50):
96 """Special function just to wrap our text into nice formatted
96 """Special function just to wrap our text into nice formatted
97 autowrapped text
97 autowrapped text
98
98
99 :param tooltip_title:
99 :param tooltip_title:
100 """
100 """
101
101
102 return wrap_paragraphs(escape(tooltip_title), trim_at)\
102 return wrap_paragraphs(escape(tooltip_title), trim_at)\
103 .replace('\n', '<br/>')
103 .replace('\n', '<br/>')
104
104
105 def activate(self):
105 def activate(self):
106 """Adds tooltip mechanism to the given Html all tooltips have to have
106 """Adds tooltip mechanism to the given Html all tooltips have to have
107 set class `tooltip` and set attribute `tooltip_title`.
107 set class `tooltip` and set attribute `tooltip_title`.
108 Then a tooltip will be generated based on that. All with yui js tooltip
108 Then a tooltip will be generated based on that. All with yui js tooltip
109 """
109 """
110
110
111 js = '''
111 js = '''
112 YAHOO.util.Event.onDOMReady(function(){
112 YAHOO.util.Event.onDOMReady(function(){
113 function toolTipsId(){
113 function toolTipsId(){
114 var ids = [];
114 var ids = [];
115 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
115 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
116
116
117 for (var i = 0; i < tts.length; i++) {
117 for (var i = 0; i < tts.length; i++) {
118 //if element doesn't not have and id autogenerate one for tooltip
118 //if element doesn't not have and id autogenerate one for tooltip
119
119
120 if (!tts[i].id){
120 if (!tts[i].id){
121 tts[i].id='tt'+i*100;
121 tts[i].id='tt'+i*100;
122 }
122 }
123 ids.push(tts[i].id);
123 ids.push(tts[i].id);
124 }
124 }
125 return ids
125 return ids
126 };
126 };
127 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
127 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
128 context: toolTipsId(),
128 context: toolTipsId(),
129 monitorresize:false,
129 monitorresize:false,
130 xyoffset :[0,0],
130 xyoffset :[0,0],
131 autodismissdelay:300000,
131 autodismissdelay:300000,
132 hidedelay:5,
132 hidedelay:5,
133 showdelay:20,
133 showdelay:20,
134 });
134 });
135
135
136 // Set the text for the tooltip just before we display it. Lazy method
136 // Set the text for the tooltip just before we display it. Lazy method
137 myToolTips.contextTriggerEvent.subscribe(
137 myToolTips.contextTriggerEvent.subscribe(
138 function(type, args) {
138 function(type, args) {
139
139
140 var context = args[0];
140 var context = args[0];
141
141
142 //positioning of tooltip
142 //positioning of tooltip
143 var tt_w = this.element.clientWidth;//tooltip width
143 var tt_w = this.element.clientWidth;//tooltip width
144 var tt_h = this.element.clientHeight;//tooltip height
144 var tt_h = this.element.clientHeight;//tooltip height
145
145
146 var context_w = context.offsetWidth;
146 var context_w = context.offsetWidth;
147 var context_h = context.offsetHeight;
147 var context_h = context.offsetHeight;
148
148
149 var pos_x = YAHOO.util.Dom.getX(context);
149 var pos_x = YAHOO.util.Dom.getX(context);
150 var pos_y = YAHOO.util.Dom.getY(context);
150 var pos_y = YAHOO.util.Dom.getY(context);
151
151
152 var display_strategy = 'right';
152 var display_strategy = 'right';
153 var xy_pos = [0,0];
153 var xy_pos = [0,0];
154 switch (display_strategy){
154 switch (display_strategy){
155
155
156 case 'top':
156 case 'top':
157 var cur_x = (pos_x+context_w/2)-(tt_w/2);
157 var cur_x = (pos_x+context_w/2)-(tt_w/2);
158 var cur_y = (pos_y-tt_h-4);
158 var cur_y = (pos_y-tt_h-4);
159 xy_pos = [cur_x,cur_y];
159 xy_pos = [cur_x,cur_y];
160 break;
160 break;
161 case 'bottom':
161 case 'bottom':
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
163 var cur_y = pos_y+context_h+4;
163 var cur_y = pos_y+context_h+4;
164 xy_pos = [cur_x,cur_y];
164 xy_pos = [cur_x,cur_y];
165 break;
165 break;
166 case 'left':
166 case 'left':
167 var cur_x = (pos_x-tt_w-4);
167 var cur_x = (pos_x-tt_w-4);
168 var cur_y = pos_y-((tt_h/2)-context_h/2);
168 var cur_y = pos_y-((tt_h/2)-context_h/2);
169 xy_pos = [cur_x,cur_y];
169 xy_pos = [cur_x,cur_y];
170 break;
170 break;
171 case 'right':
171 case 'right':
172 var cur_x = (pos_x+context_w+4);
172 var cur_x = (pos_x+context_w+4);
173 var cur_y = pos_y-((tt_h/2)-context_h/2);
173 var cur_y = pos_y-((tt_h/2)-context_h/2);
174 xy_pos = [cur_x,cur_y];
174 xy_pos = [cur_x,cur_y];
175 break;
175 break;
176 default:
176 default:
177 var cur_x = (pos_x+context_w/2)-(tt_w/2);
177 var cur_x = (pos_x+context_w/2)-(tt_w/2);
178 var cur_y = pos_y-tt_h-4;
178 var cur_y = pos_y-tt_h-4;
179 xy_pos = [cur_x,cur_y];
179 xy_pos = [cur_x,cur_y];
180 break;
180 break;
181
181
182 }
182 }
183
183
184 this.cfg.setProperty("xy",xy_pos);
184 this.cfg.setProperty("xy",xy_pos);
185
185
186 });
186 });
187
187
188 //Mouse out
188 //Mouse out
189 myToolTips.contextMouseOutEvent.subscribe(
189 myToolTips.contextMouseOutEvent.subscribe(
190 function(type, args) {
190 function(type, args) {
191 var context = args[0];
191 var context = args[0];
192
192
193 });
193 });
194 });
194 });
195 '''
195 '''
196 return literal(js)
196 return literal(js)
197
197
198 tooltip = _ToolTip()
198 tooltip = _ToolTip()
199
199
200 class _FilesBreadCrumbs(object):
200 class _FilesBreadCrumbs(object):
201
201
202 def __call__(self, repo_name, rev, paths):
202 def __call__(self, repo_name, rev, paths):
203 if isinstance(paths, str):
203 if isinstance(paths, str):
204 paths = paths.decode('utf-8')
204 paths = paths.decode('utf-8')
205 url_l = [link_to(repo_name, url('files_home',
205 url_l = [link_to(repo_name, url('files_home',
206 repo_name=repo_name,
206 repo_name=repo_name,
207 revision=rev, f_path=''))]
207 revision=rev, f_path=''))]
208 paths_l = paths.split('/')
208 paths_l = paths.split('/')
209 for cnt, p in enumerate(paths_l):
209 for cnt, p in enumerate(paths_l):
210 if p != '':
210 if p != '':
211 url_l.append(link_to(p, url('files_home',
211 url_l.append(link_to(p, url('files_home',
212 repo_name=repo_name,
212 repo_name=repo_name,
213 revision=rev,
213 revision=rev,
214 f_path='/'.join(paths_l[:cnt + 1]))))
214 f_path='/'.join(paths_l[:cnt + 1]))))
215
215
216 return literal('/'.join(url_l))
216 return literal('/'.join(url_l))
217
217
218 files_breadcrumbs = _FilesBreadCrumbs()
218 files_breadcrumbs = _FilesBreadCrumbs()
219
219
220 class CodeHtmlFormatter(HtmlFormatter):
220 class CodeHtmlFormatter(HtmlFormatter):
221 """My code Html Formatter for source codes
221 """My code Html Formatter for source codes
222 """
222 """
223
223
224 def wrap(self, source, outfile):
224 def wrap(self, source, outfile):
225 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
225 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
226
226
227 def _wrap_code(self, source):
227 def _wrap_code(self, source):
228 for cnt, it in enumerate(source):
228 for cnt, it in enumerate(source):
229 i, t = it
229 i, t = it
230 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
230 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
231 yield i, t
231 yield i, t
232
232
233 def _wrap_tablelinenos(self, inner):
233 def _wrap_tablelinenos(self, inner):
234 dummyoutfile = StringIO.StringIO()
234 dummyoutfile = StringIO.StringIO()
235 lncount = 0
235 lncount = 0
236 for t, line in inner:
236 for t, line in inner:
237 if t:
237 if t:
238 lncount += 1
238 lncount += 1
239 dummyoutfile.write(line)
239 dummyoutfile.write(line)
240
240
241 fl = self.linenostart
241 fl = self.linenostart
242 mw = len(str(lncount + fl - 1))
242 mw = len(str(lncount + fl - 1))
243 sp = self.linenospecial
243 sp = self.linenospecial
244 st = self.linenostep
244 st = self.linenostep
245 la = self.lineanchors
245 la = self.lineanchors
246 aln = self.anchorlinenos
246 aln = self.anchorlinenos
247 nocls = self.noclasses
247 nocls = self.noclasses
248 if sp:
248 if sp:
249 lines = []
249 lines = []
250
250
251 for i in range(fl, fl + lncount):
251 for i in range(fl, fl + lncount):
252 if i % st == 0:
252 if i % st == 0:
253 if i % sp == 0:
253 if i % sp == 0:
254 if aln:
254 if aln:
255 lines.append('<a href="#%s%d" class="special">%*d</a>' %
255 lines.append('<a href="#%s%d" class="special">%*d</a>' %
256 (la, i, mw, i))
256 (la, i, mw, i))
257 else:
257 else:
258 lines.append('<span class="special">%*d</span>' % (mw, i))
258 lines.append('<span class="special">%*d</span>' % (mw, i))
259 else:
259 else:
260 if aln:
260 if aln:
261 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
261 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
262 else:
262 else:
263 lines.append('%*d' % (mw, i))
263 lines.append('%*d' % (mw, i))
264 else:
264 else:
265 lines.append('')
265 lines.append('')
266 ls = '\n'.join(lines)
266 ls = '\n'.join(lines)
267 else:
267 else:
268 lines = []
268 lines = []
269 for i in range(fl, fl + lncount):
269 for i in range(fl, fl + lncount):
270 if i % st == 0:
270 if i % st == 0:
271 if aln:
271 if aln:
272 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
272 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
273 else:
273 else:
274 lines.append('%*d' % (mw, i))
274 lines.append('%*d' % (mw, i))
275 else:
275 else:
276 lines.append('')
276 lines.append('')
277 ls = '\n'.join(lines)
277 ls = '\n'.join(lines)
278
278
279 # in case you wonder about the seemingly redundant <div> here: since the
279 # in case you wonder about the seemingly redundant <div> here: since the
280 # content in the other cell also is wrapped in a div, some browsers in
280 # content in the other cell also is wrapped in a div, some browsers in
281 # some configurations seem to mess up the formatting...
281 # some configurations seem to mess up the formatting...
282 if nocls:
282 if nocls:
283 yield 0, ('<table class="%stable">' % self.cssclass +
283 yield 0, ('<table class="%stable">' % self.cssclass +
284 '<tr><td><div class="linenodiv" '
284 '<tr><td><div class="linenodiv" '
285 'style="background-color: #f0f0f0; padding-right: 10px">'
285 'style="background-color: #f0f0f0; padding-right: 10px">'
286 '<pre style="line-height: 125%">' +
286 '<pre style="line-height: 125%">' +
287 ls + '</pre></div></td><td class="code">')
287 ls + '</pre></div></td><td class="code">')
288 else:
288 else:
289 yield 0, ('<table class="%stable">' % self.cssclass +
289 yield 0, ('<table class="%stable">' % self.cssclass +
290 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
290 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
291 ls + '</pre></div></td><td class="code">')
291 ls + '</pre></div></td><td class="code">')
292 yield 0, dummyoutfile.getvalue()
292 yield 0, dummyoutfile.getvalue()
293 yield 0, '</td></tr></table>'
293 yield 0, '</td></tr></table>'
294
294
295
295
296 def pygmentize(filenode, **kwargs):
296 def pygmentize(filenode, **kwargs):
297 """pygmentize function using pygments
297 """pygmentize function using pygments
298
298
299 :param filenode:
299 :param filenode:
300 """
300 """
301
301
302 return literal(code_highlight(filenode.content,
302 return literal(code_highlight(filenode.content,
303 filenode.lexer, CodeHtmlFormatter(**kwargs)))
303 filenode.lexer, CodeHtmlFormatter(**kwargs)))
304
304
305 def pygmentize_annotation(filenode, **kwargs):
305 def pygmentize_annotation(filenode, **kwargs):
306 """pygmentize function for annotation
306 """pygmentize function for annotation
307
307
308 :param filenode:
308 :param filenode:
309 """
309 """
310
310
311 color_dict = {}
311 color_dict = {}
312 def gen_color(n=10000):
312 def gen_color(n=10000):
313 """generator for getting n of evenly distributed colors using
313 """generator for getting n of evenly distributed colors using
314 hsv color and golden ratio. It always return same order of colors
314 hsv color and golden ratio. It always return same order of colors
315
315
316 :returns: RGB tuple
316 :returns: RGB tuple
317 """
317 """
318 import colorsys
318 import colorsys
319 golden_ratio = 0.618033988749895
319 golden_ratio = 0.618033988749895
320 h = 0.22717784590367374
320 h = 0.22717784590367374
321
321
322 for c in xrange(n):
322 for c in xrange(n):
323 h += golden_ratio
323 h += golden_ratio
324 h %= 1
324 h %= 1
325 HSV_tuple = [h, 0.95, 0.95]
325 HSV_tuple = [h, 0.95, 0.95]
326 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
326 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
327 yield map(lambda x:str(int(x * 256)), RGB_tuple)
327 yield map(lambda x:str(int(x * 256)), RGB_tuple)
328
328
329 cgenerator = gen_color()
329 cgenerator = gen_color()
330
330
331 def get_color_string(cs):
331 def get_color_string(cs):
332 if color_dict.has_key(cs):
332 if color_dict.has_key(cs):
333 col = color_dict[cs]
333 col = color_dict[cs]
334 else:
334 else:
335 col = color_dict[cs] = cgenerator.next()
335 col = color_dict[cs] = cgenerator.next()
336 return "color: rgb(%s)! important;" % (', '.join(col))
336 return "color: rgb(%s)! important;" % (', '.join(col))
337
337
338 def url_func(changeset):
338 def url_func(changeset):
339 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
339 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
340 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
340 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
341
341
342 tooltip_html = tooltip_html % (changeset.author,
342 tooltip_html = tooltip_html % (changeset.author,
343 changeset.date,
343 changeset.date,
344 tooltip(changeset.message))
344 tooltip(changeset.message))
345 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
345 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
346 short_id(changeset.raw_id))
346 short_id(changeset.raw_id))
347 uri = link_to(
347 uri = link_to(
348 lnk_format,
348 lnk_format,
349 url('changeset_home', repo_name=changeset.repository.name,
349 url('changeset_home', repo_name=changeset.repository.name,
350 revision=changeset.raw_id),
350 revision=changeset.raw_id),
351 style=get_color_string(changeset.raw_id),
351 style=get_color_string(changeset.raw_id),
352 class_='tooltip',
352 class_='tooltip',
353 title=tooltip_html
353 title=tooltip_html
354 )
354 )
355
355
356 uri += '\n'
356 uri += '\n'
357 return uri
357 return uri
358 return literal(annotate_highlight(filenode, url_func, **kwargs))
358 return literal(annotate_highlight(filenode, url_func, **kwargs))
359
359
360 def repo_name_slug(value):
360 def repo_name_slug(value):
361 """Return slug of name of repository
361 """Return slug of name of repository
362 This function is called on each creation/modification
362 This function is called on each creation/modification
363 of repository to prevent bad names in repo
363 of repository to prevent bad names in repo
364 """
364 """
365
365
366 slug = remove_formatting(value)
366 slug = remove_formatting(value)
367 slug = strip_tags(slug)
367 slug = strip_tags(slug)
368
368
369 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
369 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
370 slug = slug.replace(c, '-')
370 slug = slug.replace(c, '-')
371 slug = recursive_replace(slug, '-')
371 slug = recursive_replace(slug, '-')
372 slug = collapse(slug, '-')
372 slug = collapse(slug, '-')
373 return slug
373 return slug
374
374
375 def get_changeset_safe(repo, rev):
375 def get_changeset_safe(repo, rev):
376 from vcs.backends.base import BaseRepository
376 from vcs.backends.base import BaseRepository
377 from vcs.exceptions import RepositoryError
377 from vcs.exceptions import RepositoryError
378 if not isinstance(repo, BaseRepository):
378 if not isinstance(repo, BaseRepository):
379 raise Exception('You must pass an Repository '
379 raise Exception('You must pass an Repository '
380 'object as first argument got %s', type(repo))
380 'object as first argument got %s', type(repo))
381
381
382 try:
382 try:
383 cs = repo.get_changeset(rev)
383 cs = repo.get_changeset(rev)
384 except RepositoryError:
384 except RepositoryError:
385 from rhodecode.lib.utils import EmptyChangeset
385 from rhodecode.lib.utils import EmptyChangeset
386 cs = EmptyChangeset()
386 cs = EmptyChangeset()
387 return cs
387 return cs
388
388
389
389
390 flash = _Flash()
390 flash = _Flash()
391
391
392
392
393 #==============================================================================
393 #==============================================================================
394 # MERCURIAL FILTERS available via h.
394 # MERCURIAL FILTERS available via h.
395 #==============================================================================
395 #==============================================================================
396 from mercurial import util
396 from mercurial import util
397 from mercurial.templatefilters import person as _person
397 from mercurial.templatefilters import person as _person
398
398
399 def _age(curdate):
399 def _age(curdate):
400 """turns a datetime into an age string."""
400 """turns a datetime into an age string."""
401
401
402 if not curdate:
402 if not curdate:
403 return ''
403 return ''
404
404
405 from datetime import timedelta, datetime
405 from datetime import timedelta, datetime
406
406
407 agescales = [("year", 3600 * 24 * 365),
407 agescales = [("year", 3600 * 24 * 365),
408 ("month", 3600 * 24 * 30),
408 ("month", 3600 * 24 * 30),
409 ("day", 3600 * 24),
409 ("day", 3600 * 24),
410 ("hour", 3600),
410 ("hour", 3600),
411 ("minute", 60),
411 ("minute", 60),
412 ("second", 1), ]
412 ("second", 1), ]
413
413
414 age = datetime.now() - curdate
414 age = datetime.now() - curdate
415 age_seconds = (age.days * agescales[2][1]) + age.seconds
415 age_seconds = (age.days * agescales[2][1]) + age.seconds
416 pos = 1
416 pos = 1
417 for scale in agescales:
417 for scale in agescales:
418 if scale[1] <= age_seconds:
418 if scale[1] <= age_seconds:
419 if pos == 6:pos = 5
419 if pos == 6:pos = 5
420 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
420 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
421 pos += 1
421 pos += 1
422
422
423 return _('just now')
423 return _('just now')
424
424
425 age = lambda x:_age(x)
425 age = lambda x:_age(x)
426 capitalize = lambda x: x.capitalize()
426 capitalize = lambda x: x.capitalize()
427 email = util.email
427 email = util.email
428 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
428 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
429 person = lambda x: _person(x)
429 person = lambda x: _person(x)
430 short_id = lambda x: x[:12]
430 short_id = lambda x: x[:12]
431
431
432
432
433 def bool2icon(value):
433 def bool2icon(value):
434 """Returns True/False values represented as small html image of true/false
434 """Returns True/False values represented as small html image of true/false
435 icons
435 icons
436
436
437 :param value: bool value
437 :param value: bool value
438 """
438 """
439
439
440 if value is True:
440 if value is True:
441 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
441 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
442
442
443 if value is False:
443 if value is False:
444 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
444 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
445
445
446 return value
446 return value
447
447
448
448
449 def action_parser(user_log):
449 def action_parser(user_log):
450 """This helper will map the specified string action into translated
450 """This helper will map the specified string action into translated
451 fancy names with icons and links
451 fancy names with icons and links
452
452
453 :param user_log: user log instance
453 :param user_log: user log instance
454 """
454 """
455
455
456 action = user_log.action
456 action = user_log.action
457 action_params = ' '
457 action_params = ' '
458
458
459 x = action.split(':')
459 x = action.split(':')
460
460
461 if len(x) > 1:
461 if len(x) > 1:
462 action, action_params = x
462 action, action_params = x
463
463
464 def get_cs_links():
464 def get_cs_links():
465 revs_limit = 5 #display this amount always
465 revs_limit = 5 #display this amount always
466 revs_top_limit = 50 #show upto this amount of changesets hidden
466 revs_top_limit = 50 #show upto this amount of changesets hidden
467 revs = action_params.split(',')
467 revs = action_params.split(',')
468 repo_name = user_log.repository.repo_name
468 repo_name = user_log.repository.repo_name
469 from rhodecode.model.scm import ScmModel
469 from rhodecode.model.scm import ScmModel
470
470
471 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
471 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
472 rev).message
472 rev).message
473
473
474 cs_links = " " + ', '.join ([link_to(rev,
474 cs_links = " " + ', '.join ([link_to(rev,
475 url('changeset_home',
475 url('changeset_home',
476 repo_name=repo_name,
476 repo_name=repo_name,
477 revision=rev), title=tooltip(message(rev)),
477 revision=rev), title=tooltip(message(rev)),
478 class_='tooltip') for rev in revs[:revs_limit] ])
478 class_='tooltip') for rev in revs[:revs_limit] ])
479 if len(revs) > revs_limit:
479 if len(revs) > revs_limit:
480 uniq_id = revs[0]
480 uniq_id = revs[0]
481 html_tmpl = ('<span> %s '
481 html_tmpl = ('<span> %s '
482 '<a class="show_more" id="_%s" href="#">%s</a> '
482 '<a class="show_more" id="_%s" href="#">%s</a> '
483 '%s</span>')
483 '%s</span>')
484 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
484 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
485 % (len(revs) - revs_limit),
485 % (len(revs) - revs_limit),
486 _('revisions'))
486 _('revisions'))
487
487
488 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
488 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
489 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
489 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
490 url('changeset_home',
490 url('changeset_home',
491 repo_name=repo_name, revision=rev),
491 repo_name=repo_name, revision=rev),
492 title=message(rev), class_='tooltip')
492 title=message(rev), class_='tooltip')
493 for rev in revs[revs_limit:revs_top_limit]]))
493 for rev in revs[revs_limit:revs_top_limit]]))
494
494
495 return cs_links
495 return cs_links
496
496
497 def get_fork_name():
497 def get_fork_name():
498 from rhodecode.model.scm import ScmModel
498 from rhodecode.model.scm import ScmModel
499 repo_name = action_params
499 repo_name = action_params
500 repo = ScmModel().get(repo_name)
500 repo = ScmModel().get(repo_name)
501 if repo is None:
501 if repo is None:
502 return repo_name
502 return repo_name
503 return link_to(action_params, url('summary_home',
503 return link_to(action_params, url('summary_home',
504 repo_name=repo.name,),
504 repo_name=repo.name,),
505 title=repo.dbrepo.description)
505 title=repo.dbrepo.description)
506
506
507 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
507 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
508 'user_created_repo':(_('User [created] repository'), None),
508 'user_created_repo':(_('User [created] repository'), None),
509 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
509 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
510 'user_updated_repo':(_('User [updated] repository'), None),
510 'user_updated_repo':(_('User [updated] repository'), None),
511 'admin_deleted_repo':(_('Admin [delete] repository'), None),
511 'admin_deleted_repo':(_('Admin [delete] repository'), None),
512 'admin_created_repo':(_('Admin [created] repository'), None),
512 'admin_created_repo':(_('Admin [created] repository'), None),
513 'admin_forked_repo':(_('Admin [forked] repository'), None),
513 'admin_forked_repo':(_('Admin [forked] repository'), None),
514 'admin_updated_repo':(_('Admin [updated] repository'), None),
514 'admin_updated_repo':(_('Admin [updated] repository'), None),
515 'push':(_('[Pushed]'), get_cs_links),
515 'push':(_('[Pushed]'), get_cs_links),
516 'pull':(_('[Pulled]'), None),
516 'pull':(_('[Pulled]'), None),
517 'started_following_repo':(_('User [started following] repository'), None),
517 'started_following_repo':(_('User [started following] repository'), None),
518 'stopped_following_repo':(_('User [stopped following] repository'), None),
518 'stopped_following_repo':(_('User [stopped following] repository'), None),
519 }
519 }
520
520
521 action_str = map.get(action, action)
521 action_str = map.get(action, action)
522 action = action_str[0].replace('[', '<span class="journal_highlight">')\
522 action = action_str[0].replace('[', '<span class="journal_highlight">')\
523 .replace(']', '</span>')
523 .replace(']', '</span>')
524 if action_str[1] is not None:
524 if action_str[1] is not None:
525 action = action + " " + action_str[1]()
525 action = action + " " + action_str[1]()
526
526
527 return literal(action)
527 return literal(action)
528
528
529 def action_parser_icon(user_log):
529 def action_parser_icon(user_log):
530 action = user_log.action
530 action = user_log.action
531 action_params = None
531 action_params = None
532 x = action.split(':')
532 x = action.split(':')
533
533
534 if len(x) > 1:
534 if len(x) > 1:
535 action, action_params = x
535 action, action_params = x
536
536
537 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
537 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
538 map = {'user_deleted_repo':'database_delete.png',
538 map = {'user_deleted_repo':'database_delete.png',
539 'user_created_repo':'database_add.png',
539 'user_created_repo':'database_add.png',
540 'user_forked_repo':'arrow_divide.png',
540 'user_forked_repo':'arrow_divide.png',
541 'user_updated_repo':'database_edit.png',
541 'user_updated_repo':'database_edit.png',
542 'admin_deleted_repo':'database_delete.png',
542 'admin_deleted_repo':'database_delete.png',
543 'admin_created_repo':'database_add.png',
543 'admin_created_repo':'database_add.png',
544 'admin_forked_repo':'arrow_divide.png',
544 'admin_forked_repo':'arrow_divide.png',
545 'admin_updated_repo':'database_edit.png',
545 'admin_updated_repo':'database_edit.png',
546 'push':'script_add.png',
546 'push':'script_add.png',
547 'pull':'down_16.png',
547 'pull':'down_16.png',
548 'started_following_repo':'heart_add.png',
548 'started_following_repo':'heart_add.png',
549 'stopped_following_repo':'heart_delete.png',
549 'stopped_following_repo':'heart_delete.png',
550 }
550 }
551 return literal(tmpl % (map.get(action, action), action))
551 return literal(tmpl % (map.get(action, action), action))
552
552
553
553
554 #==============================================================================
554 #==============================================================================
555 # PERMS
555 # PERMS
556 #==============================================================================
556 #==============================================================================
557 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
557 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
558 HasRepoPermissionAny, HasRepoPermissionAll
558 HasRepoPermissionAny, HasRepoPermissionAll
559
559
560 #==============================================================================
560 #==============================================================================
561 # GRAVATAR URL
561 # GRAVATAR URL
562 #==============================================================================
562 #==============================================================================
563 import hashlib
563 import hashlib
564 import urllib
564 import urllib
565 from pylons import request
565 from pylons import request
566
566
567 def gravatar_url(email_address, size=30):
567 def gravatar_url(email_address, size=30):
568 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
568 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
569 default = 'identicon'
569 default = 'identicon'
570 baseurl_nossl = "http://www.gravatar.com/avatar/"
570 baseurl_nossl = "http://www.gravatar.com/avatar/"
571 baseurl_ssl = "https://secure.gravatar.com/avatar/"
571 baseurl_ssl = "https://secure.gravatar.com/avatar/"
572 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
572 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
573
573
574
574
575 # construct the url
575 # construct the url
576 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
576 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
577 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
577 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
578
578
579 return gravatar_url
579 return gravatar_url
580
580
581 def safe_unicode(str):
581 def safe_unicode(str):
582 """safe unicode function. In case of UnicodeDecode error we try to return
582 """safe unicode function. In case of UnicodeDecode error we try to return
583 unicode with errors replace, if this failes we return unicode with
583 unicode with errors replace, if this failes we return unicode with
584 string_escape decoding """
584 string_escape decoding """
585
585
586 try:
586 try:
587 u_str = unicode(str)
587 u_str = unicode(str)
588 except UnicodeDecodeError:
588 except UnicodeDecodeError:
589 try:
589 try:
590 u_str = unicode(str, 'utf-8', 'replace')
590 u_str = unicode(str, 'utf-8', 'replace')
591 except UnicodeDecodeError:
591 except UnicodeDecodeError:
592 #incase we have a decode error just represent as byte string
592 #incase we have a decode error just represent as byte string
593 u_str = unicode(str(str).encode('string_escape'))
593 u_str = unicode(str(str).encode('string_escape'))
594
594
595 return u_str
595 return u_str
596
597 def changed_tooltip(nodes):
598 if nodes:
599 pref = ': <br/> '
600 suf = ''
601 if len(nodes) > 30:
602 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
603 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
604 else:
605 return ': ' + _('No Files')
@@ -1,174 +1,161
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
6 ${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(u'Home',h.url('/'))}
10 ${h.link_to(u'Home',h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <div class="table">
27 <div class="table">
28 % if c.pagination:
28 % if c.pagination:
29 <div id="graph">
29 <div id="graph">
30 <div id="graph_nodes">
30 <div id="graph_nodes">
31 <canvas id="graph_canvas"></canvas>
31 <canvas id="graph_canvas"></canvas>
32 </div>
32 </div>
33 <div id="graph_content">
33 <div id="graph_content">
34 <div class="container_header">
34 <div class="container_header">
35 ${h.form(h.url.current(),method='get')}
35 ${h.form(h.url.current(),method='get')}
36 <div class="info_box">
36 <div class="info_box">
37 <span>${_('Show')}:</span>
37 <span>${_('Show')}:</span>
38 ${h.text('size',size=1,value=c.size)}
38 ${h.text('size',size=1,value=c.size)}
39 <span>${_('revisions')}</span>
39 <span>${_('revisions')}</span>
40 ${h.submit('set',_('set'))}
40 ${h.submit('set',_('set'))}
41
41
42 </div>
42 </div>
43 ${h.end_form()}
43 ${h.end_form()}
44 <div id="rev_range_container" style="display:none"></div>
44 <div id="rev_range_container" style="display:none"></div>
45 </div>
45 </div>
46
46
47 %for cnt,cs in enumerate(c.pagination):
47 %for cnt,cs in enumerate(c.pagination):
48 <div id="chg_${cnt+1}" class="container">
48 <div id="chg_${cnt+1}" class="container">
49 <div class="left">
49 <div class="left">
50 <div class="date">
50 <div class="date">
51 ${h.checkbox(cs.short_id,class_="changeset_range")}
51 ${h.checkbox(cs.short_id,class_="changeset_range")}
52 <span>${_('commit')} ${cs.revision}: ${h.short_id(cs.raw_id)}@${cs.date}</span>
52 <span>${_('commit')} ${cs.revision}: ${h.short_id(cs.raw_id)}@${cs.date}</span>
53 </div>
53 </div>
54 <div class="author">
54 <div class="author">
55 <div class="gravatar">
55 <div class="gravatar">
56 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),20)}"/>
56 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),20)}"/>
57 </div>
57 </div>
58 <span>${h.person(cs.author)}</span><br/>
58 <span>${h.person(cs.author)}</span><br/>
59 <span><a href="mailto:${h.email_or_none(cs.author)}">${h.email_or_none(cs.author)}</a></span><br/>
59 <span><a href="mailto:${h.email_or_none(cs.author)}">${h.email_or_none(cs.author)}</a></span><br/>
60 </div>
60 </div>
61 <div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
61 <div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
62 </div>
62 </div>
63 <div class="right">
63 <div class="right">
64 <div class="changes">
64 <div class="changes">
65
65 <span class="removed tooltip" title="<b>${_('removed')}</b>${h.changed_tooltip(cs.removed)}">${len(cs.removed)}</span>
66 <%
66 <span class="changed tooltip" title="<b>${_('changed')}</b>${h.changed_tooltip(cs.changed)}">${len(cs.changed)}</span>
67 def changed_tooltip(cs):
67 <span class="added tooltip" title="<b>${_('added')}</b>${h.changed_tooltip(cs.added)}">${len(cs.added)}</span>
68 if cs:
69 pref = ': '
70 suf = ''
71 if len(cs) > 30:
72 suf='<br/>'+_(' and %s more') % (len(cs) - 30)
73 return pref+'<br/> '.join([x.path for x in cs[:30]]) + suf
74 else:
75 return ': '+_('No Files')
76 %>
77
78 <span class="removed tooltip" title="${_('removed')}${h.literal(changed_tooltip(cs.removed))}">${len(cs.removed)}</span>
79 <span class="changed tooltip" title="${_('changed')}${h.literal(changed_tooltip(cs.changed))}">${len(cs.changed)}</span>
80 <span class="added tooltip" title="${_('added')}${h.literal(changed_tooltip(cs.added))}">${len(cs.added)}</span>
81 </div>
68 </div>
82 %if len(cs.parents)>1:
69 %if len(cs.parents)>1:
83 <div class="merge">
70 <div class="merge">
84 ${_('merge')}<img alt="merge" src="/images/icons/arrow_join.png"/>
71 ${_('merge')}<img alt="merge" src="/images/icons/arrow_join.png"/>
85 </div>
72 </div>
86 %endif
73 %endif
87 %if cs.parents:
74 %if cs.parents:
88 %for p_cs in reversed(cs.parents):
75 %for p_cs in reversed(cs.parents):
89 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
76 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
90 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
77 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
91 </div>
78 </div>
92 %endfor
79 %endfor
93 %else:
80 %else:
94 <div class="parent">${_('No parents')}</div>
81 <div class="parent">${_('No parents')}</div>
95 %endif
82 %endif
96
83
97 <span class="logtags">
84 <span class="logtags">
98 %if cs.branch:
85 %if cs.branch:
99 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
86 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
100 ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
87 ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
101 %endif
88 %endif
102 %for tag in cs.tags:
89 %for tag in cs.tags:
103 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
90 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
104 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
91 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
105 %endfor
92 %endfor
106 </span>
93 </span>
107 </div>
94 </div>
108 </div>
95 </div>
109
96
110 %endfor
97 %endfor
111 <div class="pagination-wh pagination-left">
98 <div class="pagination-wh pagination-left">
112 ${c.pagination.pager('$link_previous ~2~ $link_next')}
99 ${c.pagination.pager('$link_previous ~2~ $link_next')}
113 </div>
100 </div>
114 </div>
101 </div>
115 </div>
102 </div>
116
103
117 <script type="text/javascript" src="/js/graph.js"></script>
104 <script type="text/javascript" src="/js/graph.js"></script>
118 <script type="text/javascript">
105 <script type="text/javascript">
119 YAHOO.util.Event.onDOMReady(function(){
106 YAHOO.util.Event.onDOMReady(function(){
120
107
121 //Monitor range checkboxes and build a link to changesets
108 //Monitor range checkboxes and build a link to changesets
122 //ranges
109 //ranges
123 var checkboxes = YUD.getElementsByClassName('changeset_range');
110 var checkboxes = YUD.getElementsByClassName('changeset_range');
124 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
111 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
125 YUE.on(checkboxes,'click',function(e){
112 YUE.on(checkboxes,'click',function(e){
126 var checked_checkboxes = [];
113 var checked_checkboxes = [];
127 for (pos in checkboxes){
114 for (pos in checkboxes){
128 if(checkboxes[pos].checked){
115 if(checkboxes[pos].checked){
129 checked_checkboxes.push(checkboxes[pos]);
116 checked_checkboxes.push(checkboxes[pos]);
130 }
117 }
131 }
118 }
132 if(checked_checkboxes.length>1){
119 if(checked_checkboxes.length>1){
133 var rev_end = checked_checkboxes[0].name;
120 var rev_end = checked_checkboxes[0].name;
134 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
121 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
135
122
136 var url = url_tmpl.replace('__REVRANGE__',
123 var url = url_tmpl.replace('__REVRANGE__',
137 rev_start+'...'+rev_end);
124 rev_start+'...'+rev_end);
138
125
139 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
126 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
140 link = link.replace('__S',rev_start);
127 link = link.replace('__S',rev_start);
141 link = link.replace('__E',rev_end);
128 link = link.replace('__E',rev_end);
142 YUD.get('rev_range_container').innerHTML = link;
129 YUD.get('rev_range_container').innerHTML = link;
143 YUD.setStyle('rev_range_container','display','');
130 YUD.setStyle('rev_range_container','display','');
144 }
131 }
145 else{
132 else{
146 YUD.setStyle('rev_range_container','display','none');
133 YUD.setStyle('rev_range_container','display','none');
147
134
148 }
135 }
149 });
136 });
150
137
151 function set_canvas() {
138 function set_canvas() {
152 var c = document.getElementById('graph_nodes');
139 var c = document.getElementById('graph_nodes');
153 var t = document.getElementById('graph_content');
140 var t = document.getElementById('graph_content');
154 canvas = document.getElementById('graph_canvas');
141 canvas = document.getElementById('graph_canvas');
155 var div_h = t.clientHeight;
142 var div_h = t.clientHeight;
156 c.style.height=div_h+'px';
143 c.style.height=div_h+'px';
157 canvas.setAttribute('height',div_h);
144 canvas.setAttribute('height',div_h);
158 canvas.setAttribute('width',160);
145 canvas.setAttribute('width',160);
159 };
146 };
160 set_canvas();
147 set_canvas();
161 var jsdata = ${c.jsdata|n};
148 var jsdata = ${c.jsdata|n};
162 var r = new BranchRenderer();
149 var r = new BranchRenderer();
163 r.render(jsdata);
150 r.render(jsdata);
164
151
165
152
166
153
167 });
154 });
168 </script>
155 </script>
169 %else:
156 %else:
170 ${_('There are no changes yet')}
157 ${_('There are no changes yet')}
171 %endif
158 %endif
172 </div>
159 </div>
173 </div>
160 </div>
174 </%def> No newline at end of file
161 </%def>
General Comments 0
You need to be logged in to leave comments. Login now