##// END OF EJS Templates
hovercards: added commit hovercard for files, and dashboard views.
marcink -
r4032:07c1bd09 default
parent child Browse files
Show More
@@ -0,0 +1,8 b''
1 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
4 <div class="clear-fix">${base.gravatar_with_user(c.commit.author, tooltip=True)}</div>
5 <br/>
6 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a> - ${c.commit.date}
7 <br/><br/>
8 <pre>${h.urlify_commit_message(c.commit.message, c.repo_name)}</pre>
@@ -1,38 +1,37 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2018-2019 RhodeCode GmbH
3 # Copyright (C) 2018-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.config import routing_links
21
20
22
21
23 def includeme(config):
22 def includeme(config):
24
23
25 config.add_route(
24 config.add_route(
26 name='hovercard_user',
25 name='hovercard_user',
27 pattern='/_hovercard/user/{user_id}')
26 pattern='/_hovercard/user/{user_id}')
28
27
29 config.add_route(
28 config.add_route(
30 name='hovercard_user_group',
29 name='hovercard_user_group',
31 pattern='/_hovercard/user_group/{user_group_id}')
30 pattern='/_hovercard/user_group/{user_group_id}')
32
31
33 config.add_route(
32 config.add_route(
34 name='hovercard_commit',
33 name='hovercard_repo_commit',
35 pattern='/_hovercard/commit/{repo_name}/{user_id}')
34 pattern='/_hovercard/commit/{repo_name:.*?[^/]}/{commit_id}', repo_route=True)
36
35
37 # Scan module for configuration decorators.
36 # Scan module for configuration decorators.
38 config.scan('.views', ignore='.tests')
37 config.scan('.views', ignore='.tests')
@@ -1,71 +1,90 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView, RepoAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired,
31 HasRepoPermissionAnyDecorator)
31 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
32 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
32 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.model.db import (
37 from rhodecode.model.db import (
37 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
38 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
38 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.scm import RepoGroupList, RepoList
41 from rhodecode.model.scm import RepoGroupList, RepoList
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user_group import UserGroupModel
43 from rhodecode.model.user_group import UserGroupModel
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 class HoverCardsView(BaseAppView):
48 class HoverCardsView(BaseAppView):
48
49
49 def load_default_context(self):
50 def load_default_context(self):
50 c = self._get_local_tmpl_context()
51 c = self._get_local_tmpl_context()
51 return c
52 return c
52
53
53 @LoginRequired()
54 @LoginRequired()
54 @view_config(
55 @view_config(
55 route_name='hovercard_user', request_method='GET', xhr=True,
56 route_name='hovercard_user', request_method='GET', xhr=True,
56 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
57 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
57 def hovercard_user(self):
58 def hovercard_user(self):
58 c = self.load_default_context()
59 c = self.load_default_context()
59 user_id = self.request.matchdict['user_id']
60 user_id = self.request.matchdict['user_id']
60 c.user = User.get_or_404(user_id)
61 c.user = User.get_or_404(user_id)
61 return self._get_template_context(c)
62 return self._get_template_context(c)
62
63
63 @LoginRequired()
64 @LoginRequired()
64 @view_config(
65 @view_config(
65 route_name='hovercard_user_group', request_method='GET', xhr=True,
66 route_name='hovercard_user_group', request_method='GET', xhr=True,
66 renderer='rhodecode:templates/hovercards/hovercard_user_group.mako')
67 renderer='rhodecode:templates/hovercards/hovercard_user_group.mako')
67 def hovercard_user_group(self):
68 def hovercard_user_group(self):
68 c = self.load_default_context()
69 c = self.load_default_context()
69 user_group_id = self.request.matchdict['user_group_id']
70 user_group_id = self.request.matchdict['user_group_id']
70 c.user_group = UserGroup.get_or_404(user_group_id)
71 c.user_group = UserGroup.get_or_404(user_group_id)
71 return self._get_template_context(c)
72 return self._get_template_context(c)
73
74
75 class HoverCardsRepoView(RepoAppView):
76 def load_default_context(self):
77 c = self._get_local_tmpl_context()
78 return c
79
80 @LoginRequired()
81 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin')
82 @view_config(
83 route_name='hovercard_repo_commit', request_method='GET', xhr=True,
84 renderer='rhodecode:templates/hovercards/hovercard_repo_commit.mako')
85 def hovercard_repo_commit(self):
86 c = self.load_default_context()
87 commit_id = self.request.matchdict['commit_id']
88 pre_load = ['author', 'branch', 'date', 'message']
89 c.commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id, pre_load=pre_load)
90 return self._get_template_context(c)
@@ -1,2089 +1,2088 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers.html import literal, HTML, escape
56 from webhelpers.html import literal, HTML, escape
57 from webhelpers.html.tools import *
57 from webhelpers.html.tools import *
58 from webhelpers.html.builder import make_tag
58 from webhelpers.html.builder import make_tag
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
62 submit, text, password, textarea, title, ul, xml_declaration, radio
62 submit, text, password, textarea, title, ul, xml_declaration, radio
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
67 replace_whitespace, urlify, truncate, wrap_paragraphs
67 replace_whitespace, urlify, truncate, wrap_paragraphs
68 from webhelpers.date import time_ago_in_words
68 from webhelpers.date import time_ago_in_words
69 from webhelpers.paginate import Page as _Page
69 from webhelpers.paginate import Page as _Page
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
72 from webhelpers2.number import format_byte_size
72 from webhelpers2.number import format_byte_size
73
73
74 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.action_parser import action_parser
75 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.ext_json import json
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
79 AttributeDict, safe_int, md5, md5_safe
79 AttributeDict, safe_int, md5, md5_safe
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89
89
90
90
91 log = logging.getLogger(__name__)
91 log = logging.getLogger(__name__)
92
92
93
93
94 DEFAULT_USER = User.DEFAULT_USER
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
96
97
97
98 def asset(path, ver=None, **kwargs):
98 def asset(path, ver=None, **kwargs):
99 """
99 """
100 Helper to generate a static asset file path for rhodecode assets
100 Helper to generate a static asset file path for rhodecode assets
101
101
102 eg. h.asset('images/image.png', ver='3923')
102 eg. h.asset('images/image.png', ver='3923')
103
103
104 :param path: path of asset
104 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
105 :param ver: optional version query param to append as ?ver=
106 """
106 """
107 request = get_current_request()
107 request = get_current_request()
108 query = {}
108 query = {}
109 query.update(kwargs)
109 query.update(kwargs)
110 if ver:
110 if ver:
111 query = {'ver': ver}
111 query = {'ver': ver}
112 return request.static_path(
112 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
113 'rhodecode:public/{}'.format(path), _query=query)
114
114
115
115
116 default_html_escape_table = {
116 default_html_escape_table = {
117 ord('&'): u'&amp;',
117 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
118 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
119 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
120 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
121 ord("'"): u'&#39;',
122 }
122 }
123
123
124
124
125 def html_escape(text, html_escape_table=default_html_escape_table):
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
126 """Produce entities within text."""
127 return text.translate(html_escape_table)
127 return text.translate(html_escape_table)
128
128
129
129
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
131 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
132 Truncate string ``s`` at the first occurrence of ``sub``.
133
133
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
135 """
136 suffix_if_chopped = suffix_if_chopped or ''
136 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
137 pos = s.find(sub)
138 if pos == -1:
138 if pos == -1:
139 return s
139 return s
140
140
141 if inclusive:
141 if inclusive:
142 pos += len(sub)
142 pos += len(sub)
143
143
144 chopped = s[:pos]
144 chopped = s[:pos]
145 left = s[pos:].strip()
145 left = s[pos:].strip()
146
146
147 if left and suffix_if_chopped:
147 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
148 chopped += suffix_if_chopped
149
149
150 return chopped
150 return chopped
151
151
152
152
153 def shorter(text, size=20, prefix=False):
153 def shorter(text, size=20, prefix=False):
154 postfix = '...'
154 postfix = '...'
155 if len(text) > size:
155 if len(text) > size:
156 if prefix:
156 if prefix:
157 # shorten in front
157 # shorten in front
158 return postfix + text[-(size - len(postfix)):]
158 return postfix + text[-(size - len(postfix)):]
159 else:
159 else:
160 return text[:size - len(postfix)] + postfix
160 return text[:size - len(postfix)] + postfix
161 return text
161 return text
162
162
163
163
164 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
164 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
165 """
165 """
166 Reset button
166 Reset button
167 """
167 """
168 _set_input_attrs(attrs, type, name, value)
168 _set_input_attrs(attrs, type, name, value)
169 _set_id_attr(attrs, id, name)
169 _set_id_attr(attrs, id, name)
170 convert_boolean_attrs(attrs, ["disabled"])
170 convert_boolean_attrs(attrs, ["disabled"])
171 return HTML.input(**attrs)
171 return HTML.input(**attrs)
172
172
173 reset = _reset
173 reset = _reset
174 safeid = _make_safe_id_component
174 safeid = _make_safe_id_component
175
175
176
176
177 def branding(name, length=40):
177 def branding(name, length=40):
178 return truncate(name, length, indicator="")
178 return truncate(name, length, indicator="")
179
179
180
180
181 def FID(raw_id, path):
181 def FID(raw_id, path):
182 """
182 """
183 Creates a unique ID for filenode based on it's hash of path and commit
183 Creates a unique ID for filenode based on it's hash of path and commit
184 it's safe to use in urls
184 it's safe to use in urls
185
185
186 :param raw_id:
186 :param raw_id:
187 :param path:
187 :param path:
188 """
188 """
189
189
190 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
190 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
191
191
192
192
193 class _GetError(object):
193 class _GetError(object):
194 """Get error from form_errors, and represent it as span wrapped error
194 """Get error from form_errors, and represent it as span wrapped error
195 message
195 message
196
196
197 :param field_name: field to fetch errors for
197 :param field_name: field to fetch errors for
198 :param form_errors: form errors dict
198 :param form_errors: form errors dict
199 """
199 """
200
200
201 def __call__(self, field_name, form_errors):
201 def __call__(self, field_name, form_errors):
202 tmpl = """<span class="error_msg">%s</span>"""
202 tmpl = """<span class="error_msg">%s</span>"""
203 if form_errors and field_name in form_errors:
203 if form_errors and field_name in form_errors:
204 return literal(tmpl % form_errors.get(field_name))
204 return literal(tmpl % form_errors.get(field_name))
205
205
206
206
207 get_error = _GetError()
207 get_error = _GetError()
208
208
209
209
210 class _ToolTip(object):
210 class _ToolTip(object):
211
211
212 def __call__(self, tooltip_title, trim_at=50):
212 def __call__(self, tooltip_title, trim_at=50):
213 """
213 """
214 Special function just to wrap our text into nice formatted
214 Special function just to wrap our text into nice formatted
215 autowrapped text
215 autowrapped text
216
216
217 :param tooltip_title:
217 :param tooltip_title:
218 """
218 """
219 tooltip_title = escape(tooltip_title)
219 tooltip_title = escape(tooltip_title)
220 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
220 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
221 return tooltip_title
221 return tooltip_title
222
222
223
223
224 tooltip = _ToolTip()
224 tooltip = _ToolTip()
225
225
226 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
226 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
227
227
228
228
229 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
229 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
230 if isinstance(file_path, str):
230 if isinstance(file_path, str):
231 file_path = safe_unicode(file_path)
231 file_path = safe_unicode(file_path)
232
232
233 route_qry = {'at': at_ref} if at_ref else None
233 route_qry = {'at': at_ref} if at_ref else None
234
234
235 # first segment is a `..` link to repo files
235 # first segment is a `..` link to repo files
236 root_name = literal(u'<i class="icon-home"></i>')
236 root_name = literal(u'<i class="icon-home"></i>')
237 url_segments = [
237 url_segments = [
238 link_to(
238 link_to(
239 root_name,
239 root_name,
240 route_path(
240 route_path(
241 'repo_files',
241 'repo_files',
242 repo_name=repo_name,
242 repo_name=repo_name,
243 commit_id=commit_id,
243 commit_id=commit_id,
244 f_path='',
244 f_path='',
245 _query=route_qry),
245 _query=route_qry),
246 )]
246 )]
247
247
248 path_segments = file_path.split('/')
248 path_segments = file_path.split('/')
249 last_cnt = len(path_segments) - 1
249 last_cnt = len(path_segments) - 1
250 for cnt, segment in enumerate(path_segments):
250 for cnt, segment in enumerate(path_segments):
251 if not segment:
251 if not segment:
252 continue
252 continue
253 segment_html = escape(segment)
253 segment_html = escape(segment)
254
254
255 last_item = cnt == last_cnt
255 last_item = cnt == last_cnt
256
256
257 if last_item and linkify_last_item is False:
257 if last_item and linkify_last_item is False:
258 # plain version
258 # plain version
259 url_segments.append(segment_html)
259 url_segments.append(segment_html)
260 else:
260 else:
261 url_segments.append(
261 url_segments.append(
262 link_to(
262 link_to(
263 segment_html,
263 segment_html,
264 route_path(
264 route_path(
265 'repo_files',
265 'repo_files',
266 repo_name=repo_name,
266 repo_name=repo_name,
267 commit_id=commit_id,
267 commit_id=commit_id,
268 f_path='/'.join(path_segments[:cnt + 1]),
268 f_path='/'.join(path_segments[:cnt + 1]),
269 _query=route_qry),
269 _query=route_qry),
270 ))
270 ))
271
271
272 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
272 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
273 if limit_items and len(limited_url_segments) < len(url_segments):
273 if limit_items and len(limited_url_segments) < len(url_segments):
274 url_segments = limited_url_segments
274 url_segments = limited_url_segments
275
275
276 full_path = file_path
276 full_path = file_path
277 icon = files_icon.format(escape(full_path))
277 icon = files_icon.format(escape(full_path))
278 if file_path == '':
278 if file_path == '':
279 return root_name
279 return root_name
280 else:
280 else:
281 return literal(' / '.join(url_segments) + icon)
281 return literal(' / '.join(url_segments) + icon)
282
282
283
283
284 def files_url_data(request):
284 def files_url_data(request):
285 matchdict = request.matchdict
285 matchdict = request.matchdict
286
286
287 if 'f_path' not in matchdict:
287 if 'f_path' not in matchdict:
288 matchdict['f_path'] = ''
288 matchdict['f_path'] = ''
289
289
290 if 'commit_id' not in matchdict:
290 if 'commit_id' not in matchdict:
291 matchdict['commit_id'] = 'tip'
291 matchdict['commit_id'] = 'tip'
292
292
293 return json.dumps(matchdict)
293 return json.dumps(matchdict)
294
294
295
295
296 def code_highlight(code, lexer, formatter, use_hl_filter=False):
296 def code_highlight(code, lexer, formatter, use_hl_filter=False):
297 """
297 """
298 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
298 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
299
299
300 If ``outfile`` is given and a valid file object (an object
300 If ``outfile`` is given and a valid file object (an object
301 with a ``write`` method), the result will be written to it, otherwise
301 with a ``write`` method), the result will be written to it, otherwise
302 it is returned as a string.
302 it is returned as a string.
303 """
303 """
304 if use_hl_filter:
304 if use_hl_filter:
305 # add HL filter
305 # add HL filter
306 from rhodecode.lib.index import search_utils
306 from rhodecode.lib.index import search_utils
307 lexer.add_filter(search_utils.ElasticSearchHLFilter())
307 lexer.add_filter(search_utils.ElasticSearchHLFilter())
308 return pygments.format(pygments.lex(code, lexer), formatter)
308 return pygments.format(pygments.lex(code, lexer), formatter)
309
309
310
310
311 class CodeHtmlFormatter(HtmlFormatter):
311 class CodeHtmlFormatter(HtmlFormatter):
312 """
312 """
313 My code Html Formatter for source codes
313 My code Html Formatter for source codes
314 """
314 """
315
315
316 def wrap(self, source, outfile):
316 def wrap(self, source, outfile):
317 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
317 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
318
318
319 def _wrap_code(self, source):
319 def _wrap_code(self, source):
320 for cnt, it in enumerate(source):
320 for cnt, it in enumerate(source):
321 i, t = it
321 i, t = it
322 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
322 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
323 yield i, t
323 yield i, t
324
324
325 def _wrap_tablelinenos(self, inner):
325 def _wrap_tablelinenos(self, inner):
326 dummyoutfile = StringIO.StringIO()
326 dummyoutfile = StringIO.StringIO()
327 lncount = 0
327 lncount = 0
328 for t, line in inner:
328 for t, line in inner:
329 if t:
329 if t:
330 lncount += 1
330 lncount += 1
331 dummyoutfile.write(line)
331 dummyoutfile.write(line)
332
332
333 fl = self.linenostart
333 fl = self.linenostart
334 mw = len(str(lncount + fl - 1))
334 mw = len(str(lncount + fl - 1))
335 sp = self.linenospecial
335 sp = self.linenospecial
336 st = self.linenostep
336 st = self.linenostep
337 la = self.lineanchors
337 la = self.lineanchors
338 aln = self.anchorlinenos
338 aln = self.anchorlinenos
339 nocls = self.noclasses
339 nocls = self.noclasses
340 if sp:
340 if sp:
341 lines = []
341 lines = []
342
342
343 for i in range(fl, fl + lncount):
343 for i in range(fl, fl + lncount):
344 if i % st == 0:
344 if i % st == 0:
345 if i % sp == 0:
345 if i % sp == 0:
346 if aln:
346 if aln:
347 lines.append('<a href="#%s%d" class="special">%*d</a>' %
347 lines.append('<a href="#%s%d" class="special">%*d</a>' %
348 (la, i, mw, i))
348 (la, i, mw, i))
349 else:
349 else:
350 lines.append('<span class="special">%*d</span>' % (mw, i))
350 lines.append('<span class="special">%*d</span>' % (mw, i))
351 else:
351 else:
352 if aln:
352 if aln:
353 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
353 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
354 else:
354 else:
355 lines.append('%*d' % (mw, i))
355 lines.append('%*d' % (mw, i))
356 else:
356 else:
357 lines.append('')
357 lines.append('')
358 ls = '\n'.join(lines)
358 ls = '\n'.join(lines)
359 else:
359 else:
360 lines = []
360 lines = []
361 for i in range(fl, fl + lncount):
361 for i in range(fl, fl + lncount):
362 if i % st == 0:
362 if i % st == 0:
363 if aln:
363 if aln:
364 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
364 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
365 else:
365 else:
366 lines.append('%*d' % (mw, i))
366 lines.append('%*d' % (mw, i))
367 else:
367 else:
368 lines.append('')
368 lines.append('')
369 ls = '\n'.join(lines)
369 ls = '\n'.join(lines)
370
370
371 # in case you wonder about the seemingly redundant <div> here: since the
371 # in case you wonder about the seemingly redundant <div> here: since the
372 # content in the other cell also is wrapped in a div, some browsers in
372 # content in the other cell also is wrapped in a div, some browsers in
373 # some configurations seem to mess up the formatting...
373 # some configurations seem to mess up the formatting...
374 if nocls:
374 if nocls:
375 yield 0, ('<table class="%stable">' % self.cssclass +
375 yield 0, ('<table class="%stable">' % self.cssclass +
376 '<tr><td><div class="linenodiv" '
376 '<tr><td><div class="linenodiv" '
377 'style="background-color: #f0f0f0; padding-right: 10px">'
377 'style="background-color: #f0f0f0; padding-right: 10px">'
378 '<pre style="line-height: 125%">' +
378 '<pre style="line-height: 125%">' +
379 ls + '</pre></div></td><td id="hlcode" class="code">')
379 ls + '</pre></div></td><td id="hlcode" class="code">')
380 else:
380 else:
381 yield 0, ('<table class="%stable">' % self.cssclass +
381 yield 0, ('<table class="%stable">' % self.cssclass +
382 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
382 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
383 ls + '</pre></div></td><td id="hlcode" class="code">')
383 ls + '</pre></div></td><td id="hlcode" class="code">')
384 yield 0, dummyoutfile.getvalue()
384 yield 0, dummyoutfile.getvalue()
385 yield 0, '</td></tr></table>'
385 yield 0, '</td></tr></table>'
386
386
387
387
388 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
388 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
389 def __init__(self, **kw):
389 def __init__(self, **kw):
390 # only show these line numbers if set
390 # only show these line numbers if set
391 self.only_lines = kw.pop('only_line_numbers', [])
391 self.only_lines = kw.pop('only_line_numbers', [])
392 self.query_terms = kw.pop('query_terms', [])
392 self.query_terms = kw.pop('query_terms', [])
393 self.max_lines = kw.pop('max_lines', 5)
393 self.max_lines = kw.pop('max_lines', 5)
394 self.line_context = kw.pop('line_context', 3)
394 self.line_context = kw.pop('line_context', 3)
395 self.url = kw.pop('url', None)
395 self.url = kw.pop('url', None)
396
396
397 super(CodeHtmlFormatter, self).__init__(**kw)
397 super(CodeHtmlFormatter, self).__init__(**kw)
398
398
399 def _wrap_code(self, source):
399 def _wrap_code(self, source):
400 for cnt, it in enumerate(source):
400 for cnt, it in enumerate(source):
401 i, t = it
401 i, t = it
402 t = '<pre>%s</pre>' % t
402 t = '<pre>%s</pre>' % t
403 yield i, t
403 yield i, t
404
404
405 def _wrap_tablelinenos(self, inner):
405 def _wrap_tablelinenos(self, inner):
406 yield 0, '<table class="code-highlight %stable">' % self.cssclass
406 yield 0, '<table class="code-highlight %stable">' % self.cssclass
407
407
408 last_shown_line_number = 0
408 last_shown_line_number = 0
409 current_line_number = 1
409 current_line_number = 1
410
410
411 for t, line in inner:
411 for t, line in inner:
412 if not t:
412 if not t:
413 yield t, line
413 yield t, line
414 continue
414 continue
415
415
416 if current_line_number in self.only_lines:
416 if current_line_number in self.only_lines:
417 if last_shown_line_number + 1 != current_line_number:
417 if last_shown_line_number + 1 != current_line_number:
418 yield 0, '<tr>'
418 yield 0, '<tr>'
419 yield 0, '<td class="line">...</td>'
419 yield 0, '<td class="line">...</td>'
420 yield 0, '<td id="hlcode" class="code"></td>'
420 yield 0, '<td id="hlcode" class="code"></td>'
421 yield 0, '</tr>'
421 yield 0, '</tr>'
422
422
423 yield 0, '<tr>'
423 yield 0, '<tr>'
424 if self.url:
424 if self.url:
425 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
425 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
426 self.url, current_line_number, current_line_number)
426 self.url, current_line_number, current_line_number)
427 else:
427 else:
428 yield 0, '<td class="line"><a href="">%i</a></td>' % (
428 yield 0, '<td class="line"><a href="">%i</a></td>' % (
429 current_line_number)
429 current_line_number)
430 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
430 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
431 yield 0, '</tr>'
431 yield 0, '</tr>'
432
432
433 last_shown_line_number = current_line_number
433 last_shown_line_number = current_line_number
434
434
435 current_line_number += 1
435 current_line_number += 1
436
436
437 yield 0, '</table>'
437 yield 0, '</table>'
438
438
439
439
440 def hsv_to_rgb(h, s, v):
440 def hsv_to_rgb(h, s, v):
441 """ Convert hsv color values to rgb """
441 """ Convert hsv color values to rgb """
442
442
443 if s == 0.0:
443 if s == 0.0:
444 return v, v, v
444 return v, v, v
445 i = int(h * 6.0) # XXX assume int() truncates!
445 i = int(h * 6.0) # XXX assume int() truncates!
446 f = (h * 6.0) - i
446 f = (h * 6.0) - i
447 p = v * (1.0 - s)
447 p = v * (1.0 - s)
448 q = v * (1.0 - s * f)
448 q = v * (1.0 - s * f)
449 t = v * (1.0 - s * (1.0 - f))
449 t = v * (1.0 - s * (1.0 - f))
450 i = i % 6
450 i = i % 6
451 if i == 0:
451 if i == 0:
452 return v, t, p
452 return v, t, p
453 if i == 1:
453 if i == 1:
454 return q, v, p
454 return q, v, p
455 if i == 2:
455 if i == 2:
456 return p, v, t
456 return p, v, t
457 if i == 3:
457 if i == 3:
458 return p, q, v
458 return p, q, v
459 if i == 4:
459 if i == 4:
460 return t, p, v
460 return t, p, v
461 if i == 5:
461 if i == 5:
462 return v, p, q
462 return v, p, q
463
463
464
464
465 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
465 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
466 """
466 """
467 Generator for getting n of evenly distributed colors using
467 Generator for getting n of evenly distributed colors using
468 hsv color and golden ratio. It always return same order of colors
468 hsv color and golden ratio. It always return same order of colors
469
469
470 :param n: number of colors to generate
470 :param n: number of colors to generate
471 :param saturation: saturation of returned colors
471 :param saturation: saturation of returned colors
472 :param lightness: lightness of returned colors
472 :param lightness: lightness of returned colors
473 :returns: RGB tuple
473 :returns: RGB tuple
474 """
474 """
475
475
476 golden_ratio = 0.618033988749895
476 golden_ratio = 0.618033988749895
477 h = 0.22717784590367374
477 h = 0.22717784590367374
478
478
479 for _ in xrange(n):
479 for _ in xrange(n):
480 h += golden_ratio
480 h += golden_ratio
481 h %= 1
481 h %= 1
482 HSV_tuple = [h, saturation, lightness]
482 HSV_tuple = [h, saturation, lightness]
483 RGB_tuple = hsv_to_rgb(*HSV_tuple)
483 RGB_tuple = hsv_to_rgb(*HSV_tuple)
484 yield map(lambda x: str(int(x * 256)), RGB_tuple)
484 yield map(lambda x: str(int(x * 256)), RGB_tuple)
485
485
486
486
487 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
487 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
488 """
488 """
489 Returns a function which when called with an argument returns a unique
489 Returns a function which when called with an argument returns a unique
490 color for that argument, eg.
490 color for that argument, eg.
491
491
492 :param n: number of colors to generate
492 :param n: number of colors to generate
493 :param saturation: saturation of returned colors
493 :param saturation: saturation of returned colors
494 :param lightness: lightness of returned colors
494 :param lightness: lightness of returned colors
495 :returns: css RGB string
495 :returns: css RGB string
496
496
497 >>> color_hash = color_hasher()
497 >>> color_hash = color_hasher()
498 >>> color_hash('hello')
498 >>> color_hash('hello')
499 'rgb(34, 12, 59)'
499 'rgb(34, 12, 59)'
500 >>> color_hash('hello')
500 >>> color_hash('hello')
501 'rgb(34, 12, 59)'
501 'rgb(34, 12, 59)'
502 >>> color_hash('other')
502 >>> color_hash('other')
503 'rgb(90, 224, 159)'
503 'rgb(90, 224, 159)'
504 """
504 """
505
505
506 color_dict = {}
506 color_dict = {}
507 cgenerator = unique_color_generator(
507 cgenerator = unique_color_generator(
508 saturation=saturation, lightness=lightness)
508 saturation=saturation, lightness=lightness)
509
509
510 def get_color_string(thing):
510 def get_color_string(thing):
511 if thing in color_dict:
511 if thing in color_dict:
512 col = color_dict[thing]
512 col = color_dict[thing]
513 else:
513 else:
514 col = color_dict[thing] = cgenerator.next()
514 col = color_dict[thing] = cgenerator.next()
515 return "rgb(%s)" % (', '.join(col))
515 return "rgb(%s)" % (', '.join(col))
516
516
517 return get_color_string
517 return get_color_string
518
518
519
519
520 def get_lexer_safe(mimetype=None, filepath=None):
520 def get_lexer_safe(mimetype=None, filepath=None):
521 """
521 """
522 Tries to return a relevant pygments lexer using mimetype/filepath name,
522 Tries to return a relevant pygments lexer using mimetype/filepath name,
523 defaulting to plain text if none could be found
523 defaulting to plain text if none could be found
524 """
524 """
525 lexer = None
525 lexer = None
526 try:
526 try:
527 if mimetype:
527 if mimetype:
528 lexer = get_lexer_for_mimetype(mimetype)
528 lexer = get_lexer_for_mimetype(mimetype)
529 if not lexer:
529 if not lexer:
530 lexer = get_lexer_for_filename(filepath)
530 lexer = get_lexer_for_filename(filepath)
531 except pygments.util.ClassNotFound:
531 except pygments.util.ClassNotFound:
532 pass
532 pass
533
533
534 if not lexer:
534 if not lexer:
535 lexer = get_lexer_by_name('text')
535 lexer = get_lexer_by_name('text')
536
536
537 return lexer
537 return lexer
538
538
539
539
540 def get_lexer_for_filenode(filenode):
540 def get_lexer_for_filenode(filenode):
541 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
541 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
542 return lexer
542 return lexer
543
543
544
544
545 def pygmentize(filenode, **kwargs):
545 def pygmentize(filenode, **kwargs):
546 """
546 """
547 pygmentize function using pygments
547 pygmentize function using pygments
548
548
549 :param filenode:
549 :param filenode:
550 """
550 """
551 lexer = get_lexer_for_filenode(filenode)
551 lexer = get_lexer_for_filenode(filenode)
552 return literal(code_highlight(filenode.content, lexer,
552 return literal(code_highlight(filenode.content, lexer,
553 CodeHtmlFormatter(**kwargs)))
553 CodeHtmlFormatter(**kwargs)))
554
554
555
555
556 def is_following_repo(repo_name, user_id):
556 def is_following_repo(repo_name, user_id):
557 from rhodecode.model.scm import ScmModel
557 from rhodecode.model.scm import ScmModel
558 return ScmModel().is_following_repo(repo_name, user_id)
558 return ScmModel().is_following_repo(repo_name, user_id)
559
559
560
560
561 class _Message(object):
561 class _Message(object):
562 """A message returned by ``Flash.pop_messages()``.
562 """A message returned by ``Flash.pop_messages()``.
563
563
564 Converting the message to a string returns the message text. Instances
564 Converting the message to a string returns the message text. Instances
565 also have the following attributes:
565 also have the following attributes:
566
566
567 * ``message``: the message text.
567 * ``message``: the message text.
568 * ``category``: the category specified when the message was created.
568 * ``category``: the category specified when the message was created.
569 """
569 """
570
570
571 def __init__(self, category, message):
571 def __init__(self, category, message):
572 self.category = category
572 self.category = category
573 self.message = message
573 self.message = message
574
574
575 def __str__(self):
575 def __str__(self):
576 return self.message
576 return self.message
577
577
578 __unicode__ = __str__
578 __unicode__ = __str__
579
579
580 def __html__(self):
580 def __html__(self):
581 return escape(safe_unicode(self.message))
581 return escape(safe_unicode(self.message))
582
582
583
583
584 class Flash(object):
584 class Flash(object):
585 # List of allowed categories. If None, allow any category.
585 # List of allowed categories. If None, allow any category.
586 categories = ["warning", "notice", "error", "success"]
586 categories = ["warning", "notice", "error", "success"]
587
587
588 # Default category if none is specified.
588 # Default category if none is specified.
589 default_category = "notice"
589 default_category = "notice"
590
590
591 def __init__(self, session_key="flash", categories=None,
591 def __init__(self, session_key="flash", categories=None,
592 default_category=None):
592 default_category=None):
593 """
593 """
594 Instantiate a ``Flash`` object.
594 Instantiate a ``Flash`` object.
595
595
596 ``session_key`` is the key to save the messages under in the user's
596 ``session_key`` is the key to save the messages under in the user's
597 session.
597 session.
598
598
599 ``categories`` is an optional list which overrides the default list
599 ``categories`` is an optional list which overrides the default list
600 of categories.
600 of categories.
601
601
602 ``default_category`` overrides the default category used for messages
602 ``default_category`` overrides the default category used for messages
603 when none is specified.
603 when none is specified.
604 """
604 """
605 self.session_key = session_key
605 self.session_key = session_key
606 if categories is not None:
606 if categories is not None:
607 self.categories = categories
607 self.categories = categories
608 if default_category is not None:
608 if default_category is not None:
609 self.default_category = default_category
609 self.default_category = default_category
610 if self.categories and self.default_category not in self.categories:
610 if self.categories and self.default_category not in self.categories:
611 raise ValueError(
611 raise ValueError(
612 "unrecognized default category %r" % (self.default_category,))
612 "unrecognized default category %r" % (self.default_category,))
613
613
614 def pop_messages(self, session=None, request=None):
614 def pop_messages(self, session=None, request=None):
615 """
615 """
616 Return all accumulated messages and delete them from the session.
616 Return all accumulated messages and delete them from the session.
617
617
618 The return value is a list of ``Message`` objects.
618 The return value is a list of ``Message`` objects.
619 """
619 """
620 messages = []
620 messages = []
621
621
622 if not session:
622 if not session:
623 if not request:
623 if not request:
624 request = get_current_request()
624 request = get_current_request()
625 session = request.session
625 session = request.session
626
626
627 # Pop the 'old' pylons flash messages. They are tuples of the form
627 # Pop the 'old' pylons flash messages. They are tuples of the form
628 # (category, message)
628 # (category, message)
629 for cat, msg in session.pop(self.session_key, []):
629 for cat, msg in session.pop(self.session_key, []):
630 messages.append(_Message(cat, msg))
630 messages.append(_Message(cat, msg))
631
631
632 # Pop the 'new' pyramid flash messages for each category as list
632 # Pop the 'new' pyramid flash messages for each category as list
633 # of strings.
633 # of strings.
634 for cat in self.categories:
634 for cat in self.categories:
635 for msg in session.pop_flash(queue=cat):
635 for msg in session.pop_flash(queue=cat):
636 messages.append(_Message(cat, msg))
636 messages.append(_Message(cat, msg))
637 # Map messages from the default queue to the 'notice' category.
637 # Map messages from the default queue to the 'notice' category.
638 for msg in session.pop_flash():
638 for msg in session.pop_flash():
639 messages.append(_Message('notice', msg))
639 messages.append(_Message('notice', msg))
640
640
641 session.save()
641 session.save()
642 return messages
642 return messages
643
643
644 def json_alerts(self, session=None, request=None):
644 def json_alerts(self, session=None, request=None):
645 payloads = []
645 payloads = []
646 messages = flash.pop_messages(session=session, request=request)
646 messages = flash.pop_messages(session=session, request=request)
647 if messages:
647 if messages:
648 for message in messages:
648 for message in messages:
649 subdata = {}
649 subdata = {}
650 if hasattr(message.message, 'rsplit'):
650 if hasattr(message.message, 'rsplit'):
651 flash_data = message.message.rsplit('|DELIM|', 1)
651 flash_data = message.message.rsplit('|DELIM|', 1)
652 org_message = flash_data[0]
652 org_message = flash_data[0]
653 if len(flash_data) > 1:
653 if len(flash_data) > 1:
654 subdata = json.loads(flash_data[1])
654 subdata = json.loads(flash_data[1])
655 else:
655 else:
656 org_message = message.message
656 org_message = message.message
657 payloads.append({
657 payloads.append({
658 'message': {
658 'message': {
659 'message': u'{}'.format(org_message),
659 'message': u'{}'.format(org_message),
660 'level': message.category,
660 'level': message.category,
661 'force': True,
661 'force': True,
662 'subdata': subdata
662 'subdata': subdata
663 }
663 }
664 })
664 })
665 return json.dumps(payloads)
665 return json.dumps(payloads)
666
666
667 def __call__(self, message, category=None, ignore_duplicate=True,
667 def __call__(self, message, category=None, ignore_duplicate=True,
668 session=None, request=None):
668 session=None, request=None):
669
669
670 if not session:
670 if not session:
671 if not request:
671 if not request:
672 request = get_current_request()
672 request = get_current_request()
673 session = request.session
673 session = request.session
674
674
675 session.flash(
675 session.flash(
676 message, queue=category, allow_duplicate=not ignore_duplicate)
676 message, queue=category, allow_duplicate=not ignore_duplicate)
677
677
678
678
679 flash = Flash()
679 flash = Flash()
680
680
681 #==============================================================================
681 #==============================================================================
682 # SCM FILTERS available via h.
682 # SCM FILTERS available via h.
683 #==============================================================================
683 #==============================================================================
684 from rhodecode.lib.vcs.utils import author_name, author_email
684 from rhodecode.lib.vcs.utils import author_name, author_email
685 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
685 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
686 from rhodecode.model.db import User, ChangesetStatus
686 from rhodecode.model.db import User, ChangesetStatus
687
687
688 capitalize = lambda x: x.capitalize()
688 capitalize = lambda x: x.capitalize()
689 email = author_email
689 email = author_email
690 short_id = lambda x: x[:12]
690 short_id = lambda x: x[:12]
691 hide_credentials = lambda x: ''.join(credentials_filter(x))
691 hide_credentials = lambda x: ''.join(credentials_filter(x))
692
692
693
693
694 import pytz
694 import pytz
695 import tzlocal
695 import tzlocal
696 local_timezone = tzlocal.get_localzone()
696 local_timezone = tzlocal.get_localzone()
697
697
698
698
699 def age_component(datetime_iso, value=None, time_is_local=False):
699 def age_component(datetime_iso, value=None, time_is_local=False):
700 title = value or format_date(datetime_iso)
700 title = value or format_date(datetime_iso)
701 tzinfo = '+00:00'
701 tzinfo = '+00:00'
702
702
703 # detect if we have a timezone info, otherwise, add it
703 # detect if we have a timezone info, otherwise, add it
704 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
704 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
705 force_timezone = os.environ.get('RC_TIMEZONE', '')
705 force_timezone = os.environ.get('RC_TIMEZONE', '')
706 if force_timezone:
706 if force_timezone:
707 force_timezone = pytz.timezone(force_timezone)
707 force_timezone = pytz.timezone(force_timezone)
708 timezone = force_timezone or local_timezone
708 timezone = force_timezone or local_timezone
709 offset = timezone.localize(datetime_iso).strftime('%z')
709 offset = timezone.localize(datetime_iso).strftime('%z')
710 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
710 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
711
711
712 return literal(
712 return literal(
713 '<time class="timeago tooltip" '
713 '<time class="timeago tooltip" '
714 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
714 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
715 datetime_iso, title, tzinfo))
715 datetime_iso, title, tzinfo))
716
716
717
717
718 def _shorten_commit_id(commit_id, commit_len=None):
718 def _shorten_commit_id(commit_id, commit_len=None):
719 if commit_len is None:
719 if commit_len is None:
720 request = get_current_request()
720 request = get_current_request()
721 commit_len = request.call_context.visual.show_sha_length
721 commit_len = request.call_context.visual.show_sha_length
722 return commit_id[:commit_len]
722 return commit_id[:commit_len]
723
723
724
724
725 def show_id(commit, show_idx=None, commit_len=None):
725 def show_id(commit, show_idx=None, commit_len=None):
726 """
726 """
727 Configurable function that shows ID
727 Configurable function that shows ID
728 by default it's r123:fffeeefffeee
728 by default it's r123:fffeeefffeee
729
729
730 :param commit: commit instance
730 :param commit: commit instance
731 """
731 """
732 if show_idx is None:
732 if show_idx is None:
733 request = get_current_request()
733 request = get_current_request()
734 show_idx = request.call_context.visual.show_revision_number
734 show_idx = request.call_context.visual.show_revision_number
735
735
736 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
736 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
737 if show_idx:
737 if show_idx:
738 return 'r%s:%s' % (commit.idx, raw_id)
738 return 'r%s:%s' % (commit.idx, raw_id)
739 else:
739 else:
740 return '%s' % (raw_id, )
740 return '%s' % (raw_id, )
741
741
742
742
743 def format_date(date):
743 def format_date(date):
744 """
744 """
745 use a standardized formatting for dates used in RhodeCode
745 use a standardized formatting for dates used in RhodeCode
746
746
747 :param date: date/datetime object
747 :param date: date/datetime object
748 :return: formatted date
748 :return: formatted date
749 """
749 """
750
750
751 if date:
751 if date:
752 _fmt = "%a, %d %b %Y %H:%M:%S"
752 _fmt = "%a, %d %b %Y %H:%M:%S"
753 return safe_unicode(date.strftime(_fmt))
753 return safe_unicode(date.strftime(_fmt))
754
754
755 return u""
755 return u""
756
756
757
757
758 class _RepoChecker(object):
758 class _RepoChecker(object):
759
759
760 def __init__(self, backend_alias):
760 def __init__(self, backend_alias):
761 self._backend_alias = backend_alias
761 self._backend_alias = backend_alias
762
762
763 def __call__(self, repository):
763 def __call__(self, repository):
764 if hasattr(repository, 'alias'):
764 if hasattr(repository, 'alias'):
765 _type = repository.alias
765 _type = repository.alias
766 elif hasattr(repository, 'repo_type'):
766 elif hasattr(repository, 'repo_type'):
767 _type = repository.repo_type
767 _type = repository.repo_type
768 else:
768 else:
769 _type = repository
769 _type = repository
770 return _type == self._backend_alias
770 return _type == self._backend_alias
771
771
772
772
773 is_git = _RepoChecker('git')
773 is_git = _RepoChecker('git')
774 is_hg = _RepoChecker('hg')
774 is_hg = _RepoChecker('hg')
775 is_svn = _RepoChecker('svn')
775 is_svn = _RepoChecker('svn')
776
776
777
777
778 def get_repo_type_by_name(repo_name):
778 def get_repo_type_by_name(repo_name):
779 repo = Repository.get_by_repo_name(repo_name)
779 repo = Repository.get_by_repo_name(repo_name)
780 if repo:
780 if repo:
781 return repo.repo_type
781 return repo.repo_type
782
782
783
783
784 def is_svn_without_proxy(repository):
784 def is_svn_without_proxy(repository):
785 if is_svn(repository):
785 if is_svn(repository):
786 from rhodecode.model.settings import VcsSettingsModel
786 from rhodecode.model.settings import VcsSettingsModel
787 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
787 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
788 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
788 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
789 return False
789 return False
790
790
791
791
792 def discover_user(author):
792 def discover_user(author):
793 """
793 """
794 Tries to discover RhodeCode User based on the autho string. Author string
794 Tries to discover RhodeCode User based on the autho string. Author string
795 is typically `FirstName LastName <email@address.com>`
795 is typically `FirstName LastName <email@address.com>`
796 """
796 """
797
797
798 # if author is already an instance use it for extraction
798 # if author is already an instance use it for extraction
799 if isinstance(author, User):
799 if isinstance(author, User):
800 return author
800 return author
801
801
802 # Valid email in the attribute passed, see if they're in the system
802 # Valid email in the attribute passed, see if they're in the system
803 _email = author_email(author)
803 _email = author_email(author)
804 if _email != '':
804 if _email != '':
805 user = User.get_by_email(_email, case_insensitive=True, cache=True)
805 user = User.get_by_email(_email, case_insensitive=True, cache=True)
806 if user is not None:
806 if user is not None:
807 return user
807 return user
808
808
809 # Maybe it's a username, we try to extract it and fetch by username ?
809 # Maybe it's a username, we try to extract it and fetch by username ?
810 _author = author_name(author)
810 _author = author_name(author)
811 user = User.get_by_username(_author, case_insensitive=True, cache=True)
811 user = User.get_by_username(_author, case_insensitive=True, cache=True)
812 if user is not None:
812 if user is not None:
813 return user
813 return user
814
814
815 return None
815 return None
816
816
817
817
818 def email_or_none(author):
818 def email_or_none(author):
819 # extract email from the commit string
819 # extract email from the commit string
820 _email = author_email(author)
820 _email = author_email(author)
821
821
822 # If we have an email, use it, otherwise
822 # If we have an email, use it, otherwise
823 # see if it contains a username we can get an email from
823 # see if it contains a username we can get an email from
824 if _email != '':
824 if _email != '':
825 return _email
825 return _email
826 else:
826 else:
827 user = User.get_by_username(
827 user = User.get_by_username(
828 author_name(author), case_insensitive=True, cache=True)
828 author_name(author), case_insensitive=True, cache=True)
829
829
830 if user is not None:
830 if user is not None:
831 return user.email
831 return user.email
832
832
833 # No valid email, not a valid user in the system, none!
833 # No valid email, not a valid user in the system, none!
834 return None
834 return None
835
835
836
836
837 def link_to_user(author, length=0, **kwargs):
837 def link_to_user(author, length=0, **kwargs):
838 user = discover_user(author)
838 user = discover_user(author)
839 # user can be None, but if we have it already it means we can re-use it
839 # user can be None, but if we have it already it means we can re-use it
840 # in the person() function, so we save 1 intensive-query
840 # in the person() function, so we save 1 intensive-query
841 if user:
841 if user:
842 author = user
842 author = user
843
843
844 display_person = person(author, 'username_or_name_or_email')
844 display_person = person(author, 'username_or_name_or_email')
845 if length:
845 if length:
846 display_person = shorter(display_person, length)
846 display_person = shorter(display_person, length)
847
847
848 if user:
848 if user:
849 return link_to(
849 return link_to(
850 escape(display_person),
850 escape(display_person),
851 route_path('user_profile', username=user.username),
851 route_path('user_profile', username=user.username),
852 **kwargs)
852 **kwargs)
853 else:
853 else:
854 return escape(display_person)
854 return escape(display_person)
855
855
856
856
857 def link_to_group(users_group_name, **kwargs):
857 def link_to_group(users_group_name, **kwargs):
858 return link_to(
858 return link_to(
859 escape(users_group_name),
859 escape(users_group_name),
860 route_path('user_group_profile', user_group_name=users_group_name),
860 route_path('user_group_profile', user_group_name=users_group_name),
861 **kwargs)
861 **kwargs)
862
862
863
863
864 def person(author, show_attr="username_and_name"):
864 def person(author, show_attr="username_and_name"):
865 user = discover_user(author)
865 user = discover_user(author)
866 if user:
866 if user:
867 return getattr(user, show_attr)
867 return getattr(user, show_attr)
868 else:
868 else:
869 _author = author_name(author)
869 _author = author_name(author)
870 _email = email(author)
870 _email = email(author)
871 return _author or _email
871 return _author or _email
872
872
873
873
874 def author_string(email):
874 def author_string(email):
875 if email:
875 if email:
876 user = User.get_by_email(email, case_insensitive=True, cache=True)
876 user = User.get_by_email(email, case_insensitive=True, cache=True)
877 if user:
877 if user:
878 if user.first_name or user.last_name:
878 if user.first_name or user.last_name:
879 return '%s %s &lt;%s&gt;' % (
879 return '%s %s &lt;%s&gt;' % (
880 user.first_name, user.last_name, email)
880 user.first_name, user.last_name, email)
881 else:
881 else:
882 return email
882 return email
883 else:
883 else:
884 return email
884 return email
885 else:
885 else:
886 return None
886 return None
887
887
888
888
889 def person_by_id(id_, show_attr="username_and_name"):
889 def person_by_id(id_, show_attr="username_and_name"):
890 # attr to return from fetched user
890 # attr to return from fetched user
891 person_getter = lambda usr: getattr(usr, show_attr)
891 person_getter = lambda usr: getattr(usr, show_attr)
892
892
893 #maybe it's an ID ?
893 #maybe it's an ID ?
894 if str(id_).isdigit() or isinstance(id_, int):
894 if str(id_).isdigit() or isinstance(id_, int):
895 id_ = int(id_)
895 id_ = int(id_)
896 user = User.get(id_)
896 user = User.get(id_)
897 if user is not None:
897 if user is not None:
898 return person_getter(user)
898 return person_getter(user)
899 return id_
899 return id_
900
900
901
901
902 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
902 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
903 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
903 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
904 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
904 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
905
905
906
906
907 tags_paterns = OrderedDict((
907 tags_paterns = OrderedDict((
908 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
908 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
909 '<div class="metatag" tag="lang">\\2</div>')),
909 '<div class="metatag" tag="lang">\\2</div>')),
910
910
911 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
911 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
912 '<div class="metatag" tag="see">see: \\1 </div>')),
912 '<div class="metatag" tag="see">see: \\1 </div>')),
913
913
914 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
914 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
915 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
915 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
916
916
917 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
917 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
918 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
918 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
919
919
920 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
920 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
921 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
921 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
922
922
923 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
923 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
924 '<div class="metatag" tag="state \\1">\\1</div>')),
924 '<div class="metatag" tag="state \\1">\\1</div>')),
925
925
926 # label in grey
926 # label in grey
927 ('label', (re.compile(r'\[([a-z]+)\]'),
927 ('label', (re.compile(r'\[([a-z]+)\]'),
928 '<div class="metatag" tag="label">\\1</div>')),
928 '<div class="metatag" tag="label">\\1</div>')),
929
929
930 # generic catch all in grey
930 # generic catch all in grey
931 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
931 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
932 '<div class="metatag" tag="generic">\\1</div>')),
932 '<div class="metatag" tag="generic">\\1</div>')),
933 ))
933 ))
934
934
935
935
936 def extract_metatags(value):
936 def extract_metatags(value):
937 """
937 """
938 Extract supported meta-tags from given text value
938 Extract supported meta-tags from given text value
939 """
939 """
940 tags = []
940 tags = []
941 if not value:
941 if not value:
942 return tags, ''
942 return tags, ''
943
943
944 for key, val in tags_paterns.items():
944 for key, val in tags_paterns.items():
945 pat, replace_html = val
945 pat, replace_html = val
946 tags.extend([(key, x.group()) for x in pat.finditer(value)])
946 tags.extend([(key, x.group()) for x in pat.finditer(value)])
947 value = pat.sub('', value)
947 value = pat.sub('', value)
948
948
949 return tags, value
949 return tags, value
950
950
951
951
952 def style_metatag(tag_type, value):
952 def style_metatag(tag_type, value):
953 """
953 """
954 converts tags from value into html equivalent
954 converts tags from value into html equivalent
955 """
955 """
956 if not value:
956 if not value:
957 return ''
957 return ''
958
958
959 html_value = value
959 html_value = value
960 tag_data = tags_paterns.get(tag_type)
960 tag_data = tags_paterns.get(tag_type)
961 if tag_data:
961 if tag_data:
962 pat, replace_html = tag_data
962 pat, replace_html = tag_data
963 # convert to plain `unicode` instead of a markup tag to be used in
963 # convert to plain `unicode` instead of a markup tag to be used in
964 # regex expressions. safe_unicode doesn't work here
964 # regex expressions. safe_unicode doesn't work here
965 html_value = pat.sub(replace_html, unicode(value))
965 html_value = pat.sub(replace_html, unicode(value))
966
966
967 return html_value
967 return html_value
968
968
969
969
970 def bool2icon(value, show_at_false=True):
970 def bool2icon(value, show_at_false=True):
971 """
971 """
972 Returns boolean value of a given value, represented as html element with
972 Returns boolean value of a given value, represented as html element with
973 classes that will represent icons
973 classes that will represent icons
974
974
975 :param value: given value to convert to html node
975 :param value: given value to convert to html node
976 """
976 """
977
977
978 if value: # does bool conversion
978 if value: # does bool conversion
979 return HTML.tag('i', class_="icon-true", title='True')
979 return HTML.tag('i', class_="icon-true", title='True')
980 else: # not true as bool
980 else: # not true as bool
981 if show_at_false:
981 if show_at_false:
982 return HTML.tag('i', class_="icon-false", title='False')
982 return HTML.tag('i', class_="icon-false", title='False')
983 return HTML.tag('i')
983 return HTML.tag('i')
984
984
985 #==============================================================================
985 #==============================================================================
986 # PERMS
986 # PERMS
987 #==============================================================================
987 #==============================================================================
988 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
988 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
989 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
989 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
990 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
990 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
991 csrf_token_key
991 csrf_token_key
992
992
993
993
994 #==============================================================================
994 #==============================================================================
995 # GRAVATAR URL
995 # GRAVATAR URL
996 #==============================================================================
996 #==============================================================================
997 class InitialsGravatar(object):
997 class InitialsGravatar(object):
998 def __init__(self, email_address, first_name, last_name, size=30,
998 def __init__(self, email_address, first_name, last_name, size=30,
999 background=None, text_color='#fff'):
999 background=None, text_color='#fff'):
1000 self.size = size
1000 self.size = size
1001 self.first_name = first_name
1001 self.first_name = first_name
1002 self.last_name = last_name
1002 self.last_name = last_name
1003 self.email_address = email_address
1003 self.email_address = email_address
1004 self.background = background or self.str2color(email_address)
1004 self.background = background or self.str2color(email_address)
1005 self.text_color = text_color
1005 self.text_color = text_color
1006
1006
1007 def get_color_bank(self):
1007 def get_color_bank(self):
1008 """
1008 """
1009 returns a predefined list of colors that gravatars can use.
1009 returns a predefined list of colors that gravatars can use.
1010 Those are randomized distinct colors that guarantee readability and
1010 Those are randomized distinct colors that guarantee readability and
1011 uniqueness.
1011 uniqueness.
1012
1012
1013 generated with: http://phrogz.net/css/distinct-colors.html
1013 generated with: http://phrogz.net/css/distinct-colors.html
1014 """
1014 """
1015 return [
1015 return [
1016 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1016 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1017 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1017 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1018 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1018 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1019 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1019 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1020 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1020 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1021 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1021 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1022 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1022 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1023 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1023 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1024 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1024 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1025 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1025 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1026 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1026 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1027 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1027 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1028 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1028 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1029 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1029 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1030 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1030 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1031 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1031 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1032 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1032 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1033 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1033 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1034 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1034 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1035 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1035 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1036 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1036 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1037 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1037 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1038 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1038 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1039 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1039 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1040 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1040 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1041 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1041 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1042 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1042 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1043 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1043 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1044 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1044 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1045 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1045 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1046 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1046 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1047 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1047 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1048 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1048 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1049 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1049 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1050 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1050 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1051 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1051 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1052 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1052 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1053 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1053 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1054 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1054 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1055 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1055 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1056 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1056 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1057 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1057 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1058 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1058 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1059 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1059 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1060 '#4f8c46', '#368dd9', '#5c0073'
1060 '#4f8c46', '#368dd9', '#5c0073'
1061 ]
1061 ]
1062
1062
1063 def rgb_to_hex_color(self, rgb_tuple):
1063 def rgb_to_hex_color(self, rgb_tuple):
1064 """
1064 """
1065 Converts an rgb_tuple passed to an hex color.
1065 Converts an rgb_tuple passed to an hex color.
1066
1066
1067 :param rgb_tuple: tuple with 3 ints represents rgb color space
1067 :param rgb_tuple: tuple with 3 ints represents rgb color space
1068 """
1068 """
1069 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1069 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1070
1070
1071 def email_to_int_list(self, email_str):
1071 def email_to_int_list(self, email_str):
1072 """
1072 """
1073 Get every byte of the hex digest value of email and turn it to integer.
1073 Get every byte of the hex digest value of email and turn it to integer.
1074 It's going to be always between 0-255
1074 It's going to be always between 0-255
1075 """
1075 """
1076 digest = md5_safe(email_str.lower())
1076 digest = md5_safe(email_str.lower())
1077 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1077 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1078
1078
1079 def pick_color_bank_index(self, email_str, color_bank):
1079 def pick_color_bank_index(self, email_str, color_bank):
1080 return self.email_to_int_list(email_str)[0] % len(color_bank)
1080 return self.email_to_int_list(email_str)[0] % len(color_bank)
1081
1081
1082 def str2color(self, email_str):
1082 def str2color(self, email_str):
1083 """
1083 """
1084 Tries to map in a stable algorithm an email to color
1084 Tries to map in a stable algorithm an email to color
1085
1085
1086 :param email_str:
1086 :param email_str:
1087 """
1087 """
1088 color_bank = self.get_color_bank()
1088 color_bank = self.get_color_bank()
1089 # pick position (module it's length so we always find it in the
1089 # pick position (module it's length so we always find it in the
1090 # bank even if it's smaller than 256 values
1090 # bank even if it's smaller than 256 values
1091 pos = self.pick_color_bank_index(email_str, color_bank)
1091 pos = self.pick_color_bank_index(email_str, color_bank)
1092 return color_bank[pos]
1092 return color_bank[pos]
1093
1093
1094 def normalize_email(self, email_address):
1094 def normalize_email(self, email_address):
1095 import unicodedata
1095 import unicodedata
1096 # default host used to fill in the fake/missing email
1096 # default host used to fill in the fake/missing email
1097 default_host = u'localhost'
1097 default_host = u'localhost'
1098
1098
1099 if not email_address:
1099 if not email_address:
1100 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1100 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1101
1101
1102 email_address = safe_unicode(email_address)
1102 email_address = safe_unicode(email_address)
1103
1103
1104 if u'@' not in email_address:
1104 if u'@' not in email_address:
1105 email_address = u'%s@%s' % (email_address, default_host)
1105 email_address = u'%s@%s' % (email_address, default_host)
1106
1106
1107 if email_address.endswith(u'@'):
1107 if email_address.endswith(u'@'):
1108 email_address = u'%s%s' % (email_address, default_host)
1108 email_address = u'%s%s' % (email_address, default_host)
1109
1109
1110 email_address = unicodedata.normalize('NFKD', email_address)\
1110 email_address = unicodedata.normalize('NFKD', email_address)\
1111 .encode('ascii', 'ignore')
1111 .encode('ascii', 'ignore')
1112 return email_address
1112 return email_address
1113
1113
1114 def get_initials(self):
1114 def get_initials(self):
1115 """
1115 """
1116 Returns 2 letter initials calculated based on the input.
1116 Returns 2 letter initials calculated based on the input.
1117 The algorithm picks first given email address, and takes first letter
1117 The algorithm picks first given email address, and takes first letter
1118 of part before @, and then the first letter of server name. In case
1118 of part before @, and then the first letter of server name. In case
1119 the part before @ is in a format of `somestring.somestring2` it replaces
1119 the part before @ is in a format of `somestring.somestring2` it replaces
1120 the server letter with first letter of somestring2
1120 the server letter with first letter of somestring2
1121
1121
1122 In case function was initialized with both first and lastname, this
1122 In case function was initialized with both first and lastname, this
1123 overrides the extraction from email by first letter of the first and
1123 overrides the extraction from email by first letter of the first and
1124 last name. We add special logic to that functionality, In case Full name
1124 last name. We add special logic to that functionality, In case Full name
1125 is compound, like Guido Von Rossum, we use last part of the last name
1125 is compound, like Guido Von Rossum, we use last part of the last name
1126 (Von Rossum) picking `R`.
1126 (Von Rossum) picking `R`.
1127
1127
1128 Function also normalizes the non-ascii characters to they ascii
1128 Function also normalizes the non-ascii characters to they ascii
1129 representation, eg Δ„ => A
1129 representation, eg Δ„ => A
1130 """
1130 """
1131 import unicodedata
1131 import unicodedata
1132 # replace non-ascii to ascii
1132 # replace non-ascii to ascii
1133 first_name = unicodedata.normalize(
1133 first_name = unicodedata.normalize(
1134 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1134 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1135 last_name = unicodedata.normalize(
1135 last_name = unicodedata.normalize(
1136 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1136 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1137
1137
1138 # do NFKD encoding, and also make sure email has proper format
1138 # do NFKD encoding, and also make sure email has proper format
1139 email_address = self.normalize_email(self.email_address)
1139 email_address = self.normalize_email(self.email_address)
1140
1140
1141 # first push the email initials
1141 # first push the email initials
1142 prefix, server = email_address.split('@', 1)
1142 prefix, server = email_address.split('@', 1)
1143
1143
1144 # check if prefix is maybe a 'first_name.last_name' syntax
1144 # check if prefix is maybe a 'first_name.last_name' syntax
1145 _dot_split = prefix.rsplit('.', 1)
1145 _dot_split = prefix.rsplit('.', 1)
1146 if len(_dot_split) == 2 and _dot_split[1]:
1146 if len(_dot_split) == 2 and _dot_split[1]:
1147 initials = [_dot_split[0][0], _dot_split[1][0]]
1147 initials = [_dot_split[0][0], _dot_split[1][0]]
1148 else:
1148 else:
1149 initials = [prefix[0], server[0]]
1149 initials = [prefix[0], server[0]]
1150
1150
1151 # then try to replace either first_name or last_name
1151 # then try to replace either first_name or last_name
1152 fn_letter = (first_name or " ")[0].strip()
1152 fn_letter = (first_name or " ")[0].strip()
1153 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1153 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1154
1154
1155 if fn_letter:
1155 if fn_letter:
1156 initials[0] = fn_letter
1156 initials[0] = fn_letter
1157
1157
1158 if ln_letter:
1158 if ln_letter:
1159 initials[1] = ln_letter
1159 initials[1] = ln_letter
1160
1160
1161 return ''.join(initials).upper()
1161 return ''.join(initials).upper()
1162
1162
1163 def get_img_data_by_type(self, font_family, img_type):
1163 def get_img_data_by_type(self, font_family, img_type):
1164 default_user = """
1164 default_user = """
1165 <svg xmlns="http://www.w3.org/2000/svg"
1165 <svg xmlns="http://www.w3.org/2000/svg"
1166 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1166 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1167 viewBox="-15 -10 439.165 429.164"
1167 viewBox="-15 -10 439.165 429.164"
1168
1168
1169 xml:space="preserve"
1169 xml:space="preserve"
1170 style="background:{background};" >
1170 style="background:{background};" >
1171
1171
1172 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1172 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1173 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1173 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1174 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1174 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1175 168.596,153.916,216.671,
1175 168.596,153.916,216.671,
1176 204.583,216.671z" fill="{text_color}"/>
1176 204.583,216.671z" fill="{text_color}"/>
1177 <path d="M407.164,374.717L360.88,
1177 <path d="M407.164,374.717L360.88,
1178 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1178 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1179 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1179 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1180 15.366-44.203,23.488-69.076,23.488c-24.877,
1180 15.366-44.203,23.488-69.076,23.488c-24.877,
1181 0-48.762-8.122-69.078-23.488
1181 0-48.762-8.122-69.078-23.488
1182 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1182 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1183 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1183 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1184 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1184 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1185 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1185 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1186 19.402-10.527 C409.699,390.129,
1186 19.402-10.527 C409.699,390.129,
1187 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1187 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1188 </svg>""".format(
1188 </svg>""".format(
1189 size=self.size,
1189 size=self.size,
1190 background='#979797', # @grey4
1190 background='#979797', # @grey4
1191 text_color=self.text_color,
1191 text_color=self.text_color,
1192 font_family=font_family)
1192 font_family=font_family)
1193
1193
1194 return {
1194 return {
1195 "default_user": default_user
1195 "default_user": default_user
1196 }[img_type]
1196 }[img_type]
1197
1197
1198 def get_img_data(self, svg_type=None):
1198 def get_img_data(self, svg_type=None):
1199 """
1199 """
1200 generates the svg metadata for image
1200 generates the svg metadata for image
1201 """
1201 """
1202 fonts = [
1202 fonts = [
1203 '-apple-system',
1203 '-apple-system',
1204 'BlinkMacSystemFont',
1204 'BlinkMacSystemFont',
1205 'Segoe UI',
1205 'Segoe UI',
1206 'Roboto',
1206 'Roboto',
1207 'Oxygen-Sans',
1207 'Oxygen-Sans',
1208 'Ubuntu',
1208 'Ubuntu',
1209 'Cantarell',
1209 'Cantarell',
1210 'Helvetica Neue',
1210 'Helvetica Neue',
1211 'sans-serif'
1211 'sans-serif'
1212 ]
1212 ]
1213 font_family = ','.join(fonts)
1213 font_family = ','.join(fonts)
1214 if svg_type:
1214 if svg_type:
1215 return self.get_img_data_by_type(font_family, svg_type)
1215 return self.get_img_data_by_type(font_family, svg_type)
1216
1216
1217 initials = self.get_initials()
1217 initials = self.get_initials()
1218 img_data = """
1218 img_data = """
1219 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1219 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1220 width="{size}" height="{size}"
1220 width="{size}" height="{size}"
1221 style="width: 100%; height: 100%; background-color: {background}"
1221 style="width: 100%; height: 100%; background-color: {background}"
1222 viewBox="0 0 {size} {size}">
1222 viewBox="0 0 {size} {size}">
1223 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1223 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1224 pointer-events="auto" fill="{text_color}"
1224 pointer-events="auto" fill="{text_color}"
1225 font-family="{font_family}"
1225 font-family="{font_family}"
1226 style="font-weight: 400; font-size: {f_size}px;">{text}
1226 style="font-weight: 400; font-size: {f_size}px;">{text}
1227 </text>
1227 </text>
1228 </svg>""".format(
1228 </svg>""".format(
1229 size=self.size,
1229 size=self.size,
1230 f_size=self.size/2.05, # scale the text inside the box nicely
1230 f_size=self.size/2.05, # scale the text inside the box nicely
1231 background=self.background,
1231 background=self.background,
1232 text_color=self.text_color,
1232 text_color=self.text_color,
1233 text=initials.upper(),
1233 text=initials.upper(),
1234 font_family=font_family)
1234 font_family=font_family)
1235
1235
1236 return img_data
1236 return img_data
1237
1237
1238 def generate_svg(self, svg_type=None):
1238 def generate_svg(self, svg_type=None):
1239 img_data = self.get_img_data(svg_type)
1239 img_data = self.get_img_data(svg_type)
1240 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1240 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1241
1241
1242
1242
1243 def initials_gravatar(email_address, first_name, last_name, size=30):
1243 def initials_gravatar(email_address, first_name, last_name, size=30):
1244 svg_type = None
1244 svg_type = None
1245 if email_address == User.DEFAULT_USER_EMAIL:
1245 if email_address == User.DEFAULT_USER_EMAIL:
1246 svg_type = 'default_user'
1246 svg_type = 'default_user'
1247 klass = InitialsGravatar(email_address, first_name, last_name, size)
1247 klass = InitialsGravatar(email_address, first_name, last_name, size)
1248 return klass.generate_svg(svg_type=svg_type)
1248 return klass.generate_svg(svg_type=svg_type)
1249
1249
1250
1250
1251 def gravatar_url(email_address, size=30, request=None):
1251 def gravatar_url(email_address, size=30, request=None):
1252 request = get_current_request()
1252 request = get_current_request()
1253 _use_gravatar = request.call_context.visual.use_gravatar
1253 _use_gravatar = request.call_context.visual.use_gravatar
1254 _gravatar_url = request.call_context.visual.gravatar_url
1254 _gravatar_url = request.call_context.visual.gravatar_url
1255
1255
1256 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1256 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1257
1257
1258 email_address = email_address or User.DEFAULT_USER_EMAIL
1258 email_address = email_address or User.DEFAULT_USER_EMAIL
1259 if isinstance(email_address, unicode):
1259 if isinstance(email_address, unicode):
1260 # hashlib crashes on unicode items
1260 # hashlib crashes on unicode items
1261 email_address = safe_str(email_address)
1261 email_address = safe_str(email_address)
1262
1262
1263 # empty email or default user
1263 # empty email or default user
1264 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1264 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1265 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1265 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1266
1266
1267 if _use_gravatar:
1267 if _use_gravatar:
1268 # TODO: Disuse pyramid thread locals. Think about another solution to
1268 # TODO: Disuse pyramid thread locals. Think about another solution to
1269 # get the host and schema here.
1269 # get the host and schema here.
1270 request = get_current_request()
1270 request = get_current_request()
1271 tmpl = safe_str(_gravatar_url)
1271 tmpl = safe_str(_gravatar_url)
1272 tmpl = tmpl.replace('{email}', email_address)\
1272 tmpl = tmpl.replace('{email}', email_address)\
1273 .replace('{md5email}', md5_safe(email_address.lower())) \
1273 .replace('{md5email}', md5_safe(email_address.lower())) \
1274 .replace('{netloc}', request.host)\
1274 .replace('{netloc}', request.host)\
1275 .replace('{scheme}', request.scheme)\
1275 .replace('{scheme}', request.scheme)\
1276 .replace('{size}', safe_str(size))
1276 .replace('{size}', safe_str(size))
1277 return tmpl
1277 return tmpl
1278 else:
1278 else:
1279 return initials_gravatar(email_address, '', '', size=size)
1279 return initials_gravatar(email_address, '', '', size=size)
1280
1280
1281
1281
1282 class Page(_Page):
1282 class Page(_Page):
1283 """
1283 """
1284 Custom pager to match rendering style with paginator
1284 Custom pager to match rendering style with paginator
1285 """
1285 """
1286
1286
1287 def _get_pos(self, cur_page, max_page, items):
1287 def _get_pos(self, cur_page, max_page, items):
1288 edge = (items / 2) + 1
1288 edge = (items / 2) + 1
1289 if (cur_page <= edge):
1289 if (cur_page <= edge):
1290 radius = max(items / 2, items - cur_page)
1290 radius = max(items / 2, items - cur_page)
1291 elif (max_page - cur_page) < edge:
1291 elif (max_page - cur_page) < edge:
1292 radius = (items - 1) - (max_page - cur_page)
1292 radius = (items - 1) - (max_page - cur_page)
1293 else:
1293 else:
1294 radius = items / 2
1294 radius = items / 2
1295
1295
1296 left = max(1, (cur_page - (radius)))
1296 left = max(1, (cur_page - (radius)))
1297 right = min(max_page, cur_page + (radius))
1297 right = min(max_page, cur_page + (radius))
1298 return left, cur_page, right
1298 return left, cur_page, right
1299
1299
1300 def _range(self, regexp_match):
1300 def _range(self, regexp_match):
1301 """
1301 """
1302 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1302 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1303
1303
1304 Arguments:
1304 Arguments:
1305
1305
1306 regexp_match
1306 regexp_match
1307 A "re" (regular expressions) match object containing the
1307 A "re" (regular expressions) match object containing the
1308 radius of linked pages around the current page in
1308 radius of linked pages around the current page in
1309 regexp_match.group(1) as a string
1309 regexp_match.group(1) as a string
1310
1310
1311 This function is supposed to be called as a callable in
1311 This function is supposed to be called as a callable in
1312 re.sub.
1312 re.sub.
1313
1313
1314 """
1314 """
1315 radius = int(regexp_match.group(1))
1315 radius = int(regexp_match.group(1))
1316
1316
1317 # Compute the first and last page number within the radius
1317 # Compute the first and last page number within the radius
1318 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1318 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1319 # -> leftmost_page = 5
1319 # -> leftmost_page = 5
1320 # -> rightmost_page = 9
1320 # -> rightmost_page = 9
1321 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1321 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1322 self.last_page,
1322 self.last_page,
1323 (radius * 2) + 1)
1323 (radius * 2) + 1)
1324 nav_items = []
1324 nav_items = []
1325
1325
1326 # Create a link to the first page (unless we are on the first page
1326 # Create a link to the first page (unless we are on the first page
1327 # or there would be no need to insert '..' spacers)
1327 # or there would be no need to insert '..' spacers)
1328 if self.page != self.first_page and self.first_page < leftmost_page:
1328 if self.page != self.first_page and self.first_page < leftmost_page:
1329 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1329 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1330
1330
1331 # Insert dots if there are pages between the first page
1331 # Insert dots if there are pages between the first page
1332 # and the currently displayed page range
1332 # and the currently displayed page range
1333 if leftmost_page - self.first_page > 1:
1333 if leftmost_page - self.first_page > 1:
1334 # Wrap in a SPAN tag if nolink_attr is set
1334 # Wrap in a SPAN tag if nolink_attr is set
1335 text = '..'
1335 text = '..'
1336 if self.dotdot_attr:
1336 if self.dotdot_attr:
1337 text = HTML.span(c=text, **self.dotdot_attr)
1337 text = HTML.span(c=text, **self.dotdot_attr)
1338 nav_items.append(text)
1338 nav_items.append(text)
1339
1339
1340 for thispage in xrange(leftmost_page, rightmost_page + 1):
1340 for thispage in xrange(leftmost_page, rightmost_page + 1):
1341 # Hilight the current page number and do not use a link
1341 # Hilight the current page number and do not use a link
1342 if thispage == self.page:
1342 if thispage == self.page:
1343 text = '%s' % (thispage,)
1343 text = '%s' % (thispage,)
1344 # Wrap in a SPAN tag if nolink_attr is set
1344 # Wrap in a SPAN tag if nolink_attr is set
1345 if self.curpage_attr:
1345 if self.curpage_attr:
1346 text = HTML.span(c=text, **self.curpage_attr)
1346 text = HTML.span(c=text, **self.curpage_attr)
1347 nav_items.append(text)
1347 nav_items.append(text)
1348 # Otherwise create just a link to that page
1348 # Otherwise create just a link to that page
1349 else:
1349 else:
1350 text = '%s' % (thispage,)
1350 text = '%s' % (thispage,)
1351 nav_items.append(self._pagerlink(thispage, text))
1351 nav_items.append(self._pagerlink(thispage, text))
1352
1352
1353 # Insert dots if there are pages between the displayed
1353 # Insert dots if there are pages between the displayed
1354 # page numbers and the end of the page range
1354 # page numbers and the end of the page range
1355 if self.last_page - rightmost_page > 1:
1355 if self.last_page - rightmost_page > 1:
1356 text = '..'
1356 text = '..'
1357 # Wrap in a SPAN tag if nolink_attr is set
1357 # Wrap in a SPAN tag if nolink_attr is set
1358 if self.dotdot_attr:
1358 if self.dotdot_attr:
1359 text = HTML.span(c=text, **self.dotdot_attr)
1359 text = HTML.span(c=text, **self.dotdot_attr)
1360 nav_items.append(text)
1360 nav_items.append(text)
1361
1361
1362 # Create a link to the very last page (unless we are on the last
1362 # Create a link to the very last page (unless we are on the last
1363 # page or there would be no need to insert '..' spacers)
1363 # page or there would be no need to insert '..' spacers)
1364 if self.page != self.last_page and rightmost_page < self.last_page:
1364 if self.page != self.last_page and rightmost_page < self.last_page:
1365 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1365 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1366
1366
1367 ## prerender links
1367 ## prerender links
1368 #_page_link = url.current()
1368 #_page_link = url.current()
1369 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1369 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1370 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1370 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1371 return self.separator.join(nav_items)
1371 return self.separator.join(nav_items)
1372
1372
1373 def pager(self, format='~2~', page_param='page', partial_param='partial',
1373 def pager(self, format='~2~', page_param='page', partial_param='partial',
1374 show_if_single_page=False, separator=' ', onclick=None,
1374 show_if_single_page=False, separator=' ', onclick=None,
1375 symbol_first='<<', symbol_last='>>',
1375 symbol_first='<<', symbol_last='>>',
1376 symbol_previous='<', symbol_next='>',
1376 symbol_previous='<', symbol_next='>',
1377 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1377 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1378 curpage_attr={'class': 'pager_curpage'},
1378 curpage_attr={'class': 'pager_curpage'},
1379 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1379 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1380
1380
1381 self.curpage_attr = curpage_attr
1381 self.curpage_attr = curpage_attr
1382 self.separator = separator
1382 self.separator = separator
1383 self.pager_kwargs = kwargs
1383 self.pager_kwargs = kwargs
1384 self.page_param = page_param
1384 self.page_param = page_param
1385 self.partial_param = partial_param
1385 self.partial_param = partial_param
1386 self.onclick = onclick
1386 self.onclick = onclick
1387 self.link_attr = link_attr
1387 self.link_attr = link_attr
1388 self.dotdot_attr = dotdot_attr
1388 self.dotdot_attr = dotdot_attr
1389
1389
1390 # Don't show navigator if there is no more than one page
1390 # Don't show navigator if there is no more than one page
1391 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1391 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1392 return ''
1392 return ''
1393
1393
1394 from string import Template
1394 from string import Template
1395 # Replace ~...~ in token format by range of pages
1395 # Replace ~...~ in token format by range of pages
1396 result = re.sub(r'~(\d+)~', self._range, format)
1396 result = re.sub(r'~(\d+)~', self._range, format)
1397
1397
1398 # Interpolate '%' variables
1398 # Interpolate '%' variables
1399 result = Template(result).safe_substitute({
1399 result = Template(result).safe_substitute({
1400 'first_page': self.first_page,
1400 'first_page': self.first_page,
1401 'last_page': self.last_page,
1401 'last_page': self.last_page,
1402 'page': self.page,
1402 'page': self.page,
1403 'page_count': self.page_count,
1403 'page_count': self.page_count,
1404 'items_per_page': self.items_per_page,
1404 'items_per_page': self.items_per_page,
1405 'first_item': self.first_item,
1405 'first_item': self.first_item,
1406 'last_item': self.last_item,
1406 'last_item': self.last_item,
1407 'item_count': self.item_count,
1407 'item_count': self.item_count,
1408 'link_first': self.page > self.first_page and \
1408 'link_first': self.page > self.first_page and \
1409 self._pagerlink(self.first_page, symbol_first) or '',
1409 self._pagerlink(self.first_page, symbol_first) or '',
1410 'link_last': self.page < self.last_page and \
1410 'link_last': self.page < self.last_page and \
1411 self._pagerlink(self.last_page, symbol_last) or '',
1411 self._pagerlink(self.last_page, symbol_last) or '',
1412 'link_previous': self.previous_page and \
1412 'link_previous': self.previous_page and \
1413 self._pagerlink(self.previous_page, symbol_previous) \
1413 self._pagerlink(self.previous_page, symbol_previous) \
1414 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1414 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1415 'link_next': self.next_page and \
1415 'link_next': self.next_page and \
1416 self._pagerlink(self.next_page, symbol_next) \
1416 self._pagerlink(self.next_page, symbol_next) \
1417 or HTML.span(symbol_next, class_="pg-next disabled")
1417 or HTML.span(symbol_next, class_="pg-next disabled")
1418 })
1418 })
1419
1419
1420 return literal(result)
1420 return literal(result)
1421
1421
1422
1422
1423 #==============================================================================
1423 #==============================================================================
1424 # REPO PAGER, PAGER FOR REPOSITORY
1424 # REPO PAGER, PAGER FOR REPOSITORY
1425 #==============================================================================
1425 #==============================================================================
1426 class RepoPage(Page):
1426 class RepoPage(Page):
1427
1427
1428 def __init__(self, collection, page=1, items_per_page=20,
1428 def __init__(self, collection, page=1, items_per_page=20,
1429 item_count=None, url=None, **kwargs):
1429 item_count=None, url=None, **kwargs):
1430
1430
1431 """Create a "RepoPage" instance. special pager for paging
1431 """Create a "RepoPage" instance. special pager for paging
1432 repository
1432 repository
1433 """
1433 """
1434 self._url_generator = url
1434 self._url_generator = url
1435
1435
1436 # Safe the kwargs class-wide so they can be used in the pager() method
1436 # Safe the kwargs class-wide so they can be used in the pager() method
1437 self.kwargs = kwargs
1437 self.kwargs = kwargs
1438
1438
1439 # Save a reference to the collection
1439 # Save a reference to the collection
1440 self.original_collection = collection
1440 self.original_collection = collection
1441
1441
1442 self.collection = collection
1442 self.collection = collection
1443
1443
1444 # The self.page is the number of the current page.
1444 # The self.page is the number of the current page.
1445 # The first page has the number 1!
1445 # The first page has the number 1!
1446 try:
1446 try:
1447 self.page = int(page) # make it int() if we get it as a string
1447 self.page = int(page) # make it int() if we get it as a string
1448 except (ValueError, TypeError):
1448 except (ValueError, TypeError):
1449 self.page = 1
1449 self.page = 1
1450
1450
1451 self.items_per_page = items_per_page
1451 self.items_per_page = items_per_page
1452
1452
1453 # Unless the user tells us how many items the collections has
1453 # Unless the user tells us how many items the collections has
1454 # we calculate that ourselves.
1454 # we calculate that ourselves.
1455 if item_count is not None:
1455 if item_count is not None:
1456 self.item_count = item_count
1456 self.item_count = item_count
1457 else:
1457 else:
1458 self.item_count = len(self.collection)
1458 self.item_count = len(self.collection)
1459
1459
1460 # Compute the number of the first and last available page
1460 # Compute the number of the first and last available page
1461 if self.item_count > 0:
1461 if self.item_count > 0:
1462 self.first_page = 1
1462 self.first_page = 1
1463 self.page_count = int(math.ceil(float(self.item_count) /
1463 self.page_count = int(math.ceil(float(self.item_count) /
1464 self.items_per_page))
1464 self.items_per_page))
1465 self.last_page = self.first_page + self.page_count - 1
1465 self.last_page = self.first_page + self.page_count - 1
1466
1466
1467 # Make sure that the requested page number is the range of
1467 # Make sure that the requested page number is the range of
1468 # valid pages
1468 # valid pages
1469 if self.page > self.last_page:
1469 if self.page > self.last_page:
1470 self.page = self.last_page
1470 self.page = self.last_page
1471 elif self.page < self.first_page:
1471 elif self.page < self.first_page:
1472 self.page = self.first_page
1472 self.page = self.first_page
1473
1473
1474 # Note: the number of items on this page can be less than
1474 # Note: the number of items on this page can be less than
1475 # items_per_page if the last page is not full
1475 # items_per_page if the last page is not full
1476 self.first_item = max(0, (self.item_count) - (self.page *
1476 self.first_item = max(0, (self.item_count) - (self.page *
1477 items_per_page))
1477 items_per_page))
1478 self.last_item = ((self.item_count - 1) - items_per_page *
1478 self.last_item = ((self.item_count - 1) - items_per_page *
1479 (self.page - 1))
1479 (self.page - 1))
1480
1480
1481 self.items = list(self.collection[self.first_item:self.last_item + 1])
1481 self.items = list(self.collection[self.first_item:self.last_item + 1])
1482
1482
1483 # Links to previous and next page
1483 # Links to previous and next page
1484 if self.page > self.first_page:
1484 if self.page > self.first_page:
1485 self.previous_page = self.page - 1
1485 self.previous_page = self.page - 1
1486 else:
1486 else:
1487 self.previous_page = None
1487 self.previous_page = None
1488
1488
1489 if self.page < self.last_page:
1489 if self.page < self.last_page:
1490 self.next_page = self.page + 1
1490 self.next_page = self.page + 1
1491 else:
1491 else:
1492 self.next_page = None
1492 self.next_page = None
1493
1493
1494 # No items available
1494 # No items available
1495 else:
1495 else:
1496 self.first_page = None
1496 self.first_page = None
1497 self.page_count = 0
1497 self.page_count = 0
1498 self.last_page = None
1498 self.last_page = None
1499 self.first_item = None
1499 self.first_item = None
1500 self.last_item = None
1500 self.last_item = None
1501 self.previous_page = None
1501 self.previous_page = None
1502 self.next_page = None
1502 self.next_page = None
1503 self.items = []
1503 self.items = []
1504
1504
1505 # This is a subclass of the 'list' type. Initialise the list now.
1505 # This is a subclass of the 'list' type. Initialise the list now.
1506 list.__init__(self, reversed(self.items))
1506 list.__init__(self, reversed(self.items))
1507
1507
1508
1508
1509 def breadcrumb_repo_link(repo):
1509 def breadcrumb_repo_link(repo):
1510 """
1510 """
1511 Makes a breadcrumbs path link to repo
1511 Makes a breadcrumbs path link to repo
1512
1512
1513 ex::
1513 ex::
1514 group >> subgroup >> repo
1514 group >> subgroup >> repo
1515
1515
1516 :param repo: a Repository instance
1516 :param repo: a Repository instance
1517 """
1517 """
1518
1518
1519 path = [
1519 path = [
1520 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1520 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1521 title='last change:{}'.format(format_date(group.last_commit_change)))
1521 title='last change:{}'.format(format_date(group.last_commit_change)))
1522 for group in repo.groups_with_parents
1522 for group in repo.groups_with_parents
1523 ] + [
1523 ] + [
1524 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1524 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1525 title='last change:{}'.format(format_date(repo.last_commit_change)))
1525 title='last change:{}'.format(format_date(repo.last_commit_change)))
1526 ]
1526 ]
1527
1527
1528 return literal(' &raquo; '.join(path))
1528 return literal(' &raquo; '.join(path))
1529
1529
1530
1530
1531 def breadcrumb_repo_group_link(repo_group):
1531 def breadcrumb_repo_group_link(repo_group):
1532 """
1532 """
1533 Makes a breadcrumbs path link to repo
1533 Makes a breadcrumbs path link to repo
1534
1534
1535 ex::
1535 ex::
1536 group >> subgroup
1536 group >> subgroup
1537
1537
1538 :param repo_group: a Repository Group instance
1538 :param repo_group: a Repository Group instance
1539 """
1539 """
1540
1540
1541 path = [
1541 path = [
1542 link_to(group.name,
1542 link_to(group.name,
1543 route_path('repo_group_home', repo_group_name=group.group_name),
1543 route_path('repo_group_home', repo_group_name=group.group_name),
1544 title='last change:{}'.format(format_date(group.last_commit_change)))
1544 title='last change:{}'.format(format_date(group.last_commit_change)))
1545 for group in repo_group.parents
1545 for group in repo_group.parents
1546 ] + [
1546 ] + [
1547 link_to(repo_group.name,
1547 link_to(repo_group.name,
1548 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1548 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1549 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1549 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1550 ]
1550 ]
1551
1551
1552 return literal(' &raquo; '.join(path))
1552 return literal(' &raquo; '.join(path))
1553
1553
1554
1554
1555 def format_byte_size_binary(file_size):
1555 def format_byte_size_binary(file_size):
1556 """
1556 """
1557 Formats file/folder sizes to standard.
1557 Formats file/folder sizes to standard.
1558 """
1558 """
1559 if file_size is None:
1559 if file_size is None:
1560 file_size = 0
1560 file_size = 0
1561
1561
1562 formatted_size = format_byte_size(file_size, binary=True)
1562 formatted_size = format_byte_size(file_size, binary=True)
1563 return formatted_size
1563 return formatted_size
1564
1564
1565
1565
1566 def urlify_text(text_, safe=True):
1566 def urlify_text(text_, safe=True):
1567 """
1567 """
1568 Extrac urls from text and make html links out of them
1568 Extrac urls from text and make html links out of them
1569
1569
1570 :param text_:
1570 :param text_:
1571 """
1571 """
1572
1572
1573 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1573 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1574 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1574 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1575
1575
1576 def url_func(match_obj):
1576 def url_func(match_obj):
1577 url_full = match_obj.groups()[0]
1577 url_full = match_obj.groups()[0]
1578 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1578 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1579 _newtext = url_pat.sub(url_func, text_)
1579 _newtext = url_pat.sub(url_func, text_)
1580 if safe:
1580 if safe:
1581 return literal(_newtext)
1581 return literal(_newtext)
1582 return _newtext
1582 return _newtext
1583
1583
1584
1584
1585 def urlify_commits(text_, repository):
1585 def urlify_commits(text_, repository):
1586 """
1586 """
1587 Extract commit ids from text and make link from them
1587 Extract commit ids from text and make link from them
1588
1588
1589 :param text_:
1589 :param text_:
1590 :param repository: repo name to build the URL with
1590 :param repository: repo name to build the URL with
1591 """
1591 """
1592
1592
1593 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1593 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1594
1594
1595 def url_func(match_obj):
1595 def url_func(match_obj):
1596 commit_id = match_obj.groups()[1]
1596 commit_id = match_obj.groups()[1]
1597 pref = match_obj.groups()[0]
1597 pref = match_obj.groups()[0]
1598 suf = match_obj.groups()[2]
1598 suf = match_obj.groups()[2]
1599
1599
1600 tmpl = (
1600 tmpl = (
1601 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1601 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1602 '%(commit_id)s</a>%(suf)s'
1602 '%(commit_id)s</a>%(suf)s'
1603 )
1603 )
1604 return tmpl % {
1604 return tmpl % {
1605 'pref': pref,
1605 'pref': pref,
1606 'cls': 'revision-link',
1606 'cls': 'revision-link',
1607 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1607 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1608 'commit_id': commit_id,
1608 'commit_id': commit_id,
1609 'suf': suf
1609 'suf': suf
1610 }
1610 }
1611
1611
1612 newtext = URL_PAT.sub(url_func, text_)
1612 newtext = URL_PAT.sub(url_func, text_)
1613
1613
1614 return newtext
1614 return newtext
1615
1615
1616
1616
1617 def _process_url_func(match_obj, repo_name, uid, entry,
1617 def _process_url_func(match_obj, repo_name, uid, entry,
1618 return_raw_data=False, link_format='html'):
1618 return_raw_data=False, link_format='html'):
1619 pref = ''
1619 pref = ''
1620 if match_obj.group().startswith(' '):
1620 if match_obj.group().startswith(' '):
1621 pref = ' '
1621 pref = ' '
1622
1622
1623 issue_id = ''.join(match_obj.groups())
1623 issue_id = ''.join(match_obj.groups())
1624
1624
1625 if link_format == 'html':
1625 if link_format == 'html':
1626 tmpl = (
1626 tmpl = (
1627 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1627 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1628 '%(issue-prefix)s%(id-repr)s'
1628 '%(issue-prefix)s%(id-repr)s'
1629 '</a>')
1629 '</a>')
1630 elif link_format == 'rst':
1630 elif link_format == 'rst':
1631 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1631 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1632 elif link_format == 'markdown':
1632 elif link_format == 'markdown':
1633 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1633 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1634 else:
1634 else:
1635 raise ValueError('Bad link_format:{}'.format(link_format))
1635 raise ValueError('Bad link_format:{}'.format(link_format))
1636
1636
1637 (repo_name_cleaned,
1637 (repo_name_cleaned,
1638 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1638 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1639
1639
1640 # variables replacement
1640 # variables replacement
1641 named_vars = {
1641 named_vars = {
1642 'id': issue_id,
1642 'id': issue_id,
1643 'repo': repo_name,
1643 'repo': repo_name,
1644 'repo_name': repo_name_cleaned,
1644 'repo_name': repo_name_cleaned,
1645 'group_name': parent_group_name,
1645 'group_name': parent_group_name,
1646 }
1646 }
1647 # named regex variables
1647 # named regex variables
1648 named_vars.update(match_obj.groupdict())
1648 named_vars.update(match_obj.groupdict())
1649 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1649 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1650
1650
1651 def quote_cleaner(input_str):
1651 def quote_cleaner(input_str):
1652 """Remove quotes as it's HTML"""
1652 """Remove quotes as it's HTML"""
1653 return input_str.replace('"', '')
1653 return input_str.replace('"', '')
1654
1654
1655 data = {
1655 data = {
1656 'pref': pref,
1656 'pref': pref,
1657 'cls': quote_cleaner('issue-tracker-link'),
1657 'cls': quote_cleaner('issue-tracker-link'),
1658 'url': quote_cleaner(_url),
1658 'url': quote_cleaner(_url),
1659 'id-repr': issue_id,
1659 'id-repr': issue_id,
1660 'issue-prefix': entry['pref'],
1660 'issue-prefix': entry['pref'],
1661 'serv': entry['url'],
1661 'serv': entry['url'],
1662 'title': entry['desc']
1662 'title': entry['desc']
1663 }
1663 }
1664 if return_raw_data:
1664 if return_raw_data:
1665 return {
1665 return {
1666 'id': issue_id,
1666 'id': issue_id,
1667 'url': _url
1667 'url': _url
1668 }
1668 }
1669 return tmpl % data
1669 return tmpl % data
1670
1670
1671
1671
1672 def get_active_pattern_entries(repo_name):
1672 def get_active_pattern_entries(repo_name):
1673 repo = None
1673 repo = None
1674 if repo_name:
1674 if repo_name:
1675 # Retrieving repo_name to avoid invalid repo_name to explode on
1675 # Retrieving repo_name to avoid invalid repo_name to explode on
1676 # IssueTrackerSettingsModel but still passing invalid name further down
1676 # IssueTrackerSettingsModel but still passing invalid name further down
1677 repo = Repository.get_by_repo_name(repo_name, cache=True)
1677 repo = Repository.get_by_repo_name(repo_name, cache=True)
1678
1678
1679 settings_model = IssueTrackerSettingsModel(repo=repo)
1679 settings_model = IssueTrackerSettingsModel(repo=repo)
1680 active_entries = settings_model.get_settings(cache=True)
1680 active_entries = settings_model.get_settings(cache=True)
1681 return active_entries
1681 return active_entries
1682
1682
1683
1683
1684 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1684 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1685
1685
1686 allowed_formats = ['html', 'rst', 'markdown']
1686 allowed_formats = ['html', 'rst', 'markdown']
1687 if link_format not in allowed_formats:
1687 if link_format not in allowed_formats:
1688 raise ValueError('Link format can be only one of:{} got {}'.format(
1688 raise ValueError('Link format can be only one of:{} got {}'.format(
1689 allowed_formats, link_format))
1689 allowed_formats, link_format))
1690
1690
1691 active_entries = active_entries or get_active_pattern_entries(repo_name)
1691 active_entries = active_entries or get_active_pattern_entries(repo_name)
1692 issues_data = []
1692 issues_data = []
1693 newtext = text_string
1693 new_text = text_string
1694
1694
1695 log.debug('Got %s entries to process', len(active_entries))
1695 for uid, entry in active_entries.items():
1696 for uid, entry in active_entries.items():
1696 log.debug('found issue tracker entry with uid %s', uid)
1697 log.debug('found issue tracker entry with uid %s', uid)
1697
1698
1698 if not (entry['pat'] and entry['url']):
1699 if not (entry['pat'] and entry['url']):
1699 log.debug('skipping due to missing data')
1700 log.debug('skipping due to missing data')
1700 continue
1701 continue
1701
1702
1702 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1703 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1703 uid, entry['pat'], entry['url'], entry['pref'])
1704 uid, entry['pat'], entry['url'], entry['pref'])
1704
1705
1705 try:
1706 try:
1706 pattern = re.compile(r'%s' % entry['pat'])
1707 pattern = re.compile(r'%s' % entry['pat'])
1707 except re.error:
1708 except re.error:
1708 log.exception(
1709 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1709 'issue tracker pattern: `%s` failed to compile',
1710 entry['pat'])
1711 continue
1710 continue
1712
1711
1713 data_func = partial(
1712 data_func = partial(
1714 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1713 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1715 return_raw_data=True)
1714 return_raw_data=True)
1716
1715
1717 for match_obj in pattern.finditer(text_string):
1716 for match_obj in pattern.finditer(text_string):
1718 issues_data.append(data_func(match_obj))
1717 issues_data.append(data_func(match_obj))
1719
1718
1720 url_func = partial(
1719 url_func = partial(
1721 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1720 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1722 link_format=link_format)
1721 link_format=link_format)
1723
1722
1724 newtext = pattern.sub(url_func, newtext)
1723 new_text = pattern.sub(url_func, new_text)
1725 log.debug('processed prefix:uid `%s`', uid)
1724 log.debug('processed prefix:uid `%s`', uid)
1726
1725
1727 return newtext, issues_data
1726 return new_text, issues_data
1728
1727
1729
1728
1730 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1729 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1731 """
1730 """
1732 Parses given text message and makes proper links.
1731 Parses given text message and makes proper links.
1733 issues are linked to given issue-server, and rest is a commit link
1732 issues are linked to given issue-server, and rest is a commit link
1734
1733
1735 :param commit_text:
1734 :param commit_text:
1736 :param repository:
1735 :param repository:
1737 """
1736 """
1738 def escaper(string):
1737 def escaper(string):
1739 return string.replace('<', '&lt;').replace('>', '&gt;')
1738 return string.replace('<', '&lt;').replace('>', '&gt;')
1740
1739
1741 newtext = escaper(commit_text)
1740 newtext = escaper(commit_text)
1742
1741
1743 # extract http/https links and make them real urls
1742 # extract http/https links and make them real urls
1744 newtext = urlify_text(newtext, safe=False)
1743 newtext = urlify_text(newtext, safe=False)
1745
1744
1746 # urlify commits - extract commit ids and make link out of them, if we have
1745 # urlify commits - extract commit ids and make link out of them, if we have
1747 # the scope of repository present.
1746 # the scope of repository present.
1748 if repository:
1747 if repository:
1749 newtext = urlify_commits(newtext, repository)
1748 newtext = urlify_commits(newtext, repository)
1750
1749
1751 # process issue tracker patterns
1750 # process issue tracker patterns
1752 newtext, issues = process_patterns(newtext, repository or '',
1751 newtext, issues = process_patterns(newtext, repository or '',
1753 active_entries=active_pattern_entries)
1752 active_entries=active_pattern_entries)
1754
1753
1755 return literal(newtext)
1754 return literal(newtext)
1756
1755
1757
1756
1758 def render_binary(repo_name, file_obj):
1757 def render_binary(repo_name, file_obj):
1759 """
1758 """
1760 Choose how to render a binary file
1759 Choose how to render a binary file
1761 """
1760 """
1762
1761
1763 filename = file_obj.name
1762 filename = file_obj.name
1764
1763
1765 # images
1764 # images
1766 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1765 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1767 if fnmatch.fnmatch(filename, pat=ext):
1766 if fnmatch.fnmatch(filename, pat=ext):
1768 alt = escape(filename)
1767 alt = escape(filename)
1769 src = route_path(
1768 src = route_path(
1770 'repo_file_raw', repo_name=repo_name,
1769 'repo_file_raw', repo_name=repo_name,
1771 commit_id=file_obj.commit.raw_id,
1770 commit_id=file_obj.commit.raw_id,
1772 f_path=file_obj.path)
1771 f_path=file_obj.path)
1773 return literal(
1772 return literal(
1774 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1773 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1775
1774
1776
1775
1777 def renderer_from_filename(filename, exclude=None):
1776 def renderer_from_filename(filename, exclude=None):
1778 """
1777 """
1779 choose a renderer based on filename, this works only for text based files
1778 choose a renderer based on filename, this works only for text based files
1780 """
1779 """
1781
1780
1782 # ipython
1781 # ipython
1783 for ext in ['*.ipynb']:
1782 for ext in ['*.ipynb']:
1784 if fnmatch.fnmatch(filename, pat=ext):
1783 if fnmatch.fnmatch(filename, pat=ext):
1785 return 'jupyter'
1784 return 'jupyter'
1786
1785
1787 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1786 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1788 if is_markup:
1787 if is_markup:
1789 return is_markup
1788 return is_markup
1790 return None
1789 return None
1791
1790
1792
1791
1793 def render(source, renderer='rst', mentions=False, relative_urls=None,
1792 def render(source, renderer='rst', mentions=False, relative_urls=None,
1794 repo_name=None):
1793 repo_name=None):
1795
1794
1796 def maybe_convert_relative_links(html_source):
1795 def maybe_convert_relative_links(html_source):
1797 if relative_urls:
1796 if relative_urls:
1798 return relative_links(html_source, relative_urls)
1797 return relative_links(html_source, relative_urls)
1799 return html_source
1798 return html_source
1800
1799
1801 if renderer == 'plain':
1800 if renderer == 'plain':
1802 return literal(
1801 return literal(
1803 MarkupRenderer.plain(source, leading_newline=False))
1802 MarkupRenderer.plain(source, leading_newline=False))
1804
1803
1805 elif renderer == 'rst':
1804 elif renderer == 'rst':
1806 if repo_name:
1805 if repo_name:
1807 # process patterns on comments if we pass in repo name
1806 # process patterns on comments if we pass in repo name
1808 source, issues = process_patterns(
1807 source, issues = process_patterns(
1809 source, repo_name, link_format='rst')
1808 source, repo_name, link_format='rst')
1810
1809
1811 return literal(
1810 return literal(
1812 '<div class="rst-block">%s</div>' %
1811 '<div class="rst-block">%s</div>' %
1813 maybe_convert_relative_links(
1812 maybe_convert_relative_links(
1814 MarkupRenderer.rst(source, mentions=mentions)))
1813 MarkupRenderer.rst(source, mentions=mentions)))
1815
1814
1816 elif renderer == 'markdown':
1815 elif renderer == 'markdown':
1817 if repo_name:
1816 if repo_name:
1818 # process patterns on comments if we pass in repo name
1817 # process patterns on comments if we pass in repo name
1819 source, issues = process_patterns(
1818 source, issues = process_patterns(
1820 source, repo_name, link_format='markdown')
1819 source, repo_name, link_format='markdown')
1821
1820
1822 return literal(
1821 return literal(
1823 '<div class="markdown-block">%s</div>' %
1822 '<div class="markdown-block">%s</div>' %
1824 maybe_convert_relative_links(
1823 maybe_convert_relative_links(
1825 MarkupRenderer.markdown(source, flavored=True,
1824 MarkupRenderer.markdown(source, flavored=True,
1826 mentions=mentions)))
1825 mentions=mentions)))
1827
1826
1828 elif renderer == 'jupyter':
1827 elif renderer == 'jupyter':
1829 return literal(
1828 return literal(
1830 '<div class="ipynb">%s</div>' %
1829 '<div class="ipynb">%s</div>' %
1831 maybe_convert_relative_links(
1830 maybe_convert_relative_links(
1832 MarkupRenderer.jupyter(source)))
1831 MarkupRenderer.jupyter(source)))
1833
1832
1834 # None means just show the file-source
1833 # None means just show the file-source
1835 return None
1834 return None
1836
1835
1837
1836
1838 def commit_status(repo, commit_id):
1837 def commit_status(repo, commit_id):
1839 return ChangesetStatusModel().get_status(repo, commit_id)
1838 return ChangesetStatusModel().get_status(repo, commit_id)
1840
1839
1841
1840
1842 def commit_status_lbl(commit_status):
1841 def commit_status_lbl(commit_status):
1843 return dict(ChangesetStatus.STATUSES).get(commit_status)
1842 return dict(ChangesetStatus.STATUSES).get(commit_status)
1844
1843
1845
1844
1846 def commit_time(repo_name, commit_id):
1845 def commit_time(repo_name, commit_id):
1847 repo = Repository.get_by_repo_name(repo_name)
1846 repo = Repository.get_by_repo_name(repo_name)
1848 commit = repo.get_commit(commit_id=commit_id)
1847 commit = repo.get_commit(commit_id=commit_id)
1849 return commit.date
1848 return commit.date
1850
1849
1851
1850
1852 def get_permission_name(key):
1851 def get_permission_name(key):
1853 return dict(Permission.PERMS).get(key)
1852 return dict(Permission.PERMS).get(key)
1854
1853
1855
1854
1856 def journal_filter_help(request):
1855 def journal_filter_help(request):
1857 _ = request.translate
1856 _ = request.translate
1858 from rhodecode.lib.audit_logger import ACTIONS
1857 from rhodecode.lib.audit_logger import ACTIONS
1859 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1858 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1860
1859
1861 return _(
1860 return _(
1862 'Example filter terms:\n' +
1861 'Example filter terms:\n' +
1863 ' repository:vcs\n' +
1862 ' repository:vcs\n' +
1864 ' username:marcin\n' +
1863 ' username:marcin\n' +
1865 ' username:(NOT marcin)\n' +
1864 ' username:(NOT marcin)\n' +
1866 ' action:*push*\n' +
1865 ' action:*push*\n' +
1867 ' ip:127.0.0.1\n' +
1866 ' ip:127.0.0.1\n' +
1868 ' date:20120101\n' +
1867 ' date:20120101\n' +
1869 ' date:[20120101100000 TO 20120102]\n' +
1868 ' date:[20120101100000 TO 20120102]\n' +
1870 '\n' +
1869 '\n' +
1871 'Actions: {actions}\n' +
1870 'Actions: {actions}\n' +
1872 '\n' +
1871 '\n' +
1873 'Generate wildcards using \'*\' character:\n' +
1872 'Generate wildcards using \'*\' character:\n' +
1874 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1873 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1875 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1874 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1876 '\n' +
1875 '\n' +
1877 'Optional AND / OR operators in queries\n' +
1876 'Optional AND / OR operators in queries\n' +
1878 ' "repository:vcs OR repository:test"\n' +
1877 ' "repository:vcs OR repository:test"\n' +
1879 ' "username:test AND repository:test*"\n'
1878 ' "username:test AND repository:test*"\n'
1880 ).format(actions=actions)
1879 ).format(actions=actions)
1881
1880
1882
1881
1883 def not_mapped_error(repo_name):
1882 def not_mapped_error(repo_name):
1884 from rhodecode.translation import _
1883 from rhodecode.translation import _
1885 flash(_('%s repository is not mapped to db perhaps'
1884 flash(_('%s repository is not mapped to db perhaps'
1886 ' it was created or renamed from the filesystem'
1885 ' it was created or renamed from the filesystem'
1887 ' please run the application again'
1886 ' please run the application again'
1888 ' in order to rescan repositories') % repo_name, category='error')
1887 ' in order to rescan repositories') % repo_name, category='error')
1889
1888
1890
1889
1891 def ip_range(ip_addr):
1890 def ip_range(ip_addr):
1892 from rhodecode.model.db import UserIpMap
1891 from rhodecode.model.db import UserIpMap
1893 s, e = UserIpMap._get_ip_range(ip_addr)
1892 s, e = UserIpMap._get_ip_range(ip_addr)
1894 return '%s - %s' % (s, e)
1893 return '%s - %s' % (s, e)
1895
1894
1896
1895
1897 def form(url, method='post', needs_csrf_token=True, **attrs):
1896 def form(url, method='post', needs_csrf_token=True, **attrs):
1898 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1897 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1899 if method.lower() != 'get' and needs_csrf_token:
1898 if method.lower() != 'get' and needs_csrf_token:
1900 raise Exception(
1899 raise Exception(
1901 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1900 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1902 'CSRF token. If the endpoint does not require such token you can ' +
1901 'CSRF token. If the endpoint does not require such token you can ' +
1903 'explicitly set the parameter needs_csrf_token to false.')
1902 'explicitly set the parameter needs_csrf_token to false.')
1904
1903
1905 return wh_form(url, method=method, **attrs)
1904 return wh_form(url, method=method, **attrs)
1906
1905
1907
1906
1908 def secure_form(form_url, method="POST", multipart=False, **attrs):
1907 def secure_form(form_url, method="POST", multipart=False, **attrs):
1909 """Start a form tag that points the action to an url. This
1908 """Start a form tag that points the action to an url. This
1910 form tag will also include the hidden field containing
1909 form tag will also include the hidden field containing
1911 the auth token.
1910 the auth token.
1912
1911
1913 The url options should be given either as a string, or as a
1912 The url options should be given either as a string, or as a
1914 ``url()`` function. The method for the form defaults to POST.
1913 ``url()`` function. The method for the form defaults to POST.
1915
1914
1916 Options:
1915 Options:
1917
1916
1918 ``multipart``
1917 ``multipart``
1919 If set to True, the enctype is set to "multipart/form-data".
1918 If set to True, the enctype is set to "multipart/form-data".
1920 ``method``
1919 ``method``
1921 The method to use when submitting the form, usually either
1920 The method to use when submitting the form, usually either
1922 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1921 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1923 hidden input with name _method is added to simulate the verb
1922 hidden input with name _method is added to simulate the verb
1924 over POST.
1923 over POST.
1925
1924
1926 """
1925 """
1927 from webhelpers.pylonslib.secure_form import insecure_form
1926 from webhelpers.pylonslib.secure_form import insecure_form
1928
1927
1929 if 'request' in attrs:
1928 if 'request' in attrs:
1930 session = attrs['request'].session
1929 session = attrs['request'].session
1931 del attrs['request']
1930 del attrs['request']
1932 else:
1931 else:
1933 raise ValueError(
1932 raise ValueError(
1934 'Calling this form requires request= to be passed as argument')
1933 'Calling this form requires request= to be passed as argument')
1935
1934
1936 form = insecure_form(form_url, method, multipart, **attrs)
1935 form = insecure_form(form_url, method, multipart, **attrs)
1937 token = literal(
1936 token = literal(
1938 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1937 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1939 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1938 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1940
1939
1941 return literal("%s\n%s" % (form, token))
1940 return literal("%s\n%s" % (form, token))
1942
1941
1943
1942
1944 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1943 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1945 select_html = select(name, selected, options, **attrs)
1944 select_html = select(name, selected, options, **attrs)
1946
1945
1947 select2 = """
1946 select2 = """
1948 <script>
1947 <script>
1949 $(document).ready(function() {
1948 $(document).ready(function() {
1950 $('#%s').select2({
1949 $('#%s').select2({
1951 containerCssClass: 'drop-menu %s',
1950 containerCssClass: 'drop-menu %s',
1952 dropdownCssClass: 'drop-menu-dropdown',
1951 dropdownCssClass: 'drop-menu-dropdown',
1953 dropdownAutoWidth: true%s
1952 dropdownAutoWidth: true%s
1954 });
1953 });
1955 });
1954 });
1956 </script>
1955 </script>
1957 """
1956 """
1958
1957
1959 filter_option = """,
1958 filter_option = """,
1960 minimumResultsForSearch: -1
1959 minimumResultsForSearch: -1
1961 """
1960 """
1962 input_id = attrs.get('id') or name
1961 input_id = attrs.get('id') or name
1963 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1962 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1964 filter_enabled = "" if enable_filter else filter_option
1963 filter_enabled = "" if enable_filter else filter_option
1965 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1964 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1966
1965
1967 return literal(select_html+select_script)
1966 return literal(select_html+select_script)
1968
1967
1969
1968
1970 def get_visual_attr(tmpl_context_var, attr_name):
1969 def get_visual_attr(tmpl_context_var, attr_name):
1971 """
1970 """
1972 A safe way to get a variable from visual variable of template context
1971 A safe way to get a variable from visual variable of template context
1973
1972
1974 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1973 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1975 :param attr_name: name of the attribute we fetch from the c.visual
1974 :param attr_name: name of the attribute we fetch from the c.visual
1976 """
1975 """
1977 visual = getattr(tmpl_context_var, 'visual', None)
1976 visual = getattr(tmpl_context_var, 'visual', None)
1978 if not visual:
1977 if not visual:
1979 return
1978 return
1980 else:
1979 else:
1981 return getattr(visual, attr_name, None)
1980 return getattr(visual, attr_name, None)
1982
1981
1983
1982
1984 def get_last_path_part(file_node):
1983 def get_last_path_part(file_node):
1985 if not file_node.path:
1984 if not file_node.path:
1986 return u'/'
1985 return u'/'
1987
1986
1988 path = safe_unicode(file_node.path.split('/')[-1])
1987 path = safe_unicode(file_node.path.split('/')[-1])
1989 return u'../' + path
1988 return u'../' + path
1990
1989
1991
1990
1992 def route_url(*args, **kwargs):
1991 def route_url(*args, **kwargs):
1993 """
1992 """
1994 Wrapper around pyramids `route_url` (fully qualified url) function.
1993 Wrapper around pyramids `route_url` (fully qualified url) function.
1995 """
1994 """
1996 req = get_current_request()
1995 req = get_current_request()
1997 return req.route_url(*args, **kwargs)
1996 return req.route_url(*args, **kwargs)
1998
1997
1999
1998
2000 def route_path(*args, **kwargs):
1999 def route_path(*args, **kwargs):
2001 """
2000 """
2002 Wrapper around pyramids `route_path` function.
2001 Wrapper around pyramids `route_path` function.
2003 """
2002 """
2004 req = get_current_request()
2003 req = get_current_request()
2005 return req.route_path(*args, **kwargs)
2004 return req.route_path(*args, **kwargs)
2006
2005
2007
2006
2008 def route_path_or_none(*args, **kwargs):
2007 def route_path_or_none(*args, **kwargs):
2009 try:
2008 try:
2010 return route_path(*args, **kwargs)
2009 return route_path(*args, **kwargs)
2011 except KeyError:
2010 except KeyError:
2012 return None
2011 return None
2013
2012
2014
2013
2015 def current_route_path(request, **kw):
2014 def current_route_path(request, **kw):
2016 new_args = request.GET.mixed()
2015 new_args = request.GET.mixed()
2017 new_args.update(kw)
2016 new_args.update(kw)
2018 return request.current_route_path(_query=new_args)
2017 return request.current_route_path(_query=new_args)
2019
2018
2020
2019
2021 def curl_api_example(method, args):
2020 def curl_api_example(method, args):
2022 args_json = json.dumps(OrderedDict([
2021 args_json = json.dumps(OrderedDict([
2023 ('id', 1),
2022 ('id', 1),
2024 ('auth_token', 'SECRET'),
2023 ('auth_token', 'SECRET'),
2025 ('method', method),
2024 ('method', method),
2026 ('args', args)
2025 ('args', args)
2027 ]))
2026 ]))
2028
2027
2029 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
2028 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
2030 api_url=route_url('apiv2'),
2029 api_url=route_url('apiv2'),
2031 args_json=args_json
2030 args_json=args_json
2032 )
2031 )
2033
2032
2034
2033
2035 def api_call_example(method, args):
2034 def api_call_example(method, args):
2036 """
2035 """
2037 Generates an API call example via CURL
2036 Generates an API call example via CURL
2038 """
2037 """
2039 curl_call = curl_api_example(method, args)
2038 curl_call = curl_api_example(method, args)
2040
2039
2041 return literal(
2040 return literal(
2042 curl_call +
2041 curl_call +
2043 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2042 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2044 "and needs to be of `api calls` role."
2043 "and needs to be of `api calls` role."
2045 .format(token_url=route_url('my_account_auth_tokens')))
2044 .format(token_url=route_url('my_account_auth_tokens')))
2046
2045
2047
2046
2048 def notification_description(notification, request):
2047 def notification_description(notification, request):
2049 """
2048 """
2050 Generate notification human readable description based on notification type
2049 Generate notification human readable description based on notification type
2051 """
2050 """
2052 from rhodecode.model.notification import NotificationModel
2051 from rhodecode.model.notification import NotificationModel
2053 return NotificationModel().make_description(
2052 return NotificationModel().make_description(
2054 notification, translate=request.translate)
2053 notification, translate=request.translate)
2055
2054
2056
2055
2057 def go_import_header(request, db_repo=None):
2056 def go_import_header(request, db_repo=None):
2058 """
2057 """
2059 Creates a header for go-import functionality in Go Lang
2058 Creates a header for go-import functionality in Go Lang
2060 """
2059 """
2061
2060
2062 if not db_repo:
2061 if not db_repo:
2063 return
2062 return
2064 if 'go-get' not in request.GET:
2063 if 'go-get' not in request.GET:
2065 return
2064 return
2066
2065
2067 clone_url = db_repo.clone_url()
2066 clone_url = db_repo.clone_url()
2068 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2067 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2069 # we have a repo and go-get flag,
2068 # we have a repo and go-get flag,
2070 return literal('<meta name="go-import" content="{} {} {}">'.format(
2069 return literal('<meta name="go-import" content="{} {} {}">'.format(
2071 prefix, db_repo.repo_type, clone_url))
2070 prefix, db_repo.repo_type, clone_url))
2072
2071
2073
2072
2074 def reviewer_as_json(*args, **kwargs):
2073 def reviewer_as_json(*args, **kwargs):
2075 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2074 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2076 return _reviewer_as_json(*args, **kwargs)
2075 return _reviewer_as_json(*args, **kwargs)
2077
2076
2078
2077
2079 def get_repo_view_type(request):
2078 def get_repo_view_type(request):
2080 route_name = request.matched_route.name
2079 route_name = request.matched_route.name
2081 route_to_view_type = {
2080 route_to_view_type = {
2082 'repo_changelog': 'commits',
2081 'repo_changelog': 'commits',
2083 'repo_commits': 'commits',
2082 'repo_commits': 'commits',
2084 'repo_files': 'files',
2083 'repo_files': 'files',
2085 'repo_summary': 'summary',
2084 'repo_summary': 'summary',
2086 'repo_commit': 'commit'
2085 'repo_commit': 'commit'
2087 }
2086 }
2088
2087
2089 return route_to_view_type.get(route_name)
2088 return route_to_view_type.get(route_name)
@@ -1,385 +1,385 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
34 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
35 pyroutes.register('hovercard_commit', '/_hovercard/commit/%(repo_name)s/%(user_id)s', ['repo_name', 'user_id']);
35 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
36 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
36 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
37 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
37 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
38 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
38 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
39 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
39 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
40 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
40 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
41 pyroutes.register('admin_home', '/_admin', []);
41 pyroutes.register('admin_home', '/_admin', []);
42 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
42 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
43 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
43 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
44 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
45 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
45 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
47 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
48 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
48 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
49 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
49 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
50 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
50 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
51 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
51 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
52 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
52 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
53 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
53 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
54 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
54 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
55 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
55 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
56 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
56 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
57 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
57 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
58 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
58 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
59 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
59 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
60 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
60 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
61 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
61 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
62 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
62 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
63 pyroutes.register('admin_settings', '/_admin/settings', []);
63 pyroutes.register('admin_settings', '/_admin/settings', []);
64 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
64 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
65 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
65 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
66 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
66 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
67 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
67 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
68 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
68 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
69 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
69 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
70 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
70 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
71 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
71 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
72 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
72 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
73 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
73 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
74 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
74 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
75 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
75 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
76 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
76 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
77 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
77 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
78 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
78 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
79 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
79 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
80 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
80 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
81 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
81 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
82 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
82 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
83 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
83 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
84 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
84 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
85 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
85 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
86 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
86 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
87 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
87 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
88 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
88 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
89 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
89 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
90 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
90 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
91 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
91 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
92 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
92 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
93 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
93 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
94 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
94 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
95 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
95 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
96 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
96 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
97 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
97 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
98 pyroutes.register('users', '/_admin/users', []);
98 pyroutes.register('users', '/_admin/users', []);
99 pyroutes.register('users_data', '/_admin/users_data', []);
99 pyroutes.register('users_data', '/_admin/users_data', []);
100 pyroutes.register('users_create', '/_admin/users/create', []);
100 pyroutes.register('users_create', '/_admin/users/create', []);
101 pyroutes.register('users_new', '/_admin/users/new', []);
101 pyroutes.register('users_new', '/_admin/users/new', []);
102 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
102 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
103 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
103 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
104 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
104 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
105 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
105 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
106 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
106 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
107 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
107 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
108 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
108 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
109 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
109 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
110 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
110 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
111 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
111 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
116 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
116 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
117 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
117 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
118 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
118 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
119 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
119 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
120 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
120 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
121 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
121 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
122 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
122 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
123 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
123 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
124 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
124 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
125 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
125 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
126 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
126 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
127 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
127 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
128 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
128 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
129 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
129 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
130 pyroutes.register('user_groups', '/_admin/user_groups', []);
130 pyroutes.register('user_groups', '/_admin/user_groups', []);
131 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
131 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
132 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
132 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
133 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
133 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
134 pyroutes.register('repos', '/_admin/repos', []);
134 pyroutes.register('repos', '/_admin/repos', []);
135 pyroutes.register('repo_new', '/_admin/repos/new', []);
135 pyroutes.register('repo_new', '/_admin/repos/new', []);
136 pyroutes.register('repo_create', '/_admin/repos/create', []);
136 pyroutes.register('repo_create', '/_admin/repos/create', []);
137 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
137 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
138 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
138 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
139 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
139 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
140 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
140 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
141 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
141 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
142 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
142 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
143 pyroutes.register('channelstream_proxy', '/_channelstream', []);
143 pyroutes.register('channelstream_proxy', '/_channelstream', []);
144 pyroutes.register('upload_file', '/_file_store/upload', []);
144 pyroutes.register('upload_file', '/_file_store/upload', []);
145 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
145 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
146 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
146 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
147 pyroutes.register('logout', '/_admin/logout', []);
147 pyroutes.register('logout', '/_admin/logout', []);
148 pyroutes.register('reset_password', '/_admin/password_reset', []);
148 pyroutes.register('reset_password', '/_admin/password_reset', []);
149 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
149 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
150 pyroutes.register('home', '/', []);
150 pyroutes.register('home', '/', []);
151 pyroutes.register('user_autocomplete_data', '/_users', []);
151 pyroutes.register('user_autocomplete_data', '/_users', []);
152 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
152 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
153 pyroutes.register('repo_list_data', '/_repos', []);
153 pyroutes.register('repo_list_data', '/_repos', []);
154 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
154 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
155 pyroutes.register('goto_switcher_data', '/_goto_data', []);
155 pyroutes.register('goto_switcher_data', '/_goto_data', []);
156 pyroutes.register('markup_preview', '/_markup_preview', []);
156 pyroutes.register('markup_preview', '/_markup_preview', []);
157 pyroutes.register('file_preview', '/_file_preview', []);
157 pyroutes.register('file_preview', '/_file_preview', []);
158 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
158 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
159 pyroutes.register('journal', '/_admin/journal', []);
159 pyroutes.register('journal', '/_admin/journal', []);
160 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
160 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
161 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
161 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
162 pyroutes.register('journal_public', '/_admin/public_journal', []);
162 pyroutes.register('journal_public', '/_admin/public_journal', []);
163 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
163 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
164 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
164 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
165 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
165 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
166 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
166 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
167 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
167 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
168 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
168 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
169 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
169 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
170 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
170 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
171 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
171 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
172 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
182 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
183 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
184 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
184 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
185 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
185 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
186 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
186 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
187 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
188 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
189 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
190 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
194 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
195 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
208 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
209 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
209 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
210 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
210 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
211 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
211 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
212 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
212 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
213 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
214 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
215 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
215 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
216 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
216 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
217 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
217 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
218 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
218 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
219 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
219 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
220 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
220 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
221 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
221 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
222 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
222 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
223 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
223 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
224 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
224 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
225 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
225 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
226 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
226 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
227 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
228 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
228 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
229 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
229 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
230 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
230 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
231 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
231 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
232 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
232 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
233 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
233 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
234 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
234 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
235 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
235 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
236 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
236 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
237 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
237 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
238 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
238 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
239 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
239 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
240 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
240 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
241 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
241 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
242 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
242 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
243 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
243 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
244 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
244 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
245 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
245 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
246 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
246 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
247 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
247 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
248 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
248 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
249 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
249 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
250 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
250 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
251 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
251 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
252 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
252 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
253 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
253 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
254 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
254 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
255 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
255 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
256 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
256 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
257 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
257 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
258 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
258 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
259 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
259 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
260 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
260 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
261 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
261 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
262 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
262 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
263 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
263 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
264 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
264 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
265 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
265 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
266 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
266 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
267 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
267 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
268 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
268 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
269 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
269 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
270 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
270 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
271 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
271 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
272 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
272 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
273 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
273 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
274 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
274 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
275 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
275 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
276 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
276 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
277 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
277 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
278 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
278 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
279 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
279 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
280 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
280 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
281 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
281 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
282 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
282 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
283 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
283 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
284 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
284 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
285 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
285 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
286 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
286 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
287 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
287 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
288 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
288 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
289 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
289 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
290 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
290 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
291 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
291 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
292 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
292 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
293 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
293 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
294 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
294 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
295 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
295 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
296 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
296 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
297 pyroutes.register('search', '/_admin/search', []);
297 pyroutes.register('search', '/_admin/search', []);
298 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
298 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
299 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
299 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
300 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
300 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
301 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
301 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
302 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
302 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
303 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
303 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
304 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
304 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
305 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
305 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
306 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
306 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
307 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
307 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
308 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
308 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
309 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
309 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
310 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
310 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
311 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
311 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
312 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
312 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
313 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
313 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
314 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
314 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
315 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
315 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
316 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
316 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
317 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
317 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
318 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
318 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
319 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
319 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
320 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
320 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
321 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
321 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
322 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
322 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
323 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
323 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
324 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
324 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
325 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
325 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
326 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
326 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
327 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
327 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
328 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
328 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
329 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
329 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
330 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
330 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
331 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
331 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
332 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
332 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
333 pyroutes.register('gists_show', '/_admin/gists', []);
333 pyroutes.register('gists_show', '/_admin/gists', []);
334 pyroutes.register('gists_new', '/_admin/gists/new', []);
334 pyroutes.register('gists_new', '/_admin/gists/new', []);
335 pyroutes.register('gists_create', '/_admin/gists/create', []);
335 pyroutes.register('gists_create', '/_admin/gists/create', []);
336 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
336 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
337 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
337 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
338 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
338 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
339 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
339 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
340 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
340 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
341 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
341 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
342 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
342 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
343 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
343 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
344 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
344 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
345 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
345 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
346 pyroutes.register('apiv2', '/_admin/api', []);
346 pyroutes.register('apiv2', '/_admin/api', []);
347 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
347 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
348 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
348 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
349 pyroutes.register('login', '/_admin/login', []);
349 pyroutes.register('login', '/_admin/login', []);
350 pyroutes.register('register', '/_admin/register', []);
350 pyroutes.register('register', '/_admin/register', []);
351 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
351 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
352 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
352 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
353 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
353 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
354 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
354 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
355 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
355 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
356 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
356 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
357 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
357 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
358 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
358 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
359 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
359 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
360 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
360 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
361 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
361 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
362 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
362 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
363 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
363 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
364 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
364 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
365 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
365 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
366 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
366 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
367 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
367 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
368 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
368 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
369 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
369 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
370 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
370 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
371 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
371 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
372 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
372 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
373 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
373 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
374 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
374 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
375 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
375 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
376 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
376 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
377 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
377 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
378 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
378 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
379 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
379 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
380 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
380 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
381 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
381 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
382 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
382 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
383 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
383 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
384 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
384 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
385 }
385 }
@@ -1,661 +1,663 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 RhodeCode JS Files
20 RhodeCode JS Files
21 **/
21 **/
22
22
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
24 console = { log: function() {} }
24 console = { log: function() {} }
25 }
25 }
26
26
27 // TODO: move the following function to submodules
27 // TODO: move the following function to submodules
28
28
29 /**
29 /**
30 * show more
30 * show more
31 */
31 */
32 var show_more_event = function(){
32 var show_more_event = function(){
33 $('table .show_more').click(function(e) {
33 $('table .show_more').click(function(e) {
34 var cid = e.target.id.substring(1);
34 var cid = e.target.id.substring(1);
35 var button = $(this);
35 var button = $(this);
36 if (button.hasClass('open')) {
36 if (button.hasClass('open')) {
37 $('#'+cid).hide();
37 $('#'+cid).hide();
38 button.removeClass('open');
38 button.removeClass('open');
39 } else {
39 } else {
40 $('#'+cid).show();
40 $('#'+cid).show();
41 button.addClass('open one');
41 button.addClass('open one');
42 }
42 }
43 });
43 });
44 };
44 };
45
45
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
47 $('#compare_action').on('click', function(e){
47 $('#compare_action').on('click', function(e){
48 e.preventDefault();
48 e.preventDefault();
49
49
50 var source = $('input[name=compare_source]:checked').val();
50 var source = $('input[name=compare_source]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
52 if(source && target){
52 if(source && target){
53 var url_data = {
53 var url_data = {
54 repo_name: repo_name,
54 repo_name: repo_name,
55 source_ref: source,
55 source_ref: source,
56 source_ref_type: compare_ref_type,
56 source_ref_type: compare_ref_type,
57 target_ref: target,
57 target_ref: target,
58 target_ref_type: compare_ref_type,
58 target_ref_type: compare_ref_type,
59 merge: 1
59 merge: 1
60 };
60 };
61 window.location = pyroutes.url('repo_compare', url_data);
61 window.location = pyroutes.url('repo_compare', url_data);
62 }
62 }
63 });
63 });
64 $('.compare-radio-button').on('click', function(e){
64 $('.compare-radio-button').on('click', function(e){
65 var source = $('input[name=compare_source]:checked').val();
65 var source = $('input[name=compare_source]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
67 if(source && target){
67 if(source && target){
68 $('#compare_action').removeAttr("disabled");
68 $('#compare_action').removeAttr("disabled");
69 $('#compare_action').removeClass("disabled");
69 $('#compare_action').removeClass("disabled");
70 }
70 }
71 })
71 })
72 };
72 };
73
73
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
75 var container = $('#' + target);
75 var container = $('#' + target);
76 var url = pyroutes.url('repo_stats',
76 var url = pyroutes.url('repo_stats',
77 {"repo_name": repo_name, "commit_id": commit_id});
77 {"repo_name": repo_name, "commit_id": commit_id});
78
78
79 container.show();
79 container.show();
80 if (!container.hasClass('loaded')) {
80 if (!container.hasClass('loaded')) {
81 $.ajax({url: url})
81 $.ajax({url: url})
82 .complete(function (data) {
82 .complete(function (data) {
83 var responseJSON = data.responseJSON;
83 var responseJSON = data.responseJSON;
84 container.addClass('loaded');
84 container.addClass('loaded');
85 container.html(responseJSON.size);
85 container.html(responseJSON.size);
86 callback(responseJSON.code_stats)
86 callback(responseJSON.code_stats)
87 })
87 })
88 .fail(function (data) {
88 .fail(function (data) {
89 console.log('failed to load repo stats');
89 console.log('failed to load repo stats');
90 });
90 });
91 }
91 }
92
92
93 };
93 };
94
94
95 var showRepoStats = function(target, data){
95 var showRepoStats = function(target, data){
96 var container = $('#' + target);
96 var container = $('#' + target);
97
97
98 if (container.hasClass('loaded')) {
98 if (container.hasClass('loaded')) {
99 return
99 return
100 }
100 }
101
101
102 var total = 0;
102 var total = 0;
103 var no_data = true;
103 var no_data = true;
104 var tbl = document.createElement('table');
104 var tbl = document.createElement('table');
105 tbl.setAttribute('class', 'trending_language_tbl rctable');
105 tbl.setAttribute('class', 'trending_language_tbl rctable');
106
106
107 $.each(data, function(key, val){
107 $.each(data, function(key, val){
108 total += val.count;
108 total += val.count;
109 });
109 });
110
110
111 var sortedStats = [];
111 var sortedStats = [];
112 for (var obj in data){
112 for (var obj in data){
113 sortedStats.push([obj, data[obj]])
113 sortedStats.push([obj, data[obj]])
114 }
114 }
115 var sortedData = sortedStats.sort(function (a, b) {
115 var sortedData = sortedStats.sort(function (a, b) {
116 return b[1].count - a[1].count
116 return b[1].count - a[1].count
117 });
117 });
118 var cnt = 0;
118 var cnt = 0;
119 $.each(sortedData, function(idx, val){
119 $.each(sortedData, function(idx, val){
120 cnt += 1;
120 cnt += 1;
121 no_data = false;
121 no_data = false;
122
122
123 var tr = document.createElement('tr');
123 var tr = document.createElement('tr');
124
124
125 var key = val[0];
125 var key = val[0];
126 var obj = {"desc": val[1].desc, "count": val[1].count};
126 var obj = {"desc": val[1].desc, "count": val[1].count};
127
127
128 // meta language names
128 // meta language names
129 var td1 = document.createElement('td');
129 var td1 = document.createElement('td');
130 var trending_language_label = document.createElement('div');
130 var trending_language_label = document.createElement('div');
131 trending_language_label.innerHTML = obj.desc;
131 trending_language_label.innerHTML = obj.desc;
132 td1.appendChild(trending_language_label);
132 td1.appendChild(trending_language_label);
133
133
134 // extensions
134 // extensions
135 var td2 = document.createElement('td');
135 var td2 = document.createElement('td');
136 var extension = document.createElement('div');
136 var extension = document.createElement('div');
137 extension.innerHTML = ".{0}".format(key)
137 extension.innerHTML = ".{0}".format(key)
138 td2.appendChild(extension);
138 td2.appendChild(extension);
139
139
140 // number of files
140 // number of files
141 var td3 = document.createElement('td');
141 var td3 = document.createElement('td');
142 var file_count = document.createElement('div');
142 var file_count = document.createElement('div');
143 var percentage_num = Math.round((obj.count / total * 100), 2);
143 var percentage_num = Math.round((obj.count / total * 100), 2);
144 var label = _ngettext('file', 'files', obj.count);
144 var label = _ngettext('file', 'files', obj.count);
145 file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ;
145 file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ;
146 td3.appendChild(file_count);
146 td3.appendChild(file_count);
147
147
148 // percentage
148 // percentage
149 var td4 = document.createElement('td');
149 var td4 = document.createElement('td');
150 td4.setAttribute("class", 'trending_language');
150 td4.setAttribute("class", 'trending_language');
151
151
152 var percentage = document.createElement('div');
152 var percentage = document.createElement('div');
153 percentage.setAttribute('class', 'lang-bar');
153 percentage.setAttribute('class', 'lang-bar');
154 percentage.innerHTML = "&nbsp;";
154 percentage.innerHTML = "&nbsp;";
155 percentage.style.width = percentage_num + '%';
155 percentage.style.width = percentage_num + '%';
156 td4.appendChild(percentage);
156 td4.appendChild(percentage);
157
157
158 tr.appendChild(td1);
158 tr.appendChild(td1);
159 tr.appendChild(td2);
159 tr.appendChild(td2);
160 tr.appendChild(td3);
160 tr.appendChild(td3);
161 tr.appendChild(td4);
161 tr.appendChild(td4);
162 tbl.appendChild(tr);
162 tbl.appendChild(tr);
163
163
164 });
164 });
165
165
166 $(container).html(tbl);
166 $(container).html(tbl);
167 $(container).addClass('loaded');
167 $(container).addClass('loaded');
168
168
169 $('#code_stats_show_more').on('click', function (e) {
169 $('#code_stats_show_more').on('click', function (e) {
170 e.preventDefault();
170 e.preventDefault();
171 $('.stats_hidden').each(function (idx) {
171 $('.stats_hidden').each(function (idx) {
172 $(this).css("display", "");
172 $(this).css("display", "");
173 });
173 });
174 $('#code_stats_show_more').hide();
174 $('#code_stats_show_more').hide();
175 });
175 });
176
176
177 };
177 };
178
178
179 // returns a node from given html;
179 // returns a node from given html;
180 var fromHTML = function(html){
180 var fromHTML = function(html){
181 var _html = document.createElement('element');
181 var _html = document.createElement('element');
182 _html.innerHTML = html;
182 _html.innerHTML = html;
183 return _html;
183 return _html;
184 };
184 };
185
185
186 // Toggle Collapsable Content
186 // Toggle Collapsable Content
187 function collapsableContent() {
187 function collapsableContent() {
188
188
189 $('.collapsable-content').not('.no-hide').hide();
189 $('.collapsable-content').not('.no-hide').hide();
190
190
191 $('.btn-collapse').unbind(); //in case we've been here before
191 $('.btn-collapse').unbind(); //in case we've been here before
192 $('.btn-collapse').click(function() {
192 $('.btn-collapse').click(function() {
193 var button = $(this);
193 var button = $(this);
194 var togglename = $(this).data("toggle");
194 var togglename = $(this).data("toggle");
195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
196 if ($(this).html()=="Show Less")
196 if ($(this).html()=="Show Less")
197 $(this).html("Show More");
197 $(this).html("Show More");
198 else
198 else
199 $(this).html("Show Less");
199 $(this).html("Show Less");
200 });
200 });
201 };
201 };
202
202
203 var timeagoActivate = function() {
203 var timeagoActivate = function() {
204 $("time.timeago").timeago();
204 $("time.timeago").timeago();
205 };
205 };
206
206
207
207
208 var clipboardActivate = function() {
208 var clipboardActivate = function() {
209 /*
209 /*
210 *
210 *
211 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
211 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
212 * */
212 * */
213 var clipboard = new ClipboardJS('.clipboard-action');
213 var clipboard = new ClipboardJS('.clipboard-action');
214
214
215 clipboard.on('success', function(e) {
215 clipboard.on('success', function(e) {
216 var callback = function () {
216 var callback = function () {
217 $(e.trigger).animate({'opacity': 1.00}, 200)
217 $(e.trigger).animate({'opacity': 1.00}, 200)
218 };
218 };
219 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
219 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
220 e.clearSelection();
220 e.clearSelection();
221 });
221 });
222 };
222 };
223
223
224 var tooltipActivate = function () {
224 var tooltipActivate = function () {
225 var delay = 50;
225 var delay = 50;
226 var animation = 'fade';
226 var animation = 'fade';
227 var theme = 'tooltipster-shadow';
227 var theme = 'tooltipster-shadow';
228 var debug = false;
228 var debug = false;
229
229
230 $('.tooltip').tooltipster({
230 $('.tooltip').tooltipster({
231 debug: debug,
231 debug: debug,
232 theme: theme,
232 theme: theme,
233 animation: animation,
233 animation: animation,
234 delay: delay,
234 delay: delay,
235 contentCloning: true,
235 contentCloning: true,
236 contentAsHTML: true,
236 contentAsHTML: true,
237
237
238 functionBefore: function (instance, helper) {
238 functionBefore: function (instance, helper) {
239 var $origin = $(helper.origin);
239 var $origin = $(helper.origin);
240 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content());
240 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content());
241 instance.content(data);
241 instance.content(data);
242 }
242 }
243 });
243 });
244 var hovercardCache = {};
244 var hovercardCache = {};
245
245
246 var loadHoverCard = function (url, callback) {
246 var loadHoverCard = function (url, callback) {
247 var id = url;
247 var id = url;
248
248
249 if (hovercardCache[id] !== undefined) {
249 if (hovercardCache[id] !== undefined) {
250 callback(hovercardCache[id]);
250 callback(hovercardCache[id]);
251 return;
251 return true;
252 }
252 }
253
253
254 hovercardCache[id] = undefined;
254 hovercardCache[id] = undefined;
255 $.get(url, function (data) {
255 $.get(url, function (data) {
256 hovercardCache[id] = data;
256 hovercardCache[id] = data;
257 callback(hovercardCache[id]);
257 callback(hovercardCache[id]);
258 return true;
258 }).fail(function (data, textStatus, errorThrown) {
259 }).fail(function (data, textStatus, errorThrown) {
259 var msg = "Error while fetching hovercard.\nError code {0} ({1}).".format(data.status,data.statusText);
260 var msg = "<p class='error-message'>Error while fetching hovercard.\nError code {0} ({1}).</p>".format(data.status,data.statusText);
260 callback(msg);
261 callback(msg);
262 return false
261 });
263 });
262 };
264 };
263
265
264 $('.tooltip-hovercard').tooltipster({
266 $('.tooltip-hovercard').tooltipster({
265 debug: debug,
267 debug: debug,
266 theme: theme,
268 theme: theme,
267 animation: animation,
269 animation: animation,
268 delay: delay,
270 delay: delay,
269 interactive: true,
271 interactive: true,
270 contentCloning: true,
272 contentCloning: true,
271
273
272 trigger: 'custom',
274 trigger: 'custom',
273 triggerOpen: {
275 triggerOpen: {
274 mouseenter: true,
276 mouseenter: true,
275 },
277 },
276 triggerClose: {
278 triggerClose: {
277 mouseleave: true,
279 mouseleave: true,
278 originClick: true,
280 originClick: true,
279 touchleave: true
281 touchleave: true
280 },
282 },
281 content: _gettext('Loading...'),
283 content: _gettext('Loading...'),
282 contentAsHTML: true,
284 contentAsHTML: true,
283 updateAnimation: null,
285 updateAnimation: null,
284
286
285 functionBefore: function (instance, helper) {
287 functionBefore: function (instance, helper) {
286
288
287 var $origin = $(helper.origin);
289 var $origin = $(helper.origin);
288
290
289 // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
291 // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
290 if ($origin.data('loaded') !== true) {
292 if ($origin.data('loaded') !== true) {
291 var hovercardUrl = $origin.data('hovercardUrl');
293 var hovercardUrl = $origin.data('hovercardUrl');
292
294
293 if (hovercardUrl !== undefined && hovercardUrl !== "") {
295 if (hovercardUrl !== undefined && hovercardUrl !== "") {
294 loadHoverCard(hovercardUrl, function (data) {
296 var loaded = loadHoverCard(hovercardUrl, function (data) {
295 instance.content(data);
297 instance.content(data);
296 })
298 })
297 } else {
299 } else {
298 if ($origin.data('hovercardAltHtml')) {
300 if ($origin.data('hovercardAltHtml')) {
299 var data = atob($origin.data('hovercardAltHtml'));
301 var data = atob($origin.data('hovercardAltHtml'));
300 } else {
302 } else {
301 var data = '<div style="white-space: pre-wrap">{0}</div>'.format($origin.data('hovercardAlt'))
303 var data = '<div style="white-space: pre-wrap">{0}</div>'.format($origin.data('hovercardAlt'))
302 }
304 }
303
305 var loaded = true;
304 instance.content(data);
306 instance.content(data);
305 }
307 }
306
308
307 // to remember that the data has been loaded
309 // to remember that the data has been loaded
308 $origin.data('loaded', true);
310 $origin.data('loaded', loaded);
309 }
311 }
310 }
312 }
311 })
313 })
312 };
314 };
313
315
314 // Formatting values in a Select2 dropdown of commit references
316 // Formatting values in a Select2 dropdown of commit references
315 var formatSelect2SelectionRefs = function(commit_ref){
317 var formatSelect2SelectionRefs = function(commit_ref){
316 var tmpl = '';
318 var tmpl = '';
317 if (!commit_ref.text || commit_ref.type === 'sha'){
319 if (!commit_ref.text || commit_ref.type === 'sha'){
318 return commit_ref.text;
320 return commit_ref.text;
319 }
321 }
320 if (commit_ref.type === 'branch'){
322 if (commit_ref.type === 'branch'){
321 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
323 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
322 } else if (commit_ref.type === 'tag'){
324 } else if (commit_ref.type === 'tag'){
323 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
325 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
324 } else if (commit_ref.type === 'book'){
326 } else if (commit_ref.type === 'book'){
325 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
327 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
326 }
328 }
327 return tmpl.concat(escapeHtml(commit_ref.text));
329 return tmpl.concat(escapeHtml(commit_ref.text));
328 };
330 };
329
331
330 // takes a given html element and scrolls it down offset pixels
332 // takes a given html element and scrolls it down offset pixels
331 function offsetScroll(element, offset) {
333 function offsetScroll(element, offset) {
332 setTimeout(function() {
334 setTimeout(function() {
333 var location = element.offset().top;
335 var location = element.offset().top;
334 // some browsers use body, some use html
336 // some browsers use body, some use html
335 $('html, body').animate({ scrollTop: (location - offset) });
337 $('html, body').animate({ scrollTop: (location - offset) });
336 }, 100);
338 }, 100);
337 }
339 }
338
340
339 // scroll an element `percent`% from the top of page in `time` ms
341 // scroll an element `percent`% from the top of page in `time` ms
340 function scrollToElement(element, percent, time) {
342 function scrollToElement(element, percent, time) {
341 percent = (percent === undefined ? 25 : percent);
343 percent = (percent === undefined ? 25 : percent);
342 time = (time === undefined ? 100 : time);
344 time = (time === undefined ? 100 : time);
343
345
344 var $element = $(element);
346 var $element = $(element);
345 if ($element.length == 0) {
347 if ($element.length == 0) {
346 throw('Cannot scroll to {0}'.format(element))
348 throw('Cannot scroll to {0}'.format(element))
347 }
349 }
348 var elOffset = $element.offset().top;
350 var elOffset = $element.offset().top;
349 var elHeight = $element.height();
351 var elHeight = $element.height();
350 var windowHeight = $(window).height();
352 var windowHeight = $(window).height();
351 var offset = elOffset;
353 var offset = elOffset;
352 if (elHeight < windowHeight) {
354 if (elHeight < windowHeight) {
353 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
355 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
354 }
356 }
355 setTimeout(function() {
357 setTimeout(function() {
356 $('html, body').animate({ scrollTop: offset});
358 $('html, body').animate({ scrollTop: offset});
357 }, time);
359 }, time);
358 }
360 }
359
361
360 /**
362 /**
361 * global hooks after DOM is loaded
363 * global hooks after DOM is loaded
362 */
364 */
363 $(document).ready(function() {
365 $(document).ready(function() {
364 firefoxAnchorFix();
366 firefoxAnchorFix();
365
367
366 $('.navigation a.menulink').on('click', function(e){
368 $('.navigation a.menulink').on('click', function(e){
367 var menuitem = $(this).parent('li');
369 var menuitem = $(this).parent('li');
368 if (menuitem.hasClass('open')) {
370 if (menuitem.hasClass('open')) {
369 menuitem.removeClass('open');
371 menuitem.removeClass('open');
370 } else {
372 } else {
371 menuitem.addClass('open');
373 menuitem.addClass('open');
372 $(document).on('click', function(event) {
374 $(document).on('click', function(event) {
373 if (!$(event.target).closest(menuitem).length) {
375 if (!$(event.target).closest(menuitem).length) {
374 menuitem.removeClass('open');
376 menuitem.removeClass('open');
375 }
377 }
376 });
378 });
377 }
379 }
378 });
380 });
379
381
380 $('body').on('click', '.cb-lineno a', function(event) {
382 $('body').on('click', '.cb-lineno a', function(event) {
381 function sortNumber(a,b) {
383 function sortNumber(a,b) {
382 return a - b;
384 return a - b;
383 }
385 }
384
386
385 var lineNo = $(this).data('lineNo');
387 var lineNo = $(this).data('lineNo');
386 var lineName = $(this).attr('name');
388 var lineName = $(this).attr('name');
387
389
388 if (lineNo) {
390 if (lineNo) {
389 var prevLine = $('.cb-line-selected a').data('lineNo');
391 var prevLine = $('.cb-line-selected a').data('lineNo');
390
392
391 // on shift, we do a range selection, if we got previous line
393 // on shift, we do a range selection, if we got previous line
392 if (event.shiftKey && prevLine !== undefined) {
394 if (event.shiftKey && prevLine !== undefined) {
393 var prevLine = parseInt(prevLine);
395 var prevLine = parseInt(prevLine);
394 var nextLine = parseInt(lineNo);
396 var nextLine = parseInt(lineNo);
395 var pos = [prevLine, nextLine].sort(sortNumber);
397 var pos = [prevLine, nextLine].sort(sortNumber);
396 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
398 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
397
399
398 // single click
400 // single click
399 } else {
401 } else {
400 var nextLine = parseInt(lineNo);
402 var nextLine = parseInt(lineNo);
401 var pos = [nextLine, nextLine];
403 var pos = [nextLine, nextLine];
402 var anchor = '#L{0}'.format(pos[0]);
404 var anchor = '#L{0}'.format(pos[0]);
403
405
404 }
406 }
405 // highlight
407 // highlight
406 var range = [];
408 var range = [];
407 for (var i = pos[0]; i <= pos[1]; i++) {
409 for (var i = pos[0]; i <= pos[1]; i++) {
408 range.push(i);
410 range.push(i);
409 }
411 }
410 // clear old selected lines
412 // clear old selected lines
411 $('.cb-line-selected').removeClass('cb-line-selected');
413 $('.cb-line-selected').removeClass('cb-line-selected');
412
414
413 $.each(range, function (i, lineNo) {
415 $.each(range, function (i, lineNo) {
414 var line_td = $('td.cb-lineno#L' + lineNo);
416 var line_td = $('td.cb-lineno#L' + lineNo);
415
417
416 if (line_td.length) {
418 if (line_td.length) {
417 line_td.addClass('cb-line-selected'); // line number td
419 line_td.addClass('cb-line-selected'); // line number td
418 line_td.prev().addClass('cb-line-selected'); // line data
420 line_td.prev().addClass('cb-line-selected'); // line data
419 line_td.next().addClass('cb-line-selected'); // line content
421 line_td.next().addClass('cb-line-selected'); // line content
420 }
422 }
421 });
423 });
422
424
423 } else if (lineName !== undefined) { // lineName only occurs in diffs
425 } else if (lineName !== undefined) { // lineName only occurs in diffs
424 // clear old selected lines
426 // clear old selected lines
425 $('td.cb-line-selected').removeClass('cb-line-selected');
427 $('td.cb-line-selected').removeClass('cb-line-selected');
426 var anchor = '#{0}'.format(lineName);
428 var anchor = '#{0}'.format(lineName);
427 var diffmode = templateContext.session_attrs.diffmode || "sideside";
429 var diffmode = templateContext.session_attrs.diffmode || "sideside";
428
430
429 if (diffmode === "unified") {
431 if (diffmode === "unified") {
430 $(this).closest('tr').find('td').addClass('cb-line-selected');
432 $(this).closest('tr').find('td').addClass('cb-line-selected');
431 } else {
433 } else {
432 var activeTd = $(this).closest('td');
434 var activeTd = $(this).closest('td');
433 activeTd.addClass('cb-line-selected');
435 activeTd.addClass('cb-line-selected');
434 activeTd.next('td').addClass('cb-line-selected');
436 activeTd.next('td').addClass('cb-line-selected');
435 }
437 }
436
438
437 }
439 }
438
440
439 // Replace URL without jumping to it if browser supports.
441 // Replace URL without jumping to it if browser supports.
440 // Default otherwise
442 // Default otherwise
441 if (history.pushState && anchor !== undefined) {
443 if (history.pushState && anchor !== undefined) {
442 var new_location = location.href.rstrip('#');
444 var new_location = location.href.rstrip('#');
443 if (location.hash) {
445 if (location.hash) {
444 // location without hash
446 // location without hash
445 new_location = new_location.replace(location.hash, "");
447 new_location = new_location.replace(location.hash, "");
446 }
448 }
447
449
448 // Make new anchor url
450 // Make new anchor url
449 new_location = new_location + anchor;
451 new_location = new_location + anchor;
450 history.pushState(true, document.title, new_location);
452 history.pushState(true, document.title, new_location);
451
453
452 return false;
454 return false;
453 }
455 }
454
456
455 });
457 });
456
458
457 $('.collapse_file').on('click', function(e) {
459 $('.collapse_file').on('click', function(e) {
458 e.stopPropagation();
460 e.stopPropagation();
459 if ($(e.target).is('a')) { return; }
461 if ($(e.target).is('a')) { return; }
460 var node = $(e.delegateTarget).first();
462 var node = $(e.delegateTarget).first();
461 var icon = $($(node.children().first()).children().first());
463 var icon = $($(node.children().first()).children().first());
462 var id = node.attr('fid');
464 var id = node.attr('fid');
463 var target = $('#'+id);
465 var target = $('#'+id);
464 var tr = $('#tr_'+id);
466 var tr = $('#tr_'+id);
465 var diff = $('#diff_'+id);
467 var diff = $('#diff_'+id);
466 if(node.hasClass('expand_file')){
468 if(node.hasClass('expand_file')){
467 node.removeClass('expand_file');
469 node.removeClass('expand_file');
468 icon.removeClass('expand_file_icon');
470 icon.removeClass('expand_file_icon');
469 node.addClass('collapse_file');
471 node.addClass('collapse_file');
470 icon.addClass('collapse_file_icon');
472 icon.addClass('collapse_file_icon');
471 diff.show();
473 diff.show();
472 tr.show();
474 tr.show();
473 target.show();
475 target.show();
474 } else {
476 } else {
475 node.removeClass('collapse_file');
477 node.removeClass('collapse_file');
476 icon.removeClass('collapse_file_icon');
478 icon.removeClass('collapse_file_icon');
477 node.addClass('expand_file');
479 node.addClass('expand_file');
478 icon.addClass('expand_file_icon');
480 icon.addClass('expand_file_icon');
479 diff.hide();
481 diff.hide();
480 tr.hide();
482 tr.hide();
481 target.hide();
483 target.hide();
482 }
484 }
483 });
485 });
484
486
485 $('#expand_all_files').click(function() {
487 $('#expand_all_files').click(function() {
486 $('.expand_file').each(function() {
488 $('.expand_file').each(function() {
487 var node = $(this);
489 var node = $(this);
488 var icon = $($(node.children().first()).children().first());
490 var icon = $($(node.children().first()).children().first());
489 var id = $(this).attr('fid');
491 var id = $(this).attr('fid');
490 var target = $('#'+id);
492 var target = $('#'+id);
491 var tr = $('#tr_'+id);
493 var tr = $('#tr_'+id);
492 var diff = $('#diff_'+id);
494 var diff = $('#diff_'+id);
493 node.removeClass('expand_file');
495 node.removeClass('expand_file');
494 icon.removeClass('expand_file_icon');
496 icon.removeClass('expand_file_icon');
495 node.addClass('collapse_file');
497 node.addClass('collapse_file');
496 icon.addClass('collapse_file_icon');
498 icon.addClass('collapse_file_icon');
497 diff.show();
499 diff.show();
498 tr.show();
500 tr.show();
499 target.show();
501 target.show();
500 });
502 });
501 });
503 });
502
504
503 $('#collapse_all_files').click(function() {
505 $('#collapse_all_files').click(function() {
504 $('.collapse_file').each(function() {
506 $('.collapse_file').each(function() {
505 var node = $(this);
507 var node = $(this);
506 var icon = $($(node.children().first()).children().first());
508 var icon = $($(node.children().first()).children().first());
507 var id = $(this).attr('fid');
509 var id = $(this).attr('fid');
508 var target = $('#'+id);
510 var target = $('#'+id);
509 var tr = $('#tr_'+id);
511 var tr = $('#tr_'+id);
510 var diff = $('#diff_'+id);
512 var diff = $('#diff_'+id);
511 node.removeClass('collapse_file');
513 node.removeClass('collapse_file');
512 icon.removeClass('collapse_file_icon');
514 icon.removeClass('collapse_file_icon');
513 node.addClass('expand_file');
515 node.addClass('expand_file');
514 icon.addClass('expand_file_icon');
516 icon.addClass('expand_file_icon');
515 diff.hide();
517 diff.hide();
516 tr.hide();
518 tr.hide();
517 target.hide();
519 target.hide();
518 });
520 });
519 });
521 });
520
522
521 // Mouse over behavior for comments and line selection
523 // Mouse over behavior for comments and line selection
522
524
523 // Select the line that comes from the url anchor
525 // Select the line that comes from the url anchor
524 // At the time of development, Chrome didn't seem to support jquery's :target
526 // At the time of development, Chrome didn't seem to support jquery's :target
525 // element, so I had to scroll manually
527 // element, so I had to scroll manually
526
528
527 if (location.hash) {
529 if (location.hash) {
528 var result = splitDelimitedHash(location.hash);
530 var result = splitDelimitedHash(location.hash);
529 var loc = result.loc;
531 var loc = result.loc;
530 if (loc.length > 1) {
532 if (loc.length > 1) {
531
533
532 var highlightable_line_tds = [];
534 var highlightable_line_tds = [];
533
535
534 // source code line format
536 // source code line format
535 var page_highlights = loc.substring(
537 var page_highlights = loc.substring(
536 loc.indexOf('#') + 1).split('L');
538 loc.indexOf('#') + 1).split('L');
537
539
538 if (page_highlights.length > 1) {
540 if (page_highlights.length > 1) {
539 var highlight_ranges = page_highlights[1].split(",");
541 var highlight_ranges = page_highlights[1].split(",");
540 var h_lines = [];
542 var h_lines = [];
541 for (var pos in highlight_ranges) {
543 for (var pos in highlight_ranges) {
542 var _range = highlight_ranges[pos].split('-');
544 var _range = highlight_ranges[pos].split('-');
543 if (_range.length === 2) {
545 if (_range.length === 2) {
544 var start = parseInt(_range[0]);
546 var start = parseInt(_range[0]);
545 var end = parseInt(_range[1]);
547 var end = parseInt(_range[1]);
546 if (start < end) {
548 if (start < end) {
547 for (var i = start; i <= end; i++) {
549 for (var i = start; i <= end; i++) {
548 h_lines.push(i);
550 h_lines.push(i);
549 }
551 }
550 }
552 }
551 }
553 }
552 else {
554 else {
553 h_lines.push(parseInt(highlight_ranges[pos]));
555 h_lines.push(parseInt(highlight_ranges[pos]));
554 }
556 }
555 }
557 }
556 for (pos in h_lines) {
558 for (pos in h_lines) {
557 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
559 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
558 if (line_td.length) {
560 if (line_td.length) {
559 highlightable_line_tds.push(line_td);
561 highlightable_line_tds.push(line_td);
560 }
562 }
561 }
563 }
562 }
564 }
563
565
564 // now check a direct id reference (diff page)
566 // now check a direct id reference (diff page)
565 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
567 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
566 highlightable_line_tds.push($(loc));
568 highlightable_line_tds.push($(loc));
567 }
569 }
568 $.each(highlightable_line_tds, function (i, $td) {
570 $.each(highlightable_line_tds, function (i, $td) {
569 $td.addClass('cb-line-selected'); // line number td
571 $td.addClass('cb-line-selected'); // line number td
570 $td.prev().addClass('cb-line-selected'); // line data
572 $td.prev().addClass('cb-line-selected'); // line data
571 $td.next().addClass('cb-line-selected'); // line content
573 $td.next().addClass('cb-line-selected'); // line content
572 });
574 });
573
575
574 if (highlightable_line_tds.length) {
576 if (highlightable_line_tds.length) {
575 var $first_line_td = highlightable_line_tds[0];
577 var $first_line_td = highlightable_line_tds[0];
576 scrollToElement($first_line_td);
578 scrollToElement($first_line_td);
577 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
579 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
578 td: $first_line_td,
580 td: $first_line_td,
579 remainder: result.remainder
581 remainder: result.remainder
580 });
582 });
581 }
583 }
582 }
584 }
583 }
585 }
584 collapsableContent();
586 collapsableContent();
585 });
587 });
586
588
587 var feedLifetimeOptions = function(query, initialData){
589 var feedLifetimeOptions = function(query, initialData){
588 var data = {results: []};
590 var data = {results: []};
589 var isQuery = typeof query.term !== 'undefined';
591 var isQuery = typeof query.term !== 'undefined';
590
592
591 var section = _gettext('Lifetime');
593 var section = _gettext('Lifetime');
592 var children = [];
594 var children = [];
593
595
594 //filter results
596 //filter results
595 $.each(initialData.results, function(idx, value) {
597 $.each(initialData.results, function(idx, value) {
596
598
597 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
599 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
598 children.push({
600 children.push({
599 'id': this.id,
601 'id': this.id,
600 'text': this.text
602 'text': this.text
601 })
603 })
602 }
604 }
603
605
604 });
606 });
605 data.results.push({
607 data.results.push({
606 'text': section,
608 'text': section,
607 'children': children
609 'children': children
608 });
610 });
609
611
610 if (isQuery) {
612 if (isQuery) {
611
613
612 var now = moment.utc();
614 var now = moment.utc();
613
615
614 var parseQuery = function(entry, now){
616 var parseQuery = function(entry, now){
615 var fmt = 'DD/MM/YYYY H:mm';
617 var fmt = 'DD/MM/YYYY H:mm';
616 var parsed = moment.utc(entry, fmt);
618 var parsed = moment.utc(entry, fmt);
617 var diffInMin = parsed.diff(now, 'minutes');
619 var diffInMin = parsed.diff(now, 'minutes');
618
620
619 if (diffInMin > 0){
621 if (diffInMin > 0){
620 return {
622 return {
621 id: diffInMin,
623 id: diffInMin,
622 text: parsed.format(fmt)
624 text: parsed.format(fmt)
623 }
625 }
624 } else {
626 } else {
625 return {
627 return {
626 id: undefined,
628 id: undefined,
627 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
629 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
628 }
630 }
629 }
631 }
630
632
631
633
632 };
634 };
633
635
634 data.results.push({
636 data.results.push({
635 'text': _gettext('Specified expiration date'),
637 'text': _gettext('Specified expiration date'),
636 'children': [{
638 'children': [{
637 'id': parseQuery(query.term, now).id,
639 'id': parseQuery(query.term, now).id,
638 'text': parseQuery(query.term, now).text
640 'text': parseQuery(query.term, now).text
639 }]
641 }]
640 });
642 });
641 }
643 }
642
644
643 query.callback(data);
645 query.callback(data);
644 };
646 };
645
647
646
648
647 var storeUserSessionAttr = function (key, val) {
649 var storeUserSessionAttr = function (key, val) {
648
650
649 var postData = {
651 var postData = {
650 'key': key,
652 'key': key,
651 'val': val,
653 'val': val,
652 'csrf_token': CSRF_TOKEN
654 'csrf_token': CSRF_TOKEN
653 };
655 };
654
656
655 var success = function(o) {
657 var success = function(o) {
656 return true
658 return true
657 };
659 };
658
660
659 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
661 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
660 return false;
662 return false;
661 };
663 };
@@ -1,469 +1,469 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 <%def name="metatags_help()">
6 <%def name="metatags_help()">
7 <table>
7 <table>
8 <%
8 <%
9 example_tags = [
9 example_tags = [
10 ('state','[stable]'),
10 ('state','[stable]'),
11 ('state','[stale]'),
11 ('state','[stale]'),
12 ('state','[featured]'),
12 ('state','[featured]'),
13 ('state','[dev]'),
13 ('state','[dev]'),
14 ('state','[dead]'),
14 ('state','[dead]'),
15 ('state','[deprecated]'),
15 ('state','[deprecated]'),
16
16
17 ('label','[personal]'),
17 ('label','[personal]'),
18 ('generic','[v2.0.0]'),
18 ('generic','[v2.0.0]'),
19
19
20 ('lang','[lang =&gt; JavaScript]'),
20 ('lang','[lang =&gt; JavaScript]'),
21 ('license','[license =&gt; LicenseName]'),
21 ('license','[license =&gt; LicenseName]'),
22
22
23 ('ref','[requires =&gt; RepoName]'),
23 ('ref','[requires =&gt; RepoName]'),
24 ('ref','[recommends =&gt; GroupName]'),
24 ('ref','[recommends =&gt; GroupName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
29 ]
29 ]
30 %>
30 %>
31 % for tag_type, tag in example_tags:
31 % for tag_type, tag in example_tags:
32 <tr>
32 <tr>
33 <td>${tag|n}</td>
33 <td>${tag|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 </tr>
35 </tr>
36 % endfor
36 % endfor
37 </table>
37 </table>
38 </%def>
38 </%def>
39
39
40 <%def name="render_description(description, stylify_metatags)">
40 <%def name="render_description(description, stylify_metatags)">
41 <%
41 <%
42 tags = []
42 tags = []
43 if stylify_metatags:
43 if stylify_metatags:
44 tags, description = h.extract_metatags(description)
44 tags, description = h.extract_metatags(description)
45 %>
45 %>
46 % for tag_type, tag in tags:
46 % for tag_type, tag in tags:
47 ${h.style_metatag(tag_type, tag)|n,trim}
47 ${h.style_metatag(tag_type, tag)|n,trim}
48 % endfor
48 % endfor
49 <code style="white-space: pre-wrap">${description}</code>
49 <code style="white-space: pre-wrap">${description}</code>
50 </%def>
50 </%def>
51
51
52 ## REPOSITORY RENDERERS
52 ## REPOSITORY RENDERERS
53 <%def name="quick_menu(repo_name)">
53 <%def name="quick_menu(repo_name)">
54 <i class="icon-more"></i>
54 <i class="icon-more"></i>
55 <div class="menu_items_container hidden">
55 <div class="menu_items_container hidden">
56 <ul class="menu_items">
56 <ul class="menu_items">
57 <li>
57 <li>
58 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
58 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
59 <span>${_('Summary')}</span>
59 <span>${_('Summary')}</span>
60 </a>
60 </a>
61 </li>
61 </li>
62 <li>
62 <li>
63 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
63 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
64 <span>${_('Commits')}</span>
64 <span>${_('Commits')}</span>
65 </a>
65 </a>
66 </li>
66 </li>
67 <li>
67 <li>
68 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
68 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
69 <span>${_('Files')}</span>
69 <span>${_('Files')}</span>
70 </a>
70 </a>
71 </li>
71 </li>
72 <li>
72 <li>
73 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
73 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
74 <span>${_('Fork')}</span>
74 <span>${_('Fork')}</span>
75 </a>
75 </a>
76 </li>
76 </li>
77 </ul>
77 </ul>
78 </div>
78 </div>
79 </%def>
79 </%def>
80
80
81 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
81 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
82 <%
82 <%
83 def get_name(name,short_name=short_name):
83 def get_name(name,short_name=short_name):
84 if short_name:
84 if short_name:
85 return name.split('/')[-1]
85 return name.split('/')[-1]
86 else:
86 else:
87 return name
87 return name
88 %>
88 %>
89 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
89 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
90 ##NAME
90 ##NAME
91 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
91 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
92
92
93 ##TYPE OF REPO
93 ##TYPE OF REPO
94 %if h.is_hg(rtype):
94 %if h.is_hg(rtype):
95 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
95 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
96 %elif h.is_git(rtype):
96 %elif h.is_git(rtype):
97 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
97 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
98 %elif h.is_svn(rtype):
98 %elif h.is_svn(rtype):
99 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
99 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
100 %endif
100 %endif
101
101
102 ##PRIVATE/PUBLIC
102 ##PRIVATE/PUBLIC
103 %if private is True and c.visual.show_private_icon:
103 %if private is True and c.visual.show_private_icon:
104 <i class="icon-lock" title="${_('Private repository')}"></i>
104 <i class="icon-lock" title="${_('Private repository')}"></i>
105 %elif private is False and c.visual.show_public_icon:
105 %elif private is False and c.visual.show_public_icon:
106 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
106 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
107 %else:
107 %else:
108 <span></span>
108 <span></span>
109 %endif
109 %endif
110 ${get_name(name)}
110 ${get_name(name)}
111 </a>
111 </a>
112 %if fork_of:
112 %if fork_of:
113 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
113 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
114 %endif
114 %endif
115 %if rstate == 'repo_state_pending':
115 %if rstate == 'repo_state_pending':
116 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
116 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
117 (${_('creating...')})
117 (${_('creating...')})
118 </span>
118 </span>
119 %endif
119 %endif
120
120
121 </div>
121 </div>
122 </%def>
122 </%def>
123
123
124 <%def name="repo_desc(description, stylify_metatags)">
124 <%def name="repo_desc(description, stylify_metatags)">
125 <%
125 <%
126 tags, description = h.extract_metatags(description)
126 tags, description = h.extract_metatags(description)
127 %>
127 %>
128
128
129 <div class="truncate-wrap">
129 <div class="truncate-wrap">
130 % if stylify_metatags:
130 % if stylify_metatags:
131 % for tag_type, tag in tags:
131 % for tag_type, tag in tags:
132 ${h.style_metatag(tag_type, tag)|n}
132 ${h.style_metatag(tag_type, tag)|n}
133 % endfor
133 % endfor
134 % endif
134 % endif
135 ${description}
135 ${description}
136 </div>
136 </div>
137
137
138 </%def>
138 </%def>
139
139
140 <%def name="last_change(last_change)">
140 <%def name="last_change(last_change)">
141 ${h.age_component(last_change, time_is_local=True)}
141 ${h.age_component(last_change, time_is_local=True)}
142 </%def>
142 </%def>
143
143
144 <%def name="revision(name,rev,tip,author,last_msg, commit_date)">
144 <%def name="revision(repo_name, rev, commit_id, author, last_msg, commit_date)">
145 <div>
145 <div>
146 %if rev >= 0:
146 %if rev >= 0:
147 <code><a title="${h.tooltip('%s\n%s\n\n%s' % (author, commit_date, last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
147 <code><a class="tooltip-hovercard" data-hovercard-alt="${last_msg}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)}" href="${h.route_path('repo_commit',repo_name=repo_name,commit_id=commit_id)}">${'r{}:{}'.format(rev,h.short_id(commit_id))}</a></code>
148 %else:
148 %else:
149 ${_('No commits yet')}
149 ${_('No commits yet')}
150 %endif
150 %endif
151 </div>
151 </div>
152 </%def>
152 </%def>
153
153
154 <%def name="rss(name)">
154 <%def name="rss(name)">
155 %if c.rhodecode_user.username != h.DEFAULT_USER:
155 %if c.rhodecode_user.username != h.DEFAULT_USER:
156 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
156 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
157 %else:
157 %else:
158 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
158 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
159 %endif
159 %endif
160 </%def>
160 </%def>
161
161
162 <%def name="atom(name)">
162 <%def name="atom(name)">
163 %if c.rhodecode_user.username != h.DEFAULT_USER:
163 %if c.rhodecode_user.username != h.DEFAULT_USER:
164 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
164 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
165 %else:
165 %else:
166 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
166 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
167 %endif
167 %endif
168 </%def>
168 </%def>
169
169
170 <%def name="repo_actions(repo_name, super_user=True)">
170 <%def name="repo_actions(repo_name, super_user=True)">
171 <div>
171 <div>
172 <div class="grid_edit">
172 <div class="grid_edit">
173 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
173 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
174 Edit
174 Edit
175 </a>
175 </a>
176 </div>
176 </div>
177 <div class="grid_delete">
177 <div class="grid_delete">
178 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
178 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
179 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
179 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
180 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
180 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
181 ${h.end_form()}
181 ${h.end_form()}
182 </div>
182 </div>
183 </div>
183 </div>
184 </%def>
184 </%def>
185
185
186 <%def name="repo_state(repo_state)">
186 <%def name="repo_state(repo_state)">
187 <div>
187 <div>
188 %if repo_state == 'repo_state_pending':
188 %if repo_state == 'repo_state_pending':
189 <div class="tag tag4">${_('Creating')}</div>
189 <div class="tag tag4">${_('Creating')}</div>
190 %elif repo_state == 'repo_state_created':
190 %elif repo_state == 'repo_state_created':
191 <div class="tag tag1">${_('Created')}</div>
191 <div class="tag tag1">${_('Created')}</div>
192 %else:
192 %else:
193 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
193 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
194 %endif
194 %endif
195 </div>
195 </div>
196 </%def>
196 </%def>
197
197
198
198
199 ## REPO GROUP RENDERERS
199 ## REPO GROUP RENDERERS
200 <%def name="quick_repo_group_menu(repo_group_name)">
200 <%def name="quick_repo_group_menu(repo_group_name)">
201 <i class="icon-more"></i>
201 <i class="icon-more"></i>
202 <div class="menu_items_container hidden">
202 <div class="menu_items_container hidden">
203 <ul class="menu_items">
203 <ul class="menu_items">
204 <li>
204 <li>
205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
206 </li>
206 </li>
207
207
208 </ul>
208 </ul>
209 </div>
209 </div>
210 </%def>
210 </%def>
211
211
212 <%def name="repo_group_name(repo_group_name, children_groups=None)">
212 <%def name="repo_group_name(repo_group_name, children_groups=None)">
213 <div>
213 <div>
214 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
214 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
215 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
215 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
216 %if children_groups:
216 %if children_groups:
217 ${h.literal(' &raquo; '.join(children_groups))}
217 ${h.literal(' &raquo; '.join(children_groups))}
218 %else:
218 %else:
219 ${repo_group_name}
219 ${repo_group_name}
220 %endif
220 %endif
221 </a>
221 </a>
222 </div>
222 </div>
223 </%def>
223 </%def>
224
224
225 <%def name="repo_group_desc(description, personal, stylify_metatags)">
225 <%def name="repo_group_desc(description, personal, stylify_metatags)">
226
226
227 <%
227 <%
228 if stylify_metatags:
228 if stylify_metatags:
229 tags, description = h.extract_metatags(description)
229 tags, description = h.extract_metatags(description)
230 %>
230 %>
231
231
232 <div class="truncate-wrap">
232 <div class="truncate-wrap">
233 % if personal:
233 % if personal:
234 <div class="metatag" tag="personal">${_('personal')}</div>
234 <div class="metatag" tag="personal">${_('personal')}</div>
235 % endif
235 % endif
236
236
237 % if stylify_metatags:
237 % if stylify_metatags:
238 % for tag_type, tag in tags:
238 % for tag_type, tag in tags:
239 ${h.style_metatag(tag_type, tag)|n}
239 ${h.style_metatag(tag_type, tag)|n}
240 % endfor
240 % endfor
241 % endif
241 % endif
242 ${description}
242 ${description}
243 </div>
243 </div>
244
244
245 </%def>
245 </%def>
246
246
247 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
247 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
248 <div class="grid_edit">
248 <div class="grid_edit">
249 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
249 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
250 </div>
250 </div>
251 <div class="grid_delete">
251 <div class="grid_delete">
252 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
252 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
253 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
253 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
254 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
254 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
255 ${h.end_form()}
255 ${h.end_form()}
256 </div>
256 </div>
257 </%def>
257 </%def>
258
258
259
259
260 <%def name="user_actions(user_id, username)">
260 <%def name="user_actions(user_id, username)">
261 <div class="grid_edit">
261 <div class="grid_edit">
262 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
262 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
263 ${_('Edit')}
263 ${_('Edit')}
264 </a>
264 </a>
265 </div>
265 </div>
266 <div class="grid_delete">
266 <div class="grid_delete">
267 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
267 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
268 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
268 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
269 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
269 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
270 ${h.end_form()}
270 ${h.end_form()}
271 </div>
271 </div>
272 </%def>
272 </%def>
273
273
274 <%def name="user_group_actions(user_group_id, user_group_name)">
274 <%def name="user_group_actions(user_group_id, user_group_name)">
275 <div class="grid_edit">
275 <div class="grid_edit">
276 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
276 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
277 </div>
277 </div>
278 <div class="grid_delete">
278 <div class="grid_delete">
279 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
279 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
280 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
280 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
281 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
281 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
282 ${h.end_form()}
282 ${h.end_form()}
283 </div>
283 </div>
284 </%def>
284 </%def>
285
285
286
286
287 <%def name="user_name(user_id, username)">
287 <%def name="user_name(user_id, username)">
288 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
288 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
289 </%def>
289 </%def>
290
290
291 <%def name="user_profile(username)">
291 <%def name="user_profile(username)">
292 ${base.gravatar_with_user(username, 16, tooltip=True)}
292 ${base.gravatar_with_user(username, 16, tooltip=True)}
293 </%def>
293 </%def>
294
294
295 <%def name="user_group_name(user_group_name)">
295 <%def name="user_group_name(user_group_name)">
296 <div>
296 <div>
297 <i class="icon-user-group" title="${_('User group')}"></i>
297 <i class="icon-user-group" title="${_('User group')}"></i>
298 ${h.link_to_group(user_group_name)}
298 ${h.link_to_group(user_group_name)}
299 </div>
299 </div>
300 </%def>
300 </%def>
301
301
302
302
303 ## GISTS
303 ## GISTS
304
304
305 <%def name="gist_gravatar(full_contact)">
305 <%def name="gist_gravatar(full_contact)">
306 <div class="gist_gravatar">
306 <div class="gist_gravatar">
307 ${base.gravatar(full_contact, 30)}
307 ${base.gravatar(full_contact, 30)}
308 </div>
308 </div>
309 </%def>
309 </%def>
310
310
311 <%def name="gist_access_id(gist_access_id, full_contact)">
311 <%def name="gist_access_id(gist_access_id, full_contact)">
312 <div>
312 <div>
313 <b>
313 <b>
314 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
314 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
315 </b>
315 </b>
316 </div>
316 </div>
317 </%def>
317 </%def>
318
318
319 <%def name="gist_author(full_contact, created_on, expires)">
319 <%def name="gist_author(full_contact, created_on, expires)">
320 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
320 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
321 </%def>
321 </%def>
322
322
323
323
324 <%def name="gist_created(created_on)">
324 <%def name="gist_created(created_on)">
325 <div class="created">
325 <div class="created">
326 ${h.age_component(created_on, time_is_local=True)}
326 ${h.age_component(created_on, time_is_local=True)}
327 </div>
327 </div>
328 </%def>
328 </%def>
329
329
330 <%def name="gist_expires(expires)">
330 <%def name="gist_expires(expires)">
331 <div class="created">
331 <div class="created">
332 %if expires == -1:
332 %if expires == -1:
333 ${_('never')}
333 ${_('never')}
334 %else:
334 %else:
335 ${h.age_component(h.time_to_utcdatetime(expires))}
335 ${h.age_component(h.time_to_utcdatetime(expires))}
336 %endif
336 %endif
337 </div>
337 </div>
338 </%def>
338 </%def>
339
339
340 <%def name="gist_type(gist_type)">
340 <%def name="gist_type(gist_type)">
341 %if gist_type != 'public':
341 %if gist_type != 'public':
342 <div class="tag">${_('Private')}</div>
342 <div class="tag">${_('Private')}</div>
343 %endif
343 %endif
344 </%def>
344 </%def>
345
345
346 <%def name="gist_description(gist_description)">
346 <%def name="gist_description(gist_description)">
347 ${gist_description}
347 ${gist_description}
348 </%def>
348 </%def>
349
349
350
350
351 ## PULL REQUESTS GRID RENDERERS
351 ## PULL REQUESTS GRID RENDERERS
352
352
353 <%def name="pullrequest_target_repo(repo_name)">
353 <%def name="pullrequest_target_repo(repo_name)">
354 <div class="truncate">
354 <div class="truncate">
355 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
355 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
356 </div>
356 </div>
357 </%def>
357 </%def>
358
358
359 <%def name="pullrequest_status(status)">
359 <%def name="pullrequest_status(status)">
360 <i class="icon-circle review-status-${status}"></i>
360 <i class="icon-circle review-status-${status}"></i>
361 </%def>
361 </%def>
362
362
363 <%def name="pullrequest_title(title, description)">
363 <%def name="pullrequest_title(title, description)">
364 ${title}
364 ${title}
365 </%def>
365 </%def>
366
366
367 <%def name="pullrequest_comments(comments_nr)">
367 <%def name="pullrequest_comments(comments_nr)">
368 <i class="icon-comment"></i> ${comments_nr}
368 <i class="icon-comment"></i> ${comments_nr}
369 </%def>
369 </%def>
370
370
371 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
371 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
372 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
372 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
373 % if short:
373 % if short:
374 #${pull_request_id}
374 #${pull_request_id}
375 % else:
375 % else:
376 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
376 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
377 % endif
377 % endif
378 </a>
378 </a>
379 </%def>
379 </%def>
380
380
381 <%def name="pullrequest_updated_on(updated_on)">
381 <%def name="pullrequest_updated_on(updated_on)">
382 ${h.age_component(h.time_to_utcdatetime(updated_on))}
382 ${h.age_component(h.time_to_utcdatetime(updated_on))}
383 </%def>
383 </%def>
384
384
385 <%def name="pullrequest_author(full_contact)">
385 <%def name="pullrequest_author(full_contact)">
386 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
386 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
387 </%def>
387 </%def>
388
388
389
389
390 ## ARTIFACT RENDERERS
390 ## ARTIFACT RENDERERS
391 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
391 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
392 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
392 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
393 ${artifact_display_name or '_EMPTY_NAME_'}
393 ${artifact_display_name or '_EMPTY_NAME_'}
394 </a>
394 </a>
395 </%def>
395 </%def>
396
396
397 <%def name="repo_artifact_uid(repo_name, file_uid)">
397 <%def name="repo_artifact_uid(repo_name, file_uid)">
398 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
398 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
399 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${h.route_url('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}" title="${_('Copy the full url')}"></i>
399 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${h.route_url('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}" title="${_('Copy the full url')}"></i>
400 </%def>
400 </%def>
401
401
402 <%def name="repo_artifact_sha256(artifact_sha256)">
402 <%def name="repo_artifact_sha256(artifact_sha256)">
403 <div class="code">${h.shorter(artifact_sha256, 12)}<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${artifact_sha256}" title="${_('Copy the sha256 ({})').format(artifact_sha256)}"></i></div>
403 <div class="code">${h.shorter(artifact_sha256, 12)}<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${artifact_sha256}" title="${_('Copy the sha256 ({})').format(artifact_sha256)}"></i></div>
404 </%def>
404 </%def>
405
405
406 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
406 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
407 ## <div class="grid_edit">
407 ## <div class="grid_edit">
408 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
408 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
409 ## </div>
409 ## </div>
410 <div class="grid_edit">
410 <div class="grid_edit">
411 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
411 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
412 </div>
412 </div>
413 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
413 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
414 <div class="grid_delete">
414 <div class="grid_delete">
415 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
415 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
416 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
416 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
417 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
417 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
418 ${h.end_form()}
418 ${h.end_form()}
419 </div>
419 </div>
420 % endif
420 % endif
421 </%def>
421 </%def>
422
422
423 <%def name="markup_form(form_id, form_text='', help_text=None)">
423 <%def name="markup_form(form_id, form_text='', help_text=None)">
424
424
425 <div class="markup-form">
425 <div class="markup-form">
426 <div class="markup-form-area">
426 <div class="markup-form-area">
427 <div class="markup-form-area-header">
427 <div class="markup-form-area-header">
428 <ul class="nav-links clearfix">
428 <ul class="nav-links clearfix">
429 <li class="active">
429 <li class="active">
430 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
430 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
431 </li>
431 </li>
432 <li class="">
432 <li class="">
433 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
433 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
434 </li>
434 </li>
435 </ul>
435 </ul>
436 </div>
436 </div>
437
437
438 <div class="markup-form-area-write" style="display: block;">
438 <div class="markup-form-area-write" style="display: block;">
439 <div id="edit-container_${form_id}">
439 <div id="edit-container_${form_id}">
440 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
440 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
441 </div>
441 </div>
442 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
442 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
443 <div id="preview-box_${form_id}" class="preview-box"></div>
443 <div id="preview-box_${form_id}" class="preview-box"></div>
444 </div>
444 </div>
445 </div>
445 </div>
446
446
447 <div class="markup-form-area-footer">
447 <div class="markup-form-area-footer">
448 <div class="toolbar">
448 <div class="toolbar">
449 <div class="toolbar-text">
449 <div class="toolbar-text">
450 ${(_('Parsed using %s syntax') % (
450 ${(_('Parsed using %s syntax') % (
451 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
451 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
452 )
452 )
453 )|n}
453 )|n}
454 </div>
454 </div>
455 </div>
455 </div>
456 </div>
456 </div>
457 </div>
457 </div>
458
458
459 <div class="markup-form-footer">
459 <div class="markup-form-footer">
460 % if help_text:
460 % if help_text:
461 <span class="help-block">${help_text}</span>
461 <span class="help-block">${help_text}</span>
462 % endif
462 % endif
463 </div>
463 </div>
464 </div>
464 </div>
465 <script type="text/javascript">
465 <script type="text/javascript">
466 new MarkupForm('${form_id}');
466 new MarkupForm('${form_id}');
467 </script>
467 </script>
468
468
469 </%def>
469 </%def>
@@ -1,94 +1,96 b''
1 <%namespace name="base" file="/base/base.mako"/>
2
1 <%
3 <%
2 if request.GET.get('at'):
4 if request.GET.get('at'):
3 query={'at': request.GET.get('at')}
5 query={'at': request.GET.get('at')}
4 else:
6 else:
5 query=None
7 query=None
6 %>
8 %>
7 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
9 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
8 <table class="code-browser rctable repo_summary">
10 <table class="code-browser rctable repo_summary">
9 <thead>
11 <thead>
10 <tr>
12 <tr>
11 <th>${_('Name')}</th>
13 <th>${_('Name')}</th>
12 <th>${_('Size')}</th>
14 <th>${_('Size')}</th>
13 <th>${_('Modified')}</th>
15 <th>${_('Modified')}</th>
14 <th>${_('Last Commit')}</th>
16 <th>${_('Last Commit')}</th>
15 <th>${_('Author')}</th>
17 <th>${_('Author')}</th>
16 </tr>
18 </tr>
17 </thead>
19 </thead>
18
20
19 <tbody id="tbody">
21 <tbody id="tbody">
20 <tr>
22 <tr>
21 <td colspan="5">
23 <td colspan="5">
22 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'), limit_items=True)}
24 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'), limit_items=True)}
23 </td>
25 </td>
24 </tr>
26 </tr>
25
27
26 <% has_files = False %>
28 <% has_files = False %>
27 % for cnt,node in enumerate(c.file):
29 % for cnt,node in enumerate(c.file):
28 <% has_files = True %>
30 <% has_files = True %>
29 <tr class="parity${(cnt % 2)}">
31 <tr class="parity${(cnt % 2)}">
30 <td class="td-componentname">
32 <td class="td-componentname">
31 % if node.is_submodule():
33 % if node.is_submodule():
32 <span class="submodule-dir">
34 <span class="submodule-dir">
33 % if node.url.startswith('http://') or node.url.startswith('https://'):
35 % if node.url.startswith('http://') or node.url.startswith('https://'):
34 <a href="${node.url}">
36 <a href="${node.url}">
35 <i class="icon-directory browser-dir"></i>${node.name}
37 <i class="icon-directory browser-dir"></i>${node.name}
36 </a>
38 </a>
37 % else:
39 % else:
38 <i class="icon-directory browser-dir"></i>${node.name}
40 <i class="icon-directory browser-dir"></i>${node.name}
39 % endif
41 % endif
40 </span>
42 </span>
41 % else:
43 % else:
42
44
43 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=h.safe_unicode(node.path), _query=query)}">
45 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=h.safe_unicode(node.path), _query=query)}">
44 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
46 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
45 </a>
47 </a>
46 % endif
48 % endif
47 </td>
49 </td>
48 %if node.is_file():
50 %if node.is_file():
49 <td class="td-size" data-attr-name="size">
51 <td class="td-size" data-attr-name="size">
50 % if c.full_load:
52 % if c.full_load:
51 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
53 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
52 % else:
54 % else:
53 ${_('Loading ...')}
55 ${_('Loading ...')}
54 % endif
56 % endif
55 </td>
57 </td>
56 <td class="td-time" data-attr-name="modified_at">
58 <td class="td-time" data-attr-name="modified_at">
57 % if c.full_load:
59 % if c.full_load:
58 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
60 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
59 % endif
61 % endif
60 </td>
62 </td>
61 <td class="td-hash" data-attr-name="commit_id">
63 <td class="td-hash" data-attr-name="commit_id">
62 % if c.full_load:
64 % if c.full_load:
63 <div class="tooltip" title="${h.tooltip(node.last_commit.message)}">
65 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
64 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
66 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
65 </div>
67 </div>
66 % endif
68 % endif
67 </td>
69 </td>
68 <td class="td-user" data-attr-name="author">
70 <td class="td-user" data-attr-name="author">
69 % if c.full_load:
71 % if c.full_load:
70 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
72 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
71 % endif
73 % endif
72 </td>
74 </td>
73 %else:
75 %else:
74 <td></td>
76 <td></td>
75 <td></td>
77 <td></td>
76 <td></td>
78 <td></td>
77 <td></td>
79 <td></td>
78 %endif
80 %endif
79 </tr>
81 </tr>
80 % endfor
82 % endfor
81
83
82 % if not has_files:
84 % if not has_files:
83 <tr>
85 <tr>
84 <td colspan="5">
86 <td colspan="5">
85 ##empty-dir mostly SVN
87 ##empty-dir mostly SVN
86 &nbsp;
88 &nbsp;
87 </td>
89 </td>
88 </tr>
90 </tr>
89 % endif
91 % endif
90
92
91 </tbody>
93 </tbody>
92 <tbody id="tbody_filtered"></tbody>
94 <tbody id="tbody_filtered"></tbody>
93 </table>
95 </table>
94 </div>
96 </div>
General Comments 0
You need to be logged in to leave comments. Login now