##// END OF EJS Templates
gists: cleanup UI and make the gist access id use monospace
dan -
r4193:e6e77c6a stable
parent child Browse files
Show More
@@ -1,391 +1,391 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 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib import helpers as h
24 from rhodecode.lib import helpers as h
25 from rhodecode.model.db import User, Gist
25 from rhodecode.model.db import User, Gist
26 from rhodecode.model.gist import GistModel
26 from rhodecode.model.gist import GistModel
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import (
28 from rhodecode.tests import (
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
30 TestController, assert_session_flash)
30 TestController, assert_session_flash)
31
31
32
32
33 def route_path(name, params=None, **kwargs):
33 def route_path(name, params=None, **kwargs):
34 import urllib
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
35 from rhodecode.apps._base import ADMIN_PREFIX
36
36
37 base_url = {
37 base_url = {
38 'gists_show': ADMIN_PREFIX + '/gists',
38 'gists_show': ADMIN_PREFIX + '/gists',
39 'gists_new': ADMIN_PREFIX + '/gists/new',
39 'gists_new': ADMIN_PREFIX + '/gists/new',
40 'gists_create': ADMIN_PREFIX + '/gists/create',
40 'gists_create': ADMIN_PREFIX + '/gists/create',
41 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}',
41 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}',
42 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete',
42 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete',
43 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit',
43 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit',
44 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision',
44 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision',
45 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update',
45 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update',
46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/{revision}',
46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/{revision}',
47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
49
49
50 }[name].format(**kwargs)
50 }[name].format(**kwargs)
51
51
52 if params:
52 if params:
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 return base_url
54 return base_url
55
55
56
56
57 class GistUtility(object):
57 class GistUtility(object):
58
58
59 def __init__(self):
59 def __init__(self):
60 self._gist_ids = []
60 self._gist_ids = []
61
61
62 def __call__(
62 def __call__(
63 self, f_name, content='some gist', lifetime=-1,
63 self, f_name, content='some gist', lifetime=-1,
64 description='gist-desc', gist_type='public',
64 description='gist-desc', gist_type='public',
65 acl_level=Gist.GIST_PUBLIC, owner=TEST_USER_ADMIN_LOGIN):
65 acl_level=Gist.GIST_PUBLIC, owner=TEST_USER_ADMIN_LOGIN):
66 gist_mapping = {
66 gist_mapping = {
67 f_name: {'content': content}
67 f_name: {'content': content}
68 }
68 }
69 user = User.get_by_username(owner)
69 user = User.get_by_username(owner)
70 gist = GistModel().create(
70 gist = GistModel().create(
71 description, owner=user, gist_mapping=gist_mapping,
71 description, owner=user, gist_mapping=gist_mapping,
72 gist_type=gist_type, lifetime=lifetime, gist_acl_level=acl_level)
72 gist_type=gist_type, lifetime=lifetime, gist_acl_level=acl_level)
73 Session().commit()
73 Session().commit()
74 self._gist_ids.append(gist.gist_id)
74 self._gist_ids.append(gist.gist_id)
75 return gist
75 return gist
76
76
77 def cleanup(self):
77 def cleanup(self):
78 for gist_id in self._gist_ids:
78 for gist_id in self._gist_ids:
79 gist = Gist.get(gist_id)
79 gist = Gist.get(gist_id)
80 if gist:
80 if gist:
81 Session().delete(gist)
81 Session().delete(gist)
82
82
83 Session().commit()
83 Session().commit()
84
84
85
85
86 @pytest.fixture()
86 @pytest.fixture()
87 def create_gist(request):
87 def create_gist(request):
88 gist_utility = GistUtility()
88 gist_utility = GistUtility()
89 request.addfinalizer(gist_utility.cleanup)
89 request.addfinalizer(gist_utility.cleanup)
90 return gist_utility
90 return gist_utility
91
91
92
92
93 class TestGistsController(TestController):
93 class TestGistsController(TestController):
94
94
95 def test_index_empty(self, create_gist):
95 def test_index_empty(self, create_gist):
96 self.log_user()
96 self.log_user()
97 response = self.app.get(route_path('gists_show'))
97 response = self.app.get(route_path('gists_show'))
98 response.mustcontain('data: [],')
98 response.mustcontain('data: [],')
99
99
100 def test_index(self, create_gist):
100 def test_index(self, create_gist):
101 self.log_user()
101 self.log_user()
102 g1 = create_gist('gist1')
102 g1 = create_gist('gist1')
103 g2 = create_gist('gist2', lifetime=1400)
103 g2 = create_gist('gist2', lifetime=1400)
104 g3 = create_gist('gist3', description='gist3-desc')
104 g3 = create_gist('gist3', description='gist3-desc')
105 g4 = create_gist('gist4', gist_type='private').gist_access_id
105 g4 = create_gist('gist4', gist_type='private').gist_access_id
106 response = self.app.get(route_path('gists_show'))
106 response = self.app.get(route_path('gists_show'))
107
107
108 response.mustcontain('gist: %s' % g1.gist_access_id)
108 response.mustcontain(g1.gist_access_id)
109 response.mustcontain('gist: %s' % g2.gist_access_id)
109 response.mustcontain(g2.gist_access_id)
110 response.mustcontain('gist: %s' % g3.gist_access_id)
110 response.mustcontain(g3.gist_access_id)
111 response.mustcontain('gist3-desc')
111 response.mustcontain('gist3-desc')
112 response.mustcontain(no=['gist: %s' % g4])
112 response.mustcontain(no=[g4])
113
113
114 # Expiration information should be visible
114 # Expiration information should be visible
115 expires_tag = '%s' % h.age_component(
115 expires_tag = '%s' % h.age_component(
116 h.time_to_utcdatetime(g2.gist_expires))
116 h.time_to_utcdatetime(g2.gist_expires))
117 response.mustcontain(expires_tag.replace('"', '\\"'))
117 response.mustcontain(expires_tag.replace('"', '\\"'))
118
118
119 def test_index_private_gists(self, create_gist):
119 def test_index_private_gists(self, create_gist):
120 self.log_user()
120 self.log_user()
121 gist = create_gist('gist5', gist_type='private')
121 gist = create_gist('gist5', gist_type='private')
122 response = self.app.get(route_path('gists_show', params=dict(private=1)))
122 response = self.app.get(route_path('gists_show', params=dict(private=1)))
123
123
124 # and privates
124 # and privates
125 response.mustcontain('gist: %s' % gist.gist_access_id)
125 response.mustcontain(gist.gist_access_id)
126
126
127 def test_index_show_all(self, create_gist):
127 def test_index_show_all(self, create_gist):
128 self.log_user()
128 self.log_user()
129 create_gist('gist1')
129 create_gist('gist1')
130 create_gist('gist2', lifetime=1400)
130 create_gist('gist2', lifetime=1400)
131 create_gist('gist3', description='gist3-desc')
131 create_gist('gist3', description='gist3-desc')
132 create_gist('gist4', gist_type='private')
132 create_gist('gist4', gist_type='private')
133
133
134 response = self.app.get(route_path('gists_show', params=dict(all=1)))
134 response = self.app.get(route_path('gists_show', params=dict(all=1)))
135
135
136 assert len(GistModel.get_all()) == 4
136 assert len(GistModel.get_all()) == 4
137 # and privates
137 # and privates
138 for gist in GistModel.get_all():
138 for gist in GistModel.get_all():
139 response.mustcontain('gist: %s' % gist.gist_access_id)
139 response.mustcontain(gist.gist_access_id)
140
140
141 def test_index_show_all_hidden_from_regular(self, create_gist):
141 def test_index_show_all_hidden_from_regular(self, create_gist):
142 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
142 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
143 create_gist('gist2', gist_type='private')
143 create_gist('gist2', gist_type='private')
144 create_gist('gist3', gist_type='private')
144 create_gist('gist3', gist_type='private')
145 create_gist('gist4', gist_type='private')
145 create_gist('gist4', gist_type='private')
146
146
147 response = self.app.get(route_path('gists_show', params=dict(all=1)))
147 response = self.app.get(route_path('gists_show', params=dict(all=1)))
148
148
149 assert len(GistModel.get_all()) == 3
149 assert len(GistModel.get_all()) == 3
150 # since we don't have access to private in this view, we
150 # since we don't have access to private in this view, we
151 # should see nothing
151 # should see nothing
152 for gist in GistModel.get_all():
152 for gist in GistModel.get_all():
153 response.mustcontain(no=['gist: %s' % gist.gist_access_id])
153 response.mustcontain(no=[gist.gist_access_id])
154
154
155 def test_create(self):
155 def test_create(self):
156 self.log_user()
156 self.log_user()
157 response = self.app.post(
157 response = self.app.post(
158 route_path('gists_create'),
158 route_path('gists_create'),
159 params={'lifetime': -1,
159 params={'lifetime': -1,
160 'content': 'gist test',
160 'content': 'gist test',
161 'filename': 'foo',
161 'filename': 'foo',
162 'gist_type': 'public',
162 'gist_type': 'public',
163 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
163 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
164 'csrf_token': self.csrf_token},
164 'csrf_token': self.csrf_token},
165 status=302)
165 status=302)
166 response = response.follow()
166 response = response.follow()
167 response.mustcontain('added file: foo')
167 response.mustcontain('added file: foo')
168 response.mustcontain('gist test')
168 response.mustcontain('gist test')
169
169
170 def test_create_with_path_with_dirs(self):
170 def test_create_with_path_with_dirs(self):
171 self.log_user()
171 self.log_user()
172 response = self.app.post(
172 response = self.app.post(
173 route_path('gists_create'),
173 route_path('gists_create'),
174 params={'lifetime': -1,
174 params={'lifetime': -1,
175 'content': 'gist test',
175 'content': 'gist test',
176 'filename': '/home/foo',
176 'filename': '/home/foo',
177 'gist_type': 'public',
177 'gist_type': 'public',
178 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
178 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
179 'csrf_token': self.csrf_token},
179 'csrf_token': self.csrf_token},
180 status=200)
180 status=200)
181 response.mustcontain('Filename /home/foo cannot be inside a directory')
181 response.mustcontain('Filename /home/foo cannot be inside a directory')
182
182
183 def test_access_expired_gist(self, create_gist):
183 def test_access_expired_gist(self, create_gist):
184 self.log_user()
184 self.log_user()
185 gist = create_gist('never-see-me')
185 gist = create_gist('never-see-me')
186 gist.gist_expires = 0 # 1970
186 gist.gist_expires = 0 # 1970
187 Session().add(gist)
187 Session().add(gist)
188 Session().commit()
188 Session().commit()
189
189
190 self.app.get(route_path('gist_show', gist_id=gist.gist_access_id),
190 self.app.get(route_path('gist_show', gist_id=gist.gist_access_id),
191 status=404)
191 status=404)
192
192
193 def test_create_private(self):
193 def test_create_private(self):
194 self.log_user()
194 self.log_user()
195 response = self.app.post(
195 response = self.app.post(
196 route_path('gists_create'),
196 route_path('gists_create'),
197 params={'lifetime': -1,
197 params={'lifetime': -1,
198 'content': 'private gist test',
198 'content': 'private gist test',
199 'filename': 'private-foo',
199 'filename': 'private-foo',
200 'gist_type': 'private',
200 'gist_type': 'private',
201 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
201 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
202 'csrf_token': self.csrf_token},
202 'csrf_token': self.csrf_token},
203 status=302)
203 status=302)
204 response = response.follow()
204 response = response.follow()
205 response.mustcontain('added file: private-foo<')
205 response.mustcontain('added file: private-foo<')
206 response.mustcontain('private gist test')
206 response.mustcontain('private gist test')
207 response.mustcontain('Private Gist')
207 response.mustcontain('Private Gist')
208 # Make sure private gists are not indexed by robots
208 # Make sure private gists are not indexed by robots
209 response.mustcontain(
209 response.mustcontain(
210 '<meta name="robots" content="noindex, nofollow">')
210 '<meta name="robots" content="noindex, nofollow">')
211
211
212 def test_create_private_acl_private(self):
212 def test_create_private_acl_private(self):
213 self.log_user()
213 self.log_user()
214 response = self.app.post(
214 response = self.app.post(
215 route_path('gists_create'),
215 route_path('gists_create'),
216 params={'lifetime': -1,
216 params={'lifetime': -1,
217 'content': 'private gist test',
217 'content': 'private gist test',
218 'filename': 'private-foo',
218 'filename': 'private-foo',
219 'gist_type': 'private',
219 'gist_type': 'private',
220 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
220 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
221 'csrf_token': self.csrf_token},
221 'csrf_token': self.csrf_token},
222 status=302)
222 status=302)
223 response = response.follow()
223 response = response.follow()
224 response.mustcontain('added file: private-foo<')
224 response.mustcontain('added file: private-foo<')
225 response.mustcontain('private gist test')
225 response.mustcontain('private gist test')
226 response.mustcontain('Private Gist')
226 response.mustcontain('Private Gist')
227 # Make sure private gists are not indexed by robots
227 # Make sure private gists are not indexed by robots
228 response.mustcontain(
228 response.mustcontain(
229 '<meta name="robots" content="noindex, nofollow">')
229 '<meta name="robots" content="noindex, nofollow">')
230
230
231 def test_create_with_description(self):
231 def test_create_with_description(self):
232 self.log_user()
232 self.log_user()
233 response = self.app.post(
233 response = self.app.post(
234 route_path('gists_create'),
234 route_path('gists_create'),
235 params={'lifetime': -1,
235 params={'lifetime': -1,
236 'content': 'gist test',
236 'content': 'gist test',
237 'filename': 'foo-desc',
237 'filename': 'foo-desc',
238 'description': 'gist-desc',
238 'description': 'gist-desc',
239 'gist_type': 'public',
239 'gist_type': 'public',
240 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
240 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
241 'csrf_token': self.csrf_token},
241 'csrf_token': self.csrf_token},
242 status=302)
242 status=302)
243 response = response.follow()
243 response = response.follow()
244 response.mustcontain('added file: foo-desc')
244 response.mustcontain('added file: foo-desc')
245 response.mustcontain('gist test')
245 response.mustcontain('gist test')
246 response.mustcontain('gist-desc')
246 response.mustcontain('gist-desc')
247
247
248 def test_create_public_with_anonymous_access(self):
248 def test_create_public_with_anonymous_access(self):
249 self.log_user()
249 self.log_user()
250 params = {
250 params = {
251 'lifetime': -1,
251 'lifetime': -1,
252 'content': 'gist test',
252 'content': 'gist test',
253 'filename': 'foo-desc',
253 'filename': 'foo-desc',
254 'description': 'gist-desc',
254 'description': 'gist-desc',
255 'gist_type': 'public',
255 'gist_type': 'public',
256 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
256 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
257 'csrf_token': self.csrf_token
257 'csrf_token': self.csrf_token
258 }
258 }
259 response = self.app.post(
259 response = self.app.post(
260 route_path('gists_create'), params=params, status=302)
260 route_path('gists_create'), params=params, status=302)
261 self.logout_user()
261 self.logout_user()
262 response = response.follow()
262 response = response.follow()
263 response.mustcontain('added file: foo-desc')
263 response.mustcontain('added file: foo-desc')
264 response.mustcontain('gist test')
264 response.mustcontain('gist test')
265 response.mustcontain('gist-desc')
265 response.mustcontain('gist-desc')
266
266
267 def test_new(self):
267 def test_new(self):
268 self.log_user()
268 self.log_user()
269 self.app.get(route_path('gists_new'))
269 self.app.get(route_path('gists_new'))
270
270
271 def test_delete(self, create_gist):
271 def test_delete(self, create_gist):
272 self.log_user()
272 self.log_user()
273 gist = create_gist('delete-me')
273 gist = create_gist('delete-me')
274 response = self.app.post(
274 response = self.app.post(
275 route_path('gist_delete', gist_id=gist.gist_id),
275 route_path('gist_delete', gist_id=gist.gist_id),
276 params={'csrf_token': self.csrf_token})
276 params={'csrf_token': self.csrf_token})
277 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
277 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
278
278
279 def test_delete_normal_user_his_gist(self, create_gist):
279 def test_delete_normal_user_his_gist(self, create_gist):
280 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
280 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
281 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
281 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
282
282
283 response = self.app.post(
283 response = self.app.post(
284 route_path('gist_delete', gist_id=gist.gist_id),
284 route_path('gist_delete', gist_id=gist.gist_id),
285 params={'csrf_token': self.csrf_token})
285 params={'csrf_token': self.csrf_token})
286 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
286 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
287
287
288 def test_delete_normal_user_not_his_own_gist(self, create_gist):
288 def test_delete_normal_user_not_his_own_gist(self, create_gist):
289 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
289 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
290 gist = create_gist('delete-me-2')
290 gist = create_gist('delete-me-2')
291
291
292 self.app.post(
292 self.app.post(
293 route_path('gist_delete', gist_id=gist.gist_id),
293 route_path('gist_delete', gist_id=gist.gist_id),
294 params={'csrf_token': self.csrf_token}, status=404)
294 params={'csrf_token': self.csrf_token}, status=404)
295
295
296 def test_show(self, create_gist):
296 def test_show(self, create_gist):
297 gist = create_gist('gist-show-me')
297 gist = create_gist('gist-show-me')
298 response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
298 response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
299
299
300 response.mustcontain('added file: gist-show-me<')
300 response.mustcontain('added file: gist-show-me<')
301
301
302 assert_response = response.assert_response()
302 assert_response = response.assert_response()
303 assert_response.element_equals_to(
303 assert_response.element_equals_to(
304 'div.rc-user span.user',
304 'div.rc-user span.user',
305 '<a href="/_profiles/test_admin">test_admin</a></span>')
305 '<a href="/_profiles/test_admin">test_admin</a></span>')
306
306
307 response.mustcontain('gist-desc')
307 response.mustcontain('gist-desc')
308
308
309 def test_show_without_hg(self, create_gist):
309 def test_show_without_hg(self, create_gist):
310 with mock.patch(
310 with mock.patch(
311 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
311 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
312 gist = create_gist('gist-show-me-again')
312 gist = create_gist('gist-show-me-again')
313 self.app.get(
313 self.app.get(
314 route_path('gist_show', gist_id=gist.gist_access_id), status=200)
314 route_path('gist_show', gist_id=gist.gist_access_id), status=200)
315
315
316 def test_show_acl_private(self, create_gist):
316 def test_show_acl_private(self, create_gist):
317 gist = create_gist('gist-show-me-only-when-im-logged-in',
317 gist = create_gist('gist-show-me-only-when-im-logged-in',
318 acl_level=Gist.ACL_LEVEL_PRIVATE)
318 acl_level=Gist.ACL_LEVEL_PRIVATE)
319 self.app.get(
319 self.app.get(
320 route_path('gist_show', gist_id=gist.gist_access_id), status=404)
320 route_path('gist_show', gist_id=gist.gist_access_id), status=404)
321
321
322 # now we log-in we should see thi gist
322 # now we log-in we should see thi gist
323 self.log_user()
323 self.log_user()
324 response = self.app.get(
324 response = self.app.get(
325 route_path('gist_show', gist_id=gist.gist_access_id))
325 route_path('gist_show', gist_id=gist.gist_access_id))
326 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
326 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
327
327
328 assert_response = response.assert_response()
328 assert_response = response.assert_response()
329 assert_response.element_equals_to(
329 assert_response.element_equals_to(
330 'div.rc-user span.user',
330 'div.rc-user span.user',
331 '<a href="/_profiles/test_admin">test_admin</a></span>')
331 '<a href="/_profiles/test_admin">test_admin</a></span>')
332 response.mustcontain('gist-desc')
332 response.mustcontain('gist-desc')
333
333
334 def test_show_as_raw(self, create_gist):
334 def test_show_as_raw(self, create_gist):
335 gist = create_gist('gist-show-me', content='GIST CONTENT')
335 gist = create_gist('gist-show-me', content='GIST CONTENT')
336 response = self.app.get(
336 response = self.app.get(
337 route_path('gist_show_formatted',
337 route_path('gist_show_formatted',
338 gist_id=gist.gist_access_id, revision='tip',
338 gist_id=gist.gist_access_id, revision='tip',
339 format='raw'))
339 format='raw'))
340 assert response.body == 'GIST CONTENT'
340 assert response.body == 'GIST CONTENT'
341
341
342 def test_show_as_raw_individual_file(self, create_gist):
342 def test_show_as_raw_individual_file(self, create_gist):
343 gist = create_gist('gist-show-me-raw', content='GIST BODY')
343 gist = create_gist('gist-show-me-raw', content='GIST BODY')
344 response = self.app.get(
344 response = self.app.get(
345 route_path('gist_show_formatted_path',
345 route_path('gist_show_formatted_path',
346 gist_id=gist.gist_access_id, format='raw',
346 gist_id=gist.gist_access_id, format='raw',
347 revision='tip', f_path='gist-show-me-raw'))
347 revision='tip', f_path='gist-show-me-raw'))
348 assert response.body == 'GIST BODY'
348 assert response.body == 'GIST BODY'
349
349
350 def test_edit_page(self, create_gist):
350 def test_edit_page(self, create_gist):
351 self.log_user()
351 self.log_user()
352 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
352 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
353 response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id))
353 response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id))
354 response.mustcontain('GIST EDIT BODY')
354 response.mustcontain('GIST EDIT BODY')
355
355
356 def test_edit_page_non_logged_user(self, create_gist):
356 def test_edit_page_non_logged_user(self, create_gist):
357 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
357 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
358 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
358 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
359 status=302)
359 status=302)
360
360
361 def test_edit_normal_user_his_gist(self, create_gist):
361 def test_edit_normal_user_his_gist(self, create_gist):
362 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
362 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
363 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
363 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
364 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id,
364 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id,
365 status=200))
365 status=200))
366
366
367 def test_edit_normal_user_not_his_own_gist(self, create_gist):
367 def test_edit_normal_user_not_his_own_gist(self, create_gist):
368 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
368 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
369 gist = create_gist('delete-me')
369 gist = create_gist('delete-me')
370 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
370 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
371 status=404)
371 status=404)
372
372
373 def test_user_first_name_is_escaped(self, user_util, create_gist):
373 def test_user_first_name_is_escaped(self, user_util, create_gist):
374 xss_atack_string = '"><script>alert(\'First Name\')</script>'
374 xss_atack_string = '"><script>alert(\'First Name\')</script>'
375 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
375 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
376 password = 'test'
376 password = 'test'
377 user = user_util.create_user(
377 user = user_util.create_user(
378 firstname=xss_atack_string, password=password)
378 firstname=xss_atack_string, password=password)
379 create_gist('gist', gist_type='public', owner=user.username)
379 create_gist('gist', gist_type='public', owner=user.username)
380 response = self.app.get(route_path('gists_show'))
380 response = self.app.get(route_path('gists_show'))
381 response.mustcontain(xss_escaped_string)
381 response.mustcontain(xss_escaped_string)
382
382
383 def test_user_last_name_is_escaped(self, user_util, create_gist):
383 def test_user_last_name_is_escaped(self, user_util, create_gist):
384 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
384 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
385 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
385 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
386 password = 'test'
386 password = 'test'
387 user = user_util.create_user(
387 user = user_util.create_user(
388 lastname=xss_atack_string, password=password)
388 lastname=xss_atack_string, password=password)
389 create_gist('gist', gist_type='public', owner=user.username)
389 create_gist('gist', gist_type='public', owner=user.username)
390 response = self.app.get(route_path('gists_show'))
390 response = self.app.get(route_path('gists_show'))
391 response.mustcontain(xss_escaped_string)
391 response.mustcontain(xss_escaped_string)
@@ -1,553 +1,560 b''
1
1
2 // tables.less
2 // tables.less
3 // For use in RhodeCode application tables;
3 // For use in RhodeCode application tables;
4 // see style guide documentation for guidelines.
4 // see style guide documentation for guidelines.
5
5
6 // TABLES
6 // TABLES
7
7
8 .rctable,
8 .rctable,
9 table.rctable,
9 table.rctable,
10 table.dataTable {
10 table.dataTable {
11 clear:both;
11 clear:both;
12 width: 100%;
12 width: 100%;
13 margin: 0 auto @padding;
13 margin: 0 auto @padding;
14 padding: 0;
14 padding: 0;
15 vertical-align: baseline;
15 vertical-align: baseline;
16 line-height:1.5em;
16 line-height:1.5em;
17 border: none;
17 border: none;
18 outline: none;
18 outline: none;
19 border-collapse: collapse;
19 border-collapse: collapse;
20 border-spacing: 0;
20 border-spacing: 0;
21 color: @grey2;
21 color: @grey2;
22
22
23 b {
23 b {
24 font-weight: normal;
24 font-weight: normal;
25 }
25 }
26
26
27 em {
27 em {
28 font-weight: bold;
28 font-weight: bold;
29 font-style: normal;
29 font-style: normal;
30 }
30 }
31
31
32 .td-user {
32 .td-user {
33 .rc-user {
33 .rc-user {
34 white-space: nowrap;
34 white-space: nowrap;
35 }
35 }
36 }
36 }
37
37
38 .td-email {
38 .td-email {
39 white-space: nowrap;
39 white-space: nowrap;
40 }
40 }
41
41
42 th,
42 th,
43 td {
43 td {
44 height: auto;
44 height: auto;
45 max-width: 20%;
45 max-width: 20%;
46 padding: .65em 0 .65em 1em;
46 padding: .65em 0 .65em 1em;
47 vertical-align: middle;
47 vertical-align: middle;
48 border-bottom: @border-thickness solid @grey5;
48 border-bottom: @border-thickness solid @grey5;
49 white-space: normal;
49 white-space: normal;
50
50
51 &.td-radio,
51 &.td-radio,
52 &.td-checkbox {
52 &.td-checkbox {
53 padding-right: 0;
53 padding-right: 0;
54 text-align: center;
54 text-align: center;
55
55
56 input {
56 input {
57 margin: 0 1em;
57 margin: 0 1em;
58 }
58 }
59 }
59 }
60
60
61 &.truncate-wrap {
61 &.truncate-wrap {
62 white-space: nowrap !important;
62 white-space: nowrap !important;
63 }
63 }
64
64
65 pre {
65 pre {
66 margin: 0;
66 margin: 0;
67 }
67 }
68
68
69 .show_more {
69 .show_more {
70 height: inherit;
70 height: inherit;
71 }
71 }
72 }
72 }
73
73
74 .expired td {
74 .expired td {
75 background-color: @grey7;
75 background-color: @grey7;
76 }
76 }
77 .inactive td {
77 .inactive td {
78 background-color: @grey6;
78 background-color: @grey6;
79 }
79 }
80 th {
80 th {
81 text-align: left;
81 text-align: left;
82 font-weight: @text-semibold-weight;
82 font-weight: @text-semibold-weight;
83 font-family: @text-semibold;
83 font-family: @text-semibold;
84 }
84 }
85
85
86 .hl {
86 .hl {
87 td {
87 td {
88 background-color: lighten(@alert4,25%);
88 background-color: lighten(@alert4,25%);
89 }
89 }
90 }
90 }
91
91
92 // Special Data Cell Types
92 // Special Data Cell Types
93 // See style guide for desciptions and examples.
93 // See style guide for desciptions and examples.
94
94
95 td {
95 td {
96
96
97 &.user {
97 &.user {
98 padding-left: 1em;
98 padding-left: 1em;
99 }
99 }
100
100
101 &.td-rss {
101 &.td-rss {
102 width: 20px;
102 width: 20px;
103 min-width: 0;
103 min-width: 0;
104 margin: 0;
104 margin: 0;
105 }
105 }
106
106
107 &.quick_repo_menu {
107 &.quick_repo_menu {
108 width: 15px;
108 width: 15px;
109 text-align: center;
109 text-align: center;
110
110
111 &:hover {
111 &:hover {
112 background-color: @grey5;
112 background-color: @grey5;
113 }
113 }
114 }
114 }
115
115
116 &.td-icon {
116 &.td-icon {
117 min-width: 20px;
117 min-width: 20px;
118 width: 20px;
118 width: 20px;
119 }
119 }
120
120
121 &.td-hash {
121 &.td-hash {
122 min-width: 80px;
122 min-width: 80px;
123 width: 200px;
123 width: 200px;
124
124
125 .obsolete {
125 .obsolete {
126 text-decoration: line-through;
126 text-decoration: line-through;
127 color: lighten(@grey2,25%);
127 color: lighten(@grey2,25%);
128 }
128 }
129 }
129 }
130
130
131 &.td-sha {
131 &.td-sha {
132 white-space: nowrap;
132 white-space: nowrap;
133 }
133 }
134
134
135 &.td-graphbox {
135 &.td-graphbox {
136 width: 100px;
136 width: 100px;
137 max-width: 100px;
137 max-width: 100px;
138 min-width: 100px;
138 min-width: 100px;
139 }
139 }
140
140
141 &.td-time {
141 &.td-time {
142 width: 160px;
142 width: 160px;
143 white-space: nowrap;
143 white-space: nowrap;
144 }
144 }
145
145
146 &.annotate{
146 &.annotate{
147 padding-right: 0;
147 padding-right: 0;
148
148
149 div.annotatediv{
149 div.annotatediv{
150 margin: 0 0.7em;
150 margin: 0 0.7em;
151 }
151 }
152 }
152 }
153
153
154 &.tags-col {
154 &.tags-col {
155 padding-right: 0;
155 padding-right: 0;
156 }
156 }
157
157
158 &.td-description {
158 &.td-description {
159 min-width: 350px;
159 min-width: 350px;
160
160
161 &.truncate, .truncate-wrap {
161 &.truncate, .truncate-wrap {
162 white-space: nowrap;
162 white-space: nowrap;
163 overflow: hidden;
163 overflow: hidden;
164 text-overflow: ellipsis;
164 text-overflow: ellipsis;
165 max-width: 350px;
165 max-width: 350px;
166 }
166 }
167 }
167 }
168
168
169 &.td-grid-name {
169 &.td-grid-name {
170 white-space: nowrap;
170 white-space: nowrap;
171 min-width: 300px;
171 min-width: 300px;
172 }
172 }
173
173
174 &.td-componentname {
174 &.td-componentname {
175 white-space: nowrap;
175 white-space: nowrap;
176 }
176 }
177
177
178 &.td-name {
178 &.td-name {
179
179
180 }
180 }
181
181
182 &.td-journalaction {
182 &.td-journalaction {
183 min-width: 300px;
183 min-width: 300px;
184
184
185 .journal_action_params {
185 .journal_action_params {
186 // waiting for feedback
186 // waiting for feedback
187 }
187 }
188 }
188 }
189
189
190 &.td-active {
190 &.td-active {
191 padding-left: .65em;
191 padding-left: .65em;
192 }
192 }
193
193
194 &.td-issue-tracker-name {
194 &.td-issue-tracker-name {
195 width: 180px;
195 width: 180px;
196 input {
196 input {
197 width: 180px;
197 width: 180px;
198 }
198 }
199
199
200 }
200 }
201
201
202 &.td-issue-tracker-regex {
202 &.td-issue-tracker-regex {
203 white-space: nowrap;
203 white-space: nowrap;
204
204
205 min-width: 300px;
205 min-width: 300px;
206 input {
206 input {
207 min-width: 300px;
207 min-width: 300px;
208 }
208 }
209
209
210 }
210 }
211
211
212 &.td-url {
212 &.td-url {
213 white-space: nowrap;
213 white-space: nowrap;
214 }
214 }
215
215
216 &.td-comments {
216 &.td-comments {
217 min-width: 3em;
217 min-width: 3em;
218 }
218 }
219
219
220 &.td-buttons {
220 &.td-buttons {
221 padding: .3em 0;
221 padding: .3em 0;
222 }
222 }
223 &.td-align-top {
223 &.td-align-top {
224 vertical-align: text-top
224 vertical-align: text-top
225 }
225 }
226 &.td-action {
226 &.td-action {
227 // this is for the remove/delete/edit buttons
227 // this is for the remove/delete/edit buttons
228 padding-right: 0;
228 padding-right: 0;
229 min-width: 95px;
229 min-width: 95px;
230 text-transform: capitalize;
230 text-transform: capitalize;
231
231
232 i {
232 i {
233 display: none;
233 display: none;
234 }
234 }
235 }
235 }
236
236
237 // TODO: lisa: this needs to be cleaned up with the buttons
237 // TODO: lisa: this needs to be cleaned up with the buttons
238 .grid_edit,
238 .grid_edit,
239 .grid_delete {
239 .grid_delete {
240 display: inline-block;
240 display: inline-block;
241 margin: 0 @padding/3 0 0;
241 margin: 0 @padding/3 0 0;
242 font-family: @text-light;
242 font-family: @text-light;
243
243
244 i {
244 i {
245 display: none;
245 display: none;
246 }
246 }
247 }
247 }
248
248
249 .grid_edit + .grid_delete {
249 .grid_edit + .grid_delete {
250 border-left: @border-thickness solid @grey5;
250 border-left: @border-thickness solid @grey5;
251 padding-left: @padding/2;
251 padding-left: @padding/2;
252 }
252 }
253
253
254 &.td-compare {
254 &.td-compare {
255
255
256 input {
256 input {
257 margin-right: 1em;
257 margin-right: 1em;
258 }
258 }
259
259
260 .compare-radio-button {
260 .compare-radio-button {
261 margin: 0 1em 0 0;
261 margin: 0 1em 0 0;
262 }
262 }
263
263
264
264
265 }
265 }
266
266
267 &.td-tags {
267 &.td-tags {
268 padding: .5em 1em .5em 0;
268 padding: .5em 1em .5em 0;
269 width: 140px;
269 width: 140px;
270
270
271 .tag {
271 .tag {
272 margin: 1px;
272 margin: 1px;
273 float: left;
273 float: left;
274 }
274 }
275 }
275 }
276
276
277 .icon-svn, .icon-hg, .icon-git {
277 .icon-svn, .icon-hg, .icon-git {
278 font-size: 1.4em;
278 font-size: 1.4em;
279 }
279 }
280
280
281 &.collapse_commit,
281 &.collapse_commit,
282 &.expand_commit {
282 &.expand_commit {
283 padding-right: 0;
283 padding-right: 0;
284 padding-left: 1em;
284 padding-left: 1em;
285 cursor: pointer;
285 cursor: pointer;
286 width: 20px;
286 width: 20px;
287 }
287 }
288 }
288 }
289
289
290 .perm_admin_row {
290 .perm_admin_row {
291 color: @grey4;
291 color: @grey4;
292 background-color: @grey6;
292 background-color: @grey6;
293 }
293 }
294
294
295 .noborder {
295 .noborder {
296 border: none;
296 border: none;
297
297
298 td {
298 td {
299 border: none;
299 border: none;
300 }
300 }
301 }
301 }
302 }
302 }
303 .rctable.audit-log {
303 .rctable.audit-log {
304 td {
304 td {
305 vertical-align: top;
305 vertical-align: top;
306 }
306 }
307 }
307 }
308
308
309 // TRUNCATING
309 // TRUNCATING
310 // TODO: lisaq: should this possibly be moved out of tables.less?
310 // TODO: lisaq: should this possibly be moved out of tables.less?
311 // for truncated text
311 // for truncated text
312 // used inside of table cells and in code block headers
312 // used inside of table cells and in code block headers
313 .truncate-wrap {
313 .truncate-wrap {
314 white-space: nowrap !important;
314 white-space: nowrap !important;
315
315
316 //truncated text
316 //truncated text
317 .truncate {
317 .truncate {
318 max-width: 450px;
318 max-width: 450px;
319 width: 300px;
319 width: 300px;
320 overflow: hidden;
320 overflow: hidden;
321 text-overflow: ellipsis;
321 text-overflow: ellipsis;
322 -o-text-overflow: ellipsis;
322 -o-text-overflow: ellipsis;
323 -ms-text-overflow: ellipsis;
323 -ms-text-overflow: ellipsis;
324
324
325 &.autoexpand {
325 &.autoexpand {
326 width: 120px;
326 width: 120px;
327 margin-right: 200px;
327 margin-right: 200px;
328 }
328 }
329 }
329 }
330 &:hover .truncate.autoexpand {
330 &:hover .truncate.autoexpand {
331 overflow: visible;
331 overflow: visible;
332 }
332 }
333
333
334 .tags-truncate {
334 .tags-truncate {
335 width: 150px;
335 width: 150px;
336 height: 22px;
336 height: 22px;
337 overflow: hidden;
337 overflow: hidden;
338
338
339 .tag {
339 .tag {
340 display: inline-block;
340 display: inline-block;
341 }
341 }
342
342
343 &.truncate {
343 &.truncate {
344 height: 22px;
344 height: 22px;
345 max-height:2em;
345 max-height:2em;
346 width: 140px;
346 width: 140px;
347 }
347 }
348 }
348 }
349 }
349 }
350
350
351 .apikeys_wrap {
351 .apikeys_wrap {
352 margin-bottom: @padding;
352 margin-bottom: @padding;
353
353
354 table.rctable td:first-child {
354 table.rctable td:first-child {
355 width: 340px;
355 width: 340px;
356 }
356 }
357 }
357 }
358
358
359
359
360
360
361 // SPECIAL CASES
361 // SPECIAL CASES
362
362
363 // Repository Followers
363 // Repository Followers
364 table.rctable.followers_data {
364 table.rctable.followers_data {
365 width: 75%;
365 width: 75%;
366 margin: 0;
366 margin: 0;
367 }
367 }
368
368
369 // Repository List
369 // Repository List
370 // Group Members List
370 // Group Members List
371 table.rctable.group_members,
371 table.rctable.group_members,
372 table#repo_list_table {
372 table#repo_list_table {
373 min-width: 600px;
373 min-width: 600px;
374 }
374 }
375
375
376 #no_grid_data {
376 #no_grid_data {
377 text-align: center;
377 text-align: center;
378 }
378 }
379
379
380 #grid_data_loading {
380 #grid_data_loading {
381 text-align: center;
381 text-align: center;
382 font-weight: 600;
382 font-weight: 600;
383 font-size: 16px;
383 font-size: 16px;
384 padding: 80px 20px;
384 padding: 80px 20px;
385 }
385 }
386
386
387 // Keyboard mappings
387 // Keyboard mappings
388 table.keyboard-mappings {
388 table.keyboard-mappings {
389 th {
389 th {
390 text-align: left;
390 text-align: left;
391 font-weight: @text-semibold-weight;
391 font-weight: @text-semibold-weight;
392 font-family: @text-semibold;
392 font-family: @text-semibold;
393 }
393 }
394 }
394 }
395
395
396 // Branches, Tags, and Bookmarks
396 // Branches, Tags, and Bookmarks
397 #obj_list_table.dataTable {
397 #obj_list_table.dataTable {
398 td.td-time {
398 td.td-time {
399 padding-right: 1em;
399 padding-right: 1em;
400 }
400 }
401 }
401 }
402
402
403 // User Admin
403 // User Admin
404 .rctable.useremails,
404 .rctable.useremails,
405 .rctable.account_emails {
405 .rctable.account_emails {
406 .tag,
406 .tag,
407 .btn {
407 .btn {
408 float: right;
408 float: right;
409 }
409 }
410 .btn { //to line up with tags
410 .btn { //to line up with tags
411 margin-right: 1.65em;
411 margin-right: 1.65em;
412 }
412 }
413 }
413 }
414
414
415 // User List
415 // User List
416 #user_list_table {
416 #user_list_table {
417
417
418 td.td-user {
418 td.td-user {
419 min-width: 100px;
419 min-width: 100px;
420 }
420 }
421 }
421 }
422
422
423 // Pull Request List Table
423 // Pull Request List Table
424 #pull_request_list_table.dataTable {
424 #pull_request_list_table.dataTable {
425
425
426 //TODO: lisa: This needs to be removed once the description is adjusted
426 //TODO: lisa: This needs to be removed once the description is adjusted
427 // for using an expand_commit button (see issue 765)
427 // for using an expand_commit button (see issue 765)
428 td {
428 td {
429 vertical-align: middle;
429 vertical-align: middle;
430 }
430 }
431 }
431 }
432
432
433 // Settings (no border)
433 // Settings (no border)
434 table.rctable.dl-settings {
434 table.rctable.dl-settings {
435 td {
435 td {
436 border: none;
436 border: none;
437 vertical-align: baseline;
437 vertical-align: baseline;
438 }
438 }
439 }
439 }
440
440
441
441
442 // Statistics
442 // Statistics
443 table.trending_language_tbl {
443 table.trending_language_tbl {
444 width: 100%;
444 width: 100%;
445 line-height: 1em;
445 line-height: 1em;
446
446
447 td div {
447 td div {
448 overflow: visible;
448 overflow: visible;
449 }
449 }
450 }
450 }
451
451
452 .trending_language_tbl, .trending_language_tbl td {
452 .trending_language_tbl, .trending_language_tbl td {
453 border: 0;
453 border: 0;
454 margin: 0;
454 margin: 0;
455 padding: 0;
455 padding: 0;
456 background: transparent;
456 background: transparent;
457 }
457 }
458
458
459 .trending_language_tbl, .trending_language_tbl tr {
459 .trending_language_tbl, .trending_language_tbl tr {
460 border-spacing: 0 3px;
460 border-spacing: 0 3px;
461 }
461 }
462
462
463 .trending_language {
463 .trending_language {
464 position: relative;
464 position: relative;
465 overflow: hidden;
465 overflow: hidden;
466 color: @text-color;
466 color: @text-color;
467 width: 400px;
467 width: 400px;
468
468
469 .lang-bar {
469 .lang-bar {
470 z-index: 1;
470 z-index: 1;
471 overflow: hidden;
471 overflow: hidden;
472 background-color: @rcblue;
472 background-color: @rcblue;
473 color: #FFF;
473 color: #FFF;
474 text-decoration: none;
474 text-decoration: none;
475 }
475 }
476
476
477 }
477 }
478
478
479 // Changesets
479 // Changesets
480 #changesets.rctable {
480 #changesets.rctable {
481 th {
481 th {
482 padding: 0 1em 0.65em 0;
482 padding: 0 1em 0.65em 0;
483 }
483 }
484
484
485 // td must be fixed height for graph
485 // td must be fixed height for graph
486 td {
486 td {
487 height: 32px;
487 height: 32px;
488 padding: 0 1em 0 0;
488 padding: 0 1em 0 0;
489 vertical-align: middle;
489 vertical-align: middle;
490 white-space: nowrap;
490 white-space: nowrap;
491
491
492 &.td-description {
492 &.td-description {
493 white-space: normal;
493 white-space: normal;
494 }
494 }
495
495
496 &.expand_commit {
496 &.expand_commit {
497 padding-right: 0;
497 padding-right: 0;
498 cursor: pointer;
498 cursor: pointer;
499 width: 20px;
499 width: 20px;
500 }
500 }
501 }
501 }
502 }
502 }
503
503
504 // Compare
504 // Compare
505 table.compare_view_commits {
505 table.compare_view_commits {
506 margin-top: @space;
506 margin-top: @space;
507
507
508 td.td-time {
508 td.td-time {
509 padding-left: .5em;
509 padding-left: .5em;
510 }
510 }
511
511
512 // special case to not show hover actions on hidden indicator
512 // special case to not show hover actions on hidden indicator
513 tr.compare_select_hidden:hover {
513 tr.compare_select_hidden:hover {
514 cursor: inherit;
514 cursor: inherit;
515
515
516 td {
516 td {
517 background-color: inherit;
517 background-color: inherit;
518 }
518 }
519 }
519 }
520
520
521 tr:hover {
521 tr:hover {
522 cursor: pointer;
522 cursor: pointer;
523
523
524 td {
524 td {
525 background-color: lighten(@alert4,25%);
525 background-color: lighten(@alert4,25%);
526 }
526 }
527 }
527 }
528
528
529
529
530 }
530 }
531
531
532 .file_history {
532 .file_history {
533 td.td-actions {
533 td.td-actions {
534 text-align: right;
534 text-align: right;
535 }
535 }
536 }
536 }
537
537
538
538
539 // Gist List
539 // Gist List
540 #gist_list_table {
540 #gist_list_table {
541 td {
541 td {
542 vertical-align: middle;
542 vertical-align: middle;
543
543
544 div{
544 div{
545 display: inline-block;
545 display: inline-block;
546 vertical-align: middle;
546 vertical-align: middle;
547 }
547 }
548
548
549 img{
549 img{
550 vertical-align: middle;
550 vertical-align: middle;
551 }
551 }
552
553 &.td-expire {
554 width: 200px;
555 }
556 &.td-gist-type {
557 width: 100px;
558 }
552 }
559 }
553 }
560 }
@@ -1,147 +1,147 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 %if c.show_private:
5 %if c.show_private:
6 ${_('Private Gists for user {}').format(c.rhodecode_user.username)}
6 ${_('Private Gists for user {}').format(c.rhodecode_user.username)}
7 %elif c.show_public:
7 %elif c.show_public:
8 ${_('Public Gists for user {}').format(c.rhodecode_user.username)}
8 ${_('Public Gists for user {}').format(c.rhodecode_user.username)}
9 %else:
9 %else:
10 ${_('Public Gists')}
10 ${_('Public Gists')}
11 %endif
11 %endif
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <%def name="breadcrumbs_links()"></%def>
17 <%def name="breadcrumbs_links()"></%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='gists')}
20 ${self.menu_items(active='gists')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24
24
25 <div class="box">
25 <div class="box">
26 <div class="title">
26 <div class="title">
27
27
28 <ul class="button-links">
28 <ul class="button-links">
29 % if c.is_super_admin:
29 % if c.is_super_admin:
30 <li class="btn ${h.is_active('all', c.active)}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
30 <li class="btn ${h.is_active('all', c.active)}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
31 %endif
31 %endif
32 <li class="btn ${h.is_active('public', c.active)}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li>
32 <li class="btn ${h.is_active('public', c.active)}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li>
33 %if c.rhodecode_user.username != h.DEFAULT_USER:
33 %if c.rhodecode_user.username != h.DEFAULT_USER:
34 <li class="btn ${h.is_active('my_all', c.active)}"><a href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
34 <li class="btn ${h.is_active('my_all', c.active)}"><a href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
35 <li class="btn ${h.is_active('my_private', c.active)}"><a href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
35 <li class="btn ${h.is_active('my_private', c.active)}"><a href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
36 <li class="btn ${h.is_active('my_public', c.active)}"><a href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
36 <li class="btn ${h.is_active('my_public', c.active)}"><a href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
37 %endif
37 %endif
38 </ul>
38 </ul>
39
39
40 % if c.rhodecode_user.username != h.DEFAULT_USER:
40 % if c.rhodecode_user.username != h.DEFAULT_USER:
41 <div class="pull-right">
41 <div class="pull-right">
42 <a class="btn btn-primary" href="${h.route_path('gists_new')}" >
42 <a class="btn btn-primary" href="${h.route_path('gists_new')}" >
43 ${_(u'Create New Gist')}
43 ${_(u'Create New Gist')}
44 </a>
44 </a>
45 </div>
45 </div>
46 % endif
46 % endif
47
47
48 <div class="grid-quick-filter">
48 <div class="grid-quick-filter">
49 <ul class="grid-filter-box">
49 <ul class="grid-filter-box">
50 <li class="grid-filter-box-icon">
50 <li class="grid-filter-box-icon">
51 <i class="icon-search"></i>
51 <i class="icon-search"></i>
52 </li>
52 </li>
53 <li class="grid-filter-box-input">
53 <li class="grid-filter-box-input">
54 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
54 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
55 </li>
55 </li>
56 </ul>
56 </ul>
57 </div>
57 </div>
58
58
59 </div>
59 </div>
60
60
61 <div class="main-content-full-width">
61 <div class="main-content-full-width">
62 <div id="repos_list_wrap">
62 <div id="repos_list_wrap">
63 <table id="gist_list_table" class="display"></table>
63 <table id="gist_list_table" class="display"></table>
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 </div>
67 </div>
68
68
69 <script type="text/javascript">
69 <script type="text/javascript">
70 $(document).ready(function() {
70 $(document).ready(function() {
71
71
72 var get_datatable_count = function(){
72 var get_datatable_count = function(){
73 var api = $('#gist_list_table').dataTable().api();
73 var api = $('#gist_list_table').dataTable().api();
74 $('#gists_count').text(api.page.info().recordsDisplay);
74 $('#gists_count').text(api.page.info().recordsDisplay);
75 };
75 };
76
76
77
77
78 // custom filter that filters by access_id, description or author
78 // custom filter that filters by access_id, description or author
79 $.fn.dataTable.ext.search.push(
79 $.fn.dataTable.ext.search.push(
80 function( settings, data, dataIndex ) {
80 function( settings, data, dataIndex ) {
81 var query = $('#q_filter').val();
81 var query = $('#q_filter').val();
82 var author = data[0].strip();
82 var author = data[0].strip();
83 var access_id = data[2].strip();
83 var access_id = data[2].strip();
84 var description = data[3].strip();
84 var description = data[3].strip();
85
85
86 var query_str = (access_id + " " + author + " " + description).toLowerCase();
86 var query_str = (access_id + " " + author + " " + description).toLowerCase();
87
87
88 if(query_str.indexOf(query.toLowerCase()) !== -1){
88 if(query_str.indexOf(query.toLowerCase()) !== -1){
89 return true;
89 return true;
90 }
90 }
91 return false;
91 return false;
92 }
92 }
93 );
93 );
94
94
95 // gists list
95 // gists list
96 $('#gist_list_table').DataTable({
96 $('#gist_list_table').DataTable({
97 data: ${c.data|n},
97 data: ${c.data|n},
98 dom: 'rtp',
98 dom: 'rtp',
99 pageLength: ${c.visual.dashboard_items},
99 pageLength: ${c.visual.dashboard_items},
100 order: [[ 4, "desc" ]],
100 order: [[ 4, "desc" ]],
101 columns: [
101 columns: [
102 { data: {"_": "author",
102 { data: {"_": "author",
103 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
103 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
104 { data: {"_": "type",
104 { data: {"_": "type",
105 "sort": "type"}, title: "${_("Type")}", width: "70px", className: "td-tags" },
105 "sort": "type"}, title: "${_("Type")}", width: "100px", className: "td-gist-type" },
106 { data: {"_": "access_id",
106 { data: {"_": "access_id",
107 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
107 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
108 { data: {"_": "description",
108 { data: {"_": "description",
109 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
109 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
110 { data: {"_": "created_on",
110 { data: {"_": "created_on",
111 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
111 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
112 { data: {"_": "expires",
112 { data: {"_": "expires",
113 "sort": "expires"}, title: "${_("Expires")}", className: "td-exp" }
113 "sort": "expires"}, title: "${_("Expires")}", width: "200px", className: "td-expire" }
114 ],
114 ],
115 language: {
115 language: {
116 paginate: DEFAULT_GRID_PAGINATION,
116 paginate: DEFAULT_GRID_PAGINATION,
117 emptyTable: _gettext("No gists available yet.")
117 emptyTable: _gettext("No gists available yet.")
118 },
118 },
119 "initComplete": function( settings, json ) {
119 "initComplete": function( settings, json ) {
120 timeagoActivate();
120 timeagoActivate();
121 tooltipActivate();
121 tooltipActivate();
122 get_datatable_count();
122 get_datatable_count();
123 }
123 }
124 });
124 });
125
125
126 // update the counter when things change
126 // update the counter when things change
127 $('#gist_list_table').on('draw.dt', function() {
127 $('#gist_list_table').on('draw.dt', function() {
128 timeagoActivate();
128 timeagoActivate();
129 tooltipActivate();
129 tooltipActivate();
130 get_datatable_count();
130 get_datatable_count();
131 });
131 });
132
132
133 // filter, filter both grids
133 // filter, filter both grids
134 $('#q_filter').on( 'keyup', function () {
134 $('#q_filter').on( 'keyup', function () {
135 var repo_api = $('#gist_list_table').dataTable().api();
135 var repo_api = $('#gist_list_table').dataTable().api();
136 repo_api
136 repo_api
137 .draw();
137 .draw();
138 });
138 });
139
139
140 // refilter table if page load via back button
140 // refilter table if page load via back button
141 $("#q_filter").trigger('keyup');
141 $("#q_filter").trigger('keyup');
142
142
143 });
143 });
144
144
145 </script>
145 </script>
146 </%def>
146 </%def>
147
147
@@ -1,479 +1,479 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(repo_name, rev, commit_id, 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 class="tooltip-hovercard" data-hovercard-alt=${h.tooltip(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>
147 <code><a class="tooltip-hovercard" data-hovercard-alt=${h.tooltip(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 <code>
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_access_id}</a>
315 </b>
315 </code>
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 <span class="tag tag-gist-public disabled">${_('Public Gist')}</span>
342 <span class="tag tag-gist-public disabled">${_('Public Gist')}</span>
343 %else:
343 %else:
344 <span class="tag tag-gist-private disabled">${_('Private Gist')}</span>
344 <span class="tag tag-gist-private disabled">${_('Private Gist')}</span>
345 %endif
345 %endif
346 </%def>
346 </%def>
347
347
348 <%def name="gist_description(gist_description)">
348 <%def name="gist_description(gist_description)">
349 ${gist_description}
349 ${gist_description}
350 </%def>
350 </%def>
351
351
352
352
353 ## PULL REQUESTS GRID RENDERERS
353 ## PULL REQUESTS GRID RENDERERS
354
354
355 <%def name="pullrequest_target_repo(repo_name)">
355 <%def name="pullrequest_target_repo(repo_name)">
356 <div class="truncate">
356 <div class="truncate">
357 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
357 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
358 </div>
358 </div>
359 </%def>
359 </%def>
360
360
361 <%def name="pullrequest_status(status)">
361 <%def name="pullrequest_status(status)">
362 <i class="icon-circle review-status-${status}"></i>
362 <i class="icon-circle review-status-${status}"></i>
363 </%def>
363 </%def>
364
364
365 <%def name="pullrequest_title(title, description)">
365 <%def name="pullrequest_title(title, description)">
366 ${title}
366 ${title}
367 </%def>
367 </%def>
368
368
369 <%def name="pullrequest_comments(comments_nr)">
369 <%def name="pullrequest_comments(comments_nr)">
370 <i class="icon-comment"></i> ${comments_nr}
370 <i class="icon-comment"></i> ${comments_nr}
371 </%def>
371 </%def>
372
372
373 <%def name="pullrequest_name(pull_request_id, state, is_wip, target_repo_name, short=False)">
373 <%def name="pullrequest_name(pull_request_id, state, is_wip, target_repo_name, short=False)">
374 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
374 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
375
375
376 % if short:
376 % if short:
377 !${pull_request_id}
377 !${pull_request_id}
378 % else:
378 % else:
379 ${_('Pull request !{}').format(pull_request_id)}
379 ${_('Pull request !{}').format(pull_request_id)}
380 % endif
380 % endif
381
381
382 % if state not in ['created']:
382 % if state not in ['created']:
383 <span class="tag tag-merge-state-${state} tooltip" title="Pull request state is changing">${state}</span>
383 <span class="tag tag-merge-state-${state} tooltip" title="Pull request state is changing">${state}</span>
384 % endif
384 % endif
385
385
386 % if is_wip:
386 % if is_wip:
387 <span class="tag tooltip" title="${_('Work in progress')}">wip</span>
387 <span class="tag tooltip" title="${_('Work in progress')}">wip</span>
388 % endif
388 % endif
389 </a>
389 </a>
390 </%def>
390 </%def>
391
391
392 <%def name="pullrequest_updated_on(updated_on)">
392 <%def name="pullrequest_updated_on(updated_on)">
393 ${h.age_component(h.time_to_utcdatetime(updated_on))}
393 ${h.age_component(h.time_to_utcdatetime(updated_on))}
394 </%def>
394 </%def>
395
395
396 <%def name="pullrequest_author(full_contact)">
396 <%def name="pullrequest_author(full_contact)">
397 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
397 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
398 </%def>
398 </%def>
399
399
400
400
401 ## ARTIFACT RENDERERS
401 ## ARTIFACT RENDERERS
402 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
402 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
403 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
403 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
404 ${artifact_display_name or '_EMPTY_NAME_'}
404 ${artifact_display_name or '_EMPTY_NAME_'}
405 </a>
405 </a>
406 </%def>
406 </%def>
407
407
408 <%def name="repo_artifact_uid(repo_name, file_uid)">
408 <%def name="repo_artifact_uid(repo_name, file_uid)">
409 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
409 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
410 </%def>
410 </%def>
411
411
412 <%def name="repo_artifact_sha256(artifact_sha256)">
412 <%def name="repo_artifact_sha256(artifact_sha256)">
413 <div class="code">${h.shorter(artifact_sha256, 12)}</div>
413 <div class="code">${h.shorter(artifact_sha256, 12)}</div>
414 </%def>
414 </%def>
415
415
416 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
416 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
417 ## <div class="grid_edit">
417 ## <div class="grid_edit">
418 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
418 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
419 ## </div>
419 ## </div>
420 <div class="grid_edit">
420 <div class="grid_edit">
421 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
421 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
422 </div>
422 </div>
423 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
423 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
424 <div class="grid_delete">
424 <div class="grid_delete">
425 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
425 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
426 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
426 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
427 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
427 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
428 ${h.end_form()}
428 ${h.end_form()}
429 </div>
429 </div>
430 % endif
430 % endif
431 </%def>
431 </%def>
432
432
433 <%def name="markup_form(form_id, form_text='', help_text=None)">
433 <%def name="markup_form(form_id, form_text='', help_text=None)">
434
434
435 <div class="markup-form">
435 <div class="markup-form">
436 <div class="markup-form-area">
436 <div class="markup-form-area">
437 <div class="markup-form-area-header">
437 <div class="markup-form-area-header">
438 <ul class="nav-links clearfix">
438 <ul class="nav-links clearfix">
439 <li class="active">
439 <li class="active">
440 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
440 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
441 </li>
441 </li>
442 <li class="">
442 <li class="">
443 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
443 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
444 </li>
444 </li>
445 </ul>
445 </ul>
446 </div>
446 </div>
447
447
448 <div class="markup-form-area-write" style="display: block;">
448 <div class="markup-form-area-write" style="display: block;">
449 <div id="edit-container_${form_id}">
449 <div id="edit-container_${form_id}">
450 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
450 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
451 </div>
451 </div>
452 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
452 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
453 <div id="preview-box_${form_id}" class="preview-box"></div>
453 <div id="preview-box_${form_id}" class="preview-box"></div>
454 </div>
454 </div>
455 </div>
455 </div>
456
456
457 <div class="markup-form-area-footer">
457 <div class="markup-form-area-footer">
458 <div class="toolbar">
458 <div class="toolbar">
459 <div class="toolbar-text">
459 <div class="toolbar-text">
460 ${(_('Parsed using %s syntax') % (
460 ${(_('Parsed using %s syntax') % (
461 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
461 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
462 )
462 )
463 )|n}
463 )|n}
464 </div>
464 </div>
465 </div>
465 </div>
466 </div>
466 </div>
467 </div>
467 </div>
468
468
469 <div class="markup-form-footer">
469 <div class="markup-form-footer">
470 % if help_text:
470 % if help_text:
471 <span class="help-block">${help_text}</span>
471 <span class="help-block">${help_text}</span>
472 % endif
472 % endif
473 </div>
473 </div>
474 </div>
474 </div>
475 <script type="text/javascript">
475 <script type="text/javascript">
476 new MarkupForm('${form_id}');
476 new MarkupForm('${form_id}');
477 </script>
477 </script>
478
478
479 </%def>
479 </%def>
General Comments 0
You need to be logged in to leave comments. Login now