##// END OF EJS Templates
hovercacrds: added new tooltips and hovercards to expose certain information for objects shown in UI
marcink -
r4026:ed756817 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,38 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2018-2019 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.config import routing_links
21
22
23 def includeme(config):
24
25 config.add_route(
26 name='hovercard_user',
27 pattern='/_hovercard/user/{user_id}')
28
29 config.add_route(
30 name='hovercard_user_group',
31 pattern='/_hovercard/user_group/{user_group_id}')
32
33 config.add_route(
34 name='hovercard_commit',
35 pattern='/_hovercard/commit/{repo_name}/{user_id}')
36
37 # Scan module for configuration decorators.
38 config.scan('.views', ignore='.tests')
@@ -0,0 +1,71 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import re
22 import logging
23 import collections
24
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
32 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.model.db import (
37 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
38 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.scm import RepoGroupList, RepoList
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.user_group import UserGroupModel
43
44 log = logging.getLogger(__name__)
45
46
47 class HoverCardsView(BaseAppView):
48
49 def load_default_context(self):
50 c = self._get_local_tmpl_context()
51 return c
52
53 @LoginRequired()
54 @view_config(
55 route_name='hovercard_user', request_method='GET', xhr=True,
56 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
57 def hovercard_user(self):
58 c = self.load_default_context()
59 user_id = self.request.matchdict['user_id']
60 c.user = User.get_or_404(user_id)
61 return self._get_template_context(c)
62
63 @LoginRequired()
64 @view_config(
65 route_name='hovercard_user_group', request_method='GET', xhr=True,
66 renderer='rhodecode:templates/hovercards/hovercard_user_group.mako')
67 def hovercard_user_group(self):
68 c = self.load_default_context()
69 user_group_id = self.request.matchdict['user_group_id']
70 c.user_group = UserGroup.get_or_404(user_group_id)
71 return self._get_template_context(c)
@@ -0,0 +1,460 b''
1 /* This is the core CSS of Tooltipster */
2
3 /* GENERAL STRUCTURE RULES (do not edit this section) */
4
5 .tooltipster-base {
6 /* this ensures that a constrained height set by functionPosition,
7 if greater that the natural height of the tooltip, will be enforced
8 in browsers that support display:flex */
9 display: flex;
10 pointer-events: none;
11 /* this may be overriden in JS for fixed position origins */
12 position: absolute;
13 }
14
15 .tooltipster-box {
16 /* see .tooltipster-base. flex-shrink 1 is only necessary for IE10-
17 and flex-basis auto for IE11- (at least) */
18 flex: 1 1 auto;
19 }
20
21 .tooltipster-content {
22 /* prevents an overflow if the user adds padding to the div */
23 box-sizing: border-box;
24 /* these make sure we'll be able to detect any overflow */
25 max-height: 100%;
26 max-width: 100%;
27 overflow: auto;
28 }
29
30 .tooltipster-ruler {
31 /* these let us test the size of the tooltip without overflowing the window */
32 bottom: 0;
33 left: 0;
34 overflow: hidden;
35 position: fixed;
36 right: 0;
37 top: 0;
38 visibility: hidden;
39 }
40
41 /* ANIMATIONS */
42
43 /* Open/close animations */
44
45 /* fade */
46
47 .tooltipster-fade {
48 opacity: 0;
49 -webkit-transition-property: opacity;
50 -moz-transition-property: opacity;
51 -o-transition-property: opacity;
52 -ms-transition-property: opacity;
53 transition-property: opacity;
54 }
55 .tooltipster-fade.tooltipster-show {
56 opacity: 1;
57 }
58
59 /* grow */
60
61 .tooltipster-grow {
62 -webkit-transform: scale(0,0);
63 -moz-transform: scale(0,0);
64 -o-transform: scale(0,0);
65 -ms-transform: scale(0,0);
66 transform: scale(0,0);
67 -webkit-transition-property: -webkit-transform;
68 -moz-transition-property: -moz-transform;
69 -o-transition-property: -o-transform;
70 -ms-transition-property: -ms-transform;
71 transition-property: transform;
72 -webkit-backface-visibility: hidden;
73 }
74 .tooltipster-grow.tooltipster-show {
75 -webkit-transform: scale(1,1);
76 -moz-transform: scale(1,1);
77 -o-transform: scale(1,1);
78 -ms-transform: scale(1,1);
79 transform: scale(1,1);
80 -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
81 -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
82 -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
83 -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
84 -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
85 transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
86 }
87
88 /* swing */
89
90 .tooltipster-swing {
91 opacity: 0;
92 -webkit-transform: rotateZ(4deg);
93 -moz-transform: rotateZ(4deg);
94 -o-transform: rotateZ(4deg);
95 -ms-transform: rotateZ(4deg);
96 transform: rotateZ(4deg);
97 -webkit-transition-property: -webkit-transform, opacity;
98 -moz-transition-property: -moz-transform;
99 -o-transition-property: -o-transform;
100 -ms-transition-property: -ms-transform;
101 transition-property: transform;
102 }
103 .tooltipster-swing.tooltipster-show {
104 opacity: 1;
105 -webkit-transform: rotateZ(0deg);
106 -moz-transform: rotateZ(0deg);
107 -o-transform: rotateZ(0deg);
108 -ms-transform: rotateZ(0deg);
109 transform: rotateZ(0deg);
110 -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 1);
111 -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
112 -moz-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
113 -ms-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
114 -o-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
115 transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
116 }
117
118 /* fall */
119
120 .tooltipster-fall {
121 -webkit-transition-property: top;
122 -moz-transition-property: top;
123 -o-transition-property: top;
124 -ms-transition-property: top;
125 transition-property: top;
126 -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
127 -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
128 -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
129 -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
130 -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
131 transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
132 }
133 .tooltipster-fall.tooltipster-initial {
134 top: 0 !important;
135 }
136 .tooltipster-fall.tooltipster-show {
137 }
138 .tooltipster-fall.tooltipster-dying {
139 -webkit-transition-property: all;
140 -moz-transition-property: all;
141 -o-transition-property: all;
142 -ms-transition-property: all;
143 transition-property: all;
144 top: 0 !important;
145 opacity: 0;
146 }
147
148 /* slide */
149
150 .tooltipster-slide {
151 -webkit-transition-property: left;
152 -moz-transition-property: left;
153 -o-transition-property: left;
154 -ms-transition-property: left;
155 transition-property: left;
156 -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
157 -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
158 -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
159 -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
160 -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
161 transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
162 }
163 .tooltipster-slide.tooltipster-initial {
164 left: -40px !important;
165 }
166 .tooltipster-slide.tooltipster-show {
167 }
168 .tooltipster-slide.tooltipster-dying {
169 -webkit-transition-property: all;
170 -moz-transition-property: all;
171 -o-transition-property: all;
172 -ms-transition-property: all;
173 transition-property: all;
174 left: 0 !important;
175 opacity: 0;
176 }
177
178 /* Update animations */
179
180 /* We use animations rather than transitions here because
181 transition durations may be specified in the style tag due to
182 animationDuration, and we try to avoid collisions and the use
183 of !important */
184
185 /* fade */
186
187 @keyframes tooltipster-fading {
188 0% {
189 opacity: 0;
190 }
191 100% {
192 opacity: 1;
193 }
194 }
195
196 .tooltipster-update-fade {
197 animation: tooltipster-fading 400ms;
198 }
199
200 /* rotate */
201
202 @keyframes tooltipster-rotating {
203 25% {
204 transform: rotate(-2deg);
205 }
206 75% {
207 transform: rotate(2deg);
208 }
209 100% {
210 transform: rotate(0);
211 }
212 }
213
214 .tooltipster-update-rotate {
215 animation: tooltipster-rotating 600ms;
216 }
217
218 /* scale */
219
220 @keyframes tooltipster-scaling {
221 50% {
222 transform: scale(1.1);
223 }
224 100% {
225 transform: scale(1);
226 }
227 }
228
229 .tooltipster-update-scale {
230 animation: tooltipster-scaling 600ms;
231 }
232
233 /**
234 * DEFAULT STYLE OF THE SIDETIP PLUGIN
235 *
236 * All styles are "namespaced" with .tooltipster-sidetip to prevent
237 * conflicts between plugins.
238 */
239
240 /* .tooltipster-box */
241
242 .tooltipster-sidetip .tooltipster-box {
243 background: #565656;
244 border: 2px solid black;
245 border-radius: 4px;
246 }
247
248 .tooltipster-sidetip.tooltipster-bottom .tooltipster-box {
249 margin-top: 8px;
250 }
251
252 .tooltipster-sidetip.tooltipster-left .tooltipster-box {
253 margin-right: 8px;
254 }
255
256 .tooltipster-sidetip.tooltipster-right .tooltipster-box {
257 margin-left: 8px;
258 }
259
260 .tooltipster-sidetip.tooltipster-top .tooltipster-box {
261 margin-bottom: 8px;
262 }
263
264 /* .tooltipster-content */
265
266 .tooltipster-sidetip .tooltipster-content {
267 color: white;
268 line-height: 18px;
269 padding: 6px 14px;
270 }
271
272 /* .tooltipster-arrow : will keep only the zone of .tooltipster-arrow-uncropped that
273 corresponds to the arrow we want to display */
274
275 .tooltipster-sidetip .tooltipster-arrow {
276 overflow: hidden;
277 position: absolute;
278 }
279
280 .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow {
281 height: 10px;
282 /* half the width, for centering */
283 margin-left: -10px;
284 top: 0;
285 width: 20px;
286 }
287
288 .tooltipster-sidetip.tooltipster-left .tooltipster-arrow {
289 height: 20px;
290 margin-top: -10px;
291 right: 0;
292 /* top 0 to keep the arrow from overflowing .tooltipster-base when it has not
293 been positioned yet */
294 top: 0;
295 width: 10px;
296 }
297
298 .tooltipster-sidetip.tooltipster-right .tooltipster-arrow {
299 height: 20px;
300 margin-top: -10px;
301 left: 0;
302 /* same as .tooltipster-left .tooltipster-arrow */
303 top: 0;
304 width: 10px;
305 }
306
307 .tooltipster-sidetip.tooltipster-top .tooltipster-arrow {
308 bottom: 0;
309 height: 10px;
310 margin-left: -10px;
311 width: 20px;
312 }
313
314 /* common rules between .tooltipster-arrow-background and .tooltipster-arrow-border */
315
316 .tooltipster-sidetip .tooltipster-arrow-background, .tooltipster-sidetip .tooltipster-arrow-border {
317 height: 0;
318 position: absolute;
319 width: 0;
320 }
321
322 /* .tooltipster-arrow-background */
323
324 .tooltipster-sidetip .tooltipster-arrow-background {
325 border: 10px solid transparent;
326 }
327
328 .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-background {
329 border-bottom-color: #565656;
330 left: 0;
331 top: 3px;
332 }
333
334 .tooltipster-sidetip.tooltipster-left .tooltipster-arrow-background {
335 border-left-color: #565656;
336 left: -3px;
337 top: 0;
338 }
339
340 .tooltipster-sidetip.tooltipster-right .tooltipster-arrow-background {
341 border-right-color: #565656;
342 left: 3px;
343 top: 0;
344 }
345
346 .tooltipster-sidetip.tooltipster-top .tooltipster-arrow-background {
347 border-top-color: #565656;
348 left: 0;
349 top: -3px;
350 }
351
352 /* .tooltipster-arrow-border */
353
354 .tooltipster-sidetip .tooltipster-arrow-border {
355 border: 10px solid transparent;
356 left: 0;
357 top: 0;
358 }
359
360 .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-border {
361 border-bottom-color: black;
362 }
363
364 .tooltipster-sidetip.tooltipster-left .tooltipster-arrow-border {
365 border-left-color: black;
366 }
367
368 .tooltipster-sidetip.tooltipster-right .tooltipster-arrow-border {
369 border-right-color: black;
370 }
371
372 .tooltipster-sidetip.tooltipster-top .tooltipster-arrow-border {
373 border-top-color: black;
374 }
375
376 /* tooltipster-arrow-uncropped */
377
378 .tooltipster-sidetip .tooltipster-arrow-uncropped {
379 position: relative;
380 }
381
382 .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-uncropped {
383 top: -10px;
384 }
385
386 .tooltipster-sidetip.tooltipster-right .tooltipster-arrow-uncropped {
387 left: -10px;
388 }
389
390 .tooltipster-sidetip.tooltipster-shadow .tooltipster-box {
391 border: none;
392 border-radius: 5px;
393 background: #fff;
394 box-shadow: 0 0 5px 3px rgba(0, 0, 0, .1)
395 }
396
397 .tooltipster-sidetip.tooltipster-shadow.tooltipster-bottom .tooltipster-box {
398 margin-top: 6px
399 }
400
401 .tooltipster-sidetip.tooltipster-shadow.tooltipster-left .tooltipster-box {
402 margin-right: 6px
403 }
404
405 .tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-box {
406 margin-left: 6px
407 }
408
409 .tooltipster-sidetip.tooltipster-shadow.tooltipster-top .tooltipster-box {
410 margin-bottom: 6px
411 }
412
413 .tooltipster-sidetip.tooltipster-shadow .tooltipster-content {
414 color: #8d8d8d
415 }
416
417 .tooltipster-sidetip.tooltipster-shadow .tooltipster-arrow {
418 height: 6px;
419 margin-left: -6px;
420 width: 12px
421 }
422
423 .tooltipster-sidetip.tooltipster-shadow.tooltipster-left .tooltipster-arrow, .tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-arrow {
424 height: 12px;
425 margin-left: 0;
426 margin-top: -6px;
427 width: 6px
428 }
429
430 .tooltipster-sidetip.tooltipster-shadow .tooltipster-arrow-background {
431 display: none
432 }
433
434 .tooltipster-sidetip.tooltipster-shadow .tooltipster-arrow-border {
435 border: 6px solid transparent
436 }
437
438 .tooltipster-sidetip.tooltipster-shadow.tooltipster-bottom .tooltipster-arrow-border {
439 border-bottom-color: #fff
440 }
441
442 .tooltipster-sidetip.tooltipster-shadow.tooltipster-left .tooltipster-arrow-border {
443 border-left-color: #fff
444 }
445
446 .tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-arrow-border {
447 border-right-color: #fff
448 }
449
450 .tooltipster-sidetip.tooltipster-shadow.tooltipster-top .tooltipster-arrow-border {
451 border-top-color: #fff
452 }
453
454 .tooltipster-sidetip.tooltipster-shadow.tooltipster-bottom .tooltipster-arrow-uncropped {
455 top: -6px
456 }
457
458 .tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-arrow-uncropped {
459 left: -6px
460 } No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,754 +1,755 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 os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26 import time
26 import time
27
27
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 import pyramid.events
29 import pyramid.events
30 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response
37
37
38 from rhodecode.model import meta
38 from rhodecode.model import meta
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config import utils as config_utils
40 from rhodecode.config import utils as config_utils
41 from rhodecode.config.environment import load_pyramid_environment
41 from rhodecode.config.environment import load_pyramid_environment
42
42
43 import rhodecode.events
43 import rhodecode.events
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
46 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.celerylib.loader import configure_celery
50 from rhodecode.lib.celerylib.loader import configure_celery
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.lib.exc_tracking import store_exception
54 from rhodecode.subscribers import (
54 from rhodecode.subscribers import (
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 write_metadata_if_needed, inject_app_settings)
56 write_metadata_if_needed, inject_app_settings)
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 def is_http_error(response):
62 def is_http_error(response):
63 # error which should have traceback
63 # error which should have traceback
64 return response.status_code > 499
64 return response.status_code > 499
65
65
66
66
67 def should_load_all():
67 def should_load_all():
68 """
68 """
69 Returns if all application components should be loaded. In some cases it's
69 Returns if all application components should be loaded. In some cases it's
70 desired to skip apps loading for faster shell script execution
70 desired to skip apps loading for faster shell script execution
71 """
71 """
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 if ssh_cmd:
73 if ssh_cmd:
74 return False
74 return False
75
75
76 return True
76 return True
77
77
78
78
79 def make_pyramid_app(global_config, **settings):
79 def make_pyramid_app(global_config, **settings):
80 """
80 """
81 Constructs the WSGI application based on Pyramid.
81 Constructs the WSGI application based on Pyramid.
82
82
83 Specials:
83 Specials:
84
84
85 * The application can also be integrated like a plugin via the call to
85 * The application can also be integrated like a plugin via the call to
86 `includeme`. This is accompanied with the other utility functions which
86 `includeme`. This is accompanied with the other utility functions which
87 are called. Changing this should be done with great care to not break
87 are called. Changing this should be done with great care to not break
88 cases when these fragments are assembled from another place.
88 cases when these fragments are assembled from another place.
89
89
90 """
90 """
91
91
92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
93 # will be replaced by the value of the environment variable "NAME" in this case.
93 # will be replaced by the value of the environment variable "NAME" in this case.
94 start_time = time.time()
94 start_time = time.time()
95
95
96 debug = asbool(global_config.get('debug'))
96 debug = asbool(global_config.get('debug'))
97 if debug:
97 if debug:
98 enable_debug()
98 enable_debug()
99
99
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101
101
102 global_config = _substitute_values(global_config, environ)
102 global_config = _substitute_values(global_config, environ)
103 settings = _substitute_values(settings, environ)
103 settings = _substitute_values(settings, environ)
104
104
105 sanitize_settings_and_apply_defaults(global_config, settings)
105 sanitize_settings_and_apply_defaults(global_config, settings)
106
106
107 config = Configurator(settings=settings)
107 config = Configurator(settings=settings)
108
108
109 # Apply compatibility patches
109 # Apply compatibility patches
110 patches.inspect_getargspec()
110 patches.inspect_getargspec()
111
111
112 load_pyramid_environment(global_config, settings)
112 load_pyramid_environment(global_config, settings)
113
113
114 # Static file view comes first
114 # Static file view comes first
115 includeme_first(config)
115 includeme_first(config)
116
116
117 includeme(config)
117 includeme(config)
118
118
119 pyramid_app = config.make_wsgi_app()
119 pyramid_app = config.make_wsgi_app()
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 pyramid_app.config = config
121 pyramid_app.config = config
122
122
123 config.configure_celery(global_config['__file__'])
123 config.configure_celery(global_config['__file__'])
124 # creating the app uses a connection - return it after we are done
124 # creating the app uses a connection - return it after we are done
125 meta.Session.remove()
125 meta.Session.remove()
126 total_time = time.time() - start_time
126 total_time = time.time() - start_time
127 log.info('Pyramid app `%s` created and configured in %.2fs',
127 log.info('Pyramid app `%s` created and configured in %.2fs',
128 pyramid_app.func_name, total_time)
128 pyramid_app.func_name, total_time)
129
129
130 return pyramid_app
130 return pyramid_app
131
131
132
132
133 def not_found_view(request):
133 def not_found_view(request):
134 """
134 """
135 This creates the view which should be registered as not-found-view to
135 This creates the view which should be registered as not-found-view to
136 pyramid.
136 pyramid.
137 """
137 """
138
138
139 if not getattr(request, 'vcs_call', None):
139 if not getattr(request, 'vcs_call', None):
140 # handle like regular case with our error_handler
140 # handle like regular case with our error_handler
141 return error_handler(HTTPNotFound(), request)
141 return error_handler(HTTPNotFound(), request)
142
142
143 # handle not found view as a vcs call
143 # handle not found view as a vcs call
144 settings = request.registry.settings
144 settings = request.registry.settings
145 ae_client = getattr(request, 'ae_client', None)
145 ae_client = getattr(request, 'ae_client', None)
146 vcs_app = VCSMiddleware(
146 vcs_app = VCSMiddleware(
147 HTTPNotFound(), request.registry, settings,
147 HTTPNotFound(), request.registry, settings,
148 appenlight_client=ae_client)
148 appenlight_client=ae_client)
149
149
150 return wsgiapp(vcs_app)(None, request)
150 return wsgiapp(vcs_app)(None, request)
151
151
152
152
153 def error_handler(exception, request):
153 def error_handler(exception, request):
154 import rhodecode
154 import rhodecode
155 from rhodecode.lib import helpers
155 from rhodecode.lib import helpers
156
156
157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
158
158
159 base_response = HTTPInternalServerError()
159 base_response = HTTPInternalServerError()
160 # prefer original exception for the response since it may have headers set
160 # prefer original exception for the response since it may have headers set
161 if isinstance(exception, HTTPException):
161 if isinstance(exception, HTTPException):
162 base_response = exception
162 base_response = exception
163 elif isinstance(exception, VCSCommunicationError):
163 elif isinstance(exception, VCSCommunicationError):
164 base_response = VCSServerUnavailable()
164 base_response = VCSServerUnavailable()
165
165
166 if is_http_error(base_response):
166 if is_http_error(base_response):
167 log.exception(
167 log.exception(
168 'error occurred handling this request for path: %s', request.path)
168 'error occurred handling this request for path: %s', request.path)
169
169
170 error_explanation = base_response.explanation or str(base_response)
170 error_explanation = base_response.explanation or str(base_response)
171 if base_response.status_code == 404:
171 if base_response.status_code == 404:
172 error_explanation += " Optionally you don't have permission to access this page."
172 error_explanation += " Optionally you don't have permission to access this page."
173 c = AttributeDict()
173 c = AttributeDict()
174 c.error_message = base_response.status
174 c.error_message = base_response.status
175 c.error_explanation = error_explanation
175 c.error_explanation = error_explanation
176 c.visual = AttributeDict()
176 c.visual = AttributeDict()
177
177
178 c.visual.rhodecode_support_url = (
178 c.visual.rhodecode_support_url = (
179 request.registry.settings.get('rhodecode_support_url') or
179 request.registry.settings.get('rhodecode_support_url') or
180 request.route_url('rhodecode_support')
180 request.route_url('rhodecode_support')
181 )
181 )
182 c.redirect_time = 0
182 c.redirect_time = 0
183 c.rhodecode_name = rhodecode_title
183 c.rhodecode_name = rhodecode_title
184 if not c.rhodecode_name:
184 if not c.rhodecode_name:
185 c.rhodecode_name = 'Rhodecode'
185 c.rhodecode_name = 'Rhodecode'
186
186
187 c.causes = []
187 c.causes = []
188 if is_http_error(base_response):
188 if is_http_error(base_response):
189 c.causes.append('Server is overloaded.')
189 c.causes.append('Server is overloaded.')
190 c.causes.append('Server database connection is lost.')
190 c.causes.append('Server database connection is lost.')
191 c.causes.append('Server expected unhandled error.')
191 c.causes.append('Server expected unhandled error.')
192
192
193 if hasattr(base_response, 'causes'):
193 if hasattr(base_response, 'causes'):
194 c.causes = base_response.causes
194 c.causes = base_response.causes
195
195
196 c.messages = helpers.flash.pop_messages(request=request)
196 c.messages = helpers.flash.pop_messages(request=request)
197
197
198 exc_info = sys.exc_info()
198 exc_info = sys.exc_info()
199 c.exception_id = id(exc_info)
199 c.exception_id = id(exc_info)
200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
201 or base_response.status_code > 499
201 or base_response.status_code > 499
202 c.exception_id_url = request.route_url(
202 c.exception_id_url = request.route_url(
203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
204
204
205 if c.show_exception_id:
205 if c.show_exception_id:
206 store_exception(c.exception_id, exc_info)
206 store_exception(c.exception_id, exc_info)
207
207
208 response = render_to_response(
208 response = render_to_response(
209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
210 response=base_response)
210 response=base_response)
211
211
212 return response
212 return response
213
213
214
214
215 def includeme_first(config):
215 def includeme_first(config):
216 # redirect automatic browser favicon.ico requests to correct place
216 # redirect automatic browser favicon.ico requests to correct place
217 def favicon_redirect(context, request):
217 def favicon_redirect(context, request):
218 return HTTPFound(
218 return HTTPFound(
219 request.static_path('rhodecode:public/images/favicon.ico'))
219 request.static_path('rhodecode:public/images/favicon.ico'))
220
220
221 config.add_view(favicon_redirect, route_name='favicon')
221 config.add_view(favicon_redirect, route_name='favicon')
222 config.add_route('favicon', '/favicon.ico')
222 config.add_route('favicon', '/favicon.ico')
223
223
224 def robots_redirect(context, request):
224 def robots_redirect(context, request):
225 return HTTPFound(
225 return HTTPFound(
226 request.static_path('rhodecode:public/robots.txt'))
226 request.static_path('rhodecode:public/robots.txt'))
227
227
228 config.add_view(robots_redirect, route_name='robots')
228 config.add_view(robots_redirect, route_name='robots')
229 config.add_route('robots', '/robots.txt')
229 config.add_route('robots', '/robots.txt')
230
230
231 config.add_static_view(
231 config.add_static_view(
232 '_static/deform', 'deform:static')
232 '_static/deform', 'deform:static')
233 config.add_static_view(
233 config.add_static_view(
234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
235
235
236
236
237 def includeme(config):
237 def includeme(config):
238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
239 settings = config.registry.settings
239 settings = config.registry.settings
240 config.set_request_factory(Request)
240 config.set_request_factory(Request)
241
241
242 # plugin information
242 # plugin information
243 config.registry.rhodecode_plugins = collections.OrderedDict()
243 config.registry.rhodecode_plugins = collections.OrderedDict()
244
244
245 config.add_directive(
245 config.add_directive(
246 'register_rhodecode_plugin', register_rhodecode_plugin)
246 'register_rhodecode_plugin', register_rhodecode_plugin)
247
247
248 config.add_directive('configure_celery', configure_celery)
248 config.add_directive('configure_celery', configure_celery)
249
249
250 if asbool(settings.get('appenlight', 'false')):
250 if asbool(settings.get('appenlight', 'false')):
251 config.include('appenlight_client.ext.pyramid_tween')
251 config.include('appenlight_client.ext.pyramid_tween')
252
252
253 load_all = should_load_all()
253 load_all = should_load_all()
254
254
255 # Includes which are required. The application would fail without them.
255 # Includes which are required. The application would fail without them.
256 config.include('pyramid_mako')
256 config.include('pyramid_mako')
257 config.include('rhodecode.lib.rc_beaker')
257 config.include('rhodecode.lib.rc_beaker')
258 config.include('rhodecode.lib.rc_cache')
258 config.include('rhodecode.lib.rc_cache')
259
259
260 config.include('rhodecode.apps._base.navigation')
260 config.include('rhodecode.apps._base.navigation')
261 config.include('rhodecode.apps._base.subscribers')
261 config.include('rhodecode.apps._base.subscribers')
262 config.include('rhodecode.tweens')
262 config.include('rhodecode.tweens')
263 config.include('rhodecode.authentication')
263 config.include('rhodecode.authentication')
264
264
265 if load_all:
265 if load_all:
266 config.include('rhodecode.integrations')
266 config.include('rhodecode.integrations')
267
267
268 if load_all:
268 if load_all:
269 from rhodecode.authentication import discover_legacy_plugins
269 from rhodecode.authentication import discover_legacy_plugins
270 # load CE authentication plugins
270 # load CE authentication plugins
271 config.include('rhodecode.authentication.plugins.auth_crowd')
271 config.include('rhodecode.authentication.plugins.auth_crowd')
272 config.include('rhodecode.authentication.plugins.auth_headers')
272 config.include('rhodecode.authentication.plugins.auth_headers')
273 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
273 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
274 config.include('rhodecode.authentication.plugins.auth_ldap')
274 config.include('rhodecode.authentication.plugins.auth_ldap')
275 config.include('rhodecode.authentication.plugins.auth_pam')
275 config.include('rhodecode.authentication.plugins.auth_pam')
276 config.include('rhodecode.authentication.plugins.auth_rhodecode')
276 config.include('rhodecode.authentication.plugins.auth_rhodecode')
277 config.include('rhodecode.authentication.plugins.auth_token')
277 config.include('rhodecode.authentication.plugins.auth_token')
278
278
279 # Auto discover authentication plugins and include their configuration.
279 # Auto discover authentication plugins and include their configuration.
280 discover_legacy_plugins(config)
280 discover_legacy_plugins(config)
281
281
282 # apps
282 # apps
283 if load_all:
283 if load_all:
284 config.include('rhodecode.apps._base')
284 config.include('rhodecode.apps._base')
285 config.include('rhodecode.apps.hovercards')
285 config.include('rhodecode.apps.ops')
286 config.include('rhodecode.apps.ops')
286 config.include('rhodecode.apps.admin')
287 config.include('rhodecode.apps.admin')
287 config.include('rhodecode.apps.channelstream')
288 config.include('rhodecode.apps.channelstream')
288 config.include('rhodecode.apps.file_store')
289 config.include('rhodecode.apps.file_store')
289 config.include('rhodecode.apps.login')
290 config.include('rhodecode.apps.login')
290 config.include('rhodecode.apps.home')
291 config.include('rhodecode.apps.home')
291 config.include('rhodecode.apps.journal')
292 config.include('rhodecode.apps.journal')
292 config.include('rhodecode.apps.repository')
293 config.include('rhodecode.apps.repository')
293 config.include('rhodecode.apps.repo_group')
294 config.include('rhodecode.apps.repo_group')
294 config.include('rhodecode.apps.user_group')
295 config.include('rhodecode.apps.user_group')
295 config.include('rhodecode.apps.search')
296 config.include('rhodecode.apps.search')
296 config.include('rhodecode.apps.user_profile')
297 config.include('rhodecode.apps.user_profile')
297 config.include('rhodecode.apps.user_group_profile')
298 config.include('rhodecode.apps.user_group_profile')
298 config.include('rhodecode.apps.my_account')
299 config.include('rhodecode.apps.my_account')
299 config.include('rhodecode.apps.svn_support')
300 config.include('rhodecode.apps.svn_support')
300 config.include('rhodecode.apps.ssh_support')
301 config.include('rhodecode.apps.ssh_support')
301 config.include('rhodecode.apps.gist')
302 config.include('rhodecode.apps.gist')
302 config.include('rhodecode.apps.debug_style')
303 config.include('rhodecode.apps.debug_style')
303 config.include('rhodecode.api')
304 config.include('rhodecode.api')
304
305
305 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
306 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
306 config.add_translation_dirs('rhodecode:i18n/')
307 config.add_translation_dirs('rhodecode:i18n/')
307 settings['default_locale_name'] = settings.get('lang', 'en')
308 settings['default_locale_name'] = settings.get('lang', 'en')
308
309
309 # Add subscribers.
310 # Add subscribers.
310 if load_all:
311 if load_all:
311 config.add_subscriber(inject_app_settings,
312 config.add_subscriber(inject_app_settings,
312 pyramid.events.ApplicationCreated)
313 pyramid.events.ApplicationCreated)
313 config.add_subscriber(scan_repositories_if_enabled,
314 config.add_subscriber(scan_repositories_if_enabled,
314 pyramid.events.ApplicationCreated)
315 pyramid.events.ApplicationCreated)
315 config.add_subscriber(write_metadata_if_needed,
316 config.add_subscriber(write_metadata_if_needed,
316 pyramid.events.ApplicationCreated)
317 pyramid.events.ApplicationCreated)
317 config.add_subscriber(write_js_routes_if_enabled,
318 config.add_subscriber(write_js_routes_if_enabled,
318 pyramid.events.ApplicationCreated)
319 pyramid.events.ApplicationCreated)
319
320
320 # request custom methods
321 # request custom methods
321 config.add_request_method(
322 config.add_request_method(
322 'rhodecode.lib.partial_renderer.get_partial_renderer',
323 'rhodecode.lib.partial_renderer.get_partial_renderer',
323 'get_partial_renderer')
324 'get_partial_renderer')
324
325
325 config.add_request_method(
326 config.add_request_method(
326 'rhodecode.lib.request_counter.get_request_counter',
327 'rhodecode.lib.request_counter.get_request_counter',
327 'request_count')
328 'request_count')
328
329
329 # Set the authorization policy.
330 # Set the authorization policy.
330 authz_policy = ACLAuthorizationPolicy()
331 authz_policy = ACLAuthorizationPolicy()
331 config.set_authorization_policy(authz_policy)
332 config.set_authorization_policy(authz_policy)
332
333
333 # Set the default renderer for HTML templates to mako.
334 # Set the default renderer for HTML templates to mako.
334 config.add_mako_renderer('.html')
335 config.add_mako_renderer('.html')
335
336
336 config.add_renderer(
337 config.add_renderer(
337 name='json_ext',
338 name='json_ext',
338 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
339 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
339
340
340 # include RhodeCode plugins
341 # include RhodeCode plugins
341 includes = aslist(settings.get('rhodecode.includes', []))
342 includes = aslist(settings.get('rhodecode.includes', []))
342 for inc in includes:
343 for inc in includes:
343 config.include(inc)
344 config.include(inc)
344
345
345 # custom not found view, if our pyramid app doesn't know how to handle
346 # custom not found view, if our pyramid app doesn't know how to handle
346 # the request pass it to potential VCS handling ap
347 # the request pass it to potential VCS handling ap
347 config.add_notfound_view(not_found_view)
348 config.add_notfound_view(not_found_view)
348 if not settings.get('debugtoolbar.enabled', False):
349 if not settings.get('debugtoolbar.enabled', False):
349 # disabled debugtoolbar handle all exceptions via the error_handlers
350 # disabled debugtoolbar handle all exceptions via the error_handlers
350 config.add_view(error_handler, context=Exception)
351 config.add_view(error_handler, context=Exception)
351
352
352 # all errors including 403/404/50X
353 # all errors including 403/404/50X
353 config.add_view(error_handler, context=HTTPError)
354 config.add_view(error_handler, context=HTTPError)
354
355
355
356
356 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
357 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
357 """
358 """
358 Apply outer WSGI middlewares around the application.
359 Apply outer WSGI middlewares around the application.
359 """
360 """
360 registry = config.registry
361 registry = config.registry
361 settings = registry.settings
362 settings = registry.settings
362
363
363 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
364 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
364 pyramid_app = HttpsFixup(pyramid_app, settings)
365 pyramid_app = HttpsFixup(pyramid_app, settings)
365
366
366 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
367 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
367 pyramid_app, settings)
368 pyramid_app, settings)
368 registry.ae_client = _ae_client
369 registry.ae_client = _ae_client
369
370
370 if settings['gzip_responses']:
371 if settings['gzip_responses']:
371 pyramid_app = make_gzip_middleware(
372 pyramid_app = make_gzip_middleware(
372 pyramid_app, settings, compress_level=1)
373 pyramid_app, settings, compress_level=1)
373
374
374 # this should be the outer most middleware in the wsgi stack since
375 # this should be the outer most middleware in the wsgi stack since
375 # middleware like Routes make database calls
376 # middleware like Routes make database calls
376 def pyramid_app_with_cleanup(environ, start_response):
377 def pyramid_app_with_cleanup(environ, start_response):
377 try:
378 try:
378 return pyramid_app(environ, start_response)
379 return pyramid_app(environ, start_response)
379 finally:
380 finally:
380 # Dispose current database session and rollback uncommitted
381 # Dispose current database session and rollback uncommitted
381 # transactions.
382 # transactions.
382 meta.Session.remove()
383 meta.Session.remove()
383
384
384 # In a single threaded mode server, on non sqlite db we should have
385 # In a single threaded mode server, on non sqlite db we should have
385 # '0 Current Checked out connections' at the end of a request,
386 # '0 Current Checked out connections' at the end of a request,
386 # if not, then something, somewhere is leaving a connection open
387 # if not, then something, somewhere is leaving a connection open
387 pool = meta.Base.metadata.bind.engine.pool
388 pool = meta.Base.metadata.bind.engine.pool
388 log.debug('sa pool status: %s', pool.status())
389 log.debug('sa pool status: %s', pool.status())
389 log.debug('Request processing finalized')
390 log.debug('Request processing finalized')
390
391
391 return pyramid_app_with_cleanup
392 return pyramid_app_with_cleanup
392
393
393
394
394 def sanitize_settings_and_apply_defaults(global_config, settings):
395 def sanitize_settings_and_apply_defaults(global_config, settings):
395 """
396 """
396 Applies settings defaults and does all type conversion.
397 Applies settings defaults and does all type conversion.
397
398
398 We would move all settings parsing and preparation into this place, so that
399 We would move all settings parsing and preparation into this place, so that
399 we have only one place left which deals with this part. The remaining parts
400 we have only one place left which deals with this part. The remaining parts
400 of the application would start to rely fully on well prepared settings.
401 of the application would start to rely fully on well prepared settings.
401
402
402 This piece would later be split up per topic to avoid a big fat monster
403 This piece would later be split up per topic to avoid a big fat monster
403 function.
404 function.
404 """
405 """
405
406
406 settings.setdefault('rhodecode.edition', 'Community Edition')
407 settings.setdefault('rhodecode.edition', 'Community Edition')
407
408
408 if 'mako.default_filters' not in settings:
409 if 'mako.default_filters' not in settings:
409 # set custom default filters if we don't have it defined
410 # set custom default filters if we don't have it defined
410 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
411 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
411 settings['mako.default_filters'] = 'h_filter'
412 settings['mako.default_filters'] = 'h_filter'
412
413
413 if 'mako.directories' not in settings:
414 if 'mako.directories' not in settings:
414 mako_directories = settings.setdefault('mako.directories', [
415 mako_directories = settings.setdefault('mako.directories', [
415 # Base templates of the original application
416 # Base templates of the original application
416 'rhodecode:templates',
417 'rhodecode:templates',
417 ])
418 ])
418 log.debug(
419 log.debug(
419 "Using the following Mako template directories: %s",
420 "Using the following Mako template directories: %s",
420 mako_directories)
421 mako_directories)
421
422
422 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
423 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
423 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
424 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
424 raw_url = settings['beaker.session.url']
425 raw_url = settings['beaker.session.url']
425 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
426 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
426 settings['beaker.session.url'] = 'redis://' + raw_url
427 settings['beaker.session.url'] = 'redis://' + raw_url
427
428
428 # Default includes, possible to change as a user
429 # Default includes, possible to change as a user
429 pyramid_includes = settings.setdefault('pyramid.includes', [
430 pyramid_includes = settings.setdefault('pyramid.includes', [
430 'rhodecode.lib.middleware.request_wrapper',
431 'rhodecode.lib.middleware.request_wrapper',
431 ])
432 ])
432 log.debug(
433 log.debug(
433 "Using the following pyramid.includes: %s",
434 "Using the following pyramid.includes: %s",
434 pyramid_includes)
435 pyramid_includes)
435
436
436 # TODO: johbo: Re-think this, usually the call to config.include
437 # TODO: johbo: Re-think this, usually the call to config.include
437 # should allow to pass in a prefix.
438 # should allow to pass in a prefix.
438 settings.setdefault('rhodecode.api.url', '/_admin/api')
439 settings.setdefault('rhodecode.api.url', '/_admin/api')
439 settings.setdefault('__file__', global_config.get('__file__'))
440 settings.setdefault('__file__', global_config.get('__file__'))
440
441
441 # Sanitize generic settings.
442 # Sanitize generic settings.
442 _list_setting(settings, 'default_encoding', 'UTF-8')
443 _list_setting(settings, 'default_encoding', 'UTF-8')
443 _bool_setting(settings, 'is_test', 'false')
444 _bool_setting(settings, 'is_test', 'false')
444 _bool_setting(settings, 'gzip_responses', 'false')
445 _bool_setting(settings, 'gzip_responses', 'false')
445
446
446 # Call split out functions that sanitize settings for each topic.
447 # Call split out functions that sanitize settings for each topic.
447 _sanitize_appenlight_settings(settings)
448 _sanitize_appenlight_settings(settings)
448 _sanitize_vcs_settings(settings)
449 _sanitize_vcs_settings(settings)
449 _sanitize_cache_settings(settings)
450 _sanitize_cache_settings(settings)
450
451
451 # configure instance id
452 # configure instance id
452 config_utils.set_instance_id(settings)
453 config_utils.set_instance_id(settings)
453
454
454 return settings
455 return settings
455
456
456
457
457 def enable_debug():
458 def enable_debug():
458 """
459 """
459 Helper to enable debug on running instance
460 Helper to enable debug on running instance
460 :return:
461 :return:
461 """
462 """
462 import tempfile
463 import tempfile
463 import textwrap
464 import textwrap
464 import logging.config
465 import logging.config
465
466
466 ini_template = textwrap.dedent("""
467 ini_template = textwrap.dedent("""
467 #####################################
468 #####################################
468 ### DEBUG LOGGING CONFIGURATION ####
469 ### DEBUG LOGGING CONFIGURATION ####
469 #####################################
470 #####################################
470 [loggers]
471 [loggers]
471 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
472 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
472
473
473 [handlers]
474 [handlers]
474 keys = console, console_sql
475 keys = console, console_sql
475
476
476 [formatters]
477 [formatters]
477 keys = generic, color_formatter, color_formatter_sql
478 keys = generic, color_formatter, color_formatter_sql
478
479
479 #############
480 #############
480 ## LOGGERS ##
481 ## LOGGERS ##
481 #############
482 #############
482 [logger_root]
483 [logger_root]
483 level = NOTSET
484 level = NOTSET
484 handlers = console
485 handlers = console
485
486
486 [logger_sqlalchemy]
487 [logger_sqlalchemy]
487 level = INFO
488 level = INFO
488 handlers = console_sql
489 handlers = console_sql
489 qualname = sqlalchemy.engine
490 qualname = sqlalchemy.engine
490 propagate = 0
491 propagate = 0
491
492
492 [logger_beaker]
493 [logger_beaker]
493 level = DEBUG
494 level = DEBUG
494 handlers =
495 handlers =
495 qualname = beaker.container
496 qualname = beaker.container
496 propagate = 1
497 propagate = 1
497
498
498 [logger_rhodecode]
499 [logger_rhodecode]
499 level = DEBUG
500 level = DEBUG
500 handlers =
501 handlers =
501 qualname = rhodecode
502 qualname = rhodecode
502 propagate = 1
503 propagate = 1
503
504
504 [logger_ssh_wrapper]
505 [logger_ssh_wrapper]
505 level = DEBUG
506 level = DEBUG
506 handlers =
507 handlers =
507 qualname = ssh_wrapper
508 qualname = ssh_wrapper
508 propagate = 1
509 propagate = 1
509
510
510 [logger_celery]
511 [logger_celery]
511 level = DEBUG
512 level = DEBUG
512 handlers =
513 handlers =
513 qualname = celery
514 qualname = celery
514
515
515
516
516 ##############
517 ##############
517 ## HANDLERS ##
518 ## HANDLERS ##
518 ##############
519 ##############
519
520
520 [handler_console]
521 [handler_console]
521 class = StreamHandler
522 class = StreamHandler
522 args = (sys.stderr, )
523 args = (sys.stderr, )
523 level = DEBUG
524 level = DEBUG
524 formatter = color_formatter
525 formatter = color_formatter
525
526
526 [handler_console_sql]
527 [handler_console_sql]
527 # "level = DEBUG" logs SQL queries and results.
528 # "level = DEBUG" logs SQL queries and results.
528 # "level = INFO" logs SQL queries.
529 # "level = INFO" logs SQL queries.
529 # "level = WARN" logs neither. (Recommended for production systems.)
530 # "level = WARN" logs neither. (Recommended for production systems.)
530 class = StreamHandler
531 class = StreamHandler
531 args = (sys.stderr, )
532 args = (sys.stderr, )
532 level = WARN
533 level = WARN
533 formatter = color_formatter_sql
534 formatter = color_formatter_sql
534
535
535 ################
536 ################
536 ## FORMATTERS ##
537 ## FORMATTERS ##
537 ################
538 ################
538
539
539 [formatter_generic]
540 [formatter_generic]
540 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
541 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
541 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
542 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
542 datefmt = %Y-%m-%d %H:%M:%S
543 datefmt = %Y-%m-%d %H:%M:%S
543
544
544 [formatter_color_formatter]
545 [formatter_color_formatter]
545 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
546 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
546 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
547 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
547 datefmt = %Y-%m-%d %H:%M:%S
548 datefmt = %Y-%m-%d %H:%M:%S
548
549
549 [formatter_color_formatter_sql]
550 [formatter_color_formatter_sql]
550 class = rhodecode.lib.logging_formatter.ColorFormatterSql
551 class = rhodecode.lib.logging_formatter.ColorFormatterSql
551 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
552 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
552 datefmt = %Y-%m-%d %H:%M:%S
553 datefmt = %Y-%m-%d %H:%M:%S
553 """)
554 """)
554
555
555 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
556 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
556 delete=False) as f:
557 delete=False) as f:
557 log.info('Saved Temporary DEBUG config at %s', f.name)
558 log.info('Saved Temporary DEBUG config at %s', f.name)
558 f.write(ini_template)
559 f.write(ini_template)
559
560
560 logging.config.fileConfig(f.name)
561 logging.config.fileConfig(f.name)
561 log.debug('DEBUG MODE ON')
562 log.debug('DEBUG MODE ON')
562 os.remove(f.name)
563 os.remove(f.name)
563
564
564
565
565 def _sanitize_appenlight_settings(settings):
566 def _sanitize_appenlight_settings(settings):
566 _bool_setting(settings, 'appenlight', 'false')
567 _bool_setting(settings, 'appenlight', 'false')
567
568
568
569
569 def _sanitize_vcs_settings(settings):
570 def _sanitize_vcs_settings(settings):
570 """
571 """
571 Applies settings defaults and does type conversion for all VCS related
572 Applies settings defaults and does type conversion for all VCS related
572 settings.
573 settings.
573 """
574 """
574 _string_setting(settings, 'vcs.svn.compatible_version', '')
575 _string_setting(settings, 'vcs.svn.compatible_version', '')
575 _string_setting(settings, 'vcs.hooks.protocol', 'http')
576 _string_setting(settings, 'vcs.hooks.protocol', 'http')
576 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
577 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
577 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
578 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
578 _string_setting(settings, 'vcs.server', '')
579 _string_setting(settings, 'vcs.server', '')
579 _string_setting(settings, 'vcs.server.log_level', 'debug')
580 _string_setting(settings, 'vcs.server.log_level', 'debug')
580 _string_setting(settings, 'vcs.server.protocol', 'http')
581 _string_setting(settings, 'vcs.server.protocol', 'http')
581 _bool_setting(settings, 'startup.import_repos', 'false')
582 _bool_setting(settings, 'startup.import_repos', 'false')
582 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
583 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
583 _bool_setting(settings, 'vcs.server.enable', 'true')
584 _bool_setting(settings, 'vcs.server.enable', 'true')
584 _bool_setting(settings, 'vcs.start_server', 'false')
585 _bool_setting(settings, 'vcs.start_server', 'false')
585 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
586 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
586 _int_setting(settings, 'vcs.connection_timeout', 3600)
587 _int_setting(settings, 'vcs.connection_timeout', 3600)
587
588
588 # Support legacy values of vcs.scm_app_implementation. Legacy
589 # Support legacy values of vcs.scm_app_implementation. Legacy
589 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
590 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
590 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
591 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
591 scm_app_impl = settings['vcs.scm_app_implementation']
592 scm_app_impl = settings['vcs.scm_app_implementation']
592 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
593 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
593 settings['vcs.scm_app_implementation'] = 'http'
594 settings['vcs.scm_app_implementation'] = 'http'
594
595
595
596
596 def _sanitize_cache_settings(settings):
597 def _sanitize_cache_settings(settings):
597 temp_store = tempfile.gettempdir()
598 temp_store = tempfile.gettempdir()
598 default_cache_dir = os.path.join(temp_store, 'rc_cache')
599 default_cache_dir = os.path.join(temp_store, 'rc_cache')
599
600
600 # save default, cache dir, and use it for all backends later.
601 # save default, cache dir, and use it for all backends later.
601 default_cache_dir = _string_setting(
602 default_cache_dir = _string_setting(
602 settings,
603 settings,
603 'cache_dir',
604 'cache_dir',
604 default_cache_dir, lower=False, default_when_empty=True)
605 default_cache_dir, lower=False, default_when_empty=True)
605
606
606 # ensure we have our dir created
607 # ensure we have our dir created
607 if not os.path.isdir(default_cache_dir):
608 if not os.path.isdir(default_cache_dir):
608 os.makedirs(default_cache_dir, mode=0o755)
609 os.makedirs(default_cache_dir, mode=0o755)
609
610
610 # exception store cache
611 # exception store cache
611 _string_setting(
612 _string_setting(
612 settings,
613 settings,
613 'exception_tracker.store_path',
614 'exception_tracker.store_path',
614 temp_store, lower=False, default_when_empty=True)
615 temp_store, lower=False, default_when_empty=True)
615
616
616 # cache_perms
617 # cache_perms
617 _string_setting(
618 _string_setting(
618 settings,
619 settings,
619 'rc_cache.cache_perms.backend',
620 'rc_cache.cache_perms.backend',
620 'dogpile.cache.rc.file_namespace', lower=False)
621 'dogpile.cache.rc.file_namespace', lower=False)
621 _int_setting(
622 _int_setting(
622 settings,
623 settings,
623 'rc_cache.cache_perms.expiration_time',
624 'rc_cache.cache_perms.expiration_time',
624 60)
625 60)
625 _string_setting(
626 _string_setting(
626 settings,
627 settings,
627 'rc_cache.cache_perms.arguments.filename',
628 'rc_cache.cache_perms.arguments.filename',
628 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
629 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
629
630
630 # cache_repo
631 # cache_repo
631 _string_setting(
632 _string_setting(
632 settings,
633 settings,
633 'rc_cache.cache_repo.backend',
634 'rc_cache.cache_repo.backend',
634 'dogpile.cache.rc.file_namespace', lower=False)
635 'dogpile.cache.rc.file_namespace', lower=False)
635 _int_setting(
636 _int_setting(
636 settings,
637 settings,
637 'rc_cache.cache_repo.expiration_time',
638 'rc_cache.cache_repo.expiration_time',
638 60)
639 60)
639 _string_setting(
640 _string_setting(
640 settings,
641 settings,
641 'rc_cache.cache_repo.arguments.filename',
642 'rc_cache.cache_repo.arguments.filename',
642 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
643 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
643
644
644 # cache_license
645 # cache_license
645 _string_setting(
646 _string_setting(
646 settings,
647 settings,
647 'rc_cache.cache_license.backend',
648 'rc_cache.cache_license.backend',
648 'dogpile.cache.rc.file_namespace', lower=False)
649 'dogpile.cache.rc.file_namespace', lower=False)
649 _int_setting(
650 _int_setting(
650 settings,
651 settings,
651 'rc_cache.cache_license.expiration_time',
652 'rc_cache.cache_license.expiration_time',
652 5*60)
653 5*60)
653 _string_setting(
654 _string_setting(
654 settings,
655 settings,
655 'rc_cache.cache_license.arguments.filename',
656 'rc_cache.cache_license.arguments.filename',
656 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
657 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
657
658
658 # cache_repo_longterm memory, 96H
659 # cache_repo_longterm memory, 96H
659 _string_setting(
660 _string_setting(
660 settings,
661 settings,
661 'rc_cache.cache_repo_longterm.backend',
662 'rc_cache.cache_repo_longterm.backend',
662 'dogpile.cache.rc.memory_lru', lower=False)
663 'dogpile.cache.rc.memory_lru', lower=False)
663 _int_setting(
664 _int_setting(
664 settings,
665 settings,
665 'rc_cache.cache_repo_longterm.expiration_time',
666 'rc_cache.cache_repo_longterm.expiration_time',
666 345600)
667 345600)
667 _int_setting(
668 _int_setting(
668 settings,
669 settings,
669 'rc_cache.cache_repo_longterm.max_size',
670 'rc_cache.cache_repo_longterm.max_size',
670 10000)
671 10000)
671
672
672 # sql_cache_short
673 # sql_cache_short
673 _string_setting(
674 _string_setting(
674 settings,
675 settings,
675 'rc_cache.sql_cache_short.backend',
676 'rc_cache.sql_cache_short.backend',
676 'dogpile.cache.rc.memory_lru', lower=False)
677 'dogpile.cache.rc.memory_lru', lower=False)
677 _int_setting(
678 _int_setting(
678 settings,
679 settings,
679 'rc_cache.sql_cache_short.expiration_time',
680 'rc_cache.sql_cache_short.expiration_time',
680 30)
681 30)
681 _int_setting(
682 _int_setting(
682 settings,
683 settings,
683 'rc_cache.sql_cache_short.max_size',
684 'rc_cache.sql_cache_short.max_size',
684 10000)
685 10000)
685
686
686
687
687 def _int_setting(settings, name, default):
688 def _int_setting(settings, name, default):
688 settings[name] = int(settings.get(name, default))
689 settings[name] = int(settings.get(name, default))
689 return settings[name]
690 return settings[name]
690
691
691
692
692 def _bool_setting(settings, name, default):
693 def _bool_setting(settings, name, default):
693 input_val = settings.get(name, default)
694 input_val = settings.get(name, default)
694 if isinstance(input_val, unicode):
695 if isinstance(input_val, unicode):
695 input_val = input_val.encode('utf8')
696 input_val = input_val.encode('utf8')
696 settings[name] = asbool(input_val)
697 settings[name] = asbool(input_val)
697 return settings[name]
698 return settings[name]
698
699
699
700
700 def _list_setting(settings, name, default):
701 def _list_setting(settings, name, default):
701 raw_value = settings.get(name, default)
702 raw_value = settings.get(name, default)
702
703
703 old_separator = ','
704 old_separator = ','
704 if old_separator in raw_value:
705 if old_separator in raw_value:
705 # If we get a comma separated list, pass it to our own function.
706 # If we get a comma separated list, pass it to our own function.
706 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
707 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
707 else:
708 else:
708 # Otherwise we assume it uses pyramids space/newline separation.
709 # Otherwise we assume it uses pyramids space/newline separation.
709 settings[name] = aslist(raw_value)
710 settings[name] = aslist(raw_value)
710 return settings[name]
711 return settings[name]
711
712
712
713
713 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
714 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
714 value = settings.get(name, default)
715 value = settings.get(name, default)
715
716
716 if default_when_empty and not value:
717 if default_when_empty and not value:
717 # use default value when value is empty
718 # use default value when value is empty
718 value = default
719 value = default
719
720
720 if lower:
721 if lower:
721 value = value.lower()
722 value = value.lower()
722 settings[name] = value
723 settings[name] = value
723 return settings[name]
724 return settings[name]
724
725
725
726
726 def _substitute_values(mapping, substitutions):
727 def _substitute_values(mapping, substitutions):
727 result = {}
728 result = {}
728
729
729 try:
730 try:
730 for key, value in mapping.items():
731 for key, value in mapping.items():
731 # initialize without substitution first
732 # initialize without substitution first
732 result[key] = value
733 result[key] = value
733
734
734 # Note: Cannot use regular replacements, since they would clash
735 # Note: Cannot use regular replacements, since they would clash
735 # with the implementation of ConfigParser. Using "format" instead.
736 # with the implementation of ConfigParser. Using "format" instead.
736 try:
737 try:
737 result[key] = value.format(**substitutions)
738 result[key] = value.format(**substitutions)
738 except KeyError as e:
739 except KeyError as e:
739 env_var = '{}'.format(e.args[0])
740 env_var = '{}'.format(e.args[0])
740
741
741 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
742 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
742 'Make sure your environment has {var} set, or remove this ' \
743 'Make sure your environment has {var} set, or remove this ' \
743 'variable from config file'.format(key=key, var=env_var)
744 'variable from config file'.format(key=key, var=env_var)
744
745
745 if env_var.startswith('ENV_'):
746 if env_var.startswith('ENV_'):
746 raise ValueError(msg)
747 raise ValueError(msg)
747 else:
748 else:
748 log.warning(msg)
749 log.warning(msg)
749
750
750 except ValueError as e:
751 except ValueError as e:
751 log.warning('Failed to substitute ENV variable: %s', e)
752 log.warning('Failed to substitute ENV variable: %s', e)
752 result = mapping
753 result = mapping
753
754
754 return result
755 return result
@@ -1,2089 +1,2089 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):
902 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
903 _render = request.get_partial_renderer(
903 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
904 'rhodecode:templates/base/base.mako')
904 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
905 return _render('gravatar_with_user', author, show_disabled=show_disabled)
906
905
907
906
908 tags_paterns = OrderedDict((
907 tags_paterns = OrderedDict((
909 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
908 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
910 '<div class="metatag" tag="lang">\\2</div>')),
909 '<div class="metatag" tag="lang">\\2</div>')),
911
910
912 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
911 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
913 '<div class="metatag" tag="see">see: \\1 </div>')),
912 '<div class="metatag" tag="see">see: \\1 </div>')),
914
913
915 ('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://|/)(.*?)\)\]'),
916 '<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>')),
917
916
918 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
917 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
919 '<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>')),
920
919
921 ('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\-\/]*)\]'),
922 '<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>')),
923
922
924 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
923 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
925 '<div class="metatag" tag="state \\1">\\1</div>')),
924 '<div class="metatag" tag="state \\1">\\1</div>')),
926
925
927 # label in grey
926 # label in grey
928 ('label', (re.compile(r'\[([a-z]+)\]'),
927 ('label', (re.compile(r'\[([a-z]+)\]'),
929 '<div class="metatag" tag="label">\\1</div>')),
928 '<div class="metatag" tag="label">\\1</div>')),
930
929
931 # generic catch all in grey
930 # generic catch all in grey
932 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
931 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
933 '<div class="metatag" tag="generic">\\1</div>')),
932 '<div class="metatag" tag="generic">\\1</div>')),
934 ))
933 ))
935
934
936
935
937 def extract_metatags(value):
936 def extract_metatags(value):
938 """
937 """
939 Extract supported meta-tags from given text value
938 Extract supported meta-tags from given text value
940 """
939 """
941 tags = []
940 tags = []
942 if not value:
941 if not value:
943 return tags, ''
942 return tags, ''
944
943
945 for key, val in tags_paterns.items():
944 for key, val in tags_paterns.items():
946 pat, replace_html = val
945 pat, replace_html = val
947 tags.extend([(key, x.group()) for x in pat.finditer(value)])
946 tags.extend([(key, x.group()) for x in pat.finditer(value)])
948 value = pat.sub('', value)
947 value = pat.sub('', value)
949
948
950 return tags, value
949 return tags, value
951
950
952
951
953 def style_metatag(tag_type, value):
952 def style_metatag(tag_type, value):
954 """
953 """
955 converts tags from value into html equivalent
954 converts tags from value into html equivalent
956 """
955 """
957 if not value:
956 if not value:
958 return ''
957 return ''
959
958
960 html_value = value
959 html_value = value
961 tag_data = tags_paterns.get(tag_type)
960 tag_data = tags_paterns.get(tag_type)
962 if tag_data:
961 if tag_data:
963 pat, replace_html = tag_data
962 pat, replace_html = tag_data
964 # 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
965 # regex expressions. safe_unicode doesn't work here
964 # regex expressions. safe_unicode doesn't work here
966 html_value = pat.sub(replace_html, unicode(value))
965 html_value = pat.sub(replace_html, unicode(value))
967
966
968 return html_value
967 return html_value
969
968
970
969
971 def bool2icon(value, show_at_false=True):
970 def bool2icon(value, show_at_false=True):
972 """
971 """
973 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
974 classes that will represent icons
973 classes that will represent icons
975
974
976 :param value: given value to convert to html node
975 :param value: given value to convert to html node
977 """
976 """
978
977
979 if value: # does bool conversion
978 if value: # does bool conversion
980 return HTML.tag('i', class_="icon-true", title='True')
979 return HTML.tag('i', class_="icon-true", title='True')
981 else: # not true as bool
980 else: # not true as bool
982 if show_at_false:
981 if show_at_false:
983 return HTML.tag('i', class_="icon-false", title='False')
982 return HTML.tag('i', class_="icon-false", title='False')
984 return HTML.tag('i')
983 return HTML.tag('i')
985
984
986 #==============================================================================
985 #==============================================================================
987 # PERMS
986 # PERMS
988 #==============================================================================
987 #==============================================================================
989 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
988 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
990 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
989 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
991 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
990 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
992 csrf_token_key
991 csrf_token_key
993
992
994
993
995 #==============================================================================
994 #==============================================================================
996 # GRAVATAR URL
995 # GRAVATAR URL
997 #==============================================================================
996 #==============================================================================
998 class InitialsGravatar(object):
997 class InitialsGravatar(object):
999 def __init__(self, email_address, first_name, last_name, size=30,
998 def __init__(self, email_address, first_name, last_name, size=30,
1000 background=None, text_color='#fff'):
999 background=None, text_color='#fff'):
1001 self.size = size
1000 self.size = size
1002 self.first_name = first_name
1001 self.first_name = first_name
1003 self.last_name = last_name
1002 self.last_name = last_name
1004 self.email_address = email_address
1003 self.email_address = email_address
1005 self.background = background or self.str2color(email_address)
1004 self.background = background or self.str2color(email_address)
1006 self.text_color = text_color
1005 self.text_color = text_color
1007
1006
1008 def get_color_bank(self):
1007 def get_color_bank(self):
1009 """
1008 """
1010 returns a predefined list of colors that gravatars can use.
1009 returns a predefined list of colors that gravatars can use.
1011 Those are randomized distinct colors that guarantee readability and
1010 Those are randomized distinct colors that guarantee readability and
1012 uniqueness.
1011 uniqueness.
1013
1012
1014 generated with: http://phrogz.net/css/distinct-colors.html
1013 generated with: http://phrogz.net/css/distinct-colors.html
1015 """
1014 """
1016 return [
1015 return [
1017 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1016 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1018 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1017 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1019 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1018 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1020 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1019 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1021 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1020 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1022 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1021 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1023 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1022 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1024 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1023 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1025 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1024 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1026 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1025 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1027 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1026 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1028 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1027 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1029 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1028 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1030 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1029 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1031 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1030 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1032 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1031 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1033 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1032 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1034 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1033 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1035 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1034 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1036 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1035 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1037 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1036 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1038 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1037 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1039 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1038 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1040 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1039 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1041 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1040 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1042 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1041 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1043 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1042 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1044 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1043 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1045 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1044 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1046 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1045 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1047 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1046 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1048 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1047 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1049 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1048 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1050 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1049 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1051 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1050 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1052 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1051 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1053 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1052 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1054 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1053 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1055 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1054 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1056 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1055 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1057 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1056 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1058 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1057 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1059 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1058 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1060 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1059 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1061 '#4f8c46', '#368dd9', '#5c0073'
1060 '#4f8c46', '#368dd9', '#5c0073'
1062 ]
1061 ]
1063
1062
1064 def rgb_to_hex_color(self, rgb_tuple):
1063 def rgb_to_hex_color(self, rgb_tuple):
1065 """
1064 """
1066 Converts an rgb_tuple passed to an hex color.
1065 Converts an rgb_tuple passed to an hex color.
1067
1066
1068 :param rgb_tuple: tuple with 3 ints represents rgb color space
1067 :param rgb_tuple: tuple with 3 ints represents rgb color space
1069 """
1068 """
1070 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1069 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1071
1070
1072 def email_to_int_list(self, email_str):
1071 def email_to_int_list(self, email_str):
1073 """
1072 """
1074 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.
1075 It's going to be always between 0-255
1074 It's going to be always between 0-255
1076 """
1075 """
1077 digest = md5_safe(email_str.lower())
1076 digest = md5_safe(email_str.lower())
1078 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)]
1079
1078
1080 def pick_color_bank_index(self, email_str, color_bank):
1079 def pick_color_bank_index(self, email_str, color_bank):
1081 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)
1082
1081
1083 def str2color(self, email_str):
1082 def str2color(self, email_str):
1084 """
1083 """
1085 Tries to map in a stable algorithm an email to color
1084 Tries to map in a stable algorithm an email to color
1086
1085
1087 :param email_str:
1086 :param email_str:
1088 """
1087 """
1089 color_bank = self.get_color_bank()
1088 color_bank = self.get_color_bank()
1090 # 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
1091 # bank even if it's smaller than 256 values
1090 # bank even if it's smaller than 256 values
1092 pos = self.pick_color_bank_index(email_str, color_bank)
1091 pos = self.pick_color_bank_index(email_str, color_bank)
1093 return color_bank[pos]
1092 return color_bank[pos]
1094
1093
1095 def normalize_email(self, email_address):
1094 def normalize_email(self, email_address):
1096 import unicodedata
1095 import unicodedata
1097 # default host used to fill in the fake/missing email
1096 # default host used to fill in the fake/missing email
1098 default_host = u'localhost'
1097 default_host = u'localhost'
1099
1098
1100 if not email_address:
1099 if not email_address:
1101 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1100 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1102
1101
1103 email_address = safe_unicode(email_address)
1102 email_address = safe_unicode(email_address)
1104
1103
1105 if u'@' not in email_address:
1104 if u'@' not in email_address:
1106 email_address = u'%s@%s' % (email_address, default_host)
1105 email_address = u'%s@%s' % (email_address, default_host)
1107
1106
1108 if email_address.endswith(u'@'):
1107 if email_address.endswith(u'@'):
1109 email_address = u'%s%s' % (email_address, default_host)
1108 email_address = u'%s%s' % (email_address, default_host)
1110
1109
1111 email_address = unicodedata.normalize('NFKD', email_address)\
1110 email_address = unicodedata.normalize('NFKD', email_address)\
1112 .encode('ascii', 'ignore')
1111 .encode('ascii', 'ignore')
1113 return email_address
1112 return email_address
1114
1113
1115 def get_initials(self):
1114 def get_initials(self):
1116 """
1115 """
1117 Returns 2 letter initials calculated based on the input.
1116 Returns 2 letter initials calculated based on the input.
1118 The algorithm picks first given email address, and takes first letter
1117 The algorithm picks first given email address, and takes first letter
1119 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
1120 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
1121 the server letter with first letter of somestring2
1120 the server letter with first letter of somestring2
1122
1121
1123 In case function was initialized with both first and lastname, this
1122 In case function was initialized with both first and lastname, this
1124 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
1125 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
1126 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
1127 (Von Rossum) picking `R`.
1126 (Von Rossum) picking `R`.
1128
1127
1129 Function also normalizes the non-ascii characters to they ascii
1128 Function also normalizes the non-ascii characters to they ascii
1130 representation, eg Ą => A
1129 representation, eg Ą => A
1131 """
1130 """
1132 import unicodedata
1131 import unicodedata
1133 # replace non-ascii to ascii
1132 # replace non-ascii to ascii
1134 first_name = unicodedata.normalize(
1133 first_name = unicodedata.normalize(
1135 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1134 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1136 last_name = unicodedata.normalize(
1135 last_name = unicodedata.normalize(
1137 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1136 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1138
1137
1139 # do NFKD encoding, and also make sure email has proper format
1138 # do NFKD encoding, and also make sure email has proper format
1140 email_address = self.normalize_email(self.email_address)
1139 email_address = self.normalize_email(self.email_address)
1141
1140
1142 # first push the email initials
1141 # first push the email initials
1143 prefix, server = email_address.split('@', 1)
1142 prefix, server = email_address.split('@', 1)
1144
1143
1145 # check if prefix is maybe a 'first_name.last_name' syntax
1144 # check if prefix is maybe a 'first_name.last_name' syntax
1146 _dot_split = prefix.rsplit('.', 1)
1145 _dot_split = prefix.rsplit('.', 1)
1147 if len(_dot_split) == 2 and _dot_split[1]:
1146 if len(_dot_split) == 2 and _dot_split[1]:
1148 initials = [_dot_split[0][0], _dot_split[1][0]]
1147 initials = [_dot_split[0][0], _dot_split[1][0]]
1149 else:
1148 else:
1150 initials = [prefix[0], server[0]]
1149 initials = [prefix[0], server[0]]
1151
1150
1152 # then try to replace either first_name or last_name
1151 # then try to replace either first_name or last_name
1153 fn_letter = (first_name or " ")[0].strip()
1152 fn_letter = (first_name or " ")[0].strip()
1154 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1153 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1155
1154
1156 if fn_letter:
1155 if fn_letter:
1157 initials[0] = fn_letter
1156 initials[0] = fn_letter
1158
1157
1159 if ln_letter:
1158 if ln_letter:
1160 initials[1] = ln_letter
1159 initials[1] = ln_letter
1161
1160
1162 return ''.join(initials).upper()
1161 return ''.join(initials).upper()
1163
1162
1164 def get_img_data_by_type(self, font_family, img_type):
1163 def get_img_data_by_type(self, font_family, img_type):
1165 default_user = """
1164 default_user = """
1166 <svg xmlns="http://www.w3.org/2000/svg"
1165 <svg xmlns="http://www.w3.org/2000/svg"
1167 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1166 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1168 viewBox="-15 -10 439.165 429.164"
1167 viewBox="-15 -10 439.165 429.164"
1169
1168
1170 xml:space="preserve"
1169 xml:space="preserve"
1171 style="background:{background};" >
1170 style="background:{background};" >
1172
1171
1173 <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,
1174 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
1175 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,
1176 168.596,153.916,216.671,
1175 168.596,153.916,216.671,
1177 204.583,216.671z" fill="{text_color}"/>
1176 204.583,216.671z" fill="{text_color}"/>
1178 <path d="M407.164,374.717L360.88,
1177 <path d="M407.164,374.717L360.88,
1179 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
1180 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,
1181 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,
1182 0-48.762-8.122-69.078-23.488
1181 0-48.762-8.122-69.078-23.488
1183 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,
1184 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
1185 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,
1186 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,
1187 19.402-10.527 C409.699,390.129,
1186 19.402-10.527 C409.699,390.129,
1188 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1187 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1189 </svg>""".format(
1188 </svg>""".format(
1190 size=self.size,
1189 size=self.size,
1191 background='#979797', # @grey4
1190 background='#979797', # @grey4
1192 text_color=self.text_color,
1191 text_color=self.text_color,
1193 font_family=font_family)
1192 font_family=font_family)
1194
1193
1195 return {
1194 return {
1196 "default_user": default_user
1195 "default_user": default_user
1197 }[img_type]
1196 }[img_type]
1198
1197
1199 def get_img_data(self, svg_type=None):
1198 def get_img_data(self, svg_type=None):
1200 """
1199 """
1201 generates the svg metadata for image
1200 generates the svg metadata for image
1202 """
1201 """
1203 fonts = [
1202 fonts = [
1204 '-apple-system',
1203 '-apple-system',
1205 'BlinkMacSystemFont',
1204 'BlinkMacSystemFont',
1206 'Segoe UI',
1205 'Segoe UI',
1207 'Roboto',
1206 'Roboto',
1208 'Oxygen-Sans',
1207 'Oxygen-Sans',
1209 'Ubuntu',
1208 'Ubuntu',
1210 'Cantarell',
1209 'Cantarell',
1211 'Helvetica Neue',
1210 'Helvetica Neue',
1212 'sans-serif'
1211 'sans-serif'
1213 ]
1212 ]
1214 font_family = ','.join(fonts)
1213 font_family = ','.join(fonts)
1215 if svg_type:
1214 if svg_type:
1216 return self.get_img_data_by_type(font_family, svg_type)
1215 return self.get_img_data_by_type(font_family, svg_type)
1217
1216
1218 initials = self.get_initials()
1217 initials = self.get_initials()
1219 img_data = """
1218 img_data = """
1220 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1219 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1221 width="{size}" height="{size}"
1220 width="{size}" height="{size}"
1222 style="width: 100%; height: 100%; background-color: {background}"
1221 style="width: 100%; height: 100%; background-color: {background}"
1223 viewBox="0 0 {size} {size}">
1222 viewBox="0 0 {size} {size}">
1224 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1223 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1225 pointer-events="auto" fill="{text_color}"
1224 pointer-events="auto" fill="{text_color}"
1226 font-family="{font_family}"
1225 font-family="{font_family}"
1227 style="font-weight: 400; font-size: {f_size}px;">{text}
1226 style="font-weight: 400; font-size: {f_size}px;">{text}
1228 </text>
1227 </text>
1229 </svg>""".format(
1228 </svg>""".format(
1230 size=self.size,
1229 size=self.size,
1231 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
1232 background=self.background,
1231 background=self.background,
1233 text_color=self.text_color,
1232 text_color=self.text_color,
1234 text=initials.upper(),
1233 text=initials.upper(),
1235 font_family=font_family)
1234 font_family=font_family)
1236
1235
1237 return img_data
1236 return img_data
1238
1237
1239 def generate_svg(self, svg_type=None):
1238 def generate_svg(self, svg_type=None):
1240 img_data = self.get_img_data(svg_type)
1239 img_data = self.get_img_data(svg_type)
1241 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1240 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1242
1241
1243
1242
1244 def initials_gravatar(email_address, first_name, last_name, size=30):
1243 def initials_gravatar(email_address, first_name, last_name, size=30):
1245 svg_type = None
1244 svg_type = None
1246 if email_address == User.DEFAULT_USER_EMAIL:
1245 if email_address == User.DEFAULT_USER_EMAIL:
1247 svg_type = 'default_user'
1246 svg_type = 'default_user'
1248 klass = InitialsGravatar(email_address, first_name, last_name, size)
1247 klass = InitialsGravatar(email_address, first_name, last_name, size)
1249 return klass.generate_svg(svg_type=svg_type)
1248 return klass.generate_svg(svg_type=svg_type)
1250
1249
1251
1250
1252 def gravatar_url(email_address, size=30, request=None):
1251 def gravatar_url(email_address, size=30, request=None):
1253 request = get_current_request()
1252 request = get_current_request()
1254 _use_gravatar = request.call_context.visual.use_gravatar
1253 _use_gravatar = request.call_context.visual.use_gravatar
1255 _gravatar_url = request.call_context.visual.gravatar_url
1254 _gravatar_url = request.call_context.visual.gravatar_url
1256
1255
1257 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1256 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1258
1257
1259 email_address = email_address or User.DEFAULT_USER_EMAIL
1258 email_address = email_address or User.DEFAULT_USER_EMAIL
1260 if isinstance(email_address, unicode):
1259 if isinstance(email_address, unicode):
1261 # hashlib crashes on unicode items
1260 # hashlib crashes on unicode items
1262 email_address = safe_str(email_address)
1261 email_address = safe_str(email_address)
1263
1262
1264 # empty email or default user
1263 # empty email or default user
1265 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1264 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1266 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1265 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1267
1266
1268 if _use_gravatar:
1267 if _use_gravatar:
1269 # TODO: Disuse pyramid thread locals. Think about another solution to
1268 # TODO: Disuse pyramid thread locals. Think about another solution to
1270 # get the host and schema here.
1269 # get the host and schema here.
1271 request = get_current_request()
1270 request = get_current_request()
1272 tmpl = safe_str(_gravatar_url)
1271 tmpl = safe_str(_gravatar_url)
1273 tmpl = tmpl.replace('{email}', email_address)\
1272 tmpl = tmpl.replace('{email}', email_address)\
1274 .replace('{md5email}', md5_safe(email_address.lower())) \
1273 .replace('{md5email}', md5_safe(email_address.lower())) \
1275 .replace('{netloc}', request.host)\
1274 .replace('{netloc}', request.host)\
1276 .replace('{scheme}', request.scheme)\
1275 .replace('{scheme}', request.scheme)\
1277 .replace('{size}', safe_str(size))
1276 .replace('{size}', safe_str(size))
1278 return tmpl
1277 return tmpl
1279 else:
1278 else:
1280 return initials_gravatar(email_address, '', '', size=size)
1279 return initials_gravatar(email_address, '', '', size=size)
1281
1280
1282
1281
1283 class Page(_Page):
1282 class Page(_Page):
1284 """
1283 """
1285 Custom pager to match rendering style with paginator
1284 Custom pager to match rendering style with paginator
1286 """
1285 """
1287
1286
1288 def _get_pos(self, cur_page, max_page, items):
1287 def _get_pos(self, cur_page, max_page, items):
1289 edge = (items / 2) + 1
1288 edge = (items / 2) + 1
1290 if (cur_page <= edge):
1289 if (cur_page <= edge):
1291 radius = max(items / 2, items - cur_page)
1290 radius = max(items / 2, items - cur_page)
1292 elif (max_page - cur_page) < edge:
1291 elif (max_page - cur_page) < edge:
1293 radius = (items - 1) - (max_page - cur_page)
1292 radius = (items - 1) - (max_page - cur_page)
1294 else:
1293 else:
1295 radius = items / 2
1294 radius = items / 2
1296
1295
1297 left = max(1, (cur_page - (radius)))
1296 left = max(1, (cur_page - (radius)))
1298 right = min(max_page, cur_page + (radius))
1297 right = min(max_page, cur_page + (radius))
1299 return left, cur_page, right
1298 return left, cur_page, right
1300
1299
1301 def _range(self, regexp_match):
1300 def _range(self, regexp_match):
1302 """
1301 """
1303 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').
1304
1303
1305 Arguments:
1304 Arguments:
1306
1305
1307 regexp_match
1306 regexp_match
1308 A "re" (regular expressions) match object containing the
1307 A "re" (regular expressions) match object containing the
1309 radius of linked pages around the current page in
1308 radius of linked pages around the current page in
1310 regexp_match.group(1) as a string
1309 regexp_match.group(1) as a string
1311
1310
1312 This function is supposed to be called as a callable in
1311 This function is supposed to be called as a callable in
1313 re.sub.
1312 re.sub.
1314
1313
1315 """
1314 """
1316 radius = int(regexp_match.group(1))
1315 radius = int(regexp_match.group(1))
1317
1316
1318 # Compute the first and last page number within the radius
1317 # Compute the first and last page number within the radius
1319 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1318 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1320 # -> leftmost_page = 5
1319 # -> leftmost_page = 5
1321 # -> rightmost_page = 9
1320 # -> rightmost_page = 9
1322 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1321 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1323 self.last_page,
1322 self.last_page,
1324 (radius * 2) + 1)
1323 (radius * 2) + 1)
1325 nav_items = []
1324 nav_items = []
1326
1325
1327 # 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
1328 # or there would be no need to insert '..' spacers)
1327 # or there would be no need to insert '..' spacers)
1329 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:
1330 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1329 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1331
1330
1332 # Insert dots if there are pages between the first page
1331 # Insert dots if there are pages between the first page
1333 # and the currently displayed page range
1332 # and the currently displayed page range
1334 if leftmost_page - self.first_page > 1:
1333 if leftmost_page - self.first_page > 1:
1335 # Wrap in a SPAN tag if nolink_attr is set
1334 # Wrap in a SPAN tag if nolink_attr is set
1336 text = '..'
1335 text = '..'
1337 if self.dotdot_attr:
1336 if self.dotdot_attr:
1338 text = HTML.span(c=text, **self.dotdot_attr)
1337 text = HTML.span(c=text, **self.dotdot_attr)
1339 nav_items.append(text)
1338 nav_items.append(text)
1340
1339
1341 for thispage in xrange(leftmost_page, rightmost_page + 1):
1340 for thispage in xrange(leftmost_page, rightmost_page + 1):
1342 # Hilight the current page number and do not use a link
1341 # Hilight the current page number and do not use a link
1343 if thispage == self.page:
1342 if thispage == self.page:
1344 text = '%s' % (thispage,)
1343 text = '%s' % (thispage,)
1345 # Wrap in a SPAN tag if nolink_attr is set
1344 # Wrap in a SPAN tag if nolink_attr is set
1346 if self.curpage_attr:
1345 if self.curpage_attr:
1347 text = HTML.span(c=text, **self.curpage_attr)
1346 text = HTML.span(c=text, **self.curpage_attr)
1348 nav_items.append(text)
1347 nav_items.append(text)
1349 # Otherwise create just a link to that page
1348 # Otherwise create just a link to that page
1350 else:
1349 else:
1351 text = '%s' % (thispage,)
1350 text = '%s' % (thispage,)
1352 nav_items.append(self._pagerlink(thispage, text))
1351 nav_items.append(self._pagerlink(thispage, text))
1353
1352
1354 # Insert dots if there are pages between the displayed
1353 # Insert dots if there are pages between the displayed
1355 # page numbers and the end of the page range
1354 # page numbers and the end of the page range
1356 if self.last_page - rightmost_page > 1:
1355 if self.last_page - rightmost_page > 1:
1357 text = '..'
1356 text = '..'
1358 # Wrap in a SPAN tag if nolink_attr is set
1357 # Wrap in a SPAN tag if nolink_attr is set
1359 if self.dotdot_attr:
1358 if self.dotdot_attr:
1360 text = HTML.span(c=text, **self.dotdot_attr)
1359 text = HTML.span(c=text, **self.dotdot_attr)
1361 nav_items.append(text)
1360 nav_items.append(text)
1362
1361
1363 # 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
1364 # page or there would be no need to insert '..' spacers)
1363 # page or there would be no need to insert '..' spacers)
1365 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:
1366 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1365 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1367
1366
1368 ## prerender links
1367 ## prerender links
1369 #_page_link = url.current()
1368 #_page_link = url.current()
1370 #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))))
1371 #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))))
1372 return self.separator.join(nav_items)
1371 return self.separator.join(nav_items)
1373
1372
1374 def pager(self, format='~2~', page_param='page', partial_param='partial',
1373 def pager(self, format='~2~', page_param='page', partial_param='partial',
1375 show_if_single_page=False, separator=' ', onclick=None,
1374 show_if_single_page=False, separator=' ', onclick=None,
1376 symbol_first='<<', symbol_last='>>',
1375 symbol_first='<<', symbol_last='>>',
1377 symbol_previous='<', symbol_next='>',
1376 symbol_previous='<', symbol_next='>',
1378 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1377 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1379 curpage_attr={'class': 'pager_curpage'},
1378 curpage_attr={'class': 'pager_curpage'},
1380 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1379 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1381
1380
1382 self.curpage_attr = curpage_attr
1381 self.curpage_attr = curpage_attr
1383 self.separator = separator
1382 self.separator = separator
1384 self.pager_kwargs = kwargs
1383 self.pager_kwargs = kwargs
1385 self.page_param = page_param
1384 self.page_param = page_param
1386 self.partial_param = partial_param
1385 self.partial_param = partial_param
1387 self.onclick = onclick
1386 self.onclick = onclick
1388 self.link_attr = link_attr
1387 self.link_attr = link_attr
1389 self.dotdot_attr = dotdot_attr
1388 self.dotdot_attr = dotdot_attr
1390
1389
1391 # 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
1392 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):
1393 return ''
1392 return ''
1394
1393
1395 from string import Template
1394 from string import Template
1396 # Replace ~...~ in token format by range of pages
1395 # Replace ~...~ in token format by range of pages
1397 result = re.sub(r'~(\d+)~', self._range, format)
1396 result = re.sub(r'~(\d+)~', self._range, format)
1398
1397
1399 # Interpolate '%' variables
1398 # Interpolate '%' variables
1400 result = Template(result).safe_substitute({
1399 result = Template(result).safe_substitute({
1401 'first_page': self.first_page,
1400 'first_page': self.first_page,
1402 'last_page': self.last_page,
1401 'last_page': self.last_page,
1403 'page': self.page,
1402 'page': self.page,
1404 'page_count': self.page_count,
1403 'page_count': self.page_count,
1405 'items_per_page': self.items_per_page,
1404 'items_per_page': self.items_per_page,
1406 'first_item': self.first_item,
1405 'first_item': self.first_item,
1407 'last_item': self.last_item,
1406 'last_item': self.last_item,
1408 'item_count': self.item_count,
1407 'item_count': self.item_count,
1409 'link_first': self.page > self.first_page and \
1408 'link_first': self.page > self.first_page and \
1410 self._pagerlink(self.first_page, symbol_first) or '',
1409 self._pagerlink(self.first_page, symbol_first) or '',
1411 'link_last': self.page < self.last_page and \
1410 'link_last': self.page < self.last_page and \
1412 self._pagerlink(self.last_page, symbol_last) or '',
1411 self._pagerlink(self.last_page, symbol_last) or '',
1413 'link_previous': self.previous_page and \
1412 'link_previous': self.previous_page and \
1414 self._pagerlink(self.previous_page, symbol_previous) \
1413 self._pagerlink(self.previous_page, symbol_previous) \
1415 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1414 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1416 'link_next': self.next_page and \
1415 'link_next': self.next_page and \
1417 self._pagerlink(self.next_page, symbol_next) \
1416 self._pagerlink(self.next_page, symbol_next) \
1418 or HTML.span(symbol_next, class_="pg-next disabled")
1417 or HTML.span(symbol_next, class_="pg-next disabled")
1419 })
1418 })
1420
1419
1421 return literal(result)
1420 return literal(result)
1422
1421
1423
1422
1424 #==============================================================================
1423 #==============================================================================
1425 # REPO PAGER, PAGER FOR REPOSITORY
1424 # REPO PAGER, PAGER FOR REPOSITORY
1426 #==============================================================================
1425 #==============================================================================
1427 class RepoPage(Page):
1426 class RepoPage(Page):
1428
1427
1429 def __init__(self, collection, page=1, items_per_page=20,
1428 def __init__(self, collection, page=1, items_per_page=20,
1430 item_count=None, url=None, **kwargs):
1429 item_count=None, url=None, **kwargs):
1431
1430
1432 """Create a "RepoPage" instance. special pager for paging
1431 """Create a "RepoPage" instance. special pager for paging
1433 repository
1432 repository
1434 """
1433 """
1435 self._url_generator = url
1434 self._url_generator = url
1436
1435
1437 # 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
1438 self.kwargs = kwargs
1437 self.kwargs = kwargs
1439
1438
1440 # Save a reference to the collection
1439 # Save a reference to the collection
1441 self.original_collection = collection
1440 self.original_collection = collection
1442
1441
1443 self.collection = collection
1442 self.collection = collection
1444
1443
1445 # The self.page is the number of the current page.
1444 # The self.page is the number of the current page.
1446 # The first page has the number 1!
1445 # The first page has the number 1!
1447 try:
1446 try:
1448 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
1449 except (ValueError, TypeError):
1448 except (ValueError, TypeError):
1450 self.page = 1
1449 self.page = 1
1451
1450
1452 self.items_per_page = items_per_page
1451 self.items_per_page = items_per_page
1453
1452
1454 # Unless the user tells us how many items the collections has
1453 # Unless the user tells us how many items the collections has
1455 # we calculate that ourselves.
1454 # we calculate that ourselves.
1456 if item_count is not None:
1455 if item_count is not None:
1457 self.item_count = item_count
1456 self.item_count = item_count
1458 else:
1457 else:
1459 self.item_count = len(self.collection)
1458 self.item_count = len(self.collection)
1460
1459
1461 # Compute the number of the first and last available page
1460 # Compute the number of the first and last available page
1462 if self.item_count > 0:
1461 if self.item_count > 0:
1463 self.first_page = 1
1462 self.first_page = 1
1464 self.page_count = int(math.ceil(float(self.item_count) /
1463 self.page_count = int(math.ceil(float(self.item_count) /
1465 self.items_per_page))
1464 self.items_per_page))
1466 self.last_page = self.first_page + self.page_count - 1
1465 self.last_page = self.first_page + self.page_count - 1
1467
1466
1468 # Make sure that the requested page number is the range of
1467 # Make sure that the requested page number is the range of
1469 # valid pages
1468 # valid pages
1470 if self.page > self.last_page:
1469 if self.page > self.last_page:
1471 self.page = self.last_page
1470 self.page = self.last_page
1472 elif self.page < self.first_page:
1471 elif self.page < self.first_page:
1473 self.page = self.first_page
1472 self.page = self.first_page
1474
1473
1475 # 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
1476 # items_per_page if the last page is not full
1475 # items_per_page if the last page is not full
1477 self.first_item = max(0, (self.item_count) - (self.page *
1476 self.first_item = max(0, (self.item_count) - (self.page *
1478 items_per_page))
1477 items_per_page))
1479 self.last_item = ((self.item_count - 1) - items_per_page *
1478 self.last_item = ((self.item_count - 1) - items_per_page *
1480 (self.page - 1))
1479 (self.page - 1))
1481
1480
1482 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])
1483
1482
1484 # Links to previous and next page
1483 # Links to previous and next page
1485 if self.page > self.first_page:
1484 if self.page > self.first_page:
1486 self.previous_page = self.page - 1
1485 self.previous_page = self.page - 1
1487 else:
1486 else:
1488 self.previous_page = None
1487 self.previous_page = None
1489
1488
1490 if self.page < self.last_page:
1489 if self.page < self.last_page:
1491 self.next_page = self.page + 1
1490 self.next_page = self.page + 1
1492 else:
1491 else:
1493 self.next_page = None
1492 self.next_page = None
1494
1493
1495 # No items available
1494 # No items available
1496 else:
1495 else:
1497 self.first_page = None
1496 self.first_page = None
1498 self.page_count = 0
1497 self.page_count = 0
1499 self.last_page = None
1498 self.last_page = None
1500 self.first_item = None
1499 self.first_item = None
1501 self.last_item = None
1500 self.last_item = None
1502 self.previous_page = None
1501 self.previous_page = None
1503 self.next_page = None
1502 self.next_page = None
1504 self.items = []
1503 self.items = []
1505
1504
1506 # 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.
1507 list.__init__(self, reversed(self.items))
1506 list.__init__(self, reversed(self.items))
1508
1507
1509
1508
1510 def breadcrumb_repo_link(repo):
1509 def breadcrumb_repo_link(repo):
1511 """
1510 """
1512 Makes a breadcrumbs path link to repo
1511 Makes a breadcrumbs path link to repo
1513
1512
1514 ex::
1513 ex::
1515 group >> subgroup >> repo
1514 group >> subgroup >> repo
1516
1515
1517 :param repo: a Repository instance
1516 :param repo: a Repository instance
1518 """
1517 """
1519
1518
1520 path = [
1519 path = [
1521 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),
1522 title='last change:{}'.format(format_date(group.last_commit_change)))
1521 title='last change:{}'.format(format_date(group.last_commit_change)))
1523 for group in repo.groups_with_parents
1522 for group in repo.groups_with_parents
1524 ] + [
1523 ] + [
1525 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),
1526 title='last change:{}'.format(format_date(repo.last_commit_change)))
1525 title='last change:{}'.format(format_date(repo.last_commit_change)))
1527 ]
1526 ]
1528
1527
1529 return literal(' &raquo; '.join(path))
1528 return literal(' &raquo; '.join(path))
1530
1529
1531
1530
1532 def breadcrumb_repo_group_link(repo_group):
1531 def breadcrumb_repo_group_link(repo_group):
1533 """
1532 """
1534 Makes a breadcrumbs path link to repo
1533 Makes a breadcrumbs path link to repo
1535
1534
1536 ex::
1535 ex::
1537 group >> subgroup
1536 group >> subgroup
1538
1537
1539 :param repo_group: a Repository Group instance
1538 :param repo_group: a Repository Group instance
1540 """
1539 """
1541
1540
1542 path = [
1541 path = [
1543 link_to(group.name,
1542 link_to(group.name,
1544 route_path('repo_group_home', repo_group_name=group.group_name),
1543 route_path('repo_group_home', repo_group_name=group.group_name),
1545 title='last change:{}'.format(format_date(group.last_commit_change)))
1544 title='last change:{}'.format(format_date(group.last_commit_change)))
1546 for group in repo_group.parents
1545 for group in repo_group.parents
1547 ] + [
1546 ] + [
1548 link_to(repo_group.name,
1547 link_to(repo_group.name,
1549 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),
1550 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1549 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1551 ]
1550 ]
1552
1551
1553 return literal(' &raquo; '.join(path))
1552 return literal(' &raquo; '.join(path))
1554
1553
1555
1554
1556 def format_byte_size_binary(file_size):
1555 def format_byte_size_binary(file_size):
1557 """
1556 """
1558 Formats file/folder sizes to standard.
1557 Formats file/folder sizes to standard.
1559 """
1558 """
1560 if file_size is None:
1559 if file_size is None:
1561 file_size = 0
1560 file_size = 0
1562
1561
1563 formatted_size = format_byte_size(file_size, binary=True)
1562 formatted_size = format_byte_size(file_size, binary=True)
1564 return formatted_size
1563 return formatted_size
1565
1564
1566
1565
1567 def urlify_text(text_, safe=True):
1566 def urlify_text(text_, safe=True):
1568 """
1567 """
1569 Extrac urls from text and make html links out of them
1568 Extrac urls from text and make html links out of them
1570
1569
1571 :param text_:
1570 :param text_:
1572 """
1571 """
1573
1572
1574 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]|[$-_@#.&+]'''
1575 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1574 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1576
1575
1577 def url_func(match_obj):
1576 def url_func(match_obj):
1578 url_full = match_obj.groups()[0]
1577 url_full = match_obj.groups()[0]
1579 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1578 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1580 _newtext = url_pat.sub(url_func, text_)
1579 _newtext = url_pat.sub(url_func, text_)
1581 if safe:
1580 if safe:
1582 return literal(_newtext)
1581 return literal(_newtext)
1583 return _newtext
1582 return _newtext
1584
1583
1585
1584
1586 def urlify_commits(text_, repository):
1585 def urlify_commits(text_, repository):
1587 """
1586 """
1588 Extract commit ids from text and make link from them
1587 Extract commit ids from text and make link from them
1589
1588
1590 :param text_:
1589 :param text_:
1591 :param repository: repo name to build the URL with
1590 :param repository: repo name to build the URL with
1592 """
1591 """
1593
1592
1594 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)')
1595
1594
1596 def url_func(match_obj):
1595 def url_func(match_obj):
1597 commit_id = match_obj.groups()[1]
1596 commit_id = match_obj.groups()[1]
1598 pref = match_obj.groups()[0]
1597 pref = match_obj.groups()[0]
1599 suf = match_obj.groups()[2]
1598 suf = match_obj.groups()[2]
1600
1599
1601 tmpl = (
1600 tmpl = (
1602 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1601 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1603 '%(commit_id)s</a>%(suf)s'
1602 '%(commit_id)s</a>%(suf)s'
1604 )
1603 )
1605 return tmpl % {
1604 return tmpl % {
1606 'pref': pref,
1605 'pref': pref,
1607 'cls': 'revision-link',
1606 'cls': 'revision-link',
1608 '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),
1609 'commit_id': commit_id,
1608 'commit_id': commit_id,
1610 'suf': suf
1609 'suf': suf
1611 }
1610 }
1612
1611
1613 newtext = URL_PAT.sub(url_func, text_)
1612 newtext = URL_PAT.sub(url_func, text_)
1614
1613
1615 return newtext
1614 return newtext
1616
1615
1617
1616
1618 def _process_url_func(match_obj, repo_name, uid, entry,
1617 def _process_url_func(match_obj, repo_name, uid, entry,
1619 return_raw_data=False, link_format='html'):
1618 return_raw_data=False, link_format='html'):
1620 pref = ''
1619 pref = ''
1621 if match_obj.group().startswith(' '):
1620 if match_obj.group().startswith(' '):
1622 pref = ' '
1621 pref = ' '
1623
1622
1624 issue_id = ''.join(match_obj.groups())
1623 issue_id = ''.join(match_obj.groups())
1625
1624
1626 if link_format == 'html':
1625 if link_format == 'html':
1627 tmpl = (
1626 tmpl = (
1628 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1627 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1629 '%(issue-prefix)s%(id-repr)s'
1628 '%(issue-prefix)s%(id-repr)s'
1630 '</a>')
1629 '</a>')
1631 elif link_format == 'rst':
1630 elif link_format == 'rst':
1632 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1631 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1633 elif link_format == 'markdown':
1632 elif link_format == 'markdown':
1634 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1633 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1635 else:
1634 else:
1636 raise ValueError('Bad link_format:{}'.format(link_format))
1635 raise ValueError('Bad link_format:{}'.format(link_format))
1637
1636
1638 (repo_name_cleaned,
1637 (repo_name_cleaned,
1639 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1638 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1640
1639
1641 # variables replacement
1640 # variables replacement
1642 named_vars = {
1641 named_vars = {
1643 'id': issue_id,
1642 'id': issue_id,
1644 'repo': repo_name,
1643 'repo': repo_name,
1645 'repo_name': repo_name_cleaned,
1644 'repo_name': repo_name_cleaned,
1646 'group_name': parent_group_name
1645 'group_name': parent_group_name,
1647 }
1646 }
1648 # named regex variables
1647 # named regex variables
1649 named_vars.update(match_obj.groupdict())
1648 named_vars.update(match_obj.groupdict())
1650 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1649 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1651
1650
1652 def quote_cleaner(input_str):
1651 def quote_cleaner(input_str):
1653 """Remove quotes as it's HTML"""
1652 """Remove quotes as it's HTML"""
1654 return input_str.replace('"', '')
1653 return input_str.replace('"', '')
1655
1654
1656 data = {
1655 data = {
1657 'pref': pref,
1656 'pref': pref,
1658 'cls': quote_cleaner('issue-tracker-link'),
1657 'cls': quote_cleaner('issue-tracker-link'),
1659 'url': quote_cleaner(_url),
1658 'url': quote_cleaner(_url),
1660 'id-repr': issue_id,
1659 'id-repr': issue_id,
1661 'issue-prefix': entry['pref'],
1660 'issue-prefix': entry['pref'],
1662 'serv': entry['url'],
1661 'serv': entry['url'],
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 newtext = text_string
1694
1694
1695 for uid, entry in active_entries.items():
1695 for uid, entry in active_entries.items():
1696 log.debug('found issue tracker entry with uid %s', uid)
1696 log.debug('found issue tracker entry with uid %s', uid)
1697
1697
1698 if not (entry['pat'] and entry['url']):
1698 if not (entry['pat'] and entry['url']):
1699 log.debug('skipping due to missing data')
1699 log.debug('skipping due to missing data')
1700 continue
1700 continue
1701
1701
1702 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1702 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1703 uid, entry['pat'], entry['url'], entry['pref'])
1703 uid, entry['pat'], entry['url'], entry['pref'])
1704
1704
1705 try:
1705 try:
1706 pattern = re.compile(r'%s' % entry['pat'])
1706 pattern = re.compile(r'%s' % entry['pat'])
1707 except re.error:
1707 except re.error:
1708 log.exception(
1708 log.exception(
1709 'issue tracker pattern: `%s` failed to compile',
1709 'issue tracker pattern: `%s` failed to compile',
1710 entry['pat'])
1710 entry['pat'])
1711 continue
1711 continue
1712
1712
1713 data_func = partial(
1713 data_func = partial(
1714 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1714 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1715 return_raw_data=True)
1715 return_raw_data=True)
1716
1716
1717 for match_obj in pattern.finditer(text_string):
1717 for match_obj in pattern.finditer(text_string):
1718 issues_data.append(data_func(match_obj))
1718 issues_data.append(data_func(match_obj))
1719
1719
1720 url_func = partial(
1720 url_func = partial(
1721 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1721 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1722 link_format=link_format)
1722 link_format=link_format)
1723
1723
1724 newtext = pattern.sub(url_func, newtext)
1724 newtext = pattern.sub(url_func, newtext)
1725 log.debug('processed prefix:uid `%s`', uid)
1725 log.debug('processed prefix:uid `%s`', uid)
1726
1726
1727 return newtext, issues_data
1727 return newtext, issues_data
1728
1728
1729
1729
1730 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1730 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1731 """
1731 """
1732 Parses given text message and makes proper links.
1732 Parses given text message and makes proper links.
1733 issues are linked to given issue-server, and rest is a commit link
1733 issues are linked to given issue-server, and rest is a commit link
1734
1734
1735 :param commit_text:
1735 :param commit_text:
1736 :param repository:
1736 :param repository:
1737 """
1737 """
1738 def escaper(string):
1738 def escaper(string):
1739 return string.replace('<', '&lt;').replace('>', '&gt;')
1739 return string.replace('<', '&lt;').replace('>', '&gt;')
1740
1740
1741 newtext = escaper(commit_text)
1741 newtext = escaper(commit_text)
1742
1742
1743 # extract http/https links and make them real urls
1743 # extract http/https links and make them real urls
1744 newtext = urlify_text(newtext, safe=False)
1744 newtext = urlify_text(newtext, safe=False)
1745
1745
1746 # urlify commits - extract commit ids and make link out of them, if we have
1746 # urlify commits - extract commit ids and make link out of them, if we have
1747 # the scope of repository present.
1747 # the scope of repository present.
1748 if repository:
1748 if repository:
1749 newtext = urlify_commits(newtext, repository)
1749 newtext = urlify_commits(newtext, repository)
1750
1750
1751 # process issue tracker patterns
1751 # process issue tracker patterns
1752 newtext, issues = process_patterns(newtext, repository or '',
1752 newtext, issues = process_patterns(newtext, repository or '',
1753 active_entries=active_pattern_entries)
1753 active_entries=active_pattern_entries)
1754
1754
1755 return literal(newtext)
1755 return literal(newtext)
1756
1756
1757
1757
1758 def render_binary(repo_name, file_obj):
1758 def render_binary(repo_name, file_obj):
1759 """
1759 """
1760 Choose how to render a binary file
1760 Choose how to render a binary file
1761 """
1761 """
1762
1762
1763 filename = file_obj.name
1763 filename = file_obj.name
1764
1764
1765 # images
1765 # images
1766 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1766 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1767 if fnmatch.fnmatch(filename, pat=ext):
1767 if fnmatch.fnmatch(filename, pat=ext):
1768 alt = escape(filename)
1768 alt = escape(filename)
1769 src = route_path(
1769 src = route_path(
1770 'repo_file_raw', repo_name=repo_name,
1770 'repo_file_raw', repo_name=repo_name,
1771 commit_id=file_obj.commit.raw_id,
1771 commit_id=file_obj.commit.raw_id,
1772 f_path=file_obj.path)
1772 f_path=file_obj.path)
1773 return literal(
1773 return literal(
1774 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1774 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1775
1775
1776
1776
1777 def renderer_from_filename(filename, exclude=None):
1777 def renderer_from_filename(filename, exclude=None):
1778 """
1778 """
1779 choose a renderer based on filename, this works only for text based files
1779 choose a renderer based on filename, this works only for text based files
1780 """
1780 """
1781
1781
1782 # ipython
1782 # ipython
1783 for ext in ['*.ipynb']:
1783 for ext in ['*.ipynb']:
1784 if fnmatch.fnmatch(filename, pat=ext):
1784 if fnmatch.fnmatch(filename, pat=ext):
1785 return 'jupyter'
1785 return 'jupyter'
1786
1786
1787 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1787 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1788 if is_markup:
1788 if is_markup:
1789 return is_markup
1789 return is_markup
1790 return None
1790 return None
1791
1791
1792
1792
1793 def render(source, renderer='rst', mentions=False, relative_urls=None,
1793 def render(source, renderer='rst', mentions=False, relative_urls=None,
1794 repo_name=None):
1794 repo_name=None):
1795
1795
1796 def maybe_convert_relative_links(html_source):
1796 def maybe_convert_relative_links(html_source):
1797 if relative_urls:
1797 if relative_urls:
1798 return relative_links(html_source, relative_urls)
1798 return relative_links(html_source, relative_urls)
1799 return html_source
1799 return html_source
1800
1800
1801 if renderer == 'plain':
1801 if renderer == 'plain':
1802 return literal(
1802 return literal(
1803 MarkupRenderer.plain(source, leading_newline=False))
1803 MarkupRenderer.plain(source, leading_newline=False))
1804
1804
1805 elif renderer == 'rst':
1805 elif renderer == 'rst':
1806 if repo_name:
1806 if repo_name:
1807 # process patterns on comments if we pass in repo name
1807 # process patterns on comments if we pass in repo name
1808 source, issues = process_patterns(
1808 source, issues = process_patterns(
1809 source, repo_name, link_format='rst')
1809 source, repo_name, link_format='rst')
1810
1810
1811 return literal(
1811 return literal(
1812 '<div class="rst-block">%s</div>' %
1812 '<div class="rst-block">%s</div>' %
1813 maybe_convert_relative_links(
1813 maybe_convert_relative_links(
1814 MarkupRenderer.rst(source, mentions=mentions)))
1814 MarkupRenderer.rst(source, mentions=mentions)))
1815
1815
1816 elif renderer == 'markdown':
1816 elif renderer == 'markdown':
1817 if repo_name:
1817 if repo_name:
1818 # process patterns on comments if we pass in repo name
1818 # process patterns on comments if we pass in repo name
1819 source, issues = process_patterns(
1819 source, issues = process_patterns(
1820 source, repo_name, link_format='markdown')
1820 source, repo_name, link_format='markdown')
1821
1821
1822 return literal(
1822 return literal(
1823 '<div class="markdown-block">%s</div>' %
1823 '<div class="markdown-block">%s</div>' %
1824 maybe_convert_relative_links(
1824 maybe_convert_relative_links(
1825 MarkupRenderer.markdown(source, flavored=True,
1825 MarkupRenderer.markdown(source, flavored=True,
1826 mentions=mentions)))
1826 mentions=mentions)))
1827
1827
1828 elif renderer == 'jupyter':
1828 elif renderer == 'jupyter':
1829 return literal(
1829 return literal(
1830 '<div class="ipynb">%s</div>' %
1830 '<div class="ipynb">%s</div>' %
1831 maybe_convert_relative_links(
1831 maybe_convert_relative_links(
1832 MarkupRenderer.jupyter(source)))
1832 MarkupRenderer.jupyter(source)))
1833
1833
1834 # None means just show the file-source
1834 # None means just show the file-source
1835 return None
1835 return None
1836
1836
1837
1837
1838 def commit_status(repo, commit_id):
1838 def commit_status(repo, commit_id):
1839 return ChangesetStatusModel().get_status(repo, commit_id)
1839 return ChangesetStatusModel().get_status(repo, commit_id)
1840
1840
1841
1841
1842 def commit_status_lbl(commit_status):
1842 def commit_status_lbl(commit_status):
1843 return dict(ChangesetStatus.STATUSES).get(commit_status)
1843 return dict(ChangesetStatus.STATUSES).get(commit_status)
1844
1844
1845
1845
1846 def commit_time(repo_name, commit_id):
1846 def commit_time(repo_name, commit_id):
1847 repo = Repository.get_by_repo_name(repo_name)
1847 repo = Repository.get_by_repo_name(repo_name)
1848 commit = repo.get_commit(commit_id=commit_id)
1848 commit = repo.get_commit(commit_id=commit_id)
1849 return commit.date
1849 return commit.date
1850
1850
1851
1851
1852 def get_permission_name(key):
1852 def get_permission_name(key):
1853 return dict(Permission.PERMS).get(key)
1853 return dict(Permission.PERMS).get(key)
1854
1854
1855
1855
1856 def journal_filter_help(request):
1856 def journal_filter_help(request):
1857 _ = request.translate
1857 _ = request.translate
1858 from rhodecode.lib.audit_logger import ACTIONS
1858 from rhodecode.lib.audit_logger import ACTIONS
1859 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1859 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1860
1860
1861 return _(
1861 return _(
1862 'Example filter terms:\n' +
1862 'Example filter terms:\n' +
1863 ' repository:vcs\n' +
1863 ' repository:vcs\n' +
1864 ' username:marcin\n' +
1864 ' username:marcin\n' +
1865 ' username:(NOT marcin)\n' +
1865 ' username:(NOT marcin)\n' +
1866 ' action:*push*\n' +
1866 ' action:*push*\n' +
1867 ' ip:127.0.0.1\n' +
1867 ' ip:127.0.0.1\n' +
1868 ' date:20120101\n' +
1868 ' date:20120101\n' +
1869 ' date:[20120101100000 TO 20120102]\n' +
1869 ' date:[20120101100000 TO 20120102]\n' +
1870 '\n' +
1870 '\n' +
1871 'Actions: {actions}\n' +
1871 'Actions: {actions}\n' +
1872 '\n' +
1872 '\n' +
1873 'Generate wildcards using \'*\' character:\n' +
1873 'Generate wildcards using \'*\' character:\n' +
1874 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1874 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1875 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1875 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1876 '\n' +
1876 '\n' +
1877 'Optional AND / OR operators in queries\n' +
1877 'Optional AND / OR operators in queries\n' +
1878 ' "repository:vcs OR repository:test"\n' +
1878 ' "repository:vcs OR repository:test"\n' +
1879 ' "username:test AND repository:test*"\n'
1879 ' "username:test AND repository:test*"\n'
1880 ).format(actions=actions)
1880 ).format(actions=actions)
1881
1881
1882
1882
1883 def not_mapped_error(repo_name):
1883 def not_mapped_error(repo_name):
1884 from rhodecode.translation import _
1884 from rhodecode.translation import _
1885 flash(_('%s repository is not mapped to db perhaps'
1885 flash(_('%s repository is not mapped to db perhaps'
1886 ' it was created or renamed from the filesystem'
1886 ' it was created or renamed from the filesystem'
1887 ' please run the application again'
1887 ' please run the application again'
1888 ' in order to rescan repositories') % repo_name, category='error')
1888 ' in order to rescan repositories') % repo_name, category='error')
1889
1889
1890
1890
1891 def ip_range(ip_addr):
1891 def ip_range(ip_addr):
1892 from rhodecode.model.db import UserIpMap
1892 from rhodecode.model.db import UserIpMap
1893 s, e = UserIpMap._get_ip_range(ip_addr)
1893 s, e = UserIpMap._get_ip_range(ip_addr)
1894 return '%s - %s' % (s, e)
1894 return '%s - %s' % (s, e)
1895
1895
1896
1896
1897 def form(url, method='post', needs_csrf_token=True, **attrs):
1897 def form(url, method='post', needs_csrf_token=True, **attrs):
1898 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1898 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1899 if method.lower() != 'get' and needs_csrf_token:
1899 if method.lower() != 'get' and needs_csrf_token:
1900 raise Exception(
1900 raise Exception(
1901 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1901 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1902 'CSRF token. If the endpoint does not require such token you can ' +
1902 'CSRF token. If the endpoint does not require such token you can ' +
1903 'explicitly set the parameter needs_csrf_token to false.')
1903 'explicitly set the parameter needs_csrf_token to false.')
1904
1904
1905 return wh_form(url, method=method, **attrs)
1905 return wh_form(url, method=method, **attrs)
1906
1906
1907
1907
1908 def secure_form(form_url, method="POST", multipart=False, **attrs):
1908 def secure_form(form_url, method="POST", multipart=False, **attrs):
1909 """Start a form tag that points the action to an url. This
1909 """Start a form tag that points the action to an url. This
1910 form tag will also include the hidden field containing
1910 form tag will also include the hidden field containing
1911 the auth token.
1911 the auth token.
1912
1912
1913 The url options should be given either as a string, or as a
1913 The url options should be given either as a string, or as a
1914 ``url()`` function. The method for the form defaults to POST.
1914 ``url()`` function. The method for the form defaults to POST.
1915
1915
1916 Options:
1916 Options:
1917
1917
1918 ``multipart``
1918 ``multipart``
1919 If set to True, the enctype is set to "multipart/form-data".
1919 If set to True, the enctype is set to "multipart/form-data".
1920 ``method``
1920 ``method``
1921 The method to use when submitting the form, usually either
1921 The method to use when submitting the form, usually either
1922 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1922 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1923 hidden input with name _method is added to simulate the verb
1923 hidden input with name _method is added to simulate the verb
1924 over POST.
1924 over POST.
1925
1925
1926 """
1926 """
1927 from webhelpers.pylonslib.secure_form import insecure_form
1927 from webhelpers.pylonslib.secure_form import insecure_form
1928
1928
1929 if 'request' in attrs:
1929 if 'request' in attrs:
1930 session = attrs['request'].session
1930 session = attrs['request'].session
1931 del attrs['request']
1931 del attrs['request']
1932 else:
1932 else:
1933 raise ValueError(
1933 raise ValueError(
1934 'Calling this form requires request= to be passed as argument')
1934 'Calling this form requires request= to be passed as argument')
1935
1935
1936 form = insecure_form(form_url, method, multipart, **attrs)
1936 form = insecure_form(form_url, method, multipart, **attrs)
1937 token = literal(
1937 token = literal(
1938 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1938 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1939 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1939 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1940
1940
1941 return literal("%s\n%s" % (form, token))
1941 return literal("%s\n%s" % (form, token))
1942
1942
1943
1943
1944 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1944 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1945 select_html = select(name, selected, options, **attrs)
1945 select_html = select(name, selected, options, **attrs)
1946
1946
1947 select2 = """
1947 select2 = """
1948 <script>
1948 <script>
1949 $(document).ready(function() {
1949 $(document).ready(function() {
1950 $('#%s').select2({
1950 $('#%s').select2({
1951 containerCssClass: 'drop-menu %s',
1951 containerCssClass: 'drop-menu %s',
1952 dropdownCssClass: 'drop-menu-dropdown',
1952 dropdownCssClass: 'drop-menu-dropdown',
1953 dropdownAutoWidth: true%s
1953 dropdownAutoWidth: true%s
1954 });
1954 });
1955 });
1955 });
1956 </script>
1956 </script>
1957 """
1957 """
1958
1958
1959 filter_option = """,
1959 filter_option = """,
1960 minimumResultsForSearch: -1
1960 minimumResultsForSearch: -1
1961 """
1961 """
1962 input_id = attrs.get('id') or name
1962 input_id = attrs.get('id') or name
1963 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1963 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1964 filter_enabled = "" if enable_filter else filter_option
1964 filter_enabled = "" if enable_filter else filter_option
1965 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1965 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1966
1966
1967 return literal(select_html+select_script)
1967 return literal(select_html+select_script)
1968
1968
1969
1969
1970 def get_visual_attr(tmpl_context_var, attr_name):
1970 def get_visual_attr(tmpl_context_var, attr_name):
1971 """
1971 """
1972 A safe way to get a variable from visual variable of template context
1972 A safe way to get a variable from visual variable of template context
1973
1973
1974 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1974 :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
1975 :param attr_name: name of the attribute we fetch from the c.visual
1976 """
1976 """
1977 visual = getattr(tmpl_context_var, 'visual', None)
1977 visual = getattr(tmpl_context_var, 'visual', None)
1978 if not visual:
1978 if not visual:
1979 return
1979 return
1980 else:
1980 else:
1981 return getattr(visual, attr_name, None)
1981 return getattr(visual, attr_name, None)
1982
1982
1983
1983
1984 def get_last_path_part(file_node):
1984 def get_last_path_part(file_node):
1985 if not file_node.path:
1985 if not file_node.path:
1986 return u'/'
1986 return u'/'
1987
1987
1988 path = safe_unicode(file_node.path.split('/')[-1])
1988 path = safe_unicode(file_node.path.split('/')[-1])
1989 return u'../' + path
1989 return u'../' + path
1990
1990
1991
1991
1992 def route_url(*args, **kwargs):
1992 def route_url(*args, **kwargs):
1993 """
1993 """
1994 Wrapper around pyramids `route_url` (fully qualified url) function.
1994 Wrapper around pyramids `route_url` (fully qualified url) function.
1995 """
1995 """
1996 req = get_current_request()
1996 req = get_current_request()
1997 return req.route_url(*args, **kwargs)
1997 return req.route_url(*args, **kwargs)
1998
1998
1999
1999
2000 def route_path(*args, **kwargs):
2000 def route_path(*args, **kwargs):
2001 """
2001 """
2002 Wrapper around pyramids `route_path` function.
2002 Wrapper around pyramids `route_path` function.
2003 """
2003 """
2004 req = get_current_request()
2004 req = get_current_request()
2005 return req.route_path(*args, **kwargs)
2005 return req.route_path(*args, **kwargs)
2006
2006
2007
2007
2008 def route_path_or_none(*args, **kwargs):
2008 def route_path_or_none(*args, **kwargs):
2009 try:
2009 try:
2010 return route_path(*args, **kwargs)
2010 return route_path(*args, **kwargs)
2011 except KeyError:
2011 except KeyError:
2012 return None
2012 return None
2013
2013
2014
2014
2015 def current_route_path(request, **kw):
2015 def current_route_path(request, **kw):
2016 new_args = request.GET.mixed()
2016 new_args = request.GET.mixed()
2017 new_args.update(kw)
2017 new_args.update(kw)
2018 return request.current_route_path(_query=new_args)
2018 return request.current_route_path(_query=new_args)
2019
2019
2020
2020
2021 def curl_api_example(method, args):
2021 def curl_api_example(method, args):
2022 args_json = json.dumps(OrderedDict([
2022 args_json = json.dumps(OrderedDict([
2023 ('id', 1),
2023 ('id', 1),
2024 ('auth_token', 'SECRET'),
2024 ('auth_token', 'SECRET'),
2025 ('method', method),
2025 ('method', method),
2026 ('args', args)
2026 ('args', args)
2027 ]))
2027 ]))
2028
2028
2029 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
2029 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
2030 api_url=route_url('apiv2'),
2030 api_url=route_url('apiv2'),
2031 args_json=args_json
2031 args_json=args_json
2032 )
2032 )
2033
2033
2034
2034
2035 def api_call_example(method, args):
2035 def api_call_example(method, args):
2036 """
2036 """
2037 Generates an API call example via CURL
2037 Generates an API call example via CURL
2038 """
2038 """
2039 curl_call = curl_api_example(method, args)
2039 curl_call = curl_api_example(method, args)
2040
2040
2041 return literal(
2041 return literal(
2042 curl_call +
2042 curl_call +
2043 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2043 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2044 "and needs to be of `api calls` role."
2044 "and needs to be of `api calls` role."
2045 .format(token_url=route_url('my_account_auth_tokens')))
2045 .format(token_url=route_url('my_account_auth_tokens')))
2046
2046
2047
2047
2048 def notification_description(notification, request):
2048 def notification_description(notification, request):
2049 """
2049 """
2050 Generate notification human readable description based on notification type
2050 Generate notification human readable description based on notification type
2051 """
2051 """
2052 from rhodecode.model.notification import NotificationModel
2052 from rhodecode.model.notification import NotificationModel
2053 return NotificationModel().make_description(
2053 return NotificationModel().make_description(
2054 notification, translate=request.translate)
2054 notification, translate=request.translate)
2055
2055
2056
2056
2057 def go_import_header(request, db_repo=None):
2057 def go_import_header(request, db_repo=None):
2058 """
2058 """
2059 Creates a header for go-import functionality in Go Lang
2059 Creates a header for go-import functionality in Go Lang
2060 """
2060 """
2061
2061
2062 if not db_repo:
2062 if not db_repo:
2063 return
2063 return
2064 if 'go-get' not in request.GET:
2064 if 'go-get' not in request.GET:
2065 return
2065 return
2066
2066
2067 clone_url = db_repo.clone_url()
2067 clone_url = db_repo.clone_url()
2068 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2068 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2069 # we have a repo and go-get flag,
2069 # we have a repo and go-get flag,
2070 return literal('<meta name="go-import" content="{} {} {}">'.format(
2070 return literal('<meta name="go-import" content="{} {} {}">'.format(
2071 prefix, db_repo.repo_type, clone_url))
2071 prefix, db_repo.repo_type, clone_url))
2072
2072
2073
2073
2074 def reviewer_as_json(*args, **kwargs):
2074 def reviewer_as_json(*args, **kwargs):
2075 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2075 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2076 return _reviewer_as_json(*args, **kwargs)
2076 return _reviewer_as_json(*args, **kwargs)
2077
2077
2078
2078
2079 def get_repo_view_type(request):
2079 def get_repo_view_type(request):
2080 route_name = request.matched_route.name
2080 route_name = request.matched_route.name
2081 route_to_view_type = {
2081 route_to_view_type = {
2082 'repo_changelog': 'commits',
2082 'repo_changelog': 'commits',
2083 'repo_commits': 'commits',
2083 'repo_commits': 'commits',
2084 'repo_files': 'files',
2084 'repo_files': 'files',
2085 'repo_summary': 'summary',
2085 'repo_summary': 'summary',
2086 'repo_commit': 'commit'
2086 'repo_commit': 'commit'
2087 }
2087 }
2088
2088
2089 return route_to_view_type.get(route_name)
2089 return route_to_view_type.get(route_name)
@@ -1,2821 +1,2897 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'variables';
8 @import 'variables';
9 @import 'bootstrap-variables';
9 @import 'bootstrap-variables';
10 @import 'form-bootstrap';
10 @import 'form-bootstrap';
11 @import 'codemirror';
11 @import 'codemirror';
12 @import 'legacy_code_styles';
12 @import 'legacy_code_styles';
13 @import 'readme-box';
13 @import 'readme-box';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29 @import 'tooltips';
29
30
30 //--- BASE ------------------//
31 //--- BASE ------------------//
31 .noscript-error {
32 .noscript-error {
32 top: 0;
33 top: 0;
33 left: 0;
34 left: 0;
34 width: 100%;
35 width: 100%;
35 z-index: 101;
36 z-index: 101;
36 text-align: center;
37 text-align: center;
37 font-size: 120%;
38 font-size: 120%;
38 color: white;
39 color: white;
39 background-color: @alert2;
40 background-color: @alert2;
40 padding: 5px 0 5px 0;
41 padding: 5px 0 5px 0;
41 font-weight: @text-semibold-weight;
42 font-weight: @text-semibold-weight;
42 font-family: @text-semibold;
43 font-family: @text-semibold;
43 }
44 }
44
45
45 html {
46 html {
46 display: table;
47 display: table;
47 height: 100%;
48 height: 100%;
48 width: 100%;
49 width: 100%;
49 }
50 }
50
51
51 body {
52 body {
52 display: table-cell;
53 display: table-cell;
53 width: 100%;
54 width: 100%;
54 }
55 }
55
56
56 //--- LAYOUT ------------------//
57 //--- LAYOUT ------------------//
57
58
58 .hidden{
59 .hidden{
59 display: none !important;
60 display: none !important;
60 }
61 }
61
62
62 .box{
63 .box{
63 float: left;
64 float: left;
64 width: 100%;
65 width: 100%;
65 }
66 }
66
67
67 .browser-header {
68 .browser-header {
68 clear: both;
69 clear: both;
69 }
70 }
70 .main {
71 .main {
71 clear: both;
72 clear: both;
72 padding:0 0 @pagepadding;
73 padding:0 0 @pagepadding;
73 height: auto;
74 height: auto;
74
75
75 &:after { //clearfix
76 &:after { //clearfix
76 content:"";
77 content:"";
77 clear:both;
78 clear:both;
78 width:100%;
79 width:100%;
79 display:block;
80 display:block;
80 }
81 }
81 }
82 }
82
83
83 .action-link{
84 .action-link{
84 margin-left: @padding;
85 margin-left: @padding;
85 padding-left: @padding;
86 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
87 border-left: @border-thickness solid @border-default-color;
87 }
88 }
88
89
89 input + .action-link, .action-link.first{
90 input + .action-link, .action-link.first{
90 border-left: none;
91 border-left: none;
91 }
92 }
92
93
93 .action-link.last{
94 .action-link.last{
94 margin-right: @padding;
95 margin-right: @padding;
95 padding-right: @padding;
96 padding-right: @padding;
96 }
97 }
97
98
98 .action-link.active,
99 .action-link.active,
99 .action-link.active a{
100 .action-link.active a{
100 color: @grey4;
101 color: @grey4;
101 }
102 }
102
103
103 .action-link.disabled {
104 .action-link.disabled {
104 color: @grey4;
105 color: @grey4;
105 cursor: inherit;
106 cursor: inherit;
106 }
107 }
107
108
108 .clipboard-action {
109 .clipboard-action {
109 cursor: pointer;
110 cursor: pointer;
110 color: @grey4;
111 color: @grey4;
111 margin-left: 5px;
112 margin-left: 5px;
112
113
113 &:hover {
114 &:hover {
114 color: @grey2;
115 color: @grey2;
115 }
116 }
116 }
117 }
117
118
118 ul.simple-list{
119 ul.simple-list{
119 list-style: none;
120 list-style: none;
120 margin: 0;
121 margin: 0;
121 padding: 0;
122 padding: 0;
122 }
123 }
123
124
124 .main-content {
125 .main-content {
125 padding-bottom: @pagepadding;
126 padding-bottom: @pagepadding;
126 }
127 }
127
128
128 .wide-mode-wrapper {
129 .wide-mode-wrapper {
129 max-width:4000px !important;
130 max-width:4000px !important;
130 }
131 }
131
132
132 .wrapper {
133 .wrapper {
133 position: relative;
134 position: relative;
134 max-width: @wrapper-maxwidth;
135 max-width: @wrapper-maxwidth;
135 margin: 0 auto;
136 margin: 0 auto;
136 }
137 }
137
138
138 #content {
139 #content {
139 clear: both;
140 clear: both;
140 padding: 0 @contentpadding;
141 padding: 0 @contentpadding;
141 }
142 }
142
143
143 .advanced-settings-fields{
144 .advanced-settings-fields{
144 input{
145 input{
145 margin-left: @textmargin;
146 margin-left: @textmargin;
146 margin-right: @padding/2;
147 margin-right: @padding/2;
147 }
148 }
148 }
149 }
149
150
150 .cs_files_title {
151 .cs_files_title {
151 margin: @pagepadding 0 0;
152 margin: @pagepadding 0 0;
152 }
153 }
153
154
154 input.inline[type="file"] {
155 input.inline[type="file"] {
155 display: inline;
156 display: inline;
156 }
157 }
157
158
158 .error_page {
159 .error_page {
159 margin: 10% auto;
160 margin: 10% auto;
160
161
161 h1 {
162 h1 {
162 color: @grey2;
163 color: @grey2;
163 }
164 }
164
165
165 .alert {
166 .alert {
166 margin: @padding 0;
167 margin: @padding 0;
167 }
168 }
168
169
169 .error-branding {
170 .error-branding {
170 color: @grey4;
171 color: @grey4;
171 font-weight: @text-semibold-weight;
172 font-weight: @text-semibold-weight;
172 font-family: @text-semibold;
173 font-family: @text-semibold;
173 }
174 }
174
175
175 .error_message {
176 .error_message {
176 font-family: @text-regular;
177 font-family: @text-regular;
177 }
178 }
178
179
179 .sidebar {
180 .sidebar {
180 min-height: 275px;
181 min-height: 275px;
181 margin: 0;
182 margin: 0;
182 padding: 0 0 @sidebarpadding @sidebarpadding;
183 padding: 0 0 @sidebarpadding @sidebarpadding;
183 border: none;
184 border: none;
184 }
185 }
185
186
186 .main-content {
187 .main-content {
187 position: relative;
188 position: relative;
188 margin: 0 @sidebarpadding @sidebarpadding;
189 margin: 0 @sidebarpadding @sidebarpadding;
189 padding: 0 0 0 @sidebarpadding;
190 padding: 0 0 0 @sidebarpadding;
190 border-left: @border-thickness solid @grey5;
191 border-left: @border-thickness solid @grey5;
191
192
192 @media (max-width:767px) {
193 @media (max-width:767px) {
193 clear: both;
194 clear: both;
194 width: 100%;
195 width: 100%;
195 margin: 0;
196 margin: 0;
196 border: none;
197 border: none;
197 }
198 }
198 }
199 }
199
200
200 .inner-column {
201 .inner-column {
201 float: left;
202 float: left;
202 width: 29.75%;
203 width: 29.75%;
203 min-height: 150px;
204 min-height: 150px;
204 margin: @sidebarpadding 2% 0 0;
205 margin: @sidebarpadding 2% 0 0;
205 padding: 0 2% 0 0;
206 padding: 0 2% 0 0;
206 border-right: @border-thickness solid @grey5;
207 border-right: @border-thickness solid @grey5;
207
208
208 @media (max-width:767px) {
209 @media (max-width:767px) {
209 clear: both;
210 clear: both;
210 width: 100%;
211 width: 100%;
211 border: none;
212 border: none;
212 }
213 }
213
214
214 ul {
215 ul {
215 padding-left: 1.25em;
216 padding-left: 1.25em;
216 }
217 }
217
218
218 &:last-child {
219 &:last-child {
219 margin: @sidebarpadding 0 0;
220 margin: @sidebarpadding 0 0;
220 border: none;
221 border: none;
221 }
222 }
222
223
223 h4 {
224 h4 {
224 margin: 0 0 @padding;
225 margin: 0 0 @padding;
225 font-weight: @text-semibold-weight;
226 font-weight: @text-semibold-weight;
226 font-family: @text-semibold;
227 font-family: @text-semibold;
227 }
228 }
228 }
229 }
229 }
230 }
230 .error-page-logo {
231 .error-page-logo {
231 width: 130px;
232 width: 130px;
232 height: 160px;
233 height: 160px;
233 }
234 }
234
235
235 // HEADER
236 // HEADER
236 .header {
237 .header {
237
238
238 // TODO: johbo: Fix login pages, so that they work without a min-height
239 // TODO: johbo: Fix login pages, so that they work without a min-height
239 // for the header and then remove the min-height. I chose a smaller value
240 // for the header and then remove the min-height. I chose a smaller value
240 // intentionally here to avoid rendering issues in the main navigation.
241 // intentionally here to avoid rendering issues in the main navigation.
241 min-height: 49px;
242 min-height: 49px;
242 min-width: 1024px;
243 min-width: 1024px;
243
244
244 position: relative;
245 position: relative;
245 vertical-align: bottom;
246 vertical-align: bottom;
246 padding: 0 @header-padding;
247 padding: 0 @header-padding;
247 background-color: @grey1;
248 background-color: @grey1;
248 color: @grey5;
249 color: @grey5;
249
250
250 .title {
251 .title {
251 overflow: visible;
252 overflow: visible;
252 }
253 }
253
254
254 &:before,
255 &:before,
255 &:after {
256 &:after {
256 content: "";
257 content: "";
257 clear: both;
258 clear: both;
258 width: 100%;
259 width: 100%;
259 }
260 }
260
261
261 // TODO: johbo: Avoids breaking "Repositories" chooser
262 // TODO: johbo: Avoids breaking "Repositories" chooser
262 .select2-container .select2-choice .select2-arrow {
263 .select2-container .select2-choice .select2-arrow {
263 display: none;
264 display: none;
264 }
265 }
265 }
266 }
266
267
267 #header-inner {
268 #header-inner {
268 &.title {
269 &.title {
269 margin: 0;
270 margin: 0;
270 }
271 }
271 &:before,
272 &:before,
272 &:after {
273 &:after {
273 content: "";
274 content: "";
274 clear: both;
275 clear: both;
275 }
276 }
276 }
277 }
277
278
278 // Gists
279 // Gists
279 #files_data {
280 #files_data {
280 clear: both; //for firefox
281 clear: both; //for firefox
281 padding-top: 10px;
282 padding-top: 10px;
282 }
283 }
283
284
284 #gistid {
285 #gistid {
285 margin-right: @padding;
286 margin-right: @padding;
286 }
287 }
287
288
288 // Global Settings Editor
289 // Global Settings Editor
289 .textarea.editor {
290 .textarea.editor {
290 float: left;
291 float: left;
291 position: relative;
292 position: relative;
292 max-width: @texteditor-width;
293 max-width: @texteditor-width;
293
294
294 select {
295 select {
295 position: absolute;
296 position: absolute;
296 top:10px;
297 top:10px;
297 right:0;
298 right:0;
298 }
299 }
299
300
300 .CodeMirror {
301 .CodeMirror {
301 margin: 0;
302 margin: 0;
302 }
303 }
303
304
304 .help-block {
305 .help-block {
305 margin: 0 0 @padding;
306 margin: 0 0 @padding;
306 padding:.5em;
307 padding:.5em;
307 background-color: @grey6;
308 background-color: @grey6;
308 &.pre-formatting {
309 &.pre-formatting {
309 white-space: pre;
310 white-space: pre;
310 }
311 }
311 }
312 }
312 }
313 }
313
314
314 ul.auth_plugins {
315 ul.auth_plugins {
315 margin: @padding 0 @padding @legend-width;
316 margin: @padding 0 @padding @legend-width;
316 padding: 0;
317 padding: 0;
317
318
318 li {
319 li {
319 margin-bottom: @padding;
320 margin-bottom: @padding;
320 line-height: 1em;
321 line-height: 1em;
321 list-style-type: none;
322 list-style-type: none;
322
323
323 .auth_buttons .btn {
324 .auth_buttons .btn {
324 margin-right: @padding;
325 margin-right: @padding;
325 }
326 }
326
327
327 }
328 }
328 }
329 }
329
330
330
331
331 // My Account PR list
332 // My Account PR list
332
333
333 #show_closed {
334 #show_closed {
334 margin: 0 1em 0 0;
335 margin: 0 1em 0 0;
335 }
336 }
336
337
337 #pull_request_list_table {
338 #pull_request_list_table {
338 .closed {
339 .closed {
339 background-color: @grey6;
340 background-color: @grey6;
340 }
341 }
341
342
342 .state-creating,
343 .state-creating,
343 .state-updating,
344 .state-updating,
344 .state-merging
345 .state-merging
345 {
346 {
346 background-color: @grey6;
347 background-color: @grey6;
347 }
348 }
348
349
349 .td-status {
350 .td-status {
350 padding-left: .5em;
351 padding-left: .5em;
351 }
352 }
352 .log-container .truncate {
353 .log-container .truncate {
353 height: 2.75em;
354 height: 2.75em;
354 white-space: pre-line;
355 white-space: pre-line;
355 }
356 }
356 table.rctable .user {
357 table.rctable .user {
357 padding-left: 0;
358 padding-left: 0;
358 }
359 }
359 table.rctable {
360 table.rctable {
360 td.td-description,
361 td.td-description,
361 .rc-user {
362 .rc-user {
362 min-width: auto;
363 min-width: auto;
363 }
364 }
364 }
365 }
365 }
366 }
366
367
367 // Pull Requests
368 // Pull Requests
368
369
369 .pullrequests_section_head {
370 .pullrequests_section_head {
370 display: block;
371 display: block;
371 clear: both;
372 clear: both;
372 margin: @padding 0;
373 margin: @padding 0;
373 font-weight: @text-bold-weight;
374 font-weight: @text-bold-weight;
374 font-family: @text-bold;
375 font-family: @text-bold;
375 }
376 }
376
377
377 .pr-origininfo, .pr-targetinfo {
378 .pr-origininfo, .pr-targetinfo {
378 position: relative;
379 position: relative;
379
380
380 .tag {
381 .tag {
381 display: inline-block;
382 display: inline-block;
382 margin: 0 1em .5em 0;
383 margin: 0 1em .5em 0;
383 }
384 }
384
385
385 .clone-url {
386 .clone-url {
386 display: inline-block;
387 display: inline-block;
387 margin: 0 0 .5em 0;
388 margin: 0 0 .5em 0;
388 padding: 0;
389 padding: 0;
389 line-height: 1.2em;
390 line-height: 1.2em;
390 }
391 }
391 }
392 }
392
393
393 .pr-mergeinfo {
394 .pr-mergeinfo {
394 min-width: 95% !important;
395 min-width: 95% !important;
395 padding: 0 !important;
396 padding: 0 !important;
396 border: 0;
397 border: 0;
397 }
398 }
398 .pr-mergeinfo-copy {
399 .pr-mergeinfo-copy {
399 padding: 0 0;
400 padding: 0 0;
400 }
401 }
401
402
402 .pr-pullinfo {
403 .pr-pullinfo {
403 min-width: 95% !important;
404 min-width: 95% !important;
404 padding: 0 !important;
405 padding: 0 !important;
405 border: 0;
406 border: 0;
406 }
407 }
407 .pr-pullinfo-copy {
408 .pr-pullinfo-copy {
408 padding: 0 0;
409 padding: 0 0;
409 }
410 }
410
411
411
412
412 #pr-title-input {
413 #pr-title-input {
413 width: 72%;
414 width: 72%;
414 font-size: 1em;
415 font-size: 1em;
415 margin: 0;
416 margin: 0;
416 padding: 0 0 0 @padding/4;
417 padding: 0 0 0 @padding/4;
417 line-height: 1.7em;
418 line-height: 1.7em;
418 color: @text-color;
419 color: @text-color;
419 letter-spacing: .02em;
420 letter-spacing: .02em;
420 font-weight: @text-bold-weight;
421 font-weight: @text-bold-weight;
421 font-family: @text-bold;
422 font-family: @text-bold;
422 }
423 }
423
424
424 #pullrequest_title {
425 #pullrequest_title {
425 width: 100%;
426 width: 100%;
426 box-sizing: border-box;
427 box-sizing: border-box;
427 }
428 }
428
429
429 #pr_open_message {
430 #pr_open_message {
430 border: @border-thickness solid #fff;
431 border: @border-thickness solid #fff;
431 border-radius: @border-radius;
432 border-radius: @border-radius;
432 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
433 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
433 text-align: left;
434 text-align: left;
434 overflow: hidden;
435 overflow: hidden;
435 }
436 }
436
437
437 .pr-submit-button {
438 .pr-submit-button {
438 float: right;
439 float: right;
439 margin: 0 0 0 5px;
440 margin: 0 0 0 5px;
440 }
441 }
441
442
442 .pr-spacing-container {
443 .pr-spacing-container {
443 padding: 20px;
444 padding: 20px;
444 clear: both
445 clear: both
445 }
446 }
446
447
447 #pr-description-input {
448 #pr-description-input {
448 margin-bottom: 0;
449 margin-bottom: 0;
449 }
450 }
450
451
451 .pr-description-label {
452 .pr-description-label {
452 vertical-align: top;
453 vertical-align: top;
453 }
454 }
454
455
455 .perms_section_head {
456 .perms_section_head {
456 min-width: 625px;
457 min-width: 625px;
457
458
458 h2 {
459 h2 {
459 margin-bottom: 0;
460 margin-bottom: 0;
460 }
461 }
461
462
462 .label-checkbox {
463 .label-checkbox {
463 float: left;
464 float: left;
464 }
465 }
465
466
466 &.field {
467 &.field {
467 margin: @space 0 @padding;
468 margin: @space 0 @padding;
468 }
469 }
469
470
470 &:first-child.field {
471 &:first-child.field {
471 margin-top: 0;
472 margin-top: 0;
472
473
473 .label {
474 .label {
474 margin-top: 0;
475 margin-top: 0;
475 padding-top: 0;
476 padding-top: 0;
476 }
477 }
477
478
478 .radios {
479 .radios {
479 padding-top: 0;
480 padding-top: 0;
480 }
481 }
481 }
482 }
482
483
483 .radios {
484 .radios {
484 position: relative;
485 position: relative;
485 width: 505px;
486 width: 505px;
486 }
487 }
487 }
488 }
488
489
489 //--- MODULES ------------------//
490 //--- MODULES ------------------//
490
491
491
492
492 // Server Announcement
493 // Server Announcement
493 #server-announcement {
494 #server-announcement {
494 width: 95%;
495 width: 95%;
495 margin: @padding auto;
496 margin: @padding auto;
496 padding: @padding;
497 padding: @padding;
497 border-width: 2px;
498 border-width: 2px;
498 border-style: solid;
499 border-style: solid;
499 .border-radius(2px);
500 .border-radius(2px);
500 font-weight: @text-bold-weight;
501 font-weight: @text-bold-weight;
501 font-family: @text-bold;
502 font-family: @text-bold;
502
503
503 &.info { border-color: @alert4; background-color: @alert4-inner; }
504 &.info { border-color: @alert4; background-color: @alert4-inner; }
504 &.warning { border-color: @alert3; background-color: @alert3-inner; }
505 &.warning { border-color: @alert3; background-color: @alert3-inner; }
505 &.error { border-color: @alert2; background-color: @alert2-inner; }
506 &.error { border-color: @alert2; background-color: @alert2-inner; }
506 &.success { border-color: @alert1; background-color: @alert1-inner; }
507 &.success { border-color: @alert1; background-color: @alert1-inner; }
507 &.neutral { border-color: @grey3; background-color: @grey6; }
508 &.neutral { border-color: @grey3; background-color: @grey6; }
508 }
509 }
509
510
510 // Fixed Sidebar Column
511 // Fixed Sidebar Column
511 .sidebar-col-wrapper {
512 .sidebar-col-wrapper {
512 padding-left: @sidebar-all-width;
513 padding-left: @sidebar-all-width;
513
514
514 .sidebar {
515 .sidebar {
515 width: @sidebar-width;
516 width: @sidebar-width;
516 margin-left: -@sidebar-all-width;
517 margin-left: -@sidebar-all-width;
517 }
518 }
518 }
519 }
519
520
520 .sidebar-col-wrapper.scw-small {
521 .sidebar-col-wrapper.scw-small {
521 padding-left: @sidebar-small-all-width;
522 padding-left: @sidebar-small-all-width;
522
523
523 .sidebar {
524 .sidebar {
524 width: @sidebar-small-width;
525 width: @sidebar-small-width;
525 margin-left: -@sidebar-small-all-width;
526 margin-left: -@sidebar-small-all-width;
526 }
527 }
527 }
528 }
528
529
529
530
530 // FOOTER
531 // FOOTER
531 #footer {
532 #footer {
532 padding: 0;
533 padding: 0;
533 text-align: center;
534 text-align: center;
534 vertical-align: middle;
535 vertical-align: middle;
535 color: @grey2;
536 color: @grey2;
536 font-size: 11px;
537 font-size: 11px;
537
538
538 p {
539 p {
539 margin: 0;
540 margin: 0;
540 padding: 1em;
541 padding: 1em;
541 line-height: 1em;
542 line-height: 1em;
542 }
543 }
543
544
544 .server-instance { //server instance
545 .server-instance { //server instance
545 display: none;
546 display: none;
546 }
547 }
547
548
548 .title {
549 .title {
549 float: none;
550 float: none;
550 margin: 0 auto;
551 margin: 0 auto;
551 }
552 }
552 }
553 }
553
554
554 button.close {
555 button.close {
555 padding: 0;
556 padding: 0;
556 cursor: pointer;
557 cursor: pointer;
557 background: transparent;
558 background: transparent;
558 border: 0;
559 border: 0;
559 .box-shadow(none);
560 .box-shadow(none);
560 -webkit-appearance: none;
561 -webkit-appearance: none;
561 }
562 }
562
563
563 .close {
564 .close {
564 float: right;
565 float: right;
565 font-size: 21px;
566 font-size: 21px;
566 font-family: @text-bootstrap;
567 font-family: @text-bootstrap;
567 line-height: 1em;
568 line-height: 1em;
568 font-weight: bold;
569 font-weight: bold;
569 color: @grey2;
570 color: @grey2;
570
571
571 &:hover,
572 &:hover,
572 &:focus {
573 &:focus {
573 color: @grey1;
574 color: @grey1;
574 text-decoration: none;
575 text-decoration: none;
575 cursor: pointer;
576 cursor: pointer;
576 }
577 }
577 }
578 }
578
579
579 // GRID
580 // GRID
580 .sorting,
581 .sorting,
581 .sorting_desc,
582 .sorting_desc,
582 .sorting_asc {
583 .sorting_asc {
583 cursor: pointer;
584 cursor: pointer;
584 }
585 }
585 .sorting_desc:after {
586 .sorting_desc:after {
586 content: "\00A0\25B2";
587 content: "\00A0\25B2";
587 font-size: .75em;
588 font-size: .75em;
588 }
589 }
589 .sorting_asc:after {
590 .sorting_asc:after {
590 content: "\00A0\25BC";
591 content: "\00A0\25BC";
591 font-size: .68em;
592 font-size: .68em;
592 }
593 }
593
594
594
595
595 .user_auth_tokens {
596 .user_auth_tokens {
596
597
597 &.truncate {
598 &.truncate {
598 white-space: nowrap;
599 white-space: nowrap;
599 overflow: hidden;
600 overflow: hidden;
600 text-overflow: ellipsis;
601 text-overflow: ellipsis;
601 }
602 }
602
603
603 .fields .field .input {
604 .fields .field .input {
604 margin: 0;
605 margin: 0;
605 }
606 }
606
607
607 input#description {
608 input#description {
608 width: 100px;
609 width: 100px;
609 margin: 0;
610 margin: 0;
610 }
611 }
611
612
612 .drop-menu {
613 .drop-menu {
613 // TODO: johbo: Remove this, should work out of the box when
614 // TODO: johbo: Remove this, should work out of the box when
614 // having multiple inputs inline
615 // having multiple inputs inline
615 margin: 0 0 0 5px;
616 margin: 0 0 0 5px;
616 }
617 }
617 }
618 }
618 #user_list_table {
619 #user_list_table {
619 .closed {
620 .closed {
620 background-color: @grey6;
621 background-color: @grey6;
621 }
622 }
622 }
623 }
623
624
624
625
625 input, textarea {
626 input, textarea {
626 &.disabled {
627 &.disabled {
627 opacity: .5;
628 opacity: .5;
628 }
629 }
629
630
630 &:hover {
631 &:hover {
631 border-color: @grey3;
632 border-color: @grey3;
632 box-shadow: @button-shadow;
633 box-shadow: @button-shadow;
633 }
634 }
634
635
635 &:focus {
636 &:focus {
636 border-color: @rcblue;
637 border-color: @rcblue;
637 box-shadow: @button-shadow;
638 box-shadow: @button-shadow;
638 }
639 }
639 }
640 }
640
641
641 // remove extra padding in firefox
642 // remove extra padding in firefox
642 input::-moz-focus-inner { border:0; padding:0 }
643 input::-moz-focus-inner { border:0; padding:0 }
643
644
644 .adjacent input {
645 .adjacent input {
645 margin-bottom: @padding;
646 margin-bottom: @padding;
646 }
647 }
647
648
648 .permissions_boxes {
649 .permissions_boxes {
649 display: block;
650 display: block;
650 }
651 }
651
652
652 //FORMS
653 //FORMS
653
654
654 .medium-inline,
655 .medium-inline,
655 input#description.medium-inline {
656 input#description.medium-inline {
656 display: inline;
657 display: inline;
657 width: @medium-inline-input-width;
658 width: @medium-inline-input-width;
658 min-width: 100px;
659 min-width: 100px;
659 }
660 }
660
661
661 select {
662 select {
662 //reset
663 //reset
663 -webkit-appearance: none;
664 -webkit-appearance: none;
664 -moz-appearance: none;
665 -moz-appearance: none;
665
666
666 display: inline-block;
667 display: inline-block;
667 height: 28px;
668 height: 28px;
668 width: auto;
669 width: auto;
669 margin: 0 @padding @padding 0;
670 margin: 0 @padding @padding 0;
670 padding: 0 18px 0 8px;
671 padding: 0 18px 0 8px;
671 line-height:1em;
672 line-height:1em;
672 font-size: @basefontsize;
673 font-size: @basefontsize;
673 border: @border-thickness solid @grey5;
674 border: @border-thickness solid @grey5;
674 border-radius: @border-radius;
675 border-radius: @border-radius;
675 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
676 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
676 color: @grey4;
677 color: @grey4;
677 box-shadow: @button-shadow;
678 box-shadow: @button-shadow;
678
679
679 &:after {
680 &:after {
680 content: "\00A0\25BE";
681 content: "\00A0\25BE";
681 }
682 }
682
683
683 &:focus, &:hover {
684 &:focus, &:hover {
684 outline: none;
685 outline: none;
685 border-color: @grey4;
686 border-color: @grey4;
686 color: @rcdarkblue;
687 color: @rcdarkblue;
687 }
688 }
688 }
689 }
689
690
690 option {
691 option {
691 &:focus {
692 &:focus {
692 outline: none;
693 outline: none;
693 }
694 }
694 }
695 }
695
696
696 input,
697 input,
697 textarea {
698 textarea {
698 padding: @input-padding;
699 padding: @input-padding;
699 border: @input-border-thickness solid @border-highlight-color;
700 border: @input-border-thickness solid @border-highlight-color;
700 .border-radius (@border-radius);
701 .border-radius (@border-radius);
701 font-family: @text-light;
702 font-family: @text-light;
702 font-size: @basefontsize;
703 font-size: @basefontsize;
703
704
704 &.input-sm {
705 &.input-sm {
705 padding: 5px;
706 padding: 5px;
706 }
707 }
707
708
708 &#description {
709 &#description {
709 min-width: @input-description-minwidth;
710 min-width: @input-description-minwidth;
710 min-height: 1em;
711 min-height: 1em;
711 padding: 10px;
712 padding: 10px;
712 }
713 }
713 }
714 }
714
715
715 .field-sm {
716 .field-sm {
716 input,
717 input,
717 textarea {
718 textarea {
718 padding: 5px;
719 padding: 5px;
719 }
720 }
720 }
721 }
721
722
722 textarea {
723 textarea {
723 display: block;
724 display: block;
724 clear: both;
725 clear: both;
725 width: 100%;
726 width: 100%;
726 min-height: 100px;
727 min-height: 100px;
727 margin-bottom: @padding;
728 margin-bottom: @padding;
728 .box-sizing(border-box);
729 .box-sizing(border-box);
729 overflow: auto;
730 overflow: auto;
730 }
731 }
731
732
732 label {
733 label {
733 font-family: @text-light;
734 font-family: @text-light;
734 }
735 }
735
736
736 // GRAVATARS
737 // GRAVATARS
737 // centers gravatar on username to the right
738 // centers gravatar on username to the right
738
739
739 .gravatar {
740 .gravatar {
740 display: inline;
741 display: inline;
741 min-width: 16px;
742 min-width: 16px;
742 min-height: 16px;
743 min-height: 16px;
743 margin: -5px 0;
744 margin: -5px 0;
744 padding: 0;
745 padding: 0;
745 line-height: 1em;
746 line-height: 1em;
746 box-sizing: content-box;
747 box-sizing: content-box;
747 border-radius: 50%;
748 border-radius: 50%;
748
749
749 &.gravatar-large {
750 &.gravatar-large {
750 margin: -0.5em .25em -0.5em 0;
751 margin: -0.5em .25em -0.5em 0;
751 }
752 }
752
753
753 & + .user {
754 & + .user {
754 display: inline;
755 display: inline;
755 margin: 0;
756 margin: 0;
756 padding: 0 0 0 .17em;
757 padding: 0 0 0 .17em;
757 line-height: 1em;
758 line-height: 1em;
758 }
759 }
759 }
760 }
760
761
761 .user-inline-data {
762 .user-inline-data {
762 display: inline-block;
763 display: inline-block;
763 float: left;
764 float: left;
764 padding-left: .5em;
765 padding-left: .5em;
765 line-height: 1.3em;
766 line-height: 1.3em;
766 }
767 }
767
768
768 .rc-user { // gravatar + user wrapper
769 .rc-user { // gravatar + user wrapper
769 float: left;
770 float: left;
770 position: relative;
771 position: relative;
771 min-width: 100px;
772 min-width: 100px;
772 max-width: 200px;
773 max-width: 200px;
773 min-height: (@gravatar-size + @border-thickness * 2); // account for border
774 min-height: (@gravatar-size + @border-thickness * 2); // account for border
774 display: block;
775 display: block;
775 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
776 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
776
777
777
778
778 .gravatar {
779 .gravatar {
779 display: block;
780 display: block;
780 position: absolute;
781 position: absolute;
781 top: 0;
782 top: 0;
782 left: 0;
783 left: 0;
783 min-width: @gravatar-size;
784 min-width: @gravatar-size;
784 min-height: @gravatar-size;
785 min-height: @gravatar-size;
785 margin: 0;
786 margin: 0;
786 }
787 }
787
788
788 .user {
789 .user {
789 display: block;
790 display: block;
790 max-width: 175px;
791 max-width: 175px;
791 padding-top: 2px;
792 padding-top: 2px;
792 overflow: hidden;
793 overflow: hidden;
793 text-overflow: ellipsis;
794 text-overflow: ellipsis;
794 }
795 }
795 }
796 }
796
797
797 .gist-gravatar,
798 .gist-gravatar,
798 .journal_container {
799 .journal_container {
799 .gravatar-large {
800 .gravatar-large {
800 margin: 0 .5em -10px 0;
801 margin: 0 .5em -10px 0;
801 }
802 }
802 }
803 }
803
804
804
805
805 // ADMIN SETTINGS
806 // ADMIN SETTINGS
806
807
807 // Tag Patterns
808 // Tag Patterns
808 .tag_patterns {
809 .tag_patterns {
809 .tag_input {
810 .tag_input {
810 margin-bottom: @padding;
811 margin-bottom: @padding;
811 }
812 }
812 }
813 }
813
814
814 .locked_input {
815 .locked_input {
815 position: relative;
816 position: relative;
816
817
817 input {
818 input {
818 display: inline;
819 display: inline;
819 margin: 3px 5px 0px 0px;
820 margin: 3px 5px 0px 0px;
820 }
821 }
821
822
822 br {
823 br {
823 display: none;
824 display: none;
824 }
825 }
825
826
826 .error-message {
827 .error-message {
827 float: left;
828 float: left;
828 width: 100%;
829 width: 100%;
829 }
830 }
830
831
831 .lock_input_button {
832 .lock_input_button {
832 display: inline;
833 display: inline;
833 }
834 }
834
835
835 .help-block {
836 .help-block {
836 clear: both;
837 clear: both;
837 }
838 }
838 }
839 }
839
840
840 // Notifications
841 // Notifications
841
842
842 .notifications_buttons {
843 .notifications_buttons {
843 margin: 0 0 @space 0;
844 margin: 0 0 @space 0;
844 padding: 0;
845 padding: 0;
845
846
846 .btn {
847 .btn {
847 display: inline-block;
848 display: inline-block;
848 }
849 }
849 }
850 }
850
851
851 .notification-list {
852 .notification-list {
852
853
853 div {
854 div {
854 display: inline-block;
855 display: inline-block;
855 vertical-align: middle;
856 vertical-align: middle;
856 }
857 }
857
858
858 .container {
859 .container {
859 display: block;
860 display: block;
860 margin: 0 0 @padding 0;
861 margin: 0 0 @padding 0;
861 }
862 }
862
863
863 .delete-notifications {
864 .delete-notifications {
864 margin-left: @padding;
865 margin-left: @padding;
865 text-align: right;
866 text-align: right;
866 cursor: pointer;
867 cursor: pointer;
867 }
868 }
868
869
869 .read-notifications {
870 .read-notifications {
870 margin-left: @padding/2;
871 margin-left: @padding/2;
871 text-align: right;
872 text-align: right;
872 width: 35px;
873 width: 35px;
873 cursor: pointer;
874 cursor: pointer;
874 }
875 }
875
876
876 .icon-minus-sign {
877 .icon-minus-sign {
877 color: @alert2;
878 color: @alert2;
878 }
879 }
879
880
880 .icon-ok-sign {
881 .icon-ok-sign {
881 color: @alert1;
882 color: @alert1;
882 }
883 }
883 }
884 }
884
885
885 .user_settings {
886 .user_settings {
886 float: left;
887 float: left;
887 clear: both;
888 clear: both;
888 display: block;
889 display: block;
889 width: 100%;
890 width: 100%;
890
891
891 .gravatar_box {
892 .gravatar_box {
892 margin-bottom: @padding;
893 margin-bottom: @padding;
893
894
894 &:after {
895 &:after {
895 content: " ";
896 content: " ";
896 clear: both;
897 clear: both;
897 width: 100%;
898 width: 100%;
898 }
899 }
899 }
900 }
900
901
901 .fields .field {
902 .fields .field {
902 clear: both;
903 clear: both;
903 }
904 }
904 }
905 }
905
906
906 .advanced_settings {
907 .advanced_settings {
907 margin-bottom: @space;
908 margin-bottom: @space;
908
909
909 .help-block {
910 .help-block {
910 margin-left: 0;
911 margin-left: 0;
911 }
912 }
912
913
913 button + .help-block {
914 button + .help-block {
914 margin-top: @padding;
915 margin-top: @padding;
915 }
916 }
916 }
917 }
917
918
918 // admin settings radio buttons and labels
919 // admin settings radio buttons and labels
919 .label-2 {
920 .label-2 {
920 float: left;
921 float: left;
921 width: @label2-width;
922 width: @label2-width;
922
923
923 label {
924 label {
924 color: @grey1;
925 color: @grey1;
925 }
926 }
926 }
927 }
927 .checkboxes {
928 .checkboxes {
928 float: left;
929 float: left;
929 width: @checkboxes-width;
930 width: @checkboxes-width;
930 margin-bottom: @padding;
931 margin-bottom: @padding;
931
932
932 .checkbox {
933 .checkbox {
933 width: 100%;
934 width: 100%;
934
935
935 label {
936 label {
936 margin: 0;
937 margin: 0;
937 padding: 0;
938 padding: 0;
938 }
939 }
939 }
940 }
940
941
941 .checkbox + .checkbox {
942 .checkbox + .checkbox {
942 display: inline-block;
943 display: inline-block;
943 }
944 }
944
945
945 label {
946 label {
946 margin-right: 1em;
947 margin-right: 1em;
947 }
948 }
948 }
949 }
949
950
950 // CHANGELOG
951 // CHANGELOG
951 .container_header {
952 .container_header {
952 float: left;
953 float: left;
953 display: block;
954 display: block;
954 width: 100%;
955 width: 100%;
955 margin: @padding 0 @padding;
956 margin: @padding 0 @padding;
956
957
957 #filter_changelog {
958 #filter_changelog {
958 float: left;
959 float: left;
959 margin-right: @padding;
960 margin-right: @padding;
960 }
961 }
961
962
962 .breadcrumbs_light {
963 .breadcrumbs_light {
963 display: inline-block;
964 display: inline-block;
964 }
965 }
965 }
966 }
966
967
967 .info_box {
968 .info_box {
968 float: right;
969 float: right;
969 }
970 }
970
971
971
972
972
973
973 #graph_content{
974 #graph_content{
974
975
975 // adjust for table headers so that graph renders properly
976 // adjust for table headers so that graph renders properly
976 // #graph_nodes padding - table cell padding
977 // #graph_nodes padding - table cell padding
977 padding-top: (@space - (@basefontsize * 2.4));
978 padding-top: (@space - (@basefontsize * 2.4));
978
979
979 &.graph_full_width {
980 &.graph_full_width {
980 width: 100%;
981 width: 100%;
981 max-width: 100%;
982 max-width: 100%;
982 }
983 }
983 }
984 }
984
985
985 #graph {
986 #graph {
986
987
987 .pagination-left {
988 .pagination-left {
988 float: left;
989 float: left;
989 clear: both;
990 clear: both;
990 }
991 }
991
992
992 .log-container {
993 .log-container {
993 max-width: 345px;
994 max-width: 345px;
994
995
995 .message{
996 .message{
996 max-width: 340px;
997 max-width: 340px;
997 }
998 }
998 }
999 }
999
1000
1000 .graph-col-wrapper {
1001 .graph-col-wrapper {
1001
1002
1002 #graph_nodes {
1003 #graph_nodes {
1003 width: 100px;
1004 width: 100px;
1004 position: absolute;
1005 position: absolute;
1005 left: 70px;
1006 left: 70px;
1006 z-index: -1;
1007 z-index: -1;
1007 }
1008 }
1008 }
1009 }
1009
1010
1010 .load-more-commits {
1011 .load-more-commits {
1011 text-align: center;
1012 text-align: center;
1012 }
1013 }
1013 .load-more-commits:hover {
1014 .load-more-commits:hover {
1014 background-color: @grey7;
1015 background-color: @grey7;
1015 }
1016 }
1016 .load-more-commits {
1017 .load-more-commits {
1017 a {
1018 a {
1018 display: block;
1019 display: block;
1019 }
1020 }
1020 }
1021 }
1021 }
1022 }
1022
1023
1023 .obsolete-toggle {
1024 .obsolete-toggle {
1024 line-height: 30px;
1025 line-height: 30px;
1025 margin-left: -15px;
1026 margin-left: -15px;
1026 }
1027 }
1027
1028
1028 #rev_range_container, #rev_range_clear, #rev_range_more {
1029 #rev_range_container, #rev_range_clear, #rev_range_more {
1029 margin-top: -5px;
1030 margin-top: -5px;
1030 margin-bottom: -5px;
1031 margin-bottom: -5px;
1031 }
1032 }
1032
1033
1033 #filter_changelog {
1034 #filter_changelog {
1034 float: left;
1035 float: left;
1035 }
1036 }
1036
1037
1037
1038
1038 //--- THEME ------------------//
1039 //--- THEME ------------------//
1039
1040
1040 #logo {
1041 #logo {
1041 float: left;
1042 float: left;
1042 margin: 9px 0 0 0;
1043 margin: 9px 0 0 0;
1043
1044
1044 .header {
1045 .header {
1045 background-color: transparent;
1046 background-color: transparent;
1046 }
1047 }
1047
1048
1048 a {
1049 a {
1049 display: inline-block;
1050 display: inline-block;
1050 }
1051 }
1051
1052
1052 img {
1053 img {
1053 height:30px;
1054 height:30px;
1054 }
1055 }
1055 }
1056 }
1056
1057
1057 .logo-wrapper {
1058 .logo-wrapper {
1058 float:left;
1059 float:left;
1059 }
1060 }
1060
1061
1061 .branding {
1062 .branding {
1062 float: left;
1063 float: left;
1063 padding: 9px 2px;
1064 padding: 9px 2px;
1064 line-height: 1em;
1065 line-height: 1em;
1065 font-size: @navigation-fontsize;
1066 font-size: @navigation-fontsize;
1066
1067
1067 a {
1068 a {
1068 color: @grey5
1069 color: @grey5
1069 }
1070 }
1070 @media screen and (max-width: 1200px) {
1071 @media screen and (max-width: 1200px) {
1071 display: none;
1072 display: none;
1072 }
1073 }
1073 }
1074 }
1074
1075
1075 img {
1076 img {
1076 border: none;
1077 border: none;
1077 outline: none;
1078 outline: none;
1078 }
1079 }
1079 user-profile-header
1080 user-profile-header
1080 label {
1081 label {
1081
1082
1082 input[type="checkbox"] {
1083 input[type="checkbox"] {
1083 margin-right: 1em;
1084 margin-right: 1em;
1084 }
1085 }
1085 input[type="radio"] {
1086 input[type="radio"] {
1086 margin-right: 1em;
1087 margin-right: 1em;
1087 }
1088 }
1088 }
1089 }
1089
1090
1090 .review-status {
1091 .review-status {
1091 &.under_review {
1092 &.under_review {
1092 color: @alert3;
1093 color: @alert3;
1093 }
1094 }
1094 &.approved {
1095 &.approved {
1095 color: @alert1;
1096 color: @alert1;
1096 }
1097 }
1097 &.rejected,
1098 &.rejected,
1098 &.forced_closed{
1099 &.forced_closed{
1099 color: @alert2;
1100 color: @alert2;
1100 }
1101 }
1101 &.not_reviewed {
1102 &.not_reviewed {
1102 color: @grey5;
1103 color: @grey5;
1103 }
1104 }
1104 }
1105 }
1105
1106
1106 .review-status-under_review {
1107 .review-status-under_review {
1107 color: @alert3;
1108 color: @alert3;
1108 }
1109 }
1109 .status-tag-under_review {
1110 .status-tag-under_review {
1110 border-color: @alert3;
1111 border-color: @alert3;
1111 }
1112 }
1112
1113
1113 .review-status-approved {
1114 .review-status-approved {
1114 color: @alert1;
1115 color: @alert1;
1115 }
1116 }
1116 .status-tag-approved {
1117 .status-tag-approved {
1117 border-color: @alert1;
1118 border-color: @alert1;
1118 }
1119 }
1119
1120
1120 .review-status-rejected,
1121 .review-status-rejected,
1121 .review-status-forced_closed {
1122 .review-status-forced_closed {
1122 color: @alert2;
1123 color: @alert2;
1123 }
1124 }
1124 .status-tag-rejected,
1125 .status-tag-rejected,
1125 .status-tag-forced_closed {
1126 .status-tag-forced_closed {
1126 border-color: @alert2;
1127 border-color: @alert2;
1127 }
1128 }
1128
1129
1129 .review-status-not_reviewed {
1130 .review-status-not_reviewed {
1130 color: @grey5;
1131 color: @grey5;
1131 }
1132 }
1132 .status-tag-not_reviewed {
1133 .status-tag-not_reviewed {
1133 border-color: @grey5;
1134 border-color: @grey5;
1134 }
1135 }
1135
1136
1136 .test_pattern_preview {
1137 .test_pattern_preview {
1137 margin: @space 0;
1138 margin: @space 0;
1138
1139
1139 p {
1140 p {
1140 margin-bottom: 0;
1141 margin-bottom: 0;
1141 border-bottom: @border-thickness solid @border-default-color;
1142 border-bottom: @border-thickness solid @border-default-color;
1142 color: @grey3;
1143 color: @grey3;
1143 }
1144 }
1144
1145
1145 .btn {
1146 .btn {
1146 margin-bottom: @padding;
1147 margin-bottom: @padding;
1147 }
1148 }
1148 }
1149 }
1149 #test_pattern_result {
1150 #test_pattern_result {
1150 display: none;
1151 display: none;
1151 &:extend(pre);
1152 &:extend(pre);
1152 padding: .9em;
1153 padding: .9em;
1153 color: @grey3;
1154 color: @grey3;
1154 background-color: @grey7;
1155 background-color: @grey7;
1155 border-right: @border-thickness solid @border-default-color;
1156 border-right: @border-thickness solid @border-default-color;
1156 border-bottom: @border-thickness solid @border-default-color;
1157 border-bottom: @border-thickness solid @border-default-color;
1157 border-left: @border-thickness solid @border-default-color;
1158 border-left: @border-thickness solid @border-default-color;
1158 }
1159 }
1159
1160
1160 #repo_vcs_settings {
1161 #repo_vcs_settings {
1161 #inherit_overlay_vcs_default {
1162 #inherit_overlay_vcs_default {
1162 display: none;
1163 display: none;
1163 }
1164 }
1164 #inherit_overlay_vcs_custom {
1165 #inherit_overlay_vcs_custom {
1165 display: custom;
1166 display: custom;
1166 }
1167 }
1167 &.inherited {
1168 &.inherited {
1168 #inherit_overlay_vcs_default {
1169 #inherit_overlay_vcs_default {
1169 display: block;
1170 display: block;
1170 }
1171 }
1171 #inherit_overlay_vcs_custom {
1172 #inherit_overlay_vcs_custom {
1172 display: none;
1173 display: none;
1173 }
1174 }
1174 }
1175 }
1175 }
1176 }
1176
1177
1177 .issue-tracker-link {
1178 .issue-tracker-link {
1178 color: @rcblue;
1179 color: @rcblue;
1179 }
1180 }
1180
1181
1181 // Issue Tracker Table Show/Hide
1182 // Issue Tracker Table Show/Hide
1182 #repo_issue_tracker {
1183 #repo_issue_tracker {
1183 #inherit_overlay {
1184 #inherit_overlay {
1184 display: none;
1185 display: none;
1185 }
1186 }
1186 #custom_overlay {
1187 #custom_overlay {
1187 display: custom;
1188 display: custom;
1188 }
1189 }
1189 &.inherited {
1190 &.inherited {
1190 #inherit_overlay {
1191 #inherit_overlay {
1191 display: block;
1192 display: block;
1192 }
1193 }
1193 #custom_overlay {
1194 #custom_overlay {
1194 display: none;
1195 display: none;
1195 }
1196 }
1196 }
1197 }
1197 }
1198 }
1198 table.issuetracker {
1199 table.issuetracker {
1199 &.readonly {
1200 &.readonly {
1200 tr, td {
1201 tr, td {
1201 color: @grey3;
1202 color: @grey3;
1202 }
1203 }
1203 }
1204 }
1204 .edit {
1205 .edit {
1205 display: none;
1206 display: none;
1206 }
1207 }
1207 .editopen {
1208 .editopen {
1208 .edit {
1209 .edit {
1209 display: inline;
1210 display: inline;
1210 }
1211 }
1211 .entry {
1212 .entry {
1212 display: none;
1213 display: none;
1213 }
1214 }
1214 }
1215 }
1215 tr td.td-action {
1216 tr td.td-action {
1216 min-width: 117px;
1217 min-width: 117px;
1217 }
1218 }
1218 td input {
1219 td input {
1219 max-width: none;
1220 max-width: none;
1220 min-width: 30px;
1221 min-width: 30px;
1221 width: 80%;
1222 width: 80%;
1222 }
1223 }
1223 .issuetracker_pref input {
1224 .issuetracker_pref input {
1224 width: 40%;
1225 width: 40%;
1225 }
1226 }
1226 input.edit_issuetracker_update {
1227 input.edit_issuetracker_update {
1227 margin-right: 0;
1228 margin-right: 0;
1228 width: auto;
1229 width: auto;
1229 }
1230 }
1230 }
1231 }
1231
1232
1232 table.integrations {
1233 table.integrations {
1233 .td-icon {
1234 .td-icon {
1234 width: 20px;
1235 width: 20px;
1235 .integration-icon {
1236 .integration-icon {
1236 height: 20px;
1237 height: 20px;
1237 width: 20px;
1238 width: 20px;
1238 }
1239 }
1239 }
1240 }
1240 }
1241 }
1241
1242
1242 .integrations {
1243 .integrations {
1243 a.integration-box {
1244 a.integration-box {
1244 color: @text-color;
1245 color: @text-color;
1245 &:hover {
1246 &:hover {
1246 .panel {
1247 .panel {
1247 background: #fbfbfb;
1248 background: #fbfbfb;
1248 }
1249 }
1249 }
1250 }
1250 .integration-icon {
1251 .integration-icon {
1251 width: 30px;
1252 width: 30px;
1252 height: 30px;
1253 height: 30px;
1253 margin-right: 20px;
1254 margin-right: 20px;
1254 float: left;
1255 float: left;
1255 }
1256 }
1256
1257
1257 .panel-body {
1258 .panel-body {
1258 padding: 10px;
1259 padding: 10px;
1259 }
1260 }
1260 .panel {
1261 .panel {
1261 margin-bottom: 10px;
1262 margin-bottom: 10px;
1262 }
1263 }
1263 h2 {
1264 h2 {
1264 display: inline-block;
1265 display: inline-block;
1265 margin: 0;
1266 margin: 0;
1266 min-width: 140px;
1267 min-width: 140px;
1267 }
1268 }
1268 }
1269 }
1269 a.integration-box.dummy-integration {
1270 a.integration-box.dummy-integration {
1270 color: @grey4
1271 color: @grey4
1271 }
1272 }
1272 }
1273 }
1273
1274
1274 //Permissions Settings
1275 //Permissions Settings
1275 #add_perm {
1276 #add_perm {
1276 margin: 0 0 @padding;
1277 margin: 0 0 @padding;
1277 cursor: pointer;
1278 cursor: pointer;
1278 }
1279 }
1279
1280
1280 .perm_ac {
1281 .perm_ac {
1281 input {
1282 input {
1282 width: 95%;
1283 width: 95%;
1283 }
1284 }
1284 }
1285 }
1285
1286
1286 .autocomplete-suggestions {
1287 .autocomplete-suggestions {
1287 width: auto !important; // overrides autocomplete.js
1288 width: auto !important; // overrides autocomplete.js
1288 min-width: 278px;
1289 min-width: 278px;
1289 margin: 0;
1290 margin: 0;
1290 border: @border-thickness solid @grey5;
1291 border: @border-thickness solid @grey5;
1291 border-radius: @border-radius;
1292 border-radius: @border-radius;
1292 color: @grey2;
1293 color: @grey2;
1293 background-color: white;
1294 background-color: white;
1294 }
1295 }
1295
1296
1296 .autocomplete-qfilter-suggestions {
1297 .autocomplete-qfilter-suggestions {
1297 width: auto !important; // overrides autocomplete.js
1298 width: auto !important; // overrides autocomplete.js
1298 max-height: 100% !important;
1299 max-height: 100% !important;
1299 min-width: 376px;
1300 min-width: 376px;
1300 margin: 0;
1301 margin: 0;
1301 border: @border-thickness solid @grey5;
1302 border: @border-thickness solid @grey5;
1302 color: @grey2;
1303 color: @grey2;
1303 background-color: white;
1304 background-color: white;
1304 }
1305 }
1305
1306
1306 .autocomplete-selected {
1307 .autocomplete-selected {
1307 background: #F0F0F0;
1308 background: #F0F0F0;
1308 }
1309 }
1309
1310
1310 .ac-container-wrap {
1311 .ac-container-wrap {
1311 margin: 0;
1312 margin: 0;
1312 padding: 8px;
1313 padding: 8px;
1313 border-bottom: @border-thickness solid @grey5;
1314 border-bottom: @border-thickness solid @grey5;
1314 list-style-type: none;
1315 list-style-type: none;
1315 cursor: pointer;
1316 cursor: pointer;
1316
1317
1317 &:hover {
1318 &:hover {
1318 background-color: @grey7;
1319 background-color: @grey7;
1319 }
1320 }
1320
1321
1321 img {
1322 img {
1322 height: @gravatar-size;
1323 height: @gravatar-size;
1323 width: @gravatar-size;
1324 width: @gravatar-size;
1324 margin-right: 1em;
1325 margin-right: 1em;
1325 }
1326 }
1326
1327
1327 strong {
1328 strong {
1328 font-weight: normal;
1329 font-weight: normal;
1329 }
1330 }
1330 }
1331 }
1331
1332
1332 // Settings Dropdown
1333 // Settings Dropdown
1333 .user-menu .container {
1334 .user-menu .container {
1334 padding: 0 4px;
1335 padding: 0 4px;
1335 margin: 0;
1336 margin: 0;
1336 }
1337 }
1337
1338
1338 .user-menu .gravatar {
1339 .user-menu .gravatar {
1339 cursor: pointer;
1340 cursor: pointer;
1340 }
1341 }
1341
1342
1342 .codeblock {
1343 .codeblock {
1343 margin-bottom: @padding;
1344 margin-bottom: @padding;
1344 clear: both;
1345 clear: both;
1345
1346
1346 .stats {
1347 .stats {
1347 overflow: hidden;
1348 overflow: hidden;
1348 }
1349 }
1349
1350
1350 .message{
1351 .message{
1351 textarea{
1352 textarea{
1352 margin: 0;
1353 margin: 0;
1353 }
1354 }
1354 }
1355 }
1355
1356
1356 .code-header {
1357 .code-header {
1357 .stats {
1358 .stats {
1358 line-height: 2em;
1359 line-height: 2em;
1359
1360
1360 .revision_id {
1361 .revision_id {
1361 margin-left: 0;
1362 margin-left: 0;
1362 }
1363 }
1363 .buttons {
1364 .buttons {
1364 padding-right: 0;
1365 padding-right: 0;
1365 }
1366 }
1366 }
1367 }
1367
1368
1368 .item{
1369 .item{
1369 margin-right: 0.5em;
1370 margin-right: 0.5em;
1370 }
1371 }
1371 }
1372 }
1372
1373
1373 #editor_container {
1374 #editor_container {
1374 position: relative;
1375 position: relative;
1375 margin: @padding 10px;
1376 margin: @padding 10px;
1376 }
1377 }
1377 }
1378 }
1378
1379
1379 #file_history_container {
1380 #file_history_container {
1380 display: none;
1381 display: none;
1381 }
1382 }
1382
1383
1383 .file-history-inner {
1384 .file-history-inner {
1384 margin-bottom: 10px;
1385 margin-bottom: 10px;
1385 }
1386 }
1386
1387
1387 // Pull Requests
1388 // Pull Requests
1388 .summary-details {
1389 .summary-details {
1389 width: 72%;
1390 width: 72%;
1390 }
1391 }
1391 .pr-summary {
1392 .pr-summary {
1392 border-bottom: @border-thickness solid @grey5;
1393 border-bottom: @border-thickness solid @grey5;
1393 margin-bottom: @space;
1394 margin-bottom: @space;
1394 }
1395 }
1395 .reviewers-title {
1396 .reviewers-title {
1396 width: 25%;
1397 width: 25%;
1397 min-width: 200px;
1398 min-width: 200px;
1398 }
1399 }
1399 .reviewers {
1400 .reviewers {
1400 width: 25%;
1401 width: 25%;
1401 min-width: 200px;
1402 min-width: 200px;
1402 }
1403 }
1403 .reviewers ul li {
1404 .reviewers ul li {
1404 position: relative;
1405 position: relative;
1405 width: 100%;
1406 width: 100%;
1406 padding-bottom: 8px;
1407 padding-bottom: 8px;
1407 list-style-type: none;
1408 list-style-type: none;
1408 }
1409 }
1409
1410
1410 .reviewer_entry {
1411 .reviewer_entry {
1411 min-height: 55px;
1412 min-height: 55px;
1412 }
1413 }
1413
1414
1414 .reviewers_member {
1415 .reviewers_member {
1415 width: 100%;
1416 width: 100%;
1416 overflow: auto;
1417 overflow: auto;
1417 }
1418 }
1418 .reviewer_reason {
1419 .reviewer_reason {
1419 padding-left: 20px;
1420 padding-left: 20px;
1420 line-height: 1.5em;
1421 line-height: 1.5em;
1421 }
1422 }
1422 .reviewer_status {
1423 .reviewer_status {
1423 display: inline-block;
1424 display: inline-block;
1424 width: 25px;
1425 width: 25px;
1425 min-width: 25px;
1426 min-width: 25px;
1426 height: 1.2em;
1427 height: 1.2em;
1427 line-height: 1em;
1428 line-height: 1em;
1428 }
1429 }
1429
1430
1430 .reviewer_name {
1431 .reviewer_name {
1431 display: inline-block;
1432 display: inline-block;
1432 max-width: 83%;
1433 max-width: 83%;
1433 padding-right: 20px;
1434 padding-right: 20px;
1434 vertical-align: middle;
1435 vertical-align: middle;
1435 line-height: 1;
1436 line-height: 1;
1436
1437
1437 .rc-user {
1438 .rc-user {
1438 min-width: 0;
1439 min-width: 0;
1439 margin: -2px 1em 0 0;
1440 margin: -2px 1em 0 0;
1440 }
1441 }
1441
1442
1442 .reviewer {
1443 .reviewer {
1443 float: left;
1444 float: left;
1444 }
1445 }
1445 }
1446 }
1446
1447
1447 .reviewer_member_mandatory {
1448 .reviewer_member_mandatory {
1448 position: absolute;
1449 position: absolute;
1449 left: 15px;
1450 left: 15px;
1450 top: 8px;
1451 top: 8px;
1451 width: 16px;
1452 width: 16px;
1452 font-size: 11px;
1453 font-size: 11px;
1453 margin: 0;
1454 margin: 0;
1454 padding: 0;
1455 padding: 0;
1455 color: black;
1456 color: black;
1456 }
1457 }
1457
1458
1458 .reviewer_member_mandatory_remove,
1459 .reviewer_member_mandatory_remove,
1459 .reviewer_member_remove {
1460 .reviewer_member_remove {
1460 position: absolute;
1461 position: absolute;
1461 right: 0;
1462 right: 0;
1462 top: 0;
1463 top: 0;
1463 width: 16px;
1464 width: 16px;
1464 margin-bottom: 10px;
1465 margin-bottom: 10px;
1465 padding: 0;
1466 padding: 0;
1466 color: black;
1467 color: black;
1467 }
1468 }
1468
1469
1469 .reviewer_member_mandatory_remove {
1470 .reviewer_member_mandatory_remove {
1470 color: @grey4;
1471 color: @grey4;
1471 }
1472 }
1472
1473
1473 .reviewer_member_status {
1474 .reviewer_member_status {
1474 margin-top: 5px;
1475 margin-top: 5px;
1475 }
1476 }
1476 .pr-summary #summary{
1477 .pr-summary #summary{
1477 width: 100%;
1478 width: 100%;
1478 }
1479 }
1479 .pr-summary .action_button:hover {
1480 .pr-summary .action_button:hover {
1480 border: 0;
1481 border: 0;
1481 cursor: pointer;
1482 cursor: pointer;
1482 }
1483 }
1483 .pr-details-title {
1484 .pr-details-title {
1484 padding-bottom: 8px;
1485 padding-bottom: 8px;
1485 border-bottom: @border-thickness solid @grey5;
1486 border-bottom: @border-thickness solid @grey5;
1486
1487
1487 .action_button.disabled {
1488 .action_button.disabled {
1488 color: @grey4;
1489 color: @grey4;
1489 cursor: inherit;
1490 cursor: inherit;
1490 }
1491 }
1491 .action_button {
1492 .action_button {
1492 color: @rcblue;
1493 color: @rcblue;
1493 }
1494 }
1494 }
1495 }
1495 .pr-details-content {
1496 .pr-details-content {
1496 margin-top: @textmargin;
1497 margin-top: @textmargin;
1497 margin-bottom: @textmargin;
1498 margin-bottom: @textmargin;
1498 }
1499 }
1499
1500
1500 .pr-reviewer-rules {
1501 .pr-reviewer-rules {
1501 padding: 10px 0px 20px 0px;
1502 padding: 10px 0px 20px 0px;
1502 }
1503 }
1503
1504
1504 .group_members {
1505 .group_members {
1505 margin-top: 0;
1506 margin-top: 0;
1506 padding: 0;
1507 padding: 0;
1507 list-style: outside none none;
1508 list-style: outside none none;
1508
1509
1509 img {
1510 img {
1510 height: @gravatar-size;
1511 height: @gravatar-size;
1511 width: @gravatar-size;
1512 width: @gravatar-size;
1512 margin-right: .5em;
1513 margin-right: .5em;
1513 margin-left: 3px;
1514 margin-left: 3px;
1514 }
1515 }
1515
1516
1516 .to-delete {
1517 .to-delete {
1517 .user {
1518 .user {
1518 text-decoration: line-through;
1519 text-decoration: line-through;
1519 }
1520 }
1520 }
1521 }
1521 }
1522 }
1522
1523
1523 .compare_view_commits_title {
1524 .compare_view_commits_title {
1524 .disabled {
1525 .disabled {
1525 cursor: inherit;
1526 cursor: inherit;
1526 &:hover{
1527 &:hover{
1527 background-color: inherit;
1528 background-color: inherit;
1528 color: inherit;
1529 color: inherit;
1529 }
1530 }
1530 }
1531 }
1531 }
1532 }
1532
1533
1533 .subtitle-compare {
1534 .subtitle-compare {
1534 margin: -15px 0px 0px 0px;
1535 margin: -15px 0px 0px 0px;
1535 }
1536 }
1536
1537
1537 // new entry in group_members
1538 // new entry in group_members
1538 .td-author-new-entry {
1539 .td-author-new-entry {
1539 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1540 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1540 }
1541 }
1541
1542
1542 .usergroup_member_remove {
1543 .usergroup_member_remove {
1543 width: 16px;
1544 width: 16px;
1544 margin-bottom: 10px;
1545 margin-bottom: 10px;
1545 padding: 0;
1546 padding: 0;
1546 color: black !important;
1547 color: black !important;
1547 cursor: pointer;
1548 cursor: pointer;
1548 }
1549 }
1549
1550
1550 .reviewer_ac .ac-input {
1551 .reviewer_ac .ac-input {
1551 width: 92%;
1552 width: 92%;
1552 margin-bottom: 1em;
1553 margin-bottom: 1em;
1553 }
1554 }
1554
1555
1555 .compare_view_commits tr{
1556 .compare_view_commits tr{
1556 height: 20px;
1557 height: 20px;
1557 }
1558 }
1558 .compare_view_commits td {
1559 .compare_view_commits td {
1559 vertical-align: top;
1560 vertical-align: top;
1560 padding-top: 10px;
1561 padding-top: 10px;
1561 }
1562 }
1562 .compare_view_commits .author {
1563 .compare_view_commits .author {
1563 margin-left: 5px;
1564 margin-left: 5px;
1564 }
1565 }
1565
1566
1566 .compare_view_commits {
1567 .compare_view_commits {
1567 .color-a {
1568 .color-a {
1568 color: @alert1;
1569 color: @alert1;
1569 }
1570 }
1570
1571
1571 .color-c {
1572 .color-c {
1572 color: @color3;
1573 color: @color3;
1573 }
1574 }
1574
1575
1575 .color-r {
1576 .color-r {
1576 color: @color5;
1577 color: @color5;
1577 }
1578 }
1578
1579
1579 .color-a-bg {
1580 .color-a-bg {
1580 background-color: @alert1;
1581 background-color: @alert1;
1581 }
1582 }
1582
1583
1583 .color-c-bg {
1584 .color-c-bg {
1584 background-color: @alert3;
1585 background-color: @alert3;
1585 }
1586 }
1586
1587
1587 .color-r-bg {
1588 .color-r-bg {
1588 background-color: @alert2;
1589 background-color: @alert2;
1589 }
1590 }
1590
1591
1591 .color-a-border {
1592 .color-a-border {
1592 border: 1px solid @alert1;
1593 border: 1px solid @alert1;
1593 }
1594 }
1594
1595
1595 .color-c-border {
1596 .color-c-border {
1596 border: 1px solid @alert3;
1597 border: 1px solid @alert3;
1597 }
1598 }
1598
1599
1599 .color-r-border {
1600 .color-r-border {
1600 border: 1px solid @alert2;
1601 border: 1px solid @alert2;
1601 }
1602 }
1602
1603
1603 .commit-change-indicator {
1604 .commit-change-indicator {
1604 width: 15px;
1605 width: 15px;
1605 height: 15px;
1606 height: 15px;
1606 position: relative;
1607 position: relative;
1607 left: 15px;
1608 left: 15px;
1608 }
1609 }
1609
1610
1610 .commit-change-content {
1611 .commit-change-content {
1611 text-align: center;
1612 text-align: center;
1612 vertical-align: middle;
1613 vertical-align: middle;
1613 line-height: 15px;
1614 line-height: 15px;
1614 }
1615 }
1615 }
1616 }
1616
1617
1617 .compare_view_filepath {
1618 .compare_view_filepath {
1618 color: @grey1;
1619 color: @grey1;
1619 }
1620 }
1620
1621
1621 .show_more {
1622 .show_more {
1622 display: inline-block;
1623 display: inline-block;
1623 width: 0;
1624 width: 0;
1624 height: 0;
1625 height: 0;
1625 vertical-align: middle;
1626 vertical-align: middle;
1626 content: "";
1627 content: "";
1627 border: 4px solid;
1628 border: 4px solid;
1628 border-right-color: transparent;
1629 border-right-color: transparent;
1629 border-bottom-color: transparent;
1630 border-bottom-color: transparent;
1630 border-left-color: transparent;
1631 border-left-color: transparent;
1631 font-size: 0;
1632 font-size: 0;
1632 }
1633 }
1633
1634
1634 .journal_more .show_more {
1635 .journal_more .show_more {
1635 display: inline;
1636 display: inline;
1636
1637
1637 &:after {
1638 &:after {
1638 content: none;
1639 content: none;
1639 }
1640 }
1640 }
1641 }
1641
1642
1642 .compare_view_commits .collapse_commit:after {
1643 .compare_view_commits .collapse_commit:after {
1643 cursor: pointer;
1644 cursor: pointer;
1644 content: "\00A0\25B4";
1645 content: "\00A0\25B4";
1645 margin-left: -3px;
1646 margin-left: -3px;
1646 font-size: 17px;
1647 font-size: 17px;
1647 color: @grey4;
1648 color: @grey4;
1648 }
1649 }
1649
1650
1650 .diff_links {
1651 .diff_links {
1651 margin-left: 8px;
1652 margin-left: 8px;
1652 }
1653 }
1653
1654
1654 #pull_request_overview {
1655 #pull_request_overview {
1655 div.ancestor {
1656 div.ancestor {
1656 margin: -33px 0;
1657 margin: -33px 0;
1657 }
1658 }
1658 }
1659 }
1659
1660
1660 div.ancestor {
1661 div.ancestor {
1661 line-height: 33px;
1662 line-height: 33px;
1662 }
1663 }
1663
1664
1664 .cs_icon_td input[type="checkbox"] {
1665 .cs_icon_td input[type="checkbox"] {
1665 display: none;
1666 display: none;
1666 }
1667 }
1667
1668
1668 .cs_icon_td .expand_file_icon:after {
1669 .cs_icon_td .expand_file_icon:after {
1669 cursor: pointer;
1670 cursor: pointer;
1670 content: "\00A0\25B6";
1671 content: "\00A0\25B6";
1671 font-size: 12px;
1672 font-size: 12px;
1672 color: @grey4;
1673 color: @grey4;
1673 }
1674 }
1674
1675
1675 .cs_icon_td .collapse_file_icon:after {
1676 .cs_icon_td .collapse_file_icon:after {
1676 cursor: pointer;
1677 cursor: pointer;
1677 content: "\00A0\25BC";
1678 content: "\00A0\25BC";
1678 font-size: 12px;
1679 font-size: 12px;
1679 color: @grey4;
1680 color: @grey4;
1680 }
1681 }
1681
1682
1682 /*new binary
1683 /*new binary
1683 NEW_FILENODE = 1
1684 NEW_FILENODE = 1
1684 DEL_FILENODE = 2
1685 DEL_FILENODE = 2
1685 MOD_FILENODE = 3
1686 MOD_FILENODE = 3
1686 RENAMED_FILENODE = 4
1687 RENAMED_FILENODE = 4
1687 COPIED_FILENODE = 5
1688 COPIED_FILENODE = 5
1688 CHMOD_FILENODE = 6
1689 CHMOD_FILENODE = 6
1689 BIN_FILENODE = 7
1690 BIN_FILENODE = 7
1690 */
1691 */
1691 .cs_files_expand {
1692 .cs_files_expand {
1692 font-size: @basefontsize + 5px;
1693 font-size: @basefontsize + 5px;
1693 line-height: 1.8em;
1694 line-height: 1.8em;
1694 float: right;
1695 float: right;
1695 }
1696 }
1696
1697
1697 .cs_files_expand span{
1698 .cs_files_expand span{
1698 color: @rcblue;
1699 color: @rcblue;
1699 cursor: pointer;
1700 cursor: pointer;
1700 }
1701 }
1701 .cs_files {
1702 .cs_files {
1702 clear: both;
1703 clear: both;
1703 padding-bottom: @padding;
1704 padding-bottom: @padding;
1704
1705
1705 .cur_cs {
1706 .cur_cs {
1706 margin: 10px 2px;
1707 margin: 10px 2px;
1707 font-weight: bold;
1708 font-weight: bold;
1708 }
1709 }
1709
1710
1710 .node {
1711 .node {
1711 float: left;
1712 float: left;
1712 }
1713 }
1713
1714
1714 .changes {
1715 .changes {
1715 float: right;
1716 float: right;
1716 color: white;
1717 color: white;
1717 font-size: @basefontsize - 4px;
1718 font-size: @basefontsize - 4px;
1718 margin-top: 4px;
1719 margin-top: 4px;
1719 opacity: 0.6;
1720 opacity: 0.6;
1720 filter: Alpha(opacity=60); /* IE8 and earlier */
1721 filter: Alpha(opacity=60); /* IE8 and earlier */
1721
1722
1722 .added {
1723 .added {
1723 background-color: @alert1;
1724 background-color: @alert1;
1724 float: left;
1725 float: left;
1725 text-align: center;
1726 text-align: center;
1726 }
1727 }
1727
1728
1728 .deleted {
1729 .deleted {
1729 background-color: @alert2;
1730 background-color: @alert2;
1730 float: left;
1731 float: left;
1731 text-align: center;
1732 text-align: center;
1732 }
1733 }
1733
1734
1734 .bin {
1735 .bin {
1735 background-color: @alert1;
1736 background-color: @alert1;
1736 text-align: center;
1737 text-align: center;
1737 }
1738 }
1738
1739
1739 /*new binary*/
1740 /*new binary*/
1740 .bin.bin1 {
1741 .bin.bin1 {
1741 background-color: @alert1;
1742 background-color: @alert1;
1742 text-align: center;
1743 text-align: center;
1743 }
1744 }
1744
1745
1745 /*deleted binary*/
1746 /*deleted binary*/
1746 .bin.bin2 {
1747 .bin.bin2 {
1747 background-color: @alert2;
1748 background-color: @alert2;
1748 text-align: center;
1749 text-align: center;
1749 }
1750 }
1750
1751
1751 /*mod binary*/
1752 /*mod binary*/
1752 .bin.bin3 {
1753 .bin.bin3 {
1753 background-color: @grey2;
1754 background-color: @grey2;
1754 text-align: center;
1755 text-align: center;
1755 }
1756 }
1756
1757
1757 /*rename file*/
1758 /*rename file*/
1758 .bin.bin4 {
1759 .bin.bin4 {
1759 background-color: @alert4;
1760 background-color: @alert4;
1760 text-align: center;
1761 text-align: center;
1761 }
1762 }
1762
1763
1763 /*copied file*/
1764 /*copied file*/
1764 .bin.bin5 {
1765 .bin.bin5 {
1765 background-color: @alert4;
1766 background-color: @alert4;
1766 text-align: center;
1767 text-align: center;
1767 }
1768 }
1768
1769
1769 /*chmod file*/
1770 /*chmod file*/
1770 .bin.bin6 {
1771 .bin.bin6 {
1771 background-color: @grey2;
1772 background-color: @grey2;
1772 text-align: center;
1773 text-align: center;
1773 }
1774 }
1774 }
1775 }
1775 }
1776 }
1776
1777
1777 .cs_files .cs_added, .cs_files .cs_A,
1778 .cs_files .cs_added, .cs_files .cs_A,
1778 .cs_files .cs_added, .cs_files .cs_M,
1779 .cs_files .cs_added, .cs_files .cs_M,
1779 .cs_files .cs_added, .cs_files .cs_D {
1780 .cs_files .cs_added, .cs_files .cs_D {
1780 height: 16px;
1781 height: 16px;
1781 padding-right: 10px;
1782 padding-right: 10px;
1782 margin-top: 7px;
1783 margin-top: 7px;
1783 text-align: left;
1784 text-align: left;
1784 }
1785 }
1785
1786
1786 .cs_icon_td {
1787 .cs_icon_td {
1787 min-width: 16px;
1788 min-width: 16px;
1788 width: 16px;
1789 width: 16px;
1789 }
1790 }
1790
1791
1791 .pull-request-merge {
1792 .pull-request-merge {
1792 border: 1px solid @grey5;
1793 border: 1px solid @grey5;
1793 padding: 10px 0px 20px;
1794 padding: 10px 0px 20px;
1794 margin-top: 10px;
1795 margin-top: 10px;
1795 margin-bottom: 20px;
1796 margin-bottom: 20px;
1796 }
1797 }
1797
1798
1798 .pull-request-merge-refresh {
1799 .pull-request-merge-refresh {
1799 margin: 2px 7px;
1800 margin: 2px 7px;
1800 }
1801 }
1801
1802
1802 .pull-request-merge ul {
1803 .pull-request-merge ul {
1803 padding: 0px 0px;
1804 padding: 0px 0px;
1804 }
1805 }
1805
1806
1806 .pull-request-merge li {
1807 .pull-request-merge li {
1807 list-style-type: none;
1808 list-style-type: none;
1808 }
1809 }
1809
1810
1810 .pull-request-merge .pull-request-wrap {
1811 .pull-request-merge .pull-request-wrap {
1811 height: auto;
1812 height: auto;
1812 padding: 0px 0px;
1813 padding: 0px 0px;
1813 text-align: right;
1814 text-align: right;
1814 }
1815 }
1815
1816
1816 .pull-request-merge span {
1817 .pull-request-merge span {
1817 margin-right: 5px;
1818 margin-right: 5px;
1818 }
1819 }
1819
1820
1820 .pull-request-merge-actions {
1821 .pull-request-merge-actions {
1821 min-height: 30px;
1822 min-height: 30px;
1822 padding: 0px 0px;
1823 padding: 0px 0px;
1823 }
1824 }
1824
1825
1825 .pull-request-merge-info {
1826 .pull-request-merge-info {
1826 padding: 0px 5px 5px 0px;
1827 padding: 0px 5px 5px 0px;
1827 }
1828 }
1828
1829
1829 .merge-status {
1830 .merge-status {
1830 margin-right: 5px;
1831 margin-right: 5px;
1831 }
1832 }
1832
1833
1833 .merge-message {
1834 .merge-message {
1834 font-size: 1.2em
1835 font-size: 1.2em
1835 }
1836 }
1836
1837
1837 .merge-message.success i,
1838 .merge-message.success i,
1838 .merge-icon.success i {
1839 .merge-icon.success i {
1839 color:@alert1;
1840 color:@alert1;
1840 }
1841 }
1841
1842
1842 .merge-message.warning i,
1843 .merge-message.warning i,
1843 .merge-icon.warning i {
1844 .merge-icon.warning i {
1844 color: @alert3;
1845 color: @alert3;
1845 }
1846 }
1846
1847
1847 .merge-message.error i,
1848 .merge-message.error i,
1848 .merge-icon.error i {
1849 .merge-icon.error i {
1849 color:@alert2;
1850 color:@alert2;
1850 }
1851 }
1851
1852
1852 .pr-versions {
1853 .pr-versions {
1853 font-size: 1.1em;
1854 font-size: 1.1em;
1854
1855
1855 table {
1856 table {
1856 padding: 0px 5px;
1857 padding: 0px 5px;
1857 }
1858 }
1858
1859
1859 td {
1860 td {
1860 line-height: 15px;
1861 line-height: 15px;
1861 }
1862 }
1862
1863
1863 .compare-radio-button {
1864 .compare-radio-button {
1864 position: relative;
1865 position: relative;
1865 top: -3px;
1866 top: -3px;
1866 }
1867 }
1867 }
1868 }
1868
1869
1869
1870
1870 #close_pull_request {
1871 #close_pull_request {
1871 margin-right: 0px;
1872 margin-right: 0px;
1872 }
1873 }
1873
1874
1874 .empty_data {
1875 .empty_data {
1875 color: @grey4;
1876 color: @grey4;
1876 }
1877 }
1877
1878
1878 #changeset_compare_view_content {
1879 #changeset_compare_view_content {
1879 clear: both;
1880 clear: both;
1880 width: 100%;
1881 width: 100%;
1881 box-sizing: border-box;
1882 box-sizing: border-box;
1882 .border-radius(@border-radius);
1883 .border-radius(@border-radius);
1883
1884
1884 .help-block {
1885 .help-block {
1885 margin: @padding 0;
1886 margin: @padding 0;
1886 color: @text-color;
1887 color: @text-color;
1887 &.pre-formatting {
1888 &.pre-formatting {
1888 white-space: pre;
1889 white-space: pre;
1889 }
1890 }
1890 }
1891 }
1891
1892
1892 .empty_data {
1893 .empty_data {
1893 margin: @padding 0;
1894 margin: @padding 0;
1894 }
1895 }
1895
1896
1896 .alert {
1897 .alert {
1897 margin-bottom: @space;
1898 margin-bottom: @space;
1898 }
1899 }
1899 }
1900 }
1900
1901
1901 .table_disp {
1902 .table_disp {
1902 .status {
1903 .status {
1903 width: auto;
1904 width: auto;
1904 }
1905 }
1905 }
1906 }
1906
1907
1907
1908
1908 .creation_in_progress {
1909 .creation_in_progress {
1909 color: @grey4
1910 color: @grey4
1910 }
1911 }
1911
1912
1912 .status_box_menu {
1913 .status_box_menu {
1913 margin: 0;
1914 margin: 0;
1914 }
1915 }
1915
1916
1916 .notification-table{
1917 .notification-table{
1917 margin-bottom: @space;
1918 margin-bottom: @space;
1918 display: table;
1919 display: table;
1919 width: 100%;
1920 width: 100%;
1920
1921
1921 .container{
1922 .container{
1922 display: table-row;
1923 display: table-row;
1923
1924
1924 .notification-header{
1925 .notification-header{
1925 border-bottom: @border-thickness solid @border-default-color;
1926 border-bottom: @border-thickness solid @border-default-color;
1926 }
1927 }
1927
1928
1928 .notification-subject{
1929 .notification-subject{
1929 display: table-cell;
1930 display: table-cell;
1930 }
1931 }
1931 }
1932 }
1932 }
1933 }
1933
1934
1934 // Notifications
1935 // Notifications
1935 .notification-header{
1936 .notification-header{
1936 display: table;
1937 display: table;
1937 width: 100%;
1938 width: 100%;
1938 padding: floor(@basefontsize/2) 0;
1939 padding: floor(@basefontsize/2) 0;
1939 line-height: 1em;
1940 line-height: 1em;
1940
1941
1941 .desc, .delete-notifications, .read-notifications{
1942 .desc, .delete-notifications, .read-notifications{
1942 display: table-cell;
1943 display: table-cell;
1943 text-align: left;
1944 text-align: left;
1944 }
1945 }
1945
1946
1946 .desc{
1947 .desc{
1947 width: 1163px;
1948 width: 1163px;
1948 }
1949 }
1949
1950
1950 .delete-notifications, .read-notifications{
1951 .delete-notifications, .read-notifications{
1951 width: 35px;
1952 width: 35px;
1952 min-width: 35px; //fixes when only one button is displayed
1953 min-width: 35px; //fixes when only one button is displayed
1953 }
1954 }
1954 }
1955 }
1955
1956
1956 .notification-body {
1957 .notification-body {
1957 .markdown-block,
1958 .markdown-block,
1958 .rst-block {
1959 .rst-block {
1959 padding: @padding 0;
1960 padding: @padding 0;
1960 }
1961 }
1961
1962
1962 .notification-subject {
1963 .notification-subject {
1963 padding: @textmargin 0;
1964 padding: @textmargin 0;
1964 border-bottom: @border-thickness solid @border-default-color;
1965 border-bottom: @border-thickness solid @border-default-color;
1965 }
1966 }
1966 }
1967 }
1967
1968
1968
1969
1969 .notifications_buttons{
1970 .notifications_buttons{
1970 float: right;
1971 float: right;
1971 }
1972 }
1972
1973
1973 #notification-status{
1974 #notification-status{
1974 display: inline;
1975 display: inline;
1975 }
1976 }
1976
1977
1977 // Repositories
1978 // Repositories
1978
1979
1979 #summary.fields{
1980 #summary.fields{
1980 display: table;
1981 display: table;
1981
1982
1982 .field{
1983 .field{
1983 display: table-row;
1984 display: table-row;
1984
1985
1985 .label-summary{
1986 .label-summary{
1986 display: table-cell;
1987 display: table-cell;
1987 min-width: @label-summary-minwidth;
1988 min-width: @label-summary-minwidth;
1988 padding-top: @padding/2;
1989 padding-top: @padding/2;
1989 padding-bottom: @padding/2;
1990 padding-bottom: @padding/2;
1990 padding-right: @padding/2;
1991 padding-right: @padding/2;
1991 }
1992 }
1992
1993
1993 .input{
1994 .input{
1994 display: table-cell;
1995 display: table-cell;
1995 padding: @padding/2;
1996 padding: @padding/2;
1996
1997
1997 input{
1998 input{
1998 min-width: 29em;
1999 min-width: 29em;
1999 padding: @padding/4;
2000 padding: @padding/4;
2000 }
2001 }
2001 }
2002 }
2002 .statistics, .downloads{
2003 .statistics, .downloads{
2003 .disabled{
2004 .disabled{
2004 color: @grey4;
2005 color: @grey4;
2005 }
2006 }
2006 }
2007 }
2007 }
2008 }
2008 }
2009 }
2009
2010
2010 #summary{
2011 #summary{
2011 width: 70%;
2012 width: 70%;
2012 }
2013 }
2013
2014
2014
2015
2015 // Journal
2016 // Journal
2016 .journal.title {
2017 .journal.title {
2017 h5 {
2018 h5 {
2018 float: left;
2019 float: left;
2019 margin: 0;
2020 margin: 0;
2020 width: 70%;
2021 width: 70%;
2021 }
2022 }
2022
2023
2023 ul {
2024 ul {
2024 float: right;
2025 float: right;
2025 display: inline-block;
2026 display: inline-block;
2026 margin: 0;
2027 margin: 0;
2027 width: 30%;
2028 width: 30%;
2028 text-align: right;
2029 text-align: right;
2029
2030
2030 li {
2031 li {
2031 display: inline;
2032 display: inline;
2032 font-size: @journal-fontsize;
2033 font-size: @journal-fontsize;
2033 line-height: 1em;
2034 line-height: 1em;
2034
2035
2035 list-style-type: none;
2036 list-style-type: none;
2036 }
2037 }
2037 }
2038 }
2038 }
2039 }
2039
2040
2040 .filterexample {
2041 .filterexample {
2041 position: absolute;
2042 position: absolute;
2042 top: 95px;
2043 top: 95px;
2043 left: @contentpadding;
2044 left: @contentpadding;
2044 color: @rcblue;
2045 color: @rcblue;
2045 font-size: 11px;
2046 font-size: 11px;
2046 font-family: @text-regular;
2047 font-family: @text-regular;
2047 cursor: help;
2048 cursor: help;
2048
2049
2049 &:hover {
2050 &:hover {
2050 color: @rcdarkblue;
2051 color: @rcdarkblue;
2051 }
2052 }
2052
2053
2053 @media (max-width:768px) {
2054 @media (max-width:768px) {
2054 position: relative;
2055 position: relative;
2055 top: auto;
2056 top: auto;
2056 left: auto;
2057 left: auto;
2057 display: block;
2058 display: block;
2058 }
2059 }
2059 }
2060 }
2060
2061
2061
2062
2062 #journal{
2063 #journal{
2063 margin-bottom: @space;
2064 margin-bottom: @space;
2064
2065
2065 .journal_day{
2066 .journal_day{
2066 margin-bottom: @textmargin/2;
2067 margin-bottom: @textmargin/2;
2067 padding-bottom: @textmargin/2;
2068 padding-bottom: @textmargin/2;
2068 font-size: @journal-fontsize;
2069 font-size: @journal-fontsize;
2069 border-bottom: @border-thickness solid @border-default-color;
2070 border-bottom: @border-thickness solid @border-default-color;
2070 }
2071 }
2071
2072
2072 .journal_container{
2073 .journal_container{
2073 margin-bottom: @space;
2074 margin-bottom: @space;
2074
2075
2075 .journal_user{
2076 .journal_user{
2076 display: inline-block;
2077 display: inline-block;
2077 }
2078 }
2078 .journal_action_container{
2079 .journal_action_container{
2079 display: block;
2080 display: block;
2080 margin-top: @textmargin;
2081 margin-top: @textmargin;
2081
2082
2082 div{
2083 div{
2083 display: inline;
2084 display: inline;
2084 }
2085 }
2085
2086
2086 div.journal_action_params{
2087 div.journal_action_params{
2087 display: block;
2088 display: block;
2088 }
2089 }
2089
2090
2090 div.journal_repo:after{
2091 div.journal_repo:after{
2091 content: "\A";
2092 content: "\A";
2092 white-space: pre;
2093 white-space: pre;
2093 }
2094 }
2094
2095
2095 div.date{
2096 div.date{
2096 display: block;
2097 display: block;
2097 margin-bottom: @textmargin;
2098 margin-bottom: @textmargin;
2098 }
2099 }
2099 }
2100 }
2100 }
2101 }
2101 }
2102 }
2102
2103
2103 // Files
2104 // Files
2104 .edit-file-title {
2105 .edit-file-title {
2105 font-size: 16px;
2106 font-size: 16px;
2106
2107
2107 .title-heading {
2108 .title-heading {
2108 padding: 2px;
2109 padding: 2px;
2109 }
2110 }
2110 }
2111 }
2111
2112
2112 .edit-file-fieldset {
2113 .edit-file-fieldset {
2113 margin: @sidebarpadding 0;
2114 margin: @sidebarpadding 0;
2114
2115
2115 .fieldset {
2116 .fieldset {
2116 .left-label {
2117 .left-label {
2117 width: 13%;
2118 width: 13%;
2118 }
2119 }
2119 .right-content {
2120 .right-content {
2120 width: 87%;
2121 width: 87%;
2121 max-width: 100%;
2122 max-width: 100%;
2122 }
2123 }
2123 .filename-label {
2124 .filename-label {
2124 margin-top: 13px;
2125 margin-top: 13px;
2125 }
2126 }
2126 .commit-message-label {
2127 .commit-message-label {
2127 margin-top: 4px;
2128 margin-top: 4px;
2128 }
2129 }
2129 .file-upload-input {
2130 .file-upload-input {
2130 input {
2131 input {
2131 display: none;
2132 display: none;
2132 }
2133 }
2133 margin-top: 10px;
2134 margin-top: 10px;
2134 }
2135 }
2135 .file-upload-label {
2136 .file-upload-label {
2136 margin-top: 10px;
2137 margin-top: 10px;
2137 }
2138 }
2138 p {
2139 p {
2139 margin-top: 5px;
2140 margin-top: 5px;
2140 }
2141 }
2141
2142
2142 }
2143 }
2143 .custom-path-link {
2144 .custom-path-link {
2144 margin-left: 5px;
2145 margin-left: 5px;
2145 }
2146 }
2146 #commit {
2147 #commit {
2147 resize: vertical;
2148 resize: vertical;
2148 }
2149 }
2149 }
2150 }
2150
2151
2151 .delete-file-preview {
2152 .delete-file-preview {
2152 max-height: 250px;
2153 max-height: 250px;
2153 }
2154 }
2154
2155
2155 .new-file,
2156 .new-file,
2156 #filter_activate,
2157 #filter_activate,
2157 #filter_deactivate {
2158 #filter_deactivate {
2158 float: right;
2159 float: right;
2159 margin: 0 0 0 10px;
2160 margin: 0 0 0 10px;
2160 }
2161 }
2161
2162
2162 .file-upload-transaction-wrapper {
2163 .file-upload-transaction-wrapper {
2163 margin-top: 57px;
2164 margin-top: 57px;
2164 clear: both;
2165 clear: both;
2165 }
2166 }
2166
2167
2167 .file-upload-transaction-wrapper .error {
2168 .file-upload-transaction-wrapper .error {
2168 color: @color5;
2169 color: @color5;
2169 }
2170 }
2170
2171
2171 .file-upload-transaction {
2172 .file-upload-transaction {
2172 min-height: 200px;
2173 min-height: 200px;
2173 padding: 54px;
2174 padding: 54px;
2174 border: 1px solid @grey5;
2175 border: 1px solid @grey5;
2175 text-align: center;
2176 text-align: center;
2176 clear: both;
2177 clear: both;
2177 }
2178 }
2178
2179
2179 .file-upload-transaction i {
2180 .file-upload-transaction i {
2180 font-size: 48px
2181 font-size: 48px
2181 }
2182 }
2182
2183
2183 h3.files_location{
2184 h3.files_location{
2184 line-height: 2.4em;
2185 line-height: 2.4em;
2185 }
2186 }
2186
2187
2187 .browser-nav {
2188 .browser-nav {
2188 width: 100%;
2189 width: 100%;
2189 display: table;
2190 display: table;
2190 margin-bottom: 20px;
2191 margin-bottom: 20px;
2191
2192
2192 .info_box {
2193 .info_box {
2193 float: left;
2194 float: left;
2194 display: inline-table;
2195 display: inline-table;
2195 height: 2.5em;
2196 height: 2.5em;
2196
2197
2197 .browser-cur-rev, .info_box_elem {
2198 .browser-cur-rev, .info_box_elem {
2198 display: table-cell;
2199 display: table-cell;
2199 vertical-align: middle;
2200 vertical-align: middle;
2200 }
2201 }
2201
2202
2202 .drop-menu {
2203 .drop-menu {
2203 margin: 0 10px;
2204 margin: 0 10px;
2204 }
2205 }
2205
2206
2206 .info_box_elem {
2207 .info_box_elem {
2207 border-top: @border-thickness solid @grey5;
2208 border-top: @border-thickness solid @grey5;
2208 border-bottom: @border-thickness solid @grey5;
2209 border-bottom: @border-thickness solid @grey5;
2209 box-shadow: @button-shadow;
2210 box-shadow: @button-shadow;
2210
2211
2211 #at_rev, a {
2212 #at_rev, a {
2212 padding: 0.6em 0.4em;
2213 padding: 0.6em 0.4em;
2213 margin: 0;
2214 margin: 0;
2214 .box-shadow(none);
2215 .box-shadow(none);
2215 border: 0;
2216 border: 0;
2216 height: 12px;
2217 height: 12px;
2217 color: @grey2;
2218 color: @grey2;
2218 }
2219 }
2219
2220
2220 input#at_rev {
2221 input#at_rev {
2221 max-width: 50px;
2222 max-width: 50px;
2222 text-align: center;
2223 text-align: center;
2223 }
2224 }
2224
2225
2225 &.previous {
2226 &.previous {
2226 border: @border-thickness solid @grey5;
2227 border: @border-thickness solid @grey5;
2227 border-top-left-radius: @border-radius;
2228 border-top-left-radius: @border-radius;
2228 border-bottom-left-radius: @border-radius;
2229 border-bottom-left-radius: @border-radius;
2229
2230
2230 &:hover {
2231 &:hover {
2231 border-color: @grey4;
2232 border-color: @grey4;
2232 }
2233 }
2233
2234
2234 .disabled {
2235 .disabled {
2235 color: @grey5;
2236 color: @grey5;
2236 cursor: not-allowed;
2237 cursor: not-allowed;
2237 opacity: 0.5;
2238 opacity: 0.5;
2238 }
2239 }
2239 }
2240 }
2240
2241
2241 &.next {
2242 &.next {
2242 border: @border-thickness solid @grey5;
2243 border: @border-thickness solid @grey5;
2243 border-top-right-radius: @border-radius;
2244 border-top-right-radius: @border-radius;
2244 border-bottom-right-radius: @border-radius;
2245 border-bottom-right-radius: @border-radius;
2245
2246
2246 &:hover {
2247 &:hover {
2247 border-color: @grey4;
2248 border-color: @grey4;
2248 }
2249 }
2249
2250
2250 .disabled {
2251 .disabled {
2251 color: @grey5;
2252 color: @grey5;
2252 cursor: not-allowed;
2253 cursor: not-allowed;
2253 opacity: 0.5;
2254 opacity: 0.5;
2254 }
2255 }
2255 }
2256 }
2256 }
2257 }
2257
2258
2258 .browser-cur-rev {
2259 .browser-cur-rev {
2259
2260
2260 span{
2261 span{
2261 margin: 0;
2262 margin: 0;
2262 color: @rcblue;
2263 color: @rcblue;
2263 height: 12px;
2264 height: 12px;
2264 display: inline-block;
2265 display: inline-block;
2265 padding: 0.7em 1em ;
2266 padding: 0.7em 1em ;
2266 border: @border-thickness solid @rcblue;
2267 border: @border-thickness solid @rcblue;
2267 margin-right: @padding;
2268 margin-right: @padding;
2268 }
2269 }
2269 }
2270 }
2270
2271
2271 }
2272 }
2272
2273
2273 .select-index-number {
2274 .select-index-number {
2274 margin: 0 0 0 20px;
2275 margin: 0 0 0 20px;
2275 color: @grey3;
2276 color: @grey3;
2276 }
2277 }
2277
2278
2278 .search_activate {
2279 .search_activate {
2279 display: table-cell;
2280 display: table-cell;
2280 vertical-align: middle;
2281 vertical-align: middle;
2281
2282
2282 input, label{
2283 input, label{
2283 margin: 0;
2284 margin: 0;
2284 padding: 0;
2285 padding: 0;
2285 }
2286 }
2286
2287
2287 input{
2288 input{
2288 margin-left: @textmargin;
2289 margin-left: @textmargin;
2289 }
2290 }
2290
2291
2291 }
2292 }
2292 }
2293 }
2293
2294
2294 .browser-cur-rev{
2295 .browser-cur-rev{
2295 margin-bottom: @textmargin;
2296 margin-bottom: @textmargin;
2296 }
2297 }
2297
2298
2298 #node_filter_box_loading{
2299 #node_filter_box_loading{
2299 .info_text;
2300 .info_text;
2300 }
2301 }
2301
2302
2302 .browser-search {
2303 .browser-search {
2303 margin: -25px 0px 5px 0px;
2304 margin: -25px 0px 5px 0px;
2304 }
2305 }
2305
2306
2306 .files-quick-filter {
2307 .files-quick-filter {
2307 float: right;
2308 float: right;
2308 width: 180px;
2309 width: 180px;
2309 position: relative;
2310 position: relative;
2310 }
2311 }
2311
2312
2312 .files-filter-box {
2313 .files-filter-box {
2313 display: flex;
2314 display: flex;
2314 padding: 0px;
2315 padding: 0px;
2315 border-radius: 3px;
2316 border-radius: 3px;
2316 margin-bottom: 0;
2317 margin-bottom: 0;
2317
2318
2318 a {
2319 a {
2319 border: none !important;
2320 border: none !important;
2320 }
2321 }
2321
2322
2322 li {
2323 li {
2323 list-style-type: none
2324 list-style-type: none
2324 }
2325 }
2325 }
2326 }
2326
2327
2327 .files-filter-box-path {
2328 .files-filter-box-path {
2328 line-height: 33px;
2329 line-height: 33px;
2329 padding: 0;
2330 padding: 0;
2330 width: 20px;
2331 width: 20px;
2331 position: absolute;
2332 position: absolute;
2332 z-index: 11;
2333 z-index: 11;
2333 left: 5px;
2334 left: 5px;
2334 }
2335 }
2335
2336
2336 .files-filter-box-input {
2337 .files-filter-box-input {
2337 margin-right: 0;
2338 margin-right: 0;
2338
2339
2339 input {
2340 input {
2340 border: 1px solid @white;
2341 border: 1px solid @white;
2341 padding-left: 25px;
2342 padding-left: 25px;
2342 width: 145px;
2343 width: 145px;
2343
2344
2344 &:hover {
2345 &:hover {
2345 border-color: @grey6;
2346 border-color: @grey6;
2346 }
2347 }
2347
2348
2348 &:focus {
2349 &:focus {
2349 border-color: @grey5;
2350 border-color: @grey5;
2350 }
2351 }
2351 }
2352 }
2352 }
2353 }
2353
2354
2354 .browser-result{
2355 .browser-result{
2355 td a{
2356 td a{
2356 margin-left: 0.5em;
2357 margin-left: 0.5em;
2357 display: inline-block;
2358 display: inline-block;
2358
2359
2359 em {
2360 em {
2360 font-weight: @text-bold-weight;
2361 font-weight: @text-bold-weight;
2361 font-family: @text-bold;
2362 font-family: @text-bold;
2362 }
2363 }
2363 }
2364 }
2364 }
2365 }
2365
2366
2366 .browser-highlight{
2367 .browser-highlight{
2367 background-color: @grey5-alpha;
2368 background-color: @grey5-alpha;
2368 }
2369 }
2369
2370
2370
2371
2371 .edit-file-fieldset #location,
2372 .edit-file-fieldset #location,
2372 .edit-file-fieldset #filename {
2373 .edit-file-fieldset #filename {
2373 display: flex;
2374 display: flex;
2374 width: -moz-available; /* WebKit-based browsers will ignore this. */
2375 width: -moz-available; /* WebKit-based browsers will ignore this. */
2375 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2376 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2376 width: fill-available;
2377 width: fill-available;
2377 border: 0;
2378 border: 0;
2378 }
2379 }
2379
2380
2380 .path-items {
2381 .path-items {
2381 display: flex;
2382 display: flex;
2382 padding: 0;
2383 padding: 0;
2383 border: 1px solid #eeeeee;
2384 border: 1px solid #eeeeee;
2384 width: 100%;
2385 width: 100%;
2385 float: left;
2386 float: left;
2386
2387
2387 .breadcrumb-path {
2388 .breadcrumb-path {
2388 line-height: 30px;
2389 line-height: 30px;
2389 padding: 0 4px;
2390 padding: 0 4px;
2390 white-space: nowrap;
2391 white-space: nowrap;
2391 }
2392 }
2392
2393
2393 .location-path {
2394 .location-path {
2394 width: -moz-available; /* WebKit-based browsers will ignore this. */
2395 width: -moz-available; /* WebKit-based browsers will ignore this. */
2395 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2396 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2396 width: fill-available;
2397 width: fill-available;
2397
2398
2398 .file-name-input {
2399 .file-name-input {
2399 padding: 0.5em 0;
2400 padding: 0.5em 0;
2400 }
2401 }
2401
2402
2402 }
2403 }
2403
2404
2404 ul {
2405 ul {
2405 display: flex;
2406 display: flex;
2406 margin: 0;
2407 margin: 0;
2407 padding: 0;
2408 padding: 0;
2408 width: 100%;
2409 width: 100%;
2409 }
2410 }
2410
2411
2411 li {
2412 li {
2412 list-style-type: none;
2413 list-style-type: none;
2413 }
2414 }
2414
2415
2415 }
2416 }
2416
2417
2417 .editor-items {
2418 .editor-items {
2418 height: 40px;
2419 height: 40px;
2419 margin: 10px 0 -17px 10px;
2420 margin: 10px 0 -17px 10px;
2420
2421
2421 .editor-action {
2422 .editor-action {
2422 cursor: pointer;
2423 cursor: pointer;
2423 }
2424 }
2424
2425
2425 .editor-action.active {
2426 .editor-action.active {
2426 border-bottom: 2px solid #5C5C5C;
2427 border-bottom: 2px solid #5C5C5C;
2427 }
2428 }
2428
2429
2429 li {
2430 li {
2430 list-style-type: none;
2431 list-style-type: none;
2431 }
2432 }
2432 }
2433 }
2433
2434
2434 .edit-file-fieldset .message textarea {
2435 .edit-file-fieldset .message textarea {
2435 border: 1px solid #eeeeee;
2436 border: 1px solid #eeeeee;
2436 }
2437 }
2437
2438
2438 #files_data .codeblock {
2439 #files_data .codeblock {
2439 background-color: #F5F5F5;
2440 background-color: #F5F5F5;
2440 }
2441 }
2441
2442
2442 #editor_preview {
2443 #editor_preview {
2443 background: white;
2444 background: white;
2444 }
2445 }
2445
2446
2446 .show-editor {
2447 .show-editor {
2447 padding: 10px;
2448 padding: 10px;
2448 background-color: white;
2449 background-color: white;
2449
2450
2450 }
2451 }
2451
2452
2452 .show-preview {
2453 .show-preview {
2453 padding: 10px;
2454 padding: 10px;
2454 background-color: white;
2455 background-color: white;
2455 border-left: 1px solid #eeeeee;
2456 border-left: 1px solid #eeeeee;
2456 }
2457 }
2457 // quick filter
2458 // quick filter
2458 .grid-quick-filter {
2459 .grid-quick-filter {
2459 float: right;
2460 float: right;
2460 position: relative;
2461 position: relative;
2461 }
2462 }
2462
2463
2463 .grid-filter-box {
2464 .grid-filter-box {
2464 display: flex;
2465 display: flex;
2465 padding: 0px;
2466 padding: 0px;
2466 border-radius: 3px;
2467 border-radius: 3px;
2467 margin-bottom: 0;
2468 margin-bottom: 0;
2468
2469
2469 a {
2470 a {
2470 border: none !important;
2471 border: none !important;
2471 }
2472 }
2472
2473
2473 li {
2474 li {
2474 list-style-type: none
2475 list-style-type: none
2475 }
2476 }
2476 }
2477 }
2477
2478
2478 .grid-filter-box-icon {
2479 .grid-filter-box-icon {
2479 line-height: 33px;
2480 line-height: 33px;
2480 padding: 0;
2481 padding: 0;
2481 width: 20px;
2482 width: 20px;
2482 position: absolute;
2483 position: absolute;
2483 z-index: 11;
2484 z-index: 11;
2484 left: 5px;
2485 left: 5px;
2485 }
2486 }
2486
2487
2487 .grid-filter-box-input {
2488 .grid-filter-box-input {
2488 margin-right: 0;
2489 margin-right: 0;
2489
2490
2490 input {
2491 input {
2491 border: 1px solid @white;
2492 border: 1px solid @white;
2492 padding-left: 25px;
2493 padding-left: 25px;
2493 width: 145px;
2494 width: 145px;
2494
2495
2495 &:hover {
2496 &:hover {
2496 border-color: @grey6;
2497 border-color: @grey6;
2497 }
2498 }
2498
2499
2499 &:focus {
2500 &:focus {
2500 border-color: @grey5;
2501 border-color: @grey5;
2501 }
2502 }
2502 }
2503 }
2503 }
2504 }
2504
2505
2505
2506
2506
2507
2507 // Search
2508 // Search
2508
2509
2509 .search-form{
2510 .search-form{
2510 #q {
2511 #q {
2511 width: @search-form-width;
2512 width: @search-form-width;
2512 }
2513 }
2513 .fields{
2514 .fields{
2514 margin: 0 0 @space;
2515 margin: 0 0 @space;
2515 }
2516 }
2516
2517
2517 label{
2518 label{
2518 display: inline-block;
2519 display: inline-block;
2519 margin-right: @textmargin;
2520 margin-right: @textmargin;
2520 padding-top: 0.25em;
2521 padding-top: 0.25em;
2521 }
2522 }
2522
2523
2523
2524
2524 .results{
2525 .results{
2525 clear: both;
2526 clear: both;
2526 margin: 0 0 @padding;
2527 margin: 0 0 @padding;
2527 }
2528 }
2528
2529
2529 .search-tags {
2530 .search-tags {
2530 padding: 5px 0;
2531 padding: 5px 0;
2531 }
2532 }
2532 }
2533 }
2533
2534
2534 div.search-feedback-items {
2535 div.search-feedback-items {
2535 display: inline-block;
2536 display: inline-block;
2536 }
2537 }
2537
2538
2538 div.search-code-body {
2539 div.search-code-body {
2539 background-color: #ffffff; padding: 5px 0 5px 10px;
2540 background-color: #ffffff; padding: 5px 0 5px 10px;
2540 pre {
2541 pre {
2541 .match { background-color: #faffa6;}
2542 .match { background-color: #faffa6;}
2542 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2543 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2543 }
2544 }
2544 }
2545 }
2545
2546
2546 .expand_commit.search {
2547 .expand_commit.search {
2547 .show_more.open {
2548 .show_more.open {
2548 height: auto;
2549 height: auto;
2549 max-height: none;
2550 max-height: none;
2550 }
2551 }
2551 }
2552 }
2552
2553
2553 .search-results {
2554 .search-results {
2554
2555
2555 h2 {
2556 h2 {
2556 margin-bottom: 0;
2557 margin-bottom: 0;
2557 }
2558 }
2558 .codeblock {
2559 .codeblock {
2559 border: none;
2560 border: none;
2560 background: transparent;
2561 background: transparent;
2561 }
2562 }
2562
2563
2563 .codeblock-header {
2564 .codeblock-header {
2564 border: none;
2565 border: none;
2565 background: transparent;
2566 background: transparent;
2566 }
2567 }
2567
2568
2568 .code-body {
2569 .code-body {
2569 border: @border-thickness solid @grey6;
2570 border: @border-thickness solid @grey6;
2570 .border-radius(@border-radius);
2571 .border-radius(@border-radius);
2571 }
2572 }
2572
2573
2573 .td-commit {
2574 .td-commit {
2574 &:extend(pre);
2575 &:extend(pre);
2575 border-bottom: @border-thickness solid @border-default-color;
2576 border-bottom: @border-thickness solid @border-default-color;
2576 }
2577 }
2577
2578
2578 .message {
2579 .message {
2579 height: auto;
2580 height: auto;
2580 max-width: 350px;
2581 max-width: 350px;
2581 white-space: normal;
2582 white-space: normal;
2582 text-overflow: initial;
2583 text-overflow: initial;
2583 overflow: visible;
2584 overflow: visible;
2584
2585
2585 .match { background-color: #faffa6;}
2586 .match { background-color: #faffa6;}
2586 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2587 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2587 }
2588 }
2588
2589
2589 .path {
2590 .path {
2590 border-bottom: none !important;
2591 border-bottom: none !important;
2591 border-left: 1px solid @grey6 !important;
2592 border-left: 1px solid @grey6 !important;
2592 border-right: 1px solid @grey6 !important;
2593 border-right: 1px solid @grey6 !important;
2593 }
2594 }
2594 }
2595 }
2595
2596
2596 table.rctable td.td-search-results div {
2597 table.rctable td.td-search-results div {
2597 max-width: 100%;
2598 max-width: 100%;
2598 }
2599 }
2599
2600
2600 #tip-box, .tip-box{
2601 #tip-box, .tip-box{
2601 padding: @menupadding/2;
2602 padding: @menupadding/2;
2602 display: block;
2603 display: block;
2603 border: @border-thickness solid @border-highlight-color;
2604 border: @border-thickness solid @border-highlight-color;
2604 .border-radius(@border-radius);
2605 .border-radius(@border-radius);
2605 background-color: white;
2606 background-color: white;
2606 z-index: 99;
2607 z-index: 99;
2607 white-space: pre-wrap;
2608 white-space: pre-wrap;
2608 }
2609 }
2609
2610
2610 #linktt {
2611 #linktt {
2611 width: 79px;
2612 width: 79px;
2612 }
2613 }
2613
2614
2614 #help_kb .modal-content{
2615 #help_kb .modal-content{
2615 max-width: 750px;
2616 max-width: 750px;
2616 margin: 10% auto;
2617 margin: 10% auto;
2617
2618
2618 table{
2619 table{
2619 td,th{
2620 td,th{
2620 border-bottom: none;
2621 border-bottom: none;
2621 line-height: 2.5em;
2622 line-height: 2.5em;
2622 }
2623 }
2623 th{
2624 th{
2624 padding-bottom: @textmargin/2;
2625 padding-bottom: @textmargin/2;
2625 }
2626 }
2626 td.keys{
2627 td.keys{
2627 text-align: center;
2628 text-align: center;
2628 }
2629 }
2629 }
2630 }
2630
2631
2631 .block-left{
2632 .block-left{
2632 width: 45%;
2633 width: 45%;
2633 margin-right: 5%;
2634 margin-right: 5%;
2634 }
2635 }
2635 .modal-footer{
2636 .modal-footer{
2636 clear: both;
2637 clear: both;
2637 }
2638 }
2638 .key.tag{
2639 .key.tag{
2639 padding: 0.5em;
2640 padding: 0.5em;
2640 background-color: @rcblue;
2641 background-color: @rcblue;
2641 color: white;
2642 color: white;
2642 border-color: @rcblue;
2643 border-color: @rcblue;
2643 .box-shadow(none);
2644 .box-shadow(none);
2644 }
2645 }
2645 }
2646 }
2646
2647
2647
2648
2648
2649
2649 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2650 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2650
2651
2651 @import 'statistics-graph';
2652 @import 'statistics-graph';
2652 @import 'tables';
2653 @import 'tables';
2653 @import 'forms';
2654 @import 'forms';
2654 @import 'diff';
2655 @import 'diff';
2655 @import 'summary';
2656 @import 'summary';
2656 @import 'navigation';
2657 @import 'navigation';
2657
2658
2658 //--- SHOW/HIDE SECTIONS --//
2659 //--- SHOW/HIDE SECTIONS --//
2659
2660
2660 .btn-collapse {
2661 .btn-collapse {
2661 float: right;
2662 float: right;
2662 text-align: right;
2663 text-align: right;
2663 font-family: @text-light;
2664 font-family: @text-light;
2664 font-size: @basefontsize;
2665 font-size: @basefontsize;
2665 cursor: pointer;
2666 cursor: pointer;
2666 border: none;
2667 border: none;
2667 color: @rcblue;
2668 color: @rcblue;
2668 }
2669 }
2669
2670
2670 table.rctable,
2671 table.rctable,
2671 table.dataTable {
2672 table.dataTable {
2672 .btn-collapse {
2673 .btn-collapse {
2673 float: right;
2674 float: right;
2674 text-align: right;
2675 text-align: right;
2675 }
2676 }
2676 }
2677 }
2677
2678
2678 table.rctable {
2679 table.rctable {
2679 &.permissions {
2680 &.permissions {
2680
2681
2681 th.td-owner {
2682 th.td-owner {
2682 padding: 0;
2683 padding: 0;
2683 }
2684 }
2684
2685
2685 th {
2686 th {
2686 font-weight: normal;
2687 font-weight: normal;
2687 padding: 0 5px;
2688 padding: 0 5px;
2688 }
2689 }
2689
2690
2690 }
2691 }
2691 }
2692 }
2692
2693
2693
2694
2694 // TODO: johbo: Fix for IE10, this avoids that we see a border
2695 // TODO: johbo: Fix for IE10, this avoids that we see a border
2695 // and padding around checkboxes and radio boxes. Move to the right place,
2696 // and padding around checkboxes and radio boxes. Move to the right place,
2696 // or better: Remove this once we did the form refactoring.
2697 // or better: Remove this once we did the form refactoring.
2697 input[type=checkbox],
2698 input[type=checkbox],
2698 input[type=radio] {
2699 input[type=radio] {
2699 padding: 0;
2700 padding: 0;
2700 border: none;
2701 border: none;
2701 }
2702 }
2702
2703
2703 .toggle-ajax-spinner{
2704 .toggle-ajax-spinner{
2704 height: 16px;
2705 height: 16px;
2705 width: 16px;
2706 width: 16px;
2706 }
2707 }
2707
2708
2708
2709
2709 .markup-form .clearfix {
2710 .markup-form .clearfix {
2710 .border-radius(@border-radius);
2711 .border-radius(@border-radius);
2711 margin: 0px;
2712 margin: 0px;
2712 }
2713 }
2713
2714
2714 .markup-form-area {
2715 .markup-form-area {
2715 padding: 8px 12px;
2716 padding: 8px 12px;
2716 border: 1px solid @grey4;
2717 border: 1px solid @grey4;
2717 .border-radius(@border-radius);
2718 .border-radius(@border-radius);
2718 }
2719 }
2719
2720
2720 .markup-form-area-header .nav-links {
2721 .markup-form-area-header .nav-links {
2721 display: flex;
2722 display: flex;
2722 flex-flow: row wrap;
2723 flex-flow: row wrap;
2723 -webkit-flex-flow: row wrap;
2724 -webkit-flex-flow: row wrap;
2724 width: 100%;
2725 width: 100%;
2725 }
2726 }
2726
2727
2727 .markup-form-area-footer {
2728 .markup-form-area-footer {
2728 display: flex;
2729 display: flex;
2729 }
2730 }
2730
2731
2731 .markup-form-area-footer .toolbar {
2732 .markup-form-area-footer .toolbar {
2732
2733
2733 }
2734 }
2734
2735
2735 // markup Form
2736 // markup Form
2736 div.markup-form {
2737 div.markup-form {
2737 margin-top: 20px;
2738 margin-top: 20px;
2738 }
2739 }
2739
2740
2740 .markup-form strong {
2741 .markup-form strong {
2741 display: block;
2742 display: block;
2742 margin-bottom: 15px;
2743 margin-bottom: 15px;
2743 }
2744 }
2744
2745
2745 .markup-form textarea {
2746 .markup-form textarea {
2746 width: 100%;
2747 width: 100%;
2747 height: 100px;
2748 height: 100px;
2748 font-family: @text-monospace;
2749 font-family: @text-monospace;
2749 }
2750 }
2750
2751
2751 form.markup-form {
2752 form.markup-form {
2752 margin-top: 10px;
2753 margin-top: 10px;
2753 margin-left: 10px;
2754 margin-left: 10px;
2754 }
2755 }
2755
2756
2756 .markup-form .comment-block-ta,
2757 .markup-form .comment-block-ta,
2757 .markup-form .preview-box {
2758 .markup-form .preview-box {
2758 .border-radius(@border-radius);
2759 .border-radius(@border-radius);
2759 .box-sizing(border-box);
2760 .box-sizing(border-box);
2760 background-color: white;
2761 background-color: white;
2761 }
2762 }
2762
2763
2763 .markup-form .preview-box.unloaded {
2764 .markup-form .preview-box.unloaded {
2764 height: 50px;
2765 height: 50px;
2765 text-align: center;
2766 text-align: center;
2766 padding: 20px;
2767 padding: 20px;
2767 background-color: white;
2768 background-color: white;
2768 }
2769 }
2769
2770
2770
2771
2771 .dropzone-wrapper {
2772 .dropzone-wrapper {
2772 border: 1px solid @grey5;
2773 border: 1px solid @grey5;
2773 padding: 20px;
2774 padding: 20px;
2774 }
2775 }
2775
2776
2776 .dropzone,
2777 .dropzone,
2777 .dropzone-pure {
2778 .dropzone-pure {
2778 border: 2px dashed @grey5;
2779 border: 2px dashed @grey5;
2779 border-radius: 5px;
2780 border-radius: 5px;
2780 background: white;
2781 background: white;
2781 min-height: 200px;
2782 min-height: 200px;
2782 padding: 54px;
2783 padding: 54px;
2783
2784
2784 .dz-message {
2785 .dz-message {
2785 font-weight: 700;
2786 font-weight: 700;
2786 text-align: center;
2787 text-align: center;
2787 margin: 2em 0;
2788 margin: 2em 0;
2788 }
2789 }
2789
2790
2790 }
2791 }
2791
2792
2792 .dz-preview {
2793 .dz-preview {
2793 margin: 10px 0 !important;
2794 margin: 10px 0 !important;
2794 position: relative;
2795 position: relative;
2795 vertical-align: top;
2796 vertical-align: top;
2796 padding: 10px;
2797 padding: 10px;
2797 border-bottom: 1px solid @grey5;
2798 border-bottom: 1px solid @grey5;
2798 }
2799 }
2799
2800
2800 .dz-filename {
2801 .dz-filename {
2801 font-weight: 700;
2802 font-weight: 700;
2802 float:left;
2803 float: left;
2803 }
2804 }
2804
2805
2805 .dz-sending {
2806 .dz-sending {
2806 float: right;
2807 float: right;
2807 }
2808 }
2808
2809
2809 .dz-response {
2810 .dz-response {
2810 clear:both
2811 clear: both
2811 }
2812 }
2812
2813
2813 .dz-filename-size {
2814 .dz-filename-size {
2814 float:right
2815 float: right
2815 }
2816 }
2816
2817
2817 .dz-error-message {
2818 .dz-error-message {
2818 color: @alert2;
2819 color: @alert2;
2819 padding-top: 10px;
2820 padding-top: 10px;
2820 clear: both;
2821 clear: both;
2822 }
2823
2824
2825 .user-hovercard {
2826 padding: 5px;
2827 }
2828
2829 .user-hovercard-icon {
2830 display: inline;
2831 padding: 0;
2832 box-sizing: content-box;
2833 border-radius: 50%;
2834 float: left;
2835 }
2836
2837 .user-hovercard-name {
2838 float: right;
2839 vertical-align: top;
2840 padding-left: 10px;
2841 min-width: 150px;
2842 }
2843
2844 .user-hovercard-bio {
2845 clear: both;
2846 padding-top: 10px;
2847 }
2848
2849 .user-hovercard-header {
2850 clear: both;
2851 min-height: 10px;
2852 }
2853
2854 .user-hovercard-footer {
2855 clear: both;
2856 min-height: 10px;
2821 }
2857 }
2858
2859 .user-group-hovercard {
2860 padding: 5px;
2861 }
2862
2863 .user-group-hovercard-icon {
2864 display: inline;
2865 padding: 0;
2866 box-sizing: content-box;
2867 border-radius: 50%;
2868 float: left;
2869 }
2870
2871 .user-group-hovercard-name {
2872 float: left;
2873 vertical-align: top;
2874 padding-left: 10px;
2875 min-width: 150px;
2876 }
2877
2878 .user-group-hovercard-icon i {
2879 border: 1px solid @grey4;
2880 border-radius: 4px;
2881 }
2882
2883 .user-group-hovercard-bio {
2884 clear: both;
2885 padding-top: 10px;
2886 line-height: 1.0em;
2887 }
2888
2889 .user-group-hovercard-header {
2890 clear: both;
2891 min-height: 10px;
2892 }
2893
2894 .user-group-hovercard-footer {
2895 clear: both;
2896 min-height: 10px;
2897 }
@@ -1,289 +1,290 b''
1 @font-face {
1 @font-face {
2 font-family: 'rcicons';
2 font-family: 'rcicons';
3
3
4 src: url('../fonts/RCIcons/rcicons.eot?44705679');
4 src: url('../fonts/RCIcons/rcicons.eot?44705679');
5 src: url('../fonts/RCIcons/rcicons.eot?44705679#iefix') format('embedded-opentype'),
5 src: url('../fonts/RCIcons/rcicons.eot?44705679#iefix') format('embedded-opentype'),
6 url('../fonts/RCIcons/rcicons.woff2?44705679') format('woff2'),
6 url('../fonts/RCIcons/rcicons.woff2?44705679') format('woff2'),
7 url('../fonts/RCIcons/rcicons.woff?44705679') format('woff'),
7 url('../fonts/RCIcons/rcicons.woff?44705679') format('woff'),
8 url('../fonts/RCIcons/rcicons.ttf?44705679') format('truetype'),
8 url('../fonts/RCIcons/rcicons.ttf?44705679') format('truetype'),
9 url('../fonts/RCIcons/rcicons.svg?44705679#rcicons') format('svg');
9 url('../fonts/RCIcons/rcicons.svg?44705679#rcicons') format('svg');
10
10
11 font-weight: normal;
11 font-weight: normal;
12 font-style: normal;
12 font-style: normal;
13 }
13 }
14 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
14 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
15 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
15 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
16 /*
16 /*
17 @media screen and (-webkit-min-device-pixel-ratio:0) {
17 @media screen and (-webkit-min-device-pixel-ratio:0) {
18 @font-face {
18 @font-face {
19 font-family: 'rcicons';
19 font-family: 'rcicons';
20 src: url('../fonts/RCIcons/rcicons.svg?74666722#rcicons') format('svg');
20 src: url('../fonts/RCIcons/rcicons.svg?74666722#rcicons') format('svg');
21 }
21 }
22 }
22 }
23 */
23 */
24
24
25 [class^="icon-"]:before, [class*=" icon-"]:before {
25 [class^="icon-"]:before, [class*=" icon-"]:before {
26 font-family: "rcicons";
26 font-family: "rcicons";
27 font-style: normal;
27 font-style: normal;
28 font-weight: normal;
28 font-weight: normal;
29 speak: none;
29 speak: none;
30
30
31 display: inline-block;
31 display: inline-block;
32 text-decoration: inherit;
32 text-decoration: inherit;
33 width: 1em;
33 width: 1em;
34 margin-right: .2em;
34 margin-right: .2em;
35 text-align: center;
35 text-align: center;
36 /* opacity: .8; */
36 /* opacity: .8; */
37
37
38 /* For safety - reset parent styles, that can break glyph codes*/
38 /* For safety - reset parent styles, that can break glyph codes*/
39 font-variant: normal;
39 font-variant: normal;
40 text-transform: none;
40 text-transform: none;
41
41
42 /* fix buttons height, for twitter bootstrap */
42 /* fix buttons height, for twitter bootstrap */
43 line-height: 1em;
43 line-height: 1em;
44
44
45 /* Animation center compensation - margins should be symmetric */
45 /* Animation center compensation - margins should be symmetric */
46 /* remove if not needed */
46 /* remove if not needed */
47 margin-left: .2em;
47 margin-left: .2em;
48
48
49 /* you can be more comfortable with increased icons size */
49 /* you can be more comfortable with increased icons size */
50 /* font-size: 120%; */
50 /* font-size: 120%; */
51
51
52 /* Font smoothing. That was taken from TWBS */
52 /* Font smoothing. That was taken from TWBS */
53 -webkit-font-smoothing: antialiased;
53 -webkit-font-smoothing: antialiased;
54 -moz-osx-font-smoothing: grayscale;
54 -moz-osx-font-smoothing: grayscale;
55
55
56 /* Uncomment for 3D effect */
56 /* Uncomment for 3D effect */
57 /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
57 /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
58 }
58 }
59
59
60 .animate-spin {
60 .animate-spin {
61 -moz-animation: spin 2s infinite linear;
61 -moz-animation: spin 2s infinite linear;
62 -o-animation: spin 2s infinite linear;
62 -o-animation: spin 2s infinite linear;
63 -webkit-animation: spin 2s infinite linear;
63 -webkit-animation: spin 2s infinite linear;
64 animation: spin 2s infinite linear;
64 animation: spin 2s infinite linear;
65 display: inline-block;
65 display: inline-block;
66 }
66 }
67 @-moz-keyframes spin {
67 @-moz-keyframes spin {
68 0% {
68 0% {
69 -moz-transform: rotate(0deg);
69 -moz-transform: rotate(0deg);
70 -o-transform: rotate(0deg);
70 -o-transform: rotate(0deg);
71 -webkit-transform: rotate(0deg);
71 -webkit-transform: rotate(0deg);
72 transform: rotate(0deg);
72 transform: rotate(0deg);
73 }
73 }
74
74
75 100% {
75 100% {
76 -moz-transform: rotate(359deg);
76 -moz-transform: rotate(359deg);
77 -o-transform: rotate(359deg);
77 -o-transform: rotate(359deg);
78 -webkit-transform: rotate(359deg);
78 -webkit-transform: rotate(359deg);
79 transform: rotate(359deg);
79 transform: rotate(359deg);
80 }
80 }
81 }
81 }
82 @-webkit-keyframes spin {
82 @-webkit-keyframes spin {
83 0% {
83 0% {
84 -moz-transform: rotate(0deg);
84 -moz-transform: rotate(0deg);
85 -o-transform: rotate(0deg);
85 -o-transform: rotate(0deg);
86 -webkit-transform: rotate(0deg);
86 -webkit-transform: rotate(0deg);
87 transform: rotate(0deg);
87 transform: rotate(0deg);
88 }
88 }
89
89
90 100% {
90 100% {
91 -moz-transform: rotate(359deg);
91 -moz-transform: rotate(359deg);
92 -o-transform: rotate(359deg);
92 -o-transform: rotate(359deg);
93 -webkit-transform: rotate(359deg);
93 -webkit-transform: rotate(359deg);
94 transform: rotate(359deg);
94 transform: rotate(359deg);
95 }
95 }
96 }
96 }
97 @-o-keyframes spin {
97 @-o-keyframes spin {
98 0% {
98 0% {
99 -moz-transform: rotate(0deg);
99 -moz-transform: rotate(0deg);
100 -o-transform: rotate(0deg);
100 -o-transform: rotate(0deg);
101 -webkit-transform: rotate(0deg);
101 -webkit-transform: rotate(0deg);
102 transform: rotate(0deg);
102 transform: rotate(0deg);
103 }
103 }
104
104
105 100% {
105 100% {
106 -moz-transform: rotate(359deg);
106 -moz-transform: rotate(359deg);
107 -o-transform: rotate(359deg);
107 -o-transform: rotate(359deg);
108 -webkit-transform: rotate(359deg);
108 -webkit-transform: rotate(359deg);
109 transform: rotate(359deg);
109 transform: rotate(359deg);
110 }
110 }
111 }
111 }
112 @-ms-keyframes spin {
112 @-ms-keyframes spin {
113 0% {
113 0% {
114 -moz-transform: rotate(0deg);
114 -moz-transform: rotate(0deg);
115 -o-transform: rotate(0deg);
115 -o-transform: rotate(0deg);
116 -webkit-transform: rotate(0deg);
116 -webkit-transform: rotate(0deg);
117 transform: rotate(0deg);
117 transform: rotate(0deg);
118 }
118 }
119
119
120 100% {
120 100% {
121 -moz-transform: rotate(359deg);
121 -moz-transform: rotate(359deg);
122 -o-transform: rotate(359deg);
122 -o-transform: rotate(359deg);
123 -webkit-transform: rotate(359deg);
123 -webkit-transform: rotate(359deg);
124 transform: rotate(359deg);
124 transform: rotate(359deg);
125 }
125 }
126 }
126 }
127 @keyframes spin {
127 @keyframes spin {
128 0% {
128 0% {
129 -moz-transform: rotate(0deg);
129 -moz-transform: rotate(0deg);
130 -o-transform: rotate(0deg);
130 -o-transform: rotate(0deg);
131 -webkit-transform: rotate(0deg);
131 -webkit-transform: rotate(0deg);
132 transform: rotate(0deg);
132 transform: rotate(0deg);
133 }
133 }
134
134
135 100% {
135 100% {
136 -moz-transform: rotate(359deg);
136 -moz-transform: rotate(359deg);
137 -o-transform: rotate(359deg);
137 -o-transform: rotate(359deg);
138 -webkit-transform: rotate(359deg);
138 -webkit-transform: rotate(359deg);
139 transform: rotate(359deg);
139 transform: rotate(359deg);
140 }
140 }
141 }
141 }
142
142
143
143
144
144
145 .icon-no-margin::before {
145 .icon-no-margin::before {
146 margin: 0;
146 margin: 0;
147
147
148 }
148 }
149 // -- ICON CLASSES -- //
149 // -- ICON CLASSES -- //
150 // sorter = lambda s: '\n'.join(sorted(s.splitlines()))
150 // sorter = lambda s: '\n'.join(sorted(s.splitlines()))
151
151
152 .icon-delete:before { content: '\e800'; } /* '' */
152 .icon-delete:before { content: '\e800'; } /* '' */
153 .icon-ok:before { content: '\e801'; } /* '' */
153 .icon-ok:before { content: '\e801'; } /* '' */
154 .icon-comment:before { content: '\e802'; } /* '' */
154 .icon-comment:before { content: '\e802'; } /* '' */
155 .icon-bookmark:before { content: '\e803'; } /* '' */
155 .icon-bookmark:before { content: '\e803'; } /* '' */
156 .icon-branch:before { content: '\e804'; } /* '' */
156 .icon-branch:before { content: '\e804'; } /* '' */
157 .icon-tag:before { content: '\e805'; } /* '' */
157 .icon-tag:before { content: '\e805'; } /* '' */
158 .icon-lock:before { content: '\e806'; } /* '' */
158 .icon-lock:before { content: '\e806'; } /* '' */
159 .icon-unlock:before { content: '\e807'; } /* '' */
159 .icon-unlock:before { content: '\e807'; } /* '' */
160 .icon-feed:before { content: '\e808'; } /* '' */
160 .icon-feed:before { content: '\e808'; } /* '' */
161 .icon-left:before { content: '\e809'; } /* '' */
161 .icon-left:before { content: '\e809'; } /* '' */
162 .icon-right:before { content: '\e80a'; } /* '' */
162 .icon-right:before { content: '\e80a'; } /* '' */
163 .icon-down:before { content: '\e80b'; } /* '' */
163 .icon-down:before { content: '\e80b'; } /* '' */
164 .icon-folder:before { content: '\e80c'; } /* '' */
164 .icon-folder:before { content: '\e80c'; } /* '' */
165 .icon-folder-open:before { content: '\e80d'; } /* '' */
165 .icon-folder-open:before { content: '\e80d'; } /* '' */
166 .icon-trash-empty:before { content: '\e80e'; } /* '' */
166 .icon-trash-empty:before { content: '\e80e'; } /* '' */
167 .icon-group:before { content: '\e80f'; } /* '' */
167 .icon-group:before { content: '\e80f'; } /* '' */
168 .icon-remove:before { content: '\e810'; } /* '' */
168 .icon-remove:before { content: '\e810'; } /* '' */
169 .icon-fork:before { content: '\e811'; } /* '' */
169 .icon-fork:before { content: '\e811'; } /* '' */
170 .icon-more:before { content: '\e812'; } /* '' */
170 .icon-more:before { content: '\e812'; } /* '' */
171 .icon-search:before { content: '\e813'; } /* '' */
171 .icon-search:before { content: '\e813'; } /* '' */
172 .icon-scissors:before { content: '\e814'; } /* '' */
172 .icon-scissors:before { content: '\e814'; } /* '' */
173 .icon-download:before { content: '\e815'; } /* '' */
173 .icon-download:before { content: '\e815'; } /* '' */
174 .icon-doc:before { content: '\e816'; } /* '' */
174 .icon-doc:before { content: '\e816'; } /* '' */
175 .icon-cog:before { content: '\e817'; } /* '' */
175 .icon-cog:before { content: '\e817'; } /* '' */
176 .icon-cog-alt:before { content: '\e818'; } /* '' */
176 .icon-cog-alt:before { content: '\e818'; } /* '' */
177 .icon-eye:before { content: '\e819'; } /* '' */
177 .icon-eye:before { content: '\e819'; } /* '' */
178 .icon-eye-off:before { content: '\e81a'; } /* '' */
178 .icon-eye-off:before { content: '\e81a'; } /* '' */
179 .icon-cancel-circled2:before { content: '\e81b'; } /* '' */
179 .icon-cancel-circled2:before { content: '\e81b'; } /* '' */
180 .icon-cancel-circled:before { content: '\e81c'; } /* '' */
180 .icon-cancel-circled:before { content: '\e81c'; } /* '' */
181 .icon-plus:before { content: '\e81d'; } /* '' */
181 .icon-plus:before { content: '\e81d'; } /* '' */
182 .icon-plus-circled:before { content: '\e81e'; } /* '' */
182 .icon-plus-circled:before { content: '\e81e'; } /* '' */
183 .icon-minus-circled:before { content: '\e81f'; } /* '' */
183 .icon-minus-circled:before { content: '\e81f'; } /* '' */
184 .icon-minus:before { content: '\e820'; } /* '' */
184 .icon-minus:before { content: '\e820'; } /* '' */
185 .icon-info-circled:before { content: '\e821'; } /* '' */
185 .icon-info-circled:before { content: '\e821'; } /* '' */
186 .icon-upload:before { content: '\e822'; } /* '' */
186 .icon-upload:before { content: '\e822'; } /* '' */
187 .icon-home:before { content: '\e823'; } /* '' */
187 .icon-home:before { content: '\e823'; } /* '' */
188 .icon-flag-filled:before { content: '\e824'; } /* '' */
188 .icon-flag-filled:before { content: '\e824'; } /* '' */
189 .icon-git:before { content: '\e82a'; } /* '' */
189 .icon-git:before { content: '\e82a'; } /* '' */
190 .icon-hg:before { content: '\e82d'; } /* '' */
190 .icon-hg:before { content: '\e82d'; } /* '' */
191 .icon-svn:before { content: '\e82e'; } /* '' */
191 .icon-svn:before { content: '\e82e'; } /* '' */
192 .icon-comment-add:before { content: '\e82f'; } /* '' */
192 .icon-comment-add:before { content: '\e82f'; } /* '' */
193 .icon-comment-toggle:before { content: '\e830'; } /* '' */
193 .icon-comment-toggle:before { content: '\e830'; } /* '' */
194 .icon-rhodecode:before { content: '\e831'; } /* '' */
194 .icon-rhodecode:before { content: '\e831'; } /* '' */
195 .icon-up:before { content: '\e832'; } /* '' */
195 .icon-up:before { content: '\e832'; } /* '' */
196 .icon-merge:before { content: '\e833'; } /* '' */
196 .icon-merge:before { content: '\e833'; } /* '' */
197 .icon-spin-alt:before { content: '\e834'; } /* '' */
197 .icon-spin-alt:before { content: '\e834'; } /* '' */
198 .icon-spin:before { content: '\e838'; } /* '' */
198 .icon-spin:before { content: '\e838'; } /* '' */
199 .icon-docs:before { content: '\f0c5'; } /* '' */
199 .icon-docs:before { content: '\f0c5'; } /* '' */
200 .icon-menu:before { content: '\f0c9'; } /* '' */
200 .icon-menu:before { content: '\f0c9'; } /* '' */
201 .icon-sort:before { content: '\f0dc'; } /* '' */
201 .icon-sort:before { content: '\f0dc'; } /* '' */
202 .icon-paste:before { content: '\f0ea'; } /* '' */
202 .icon-paste:before { content: '\f0ea'; } /* '' */
203 .icon-doc-text:before { content: '\f0f6'; } /* '' */
203 .icon-doc-text:before { content: '\f0f6'; } /* '' */
204 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
204 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
205 .icon-angle-left:before { content: '\f104'; } /* '' */
205 .icon-angle-left:before { content: '\f104'; } /* '' */
206 .icon-angle-right:before { content: '\f105'; } /* '' */
206 .icon-angle-right:before { content: '\f105'; } /* '' */
207 .icon-angle-up:before { content: '\f106'; } /* '' */
207 .icon-angle-up:before { content: '\f106'; } /* '' */
208 .icon-angle-down:before { content: '\f107'; } /* '' */
208 .icon-angle-down:before { content: '\f107'; } /* '' */
209 .icon-circle-empty:before { content: '\f10c'; } /* '' */
209 .icon-circle-empty:before { content: '\f10c'; } /* '' */
210 .icon-circle:before { content: '\f111'; } /* '' */
210 .icon-circle:before { content: '\f111'; } /* '' */
211 .icon-folder-empty:before { content: '\f114'; } /* '' */
211 .icon-folder-empty:before { content: '\f114'; } /* '' */
212 .icon-folder-open-empty:before { content: '\f115'; } /* '' */
212 .icon-folder-open-empty:before { content: '\f115'; } /* '' */
213 .icon-code:before { content: '\f121'; } /* '' */
213 .icon-code:before { content: '\f121'; } /* '' */
214 .icon-info:before { content: '\f129'; } /* '' */
214 .icon-info:before { content: '\f129'; } /* '' */
215 .icon-minus-squared:before { content: '\f146'; } /* '' */
215 .icon-minus-squared:before { content: '\f146'; } /* '' */
216 .icon-minus-squared-alt:before { content: '\f147'; } /* '' */
216 .icon-minus-squared-alt:before { content: '\f147'; } /* '' */
217 .icon-doc-inv:before { content: '\f15b'; } /* '' */
217 .icon-doc-inv:before { content: '\f15b'; } /* '' */
218 .icon-doc-text-inv:before { content: '\f15c'; } /* '' */
218 .icon-doc-text-inv:before { content: '\f15c'; } /* '' */
219 .icon-plus-squared-alt:before { content: '\f196'; } /* '' */
219 .icon-plus-squared-alt:before { content: '\f196'; } /* '' */
220 .icon-file-code:before { content: '\f1c9'; } /* '' */
220 .icon-file-code:before { content: '\f1c9'; } /* '' */
221 .icon-history:before { content: '\f1da'; } /* '' */
221 .icon-history:before { content: '\f1da'; } /* '' */
222 .icon-circle-thin:before { content: '\f1db'; } /* '' */
222 .icon-circle-thin:before { content: '\f1db'; } /* '' */
223 .icon-sliders:before { content: '\f1de'; } /* '' */
223 .icon-sliders:before { content: '\f1de'; } /* '' */
224 .icon-trash:before { content: '\f1f8'; } /* '' */
224 .icon-trash:before { content: '\f1f8'; } /* '' */
225
225
226
226
227 // MERGED ICONS BASED ON CURRENT ONES
227 // MERGED ICONS BASED ON CURRENT ONES
228 .icon-repo-group:before { &:extend(.icon-folder-open:before); }
228 .icon-repo-group:before { &:extend(.icon-folder-open:before); }
229 .icon-repo-private:before { &:extend(.icon-lock:before); }
229 .icon-repo-private:before { &:extend(.icon-lock:before); }
230 .icon-repo-lock:before { &:extend(.icon-lock:before); }
230 .icon-repo-lock:before { &:extend(.icon-lock:before); }
231 .icon-unlock-alt:before { &:extend(.icon-unlock:before); }
231 .icon-unlock-alt:before { &:extend(.icon-unlock:before); }
232 .icon-repo-unlock:before { &:extend(.icon-unlock:before); }
232 .icon-repo-unlock:before { &:extend(.icon-unlock:before); }
233 .icon-repo-public:before { &:extend(.icon-unlock:before); }
233 .icon-repo-public:before { &:extend(.icon-unlock:before); }
234 .icon-rss-sign:before { &:extend(.icon-feed:before); }
234 .icon-rss-sign:before { &:extend(.icon-feed:before); }
235 .icon-code-fork:before { &:extend(.icon-fork:before); }
235 .icon-code-fork:before { &:extend(.icon-fork:before); }
236 .icon-arrow_up:before { &:extend(.icon-up:before); }
236 .icon-arrow_up:before { &:extend(.icon-up:before); }
237 .icon-file:before { &:extend(.icon-file-code:before); }
237 .icon-file:before { &:extend(.icon-file-code:before); }
238 .icon-file-text:before { &:extend(.icon-file-code:before); }
238 .icon-file-text:before { &:extend(.icon-file-code:before); }
239 .icon-directory:before { &:extend(.icon-folder:before); }
239 .icon-directory:before { &:extend(.icon-folder:before); }
240 .icon-more-linked:before { &:extend(.icon-more:before); }
240 .icon-more-linked:before { &:extend(.icon-more:before); }
241 .icon-clipboard:before { &:extend(.icon-docs:before); }
241 .icon-clipboard:before { &:extend(.icon-docs:before); }
242 .icon-copy:before { &:extend(.icon-docs:before); }
242 .icon-copy:before { &:extend(.icon-docs:before); }
243 .icon-true:before { &:extend(.icon-ok:before); }
243 .icon-true:before { &:extend(.icon-ok:before); }
244 .icon-false:before { &:extend(.icon-delete:before); }
244 .icon-false:before { &:extend(.icon-delete:before); }
245 .icon-expand-linked:before { &:extend(.icon-down:before); }
245 .icon-expand-linked:before { &:extend(.icon-down:before); }
246 .icon-pr-merge-fail:before { &:extend(.icon-delete:before); }
246 .icon-pr-merge-fail:before { &:extend(.icon-delete:before); }
247 .icon-wide-mode:before { &:extend(.icon-sort:before); }
247 .icon-wide-mode:before { &:extend(.icon-sort:before); }
248 .icon-flag-filled-red:before { &:extend(.icon-flag-filled:before); }
248 .icon-flag-filled-red:before { &:extend(.icon-flag-filled:before); }
249 .icon-user-group-alt:before { &:extend(.icon-group:before); }
249
250
250 // TRANSFORM
251 // TRANSFORM
251 .icon-merge:before {transform: rotate(180deg);}
252 .icon-merge:before {transform: rotate(180deg);}
252 .icon-wide-mode:before {transform: rotate(90deg);}
253 .icon-wide-mode:before {transform: rotate(90deg);}
253
254
254 // -- END ICON CLASSES -- //
255 // -- END ICON CLASSES -- //
255
256
256
257
257 //--- ICONS STYLING ------------------//
258 //--- ICONS STYLING ------------------//
258
259
259 .icon-git { color: @color4 !important; }
260 .icon-git { color: @color4 !important; }
260 .icon-hg { color: @color8 !important; }
261 .icon-hg { color: @color8 !important; }
261 .icon-svn { color: @color1 !important; }
262 .icon-svn { color: @color1 !important; }
262 .icon-git-inv { color: @color4 !important; }
263 .icon-git-inv { color: @color4 !important; }
263 .icon-hg-inv { color: @color8 !important; }
264 .icon-hg-inv { color: @color8 !important; }
264 .icon-svn-inv { color: @color1 !important; }
265 .icon-svn-inv { color: @color1 !important; }
265 .icon-repo-lock { color: #FF0000; }
266 .icon-repo-lock { color: #FF0000; }
266 .icon-repo-unlock { color: #FF0000; }
267 .icon-repo-unlock { color: #FF0000; }
267 .icon-false { color: @grey5 }
268 .icon-false { color: @grey5 }
268 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
269 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
269 .icon-more-linked { cursor: pointer; color: @grey3 }
270 .icon-more-linked { cursor: pointer; color: @grey3 }
270 .icon-flag-filled-red { color: @color5 !important; }
271 .icon-flag-filled-red { color: @color5 !important; }
271
272
272 .repo-switcher-dropdown .select2-result-label {
273 .repo-switcher-dropdown .select2-result-label {
273 .icon-git:before {
274 .icon-git:before {
274 &:extend(.icon-git-transparent:before);
275 &:extend(.icon-git-transparent:before);
275 }
276 }
276 .icon-hg:before {
277 .icon-hg:before {
277 &:extend(.icon-hg-transparent:before);
278 &:extend(.icon-hg-transparent:before);
278 color: @alert4;
279 color: @alert4;
279 }
280 }
280 .icon-svn:before {
281 .icon-svn:before {
281 &:extend(.icon-svn-transparent:before);
282 &:extend(.icon-svn-transparent:before);
282 }
283 }
283 }
284 }
284
285
285 .icon-user-group:before {
286 .icon-user-group:before {
286 &:extend(.icon-group:before);
287 &:extend(.icon-group:before);
287 margin: 0;
288 margin: 0;
288 font-size: 16px;
289 font-size: 16px;
289 }
290 }
@@ -1,382 +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']);
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']);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
36 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
37 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
38 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
39 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
40 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('admin_home', '/_admin', []);
41 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
42 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 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']);
41 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']);
42 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']);
43 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']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
47 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 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', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
49 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
50 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
51 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
49 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', []);
50 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']);
51 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']);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
55 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
56 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
57 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
55 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', []);
56 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', []);
57 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', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
61 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
62 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
63 pyroutes.register('admin_settings', '/_admin/settings', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
64 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
65 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
66 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
67 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
68 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
66 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', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
70 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
71 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
72 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
73 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
74 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
75 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
76 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
77 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
78 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
79 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
80 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
81 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
82 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
83 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
84 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
85 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
83 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
86 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
84 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
87 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
85 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
88 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
86 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
89 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
87 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
90 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
88 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
91 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
89 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
92 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
90 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
93 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
91 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', []);
92 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
95 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
93 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', []);
94 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', []);
95 pyroutes.register('users', '/_admin/users', []);
98 pyroutes.register('users', '/_admin/users', []);
96 pyroutes.register('users_data', '/_admin/users_data', []);
99 pyroutes.register('users_data', '/_admin/users_data', []);
97 pyroutes.register('users_create', '/_admin/users/create', []);
100 pyroutes.register('users_create', '/_admin/users/create', []);
98 pyroutes.register('users_new', '/_admin/users/new', []);
101 pyroutes.register('users_new', '/_admin/users/new', []);
99 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']);
100 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']);
101 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']);
102 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']);
103 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']);
104 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']);
105 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']);
106 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']);
107 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']);
108 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']);
109 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']);
110 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']);
111 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']);
112 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']);
113 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']);
114 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']);
115 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']);
116 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']);
117 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']);
118 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']);
119 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']);
120 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']);
121 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']);
122 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']);
123 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']);
124 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']);
125 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']);
126 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']);
127 pyroutes.register('user_groups', '/_admin/user_groups', []);
130 pyroutes.register('user_groups', '/_admin/user_groups', []);
128 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
131 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
129 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
132 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
130 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
133 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
131 pyroutes.register('repos', '/_admin/repos', []);
134 pyroutes.register('repos', '/_admin/repos', []);
132 pyroutes.register('repo_new', '/_admin/repos/new', []);
135 pyroutes.register('repo_new', '/_admin/repos/new', []);
133 pyroutes.register('repo_create', '/_admin/repos/create', []);
136 pyroutes.register('repo_create', '/_admin/repos/create', []);
134 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
137 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
135 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
138 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
136 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
139 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
137 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
140 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
138 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
141 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
139 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
142 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
140 pyroutes.register('channelstream_proxy', '/_channelstream', []);
143 pyroutes.register('channelstream_proxy', '/_channelstream', []);
141 pyroutes.register('upload_file', '/_file_store/upload', []);
144 pyroutes.register('upload_file', '/_file_store/upload', []);
142 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
145 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
143 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']);
144 pyroutes.register('logout', '/_admin/logout', []);
147 pyroutes.register('logout', '/_admin/logout', []);
145 pyroutes.register('reset_password', '/_admin/password_reset', []);
148 pyroutes.register('reset_password', '/_admin/password_reset', []);
146 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
149 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
147 pyroutes.register('home', '/', []);
150 pyroutes.register('home', '/', []);
148 pyroutes.register('user_autocomplete_data', '/_users', []);
151 pyroutes.register('user_autocomplete_data', '/_users', []);
149 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
152 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
150 pyroutes.register('repo_list_data', '/_repos', []);
153 pyroutes.register('repo_list_data', '/_repos', []);
151 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
154 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
152 pyroutes.register('goto_switcher_data', '/_goto_data', []);
155 pyroutes.register('goto_switcher_data', '/_goto_data', []);
153 pyroutes.register('markup_preview', '/_markup_preview', []);
156 pyroutes.register('markup_preview', '/_markup_preview', []);
154 pyroutes.register('file_preview', '/_file_preview', []);
157 pyroutes.register('file_preview', '/_file_preview', []);
155 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
158 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
156 pyroutes.register('journal', '/_admin/journal', []);
159 pyroutes.register('journal', '/_admin/journal', []);
157 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
160 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
158 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
161 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
159 pyroutes.register('journal_public', '/_admin/public_journal', []);
162 pyroutes.register('journal_public', '/_admin/public_journal', []);
160 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
163 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
161 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
164 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
162 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
165 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
163 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
166 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
164 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
167 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
165 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
168 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
166 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']);
167 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
170 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
168 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']);
169 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']);
170 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']);
171 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']);
172 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']);
173 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']);
174 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']);
175 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']);
176 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']);
177 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']);
178 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']);
179 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']);
180 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']);
181 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']);
182 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']);
183 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']);
184 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']);
185 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']);
186 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']);
187 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']);
188 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']);
189 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']);
190 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']);
191 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']);
192 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']);
193 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']);
194 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']);
195 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']);
196 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']);
197 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']);
198 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']);
199 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']);
200 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']);
201 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']);
202 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']);
203 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']);
204 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']);
205 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']);
206 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']);
207 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']);
208 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
211 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
209 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']);
210 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']);
211 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']);
212 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
215 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
213 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']);
214 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
217 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
215 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']);
216 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
219 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
217 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
220 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
218 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
221 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
219 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
222 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
220 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']);
221 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']);
222 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']);
223 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']);
224 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']);
225 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']);
226 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']);
227 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']);
228 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']);
229 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']);
230 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']);
231 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']);
232 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']);
233 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']);
234 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']);
235 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
238 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
236 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']);
237 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']);
238 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']);
239 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']);
240 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']);
241 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']);
242 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']);
243 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']);
244 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']);
245 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']);
246 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']);
247 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']);
248 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']);
249 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']);
250 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']);
251 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']);
252 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']);
253 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']);
254 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']);
255 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']);
256 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']);
257 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']);
258 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']);
259 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']);
260 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']);
261 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']);
262 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']);
263 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']);
264 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']);
265 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']);
266 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']);
267 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']);
268 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']);
269 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']);
270 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']);
271 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']);
272 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']);
273 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
276 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
274 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
277 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
275 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']);
276 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']);
277 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']);
278 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']);
279 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']);
280 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']);
281 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']);
282 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']);
283 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']);
284 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']);
285 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']);
286 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']);
287 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']);
288 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']);
289 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']);
290 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']);
291 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']);
292 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']);
293 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']);
294 pyroutes.register('search', '/_admin/search', []);
297 pyroutes.register('search', '/_admin/search', []);
295 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
298 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
296 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
299 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
297 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']);
298 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
301 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
299 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']);
300 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
303 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
301 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
304 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
302 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
305 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
303 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
306 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
304 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
307 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
305 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', []);
306 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
309 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
307 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', []);
308 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', []);
309 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', []);
310 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', []);
311 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
314 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
312 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
315 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
313 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
316 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
314 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
317 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
315 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
318 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
316 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
319 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
317 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
320 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
318 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']);
319 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
322 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
320 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
323 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
321 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', []);
322 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
325 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
323 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', []);
324 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
327 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
325 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
328 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
326 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
329 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
327 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']);
328 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']);
329 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', []);
330 pyroutes.register('gists_show', '/_admin/gists', []);
333 pyroutes.register('gists_show', '/_admin/gists', []);
331 pyroutes.register('gists_new', '/_admin/gists/new', []);
334 pyroutes.register('gists_new', '/_admin/gists/new', []);
332 pyroutes.register('gists_create', '/_admin/gists/create', []);
335 pyroutes.register('gists_create', '/_admin/gists/create', []);
333 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
336 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
334 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']);
335 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']);
336 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']);
337 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']);
338 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']);
339 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']);
340 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']);
341 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
344 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
342 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']);
343 pyroutes.register('apiv2', '/_admin/api', []);
346 pyroutes.register('apiv2', '/_admin/api', []);
344 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
347 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
345 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
348 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
346 pyroutes.register('login', '/_admin/login', []);
349 pyroutes.register('login', '/_admin/login', []);
347 pyroutes.register('register', '/_admin/register', []);
350 pyroutes.register('register', '/_admin/register', []);
348 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']);
349 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']);
350 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']);
351 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']);
352 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']);
353 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']);
354 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
357 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
355 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
358 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
356 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
359 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
357 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
360 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
358 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']);
359 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']);
360 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']);
361 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']);
362 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
365 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
363 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']);
364 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
367 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
365 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
368 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
366 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
369 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
367 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', []);
368 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
371 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
369 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', []);
370 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
373 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
371 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']);
372 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']);
373 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']);
374 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']);
375 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']);
376 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']);
377 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
380 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
378 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']);
379 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']);
380 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']);
381 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']);
382 }
385 }
@@ -1,572 +1,656 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 () {
225 var delay = 50;
226 var animation = 'fade';
227 var theme = 'tooltipster-shadow';
228 var debug = false;
229
230 $('.tooltip').tooltipster({
231 debug: debug,
232 theme: theme,
233 animation: animation,
234 delay: delay,
235 contentCloning: true,
236 contentAsHTML: true,
237
238 functionBefore: function (instance, helper) {
239 var $origin = $(helper.origin);
240 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content());
241 instance.content(data);
242 }
243 });
244 var hovercardCache = {};
245
246 var loadHoverCard = function (url, callback) {
247 var id = url;
248
249 if (hovercardCache[id] !== undefined) {
250 callback(hovercardCache[id]);
251 return;
252 }
253
254 hovercardCache[id] = undefined;
255 $.get(url, function (data) {
256 hovercardCache[id] = data;
257 callback(hovercardCache[id]);
258 }).fail(function (data, textStatus, errorThrown) {
259 var msg = "Error while fetching hovercard.\nError code {0} ({1}).".format(data.status,data.statusText);
260 callback(msg);
261 });
262 };
263
264 $('.tooltip-hovercard').tooltipster({
265 debug: debug,
266 theme: theme,
267 animation: animation,
268 delay: delay,
269 interactive: true,
270 contentCloning: true,
271
272 trigger: 'custom',
273 triggerOpen: {
274 mouseenter: true,
275 },
276 triggerClose: {
277 mouseleave: true,
278 originClick: true,
279 touchleave: true
280 },
281 content: _gettext('Loading...'),
282 contentAsHTML: true,
283 updateAnimation: null,
284
285 functionBefore: function (instance, helper) {
286
287 var $origin = $(helper.origin);
288
289 // 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) {
291 var hovercardUrl = $origin.data('hovercardUrl');
292
293 if (hovercardUrl !== undefined && hovercardUrl !== "") {
294 loadHoverCard(hovercardUrl, function (data) {
295 instance.content(data);
296 })
297 } else {
298 var data = '<div style="white-space: pre-wrap">{0}</div>'.format($origin.data('hovercardAlt'))
299 instance.content(data);
300 }
301
302 // to remember that the data has been loaded
303 $origin.data('loaded', true);
304 }
305 }
306 })
307 };
224
308
225 // Formatting values in a Select2 dropdown of commit references
309 // Formatting values in a Select2 dropdown of commit references
226 var formatSelect2SelectionRefs = function(commit_ref){
310 var formatSelect2SelectionRefs = function(commit_ref){
227 var tmpl = '';
311 var tmpl = '';
228 if (!commit_ref.text || commit_ref.type === 'sha'){
312 if (!commit_ref.text || commit_ref.type === 'sha'){
229 return commit_ref.text;
313 return commit_ref.text;
230 }
314 }
231 if (commit_ref.type === 'branch'){
315 if (commit_ref.type === 'branch'){
232 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
316 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
233 } else if (commit_ref.type === 'tag'){
317 } else if (commit_ref.type === 'tag'){
234 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
318 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
235 } else if (commit_ref.type === 'book'){
319 } else if (commit_ref.type === 'book'){
236 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
320 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
237 }
321 }
238 return tmpl.concat(escapeHtml(commit_ref.text));
322 return tmpl.concat(escapeHtml(commit_ref.text));
239 };
323 };
240
324
241 // takes a given html element and scrolls it down offset pixels
325 // takes a given html element and scrolls it down offset pixels
242 function offsetScroll(element, offset) {
326 function offsetScroll(element, offset) {
243 setTimeout(function() {
327 setTimeout(function() {
244 var location = element.offset().top;
328 var location = element.offset().top;
245 // some browsers use body, some use html
329 // some browsers use body, some use html
246 $('html, body').animate({ scrollTop: (location - offset) });
330 $('html, body').animate({ scrollTop: (location - offset) });
247 }, 100);
331 }, 100);
248 }
332 }
249
333
250 // scroll an element `percent`% from the top of page in `time` ms
334 // scroll an element `percent`% from the top of page in `time` ms
251 function scrollToElement(element, percent, time) {
335 function scrollToElement(element, percent, time) {
252 percent = (percent === undefined ? 25 : percent);
336 percent = (percent === undefined ? 25 : percent);
253 time = (time === undefined ? 100 : time);
337 time = (time === undefined ? 100 : time);
254
338
255 var $element = $(element);
339 var $element = $(element);
256 if ($element.length == 0) {
340 if ($element.length == 0) {
257 throw('Cannot scroll to {0}'.format(element))
341 throw('Cannot scroll to {0}'.format(element))
258 }
342 }
259 var elOffset = $element.offset().top;
343 var elOffset = $element.offset().top;
260 var elHeight = $element.height();
344 var elHeight = $element.height();
261 var windowHeight = $(window).height();
345 var windowHeight = $(window).height();
262 var offset = elOffset;
346 var offset = elOffset;
263 if (elHeight < windowHeight) {
347 if (elHeight < windowHeight) {
264 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
348 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
265 }
349 }
266 setTimeout(function() {
350 setTimeout(function() {
267 $('html, body').animate({ scrollTop: offset});
351 $('html, body').animate({ scrollTop: offset});
268 }, time);
352 }, time);
269 }
353 }
270
354
271 /**
355 /**
272 * global hooks after DOM is loaded
356 * global hooks after DOM is loaded
273 */
357 */
274 $(document).ready(function() {
358 $(document).ready(function() {
275 firefoxAnchorFix();
359 firefoxAnchorFix();
276
360
277 $('.navigation a.menulink').on('click', function(e){
361 $('.navigation a.menulink').on('click', function(e){
278 var menuitem = $(this).parent('li');
362 var menuitem = $(this).parent('li');
279 if (menuitem.hasClass('open')) {
363 if (menuitem.hasClass('open')) {
280 menuitem.removeClass('open');
364 menuitem.removeClass('open');
281 } else {
365 } else {
282 menuitem.addClass('open');
366 menuitem.addClass('open');
283 $(document).on('click', function(event) {
367 $(document).on('click', function(event) {
284 if (!$(event.target).closest(menuitem).length) {
368 if (!$(event.target).closest(menuitem).length) {
285 menuitem.removeClass('open');
369 menuitem.removeClass('open');
286 }
370 }
287 });
371 });
288 }
372 }
289 });
373 });
290
374
291 $('body').on('click', '.cb-lineno a', function(event) {
375 $('body').on('click', '.cb-lineno a', function(event) {
292 function sortNumber(a,b) {
376 function sortNumber(a,b) {
293 return a - b;
377 return a - b;
294 }
378 }
295
379
296 var lineNo = $(this).data('lineNo');
380 var lineNo = $(this).data('lineNo');
297 var lineName = $(this).attr('name');
381 var lineName = $(this).attr('name');
298
382
299 if (lineNo) {
383 if (lineNo) {
300 var prevLine = $('.cb-line-selected a').data('lineNo');
384 var prevLine = $('.cb-line-selected a').data('lineNo');
301
385
302 // on shift, we do a range selection, if we got previous line
386 // on shift, we do a range selection, if we got previous line
303 if (event.shiftKey && prevLine !== undefined) {
387 if (event.shiftKey && prevLine !== undefined) {
304 var prevLine = parseInt(prevLine);
388 var prevLine = parseInt(prevLine);
305 var nextLine = parseInt(lineNo);
389 var nextLine = parseInt(lineNo);
306 var pos = [prevLine, nextLine].sort(sortNumber);
390 var pos = [prevLine, nextLine].sort(sortNumber);
307 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
391 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
308
392
309 // single click
393 // single click
310 } else {
394 } else {
311 var nextLine = parseInt(lineNo);
395 var nextLine = parseInt(lineNo);
312 var pos = [nextLine, nextLine];
396 var pos = [nextLine, nextLine];
313 var anchor = '#L{0}'.format(pos[0]);
397 var anchor = '#L{0}'.format(pos[0]);
314
398
315 }
399 }
316 // highlight
400 // highlight
317 var range = [];
401 var range = [];
318 for (var i = pos[0]; i <= pos[1]; i++) {
402 for (var i = pos[0]; i <= pos[1]; i++) {
319 range.push(i);
403 range.push(i);
320 }
404 }
321 // clear old selected lines
405 // clear old selected lines
322 $('.cb-line-selected').removeClass('cb-line-selected');
406 $('.cb-line-selected').removeClass('cb-line-selected');
323
407
324 $.each(range, function (i, lineNo) {
408 $.each(range, function (i, lineNo) {
325 var line_td = $('td.cb-lineno#L' + lineNo);
409 var line_td = $('td.cb-lineno#L' + lineNo);
326
410
327 if (line_td.length) {
411 if (line_td.length) {
328 line_td.addClass('cb-line-selected'); // line number td
412 line_td.addClass('cb-line-selected'); // line number td
329 line_td.prev().addClass('cb-line-selected'); // line data
413 line_td.prev().addClass('cb-line-selected'); // line data
330 line_td.next().addClass('cb-line-selected'); // line content
414 line_td.next().addClass('cb-line-selected'); // line content
331 }
415 }
332 });
416 });
333
417
334 } else if (lineName !== undefined) { // lineName only occurs in diffs
418 } else if (lineName !== undefined) { // lineName only occurs in diffs
335 // clear old selected lines
419 // clear old selected lines
336 $('td.cb-line-selected').removeClass('cb-line-selected');
420 $('td.cb-line-selected').removeClass('cb-line-selected');
337 var anchor = '#{0}'.format(lineName);
421 var anchor = '#{0}'.format(lineName);
338 var diffmode = templateContext.session_attrs.diffmode || "sideside";
422 var diffmode = templateContext.session_attrs.diffmode || "sideside";
339
423
340 if (diffmode === "unified") {
424 if (diffmode === "unified") {
341 $(this).closest('tr').find('td').addClass('cb-line-selected');
425 $(this).closest('tr').find('td').addClass('cb-line-selected');
342 } else {
426 } else {
343 var activeTd = $(this).closest('td');
427 var activeTd = $(this).closest('td');
344 activeTd.addClass('cb-line-selected');
428 activeTd.addClass('cb-line-selected');
345 activeTd.next('td').addClass('cb-line-selected');
429 activeTd.next('td').addClass('cb-line-selected');
346 }
430 }
347
431
348 }
432 }
349
433
350 // Replace URL without jumping to it if browser supports.
434 // Replace URL without jumping to it if browser supports.
351 // Default otherwise
435 // Default otherwise
352 if (history.pushState && anchor !== undefined) {
436 if (history.pushState && anchor !== undefined) {
353 var new_location = location.href.rstrip('#');
437 var new_location = location.href.rstrip('#');
354 if (location.hash) {
438 if (location.hash) {
355 // location without hash
439 // location without hash
356 new_location = new_location.replace(location.hash, "");
440 new_location = new_location.replace(location.hash, "");
357 }
441 }
358
442
359 // Make new anchor url
443 // Make new anchor url
360 new_location = new_location + anchor;
444 new_location = new_location + anchor;
361 history.pushState(true, document.title, new_location);
445 history.pushState(true, document.title, new_location);
362
446
363 return false;
447 return false;
364 }
448 }
365
449
366 });
450 });
367
451
368 $('.collapse_file').on('click', function(e) {
452 $('.collapse_file').on('click', function(e) {
369 e.stopPropagation();
453 e.stopPropagation();
370 if ($(e.target).is('a')) { return; }
454 if ($(e.target).is('a')) { return; }
371 var node = $(e.delegateTarget).first();
455 var node = $(e.delegateTarget).first();
372 var icon = $($(node.children().first()).children().first());
456 var icon = $($(node.children().first()).children().first());
373 var id = node.attr('fid');
457 var id = node.attr('fid');
374 var target = $('#'+id);
458 var target = $('#'+id);
375 var tr = $('#tr_'+id);
459 var tr = $('#tr_'+id);
376 var diff = $('#diff_'+id);
460 var diff = $('#diff_'+id);
377 if(node.hasClass('expand_file')){
461 if(node.hasClass('expand_file')){
378 node.removeClass('expand_file');
462 node.removeClass('expand_file');
379 icon.removeClass('expand_file_icon');
463 icon.removeClass('expand_file_icon');
380 node.addClass('collapse_file');
464 node.addClass('collapse_file');
381 icon.addClass('collapse_file_icon');
465 icon.addClass('collapse_file_icon');
382 diff.show();
466 diff.show();
383 tr.show();
467 tr.show();
384 target.show();
468 target.show();
385 } else {
469 } else {
386 node.removeClass('collapse_file');
470 node.removeClass('collapse_file');
387 icon.removeClass('collapse_file_icon');
471 icon.removeClass('collapse_file_icon');
388 node.addClass('expand_file');
472 node.addClass('expand_file');
389 icon.addClass('expand_file_icon');
473 icon.addClass('expand_file_icon');
390 diff.hide();
474 diff.hide();
391 tr.hide();
475 tr.hide();
392 target.hide();
476 target.hide();
393 }
477 }
394 });
478 });
395
479
396 $('#expand_all_files').click(function() {
480 $('#expand_all_files').click(function() {
397 $('.expand_file').each(function() {
481 $('.expand_file').each(function() {
398 var node = $(this);
482 var node = $(this);
399 var icon = $($(node.children().first()).children().first());
483 var icon = $($(node.children().first()).children().first());
400 var id = $(this).attr('fid');
484 var id = $(this).attr('fid');
401 var target = $('#'+id);
485 var target = $('#'+id);
402 var tr = $('#tr_'+id);
486 var tr = $('#tr_'+id);
403 var diff = $('#diff_'+id);
487 var diff = $('#diff_'+id);
404 node.removeClass('expand_file');
488 node.removeClass('expand_file');
405 icon.removeClass('expand_file_icon');
489 icon.removeClass('expand_file_icon');
406 node.addClass('collapse_file');
490 node.addClass('collapse_file');
407 icon.addClass('collapse_file_icon');
491 icon.addClass('collapse_file_icon');
408 diff.show();
492 diff.show();
409 tr.show();
493 tr.show();
410 target.show();
494 target.show();
411 });
495 });
412 });
496 });
413
497
414 $('#collapse_all_files').click(function() {
498 $('#collapse_all_files').click(function() {
415 $('.collapse_file').each(function() {
499 $('.collapse_file').each(function() {
416 var node = $(this);
500 var node = $(this);
417 var icon = $($(node.children().first()).children().first());
501 var icon = $($(node.children().first()).children().first());
418 var id = $(this).attr('fid');
502 var id = $(this).attr('fid');
419 var target = $('#'+id);
503 var target = $('#'+id);
420 var tr = $('#tr_'+id);
504 var tr = $('#tr_'+id);
421 var diff = $('#diff_'+id);
505 var diff = $('#diff_'+id);
422 node.removeClass('collapse_file');
506 node.removeClass('collapse_file');
423 icon.removeClass('collapse_file_icon');
507 icon.removeClass('collapse_file_icon');
424 node.addClass('expand_file');
508 node.addClass('expand_file');
425 icon.addClass('expand_file_icon');
509 icon.addClass('expand_file_icon');
426 diff.hide();
510 diff.hide();
427 tr.hide();
511 tr.hide();
428 target.hide();
512 target.hide();
429 });
513 });
430 });
514 });
431
515
432 // Mouse over behavior for comments and line selection
516 // Mouse over behavior for comments and line selection
433
517
434 // Select the line that comes from the url anchor
518 // Select the line that comes from the url anchor
435 // At the time of development, Chrome didn't seem to support jquery's :target
519 // At the time of development, Chrome didn't seem to support jquery's :target
436 // element, so I had to scroll manually
520 // element, so I had to scroll manually
437
521
438 if (location.hash) {
522 if (location.hash) {
439 var result = splitDelimitedHash(location.hash);
523 var result = splitDelimitedHash(location.hash);
440 var loc = result.loc;
524 var loc = result.loc;
441 if (loc.length > 1) {
525 if (loc.length > 1) {
442
526
443 var highlightable_line_tds = [];
527 var highlightable_line_tds = [];
444
528
445 // source code line format
529 // source code line format
446 var page_highlights = loc.substring(
530 var page_highlights = loc.substring(
447 loc.indexOf('#') + 1).split('L');
531 loc.indexOf('#') + 1).split('L');
448
532
449 if (page_highlights.length > 1) {
533 if (page_highlights.length > 1) {
450 var highlight_ranges = page_highlights[1].split(",");
534 var highlight_ranges = page_highlights[1].split(",");
451 var h_lines = [];
535 var h_lines = [];
452 for (var pos in highlight_ranges) {
536 for (var pos in highlight_ranges) {
453 var _range = highlight_ranges[pos].split('-');
537 var _range = highlight_ranges[pos].split('-');
454 if (_range.length === 2) {
538 if (_range.length === 2) {
455 var start = parseInt(_range[0]);
539 var start = parseInt(_range[0]);
456 var end = parseInt(_range[1]);
540 var end = parseInt(_range[1]);
457 if (start < end) {
541 if (start < end) {
458 for (var i = start; i <= end; i++) {
542 for (var i = start; i <= end; i++) {
459 h_lines.push(i);
543 h_lines.push(i);
460 }
544 }
461 }
545 }
462 }
546 }
463 else {
547 else {
464 h_lines.push(parseInt(highlight_ranges[pos]));
548 h_lines.push(parseInt(highlight_ranges[pos]));
465 }
549 }
466 }
550 }
467 for (pos in h_lines) {
551 for (pos in h_lines) {
468 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
552 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
469 if (line_td.length) {
553 if (line_td.length) {
470 highlightable_line_tds.push(line_td);
554 highlightable_line_tds.push(line_td);
471 }
555 }
472 }
556 }
473 }
557 }
474
558
475 // now check a direct id reference (diff page)
559 // now check a direct id reference (diff page)
476 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
560 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
477 highlightable_line_tds.push($(loc));
561 highlightable_line_tds.push($(loc));
478 }
562 }
479 $.each(highlightable_line_tds, function (i, $td) {
563 $.each(highlightable_line_tds, function (i, $td) {
480 $td.addClass('cb-line-selected'); // line number td
564 $td.addClass('cb-line-selected'); // line number td
481 $td.prev().addClass('cb-line-selected'); // line data
565 $td.prev().addClass('cb-line-selected'); // line data
482 $td.next().addClass('cb-line-selected'); // line content
566 $td.next().addClass('cb-line-selected'); // line content
483 });
567 });
484
568
485 if (highlightable_line_tds.length) {
569 if (highlightable_line_tds.length) {
486 var $first_line_td = highlightable_line_tds[0];
570 var $first_line_td = highlightable_line_tds[0];
487 scrollToElement($first_line_td);
571 scrollToElement($first_line_td);
488 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
572 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
489 td: $first_line_td,
573 td: $first_line_td,
490 remainder: result.remainder
574 remainder: result.remainder
491 });
575 });
492 }
576 }
493 }
577 }
494 }
578 }
495 collapsableContent();
579 collapsableContent();
496 });
580 });
497
581
498 var feedLifetimeOptions = function(query, initialData){
582 var feedLifetimeOptions = function(query, initialData){
499 var data = {results: []};
583 var data = {results: []};
500 var isQuery = typeof query.term !== 'undefined';
584 var isQuery = typeof query.term !== 'undefined';
501
585
502 var section = _gettext('Lifetime');
586 var section = _gettext('Lifetime');
503 var children = [];
587 var children = [];
504
588
505 //filter results
589 //filter results
506 $.each(initialData.results, function(idx, value) {
590 $.each(initialData.results, function(idx, value) {
507
591
508 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
592 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
509 children.push({
593 children.push({
510 'id': this.id,
594 'id': this.id,
511 'text': this.text
595 'text': this.text
512 })
596 })
513 }
597 }
514
598
515 });
599 });
516 data.results.push({
600 data.results.push({
517 'text': section,
601 'text': section,
518 'children': children
602 'children': children
519 });
603 });
520
604
521 if (isQuery) {
605 if (isQuery) {
522
606
523 var now = moment.utc();
607 var now = moment.utc();
524
608
525 var parseQuery = function(entry, now){
609 var parseQuery = function(entry, now){
526 var fmt = 'DD/MM/YYYY H:mm';
610 var fmt = 'DD/MM/YYYY H:mm';
527 var parsed = moment.utc(entry, fmt);
611 var parsed = moment.utc(entry, fmt);
528 var diffInMin = parsed.diff(now, 'minutes');
612 var diffInMin = parsed.diff(now, 'minutes');
529
613
530 if (diffInMin > 0){
614 if (diffInMin > 0){
531 return {
615 return {
532 id: diffInMin,
616 id: diffInMin,
533 text: parsed.format(fmt)
617 text: parsed.format(fmt)
534 }
618 }
535 } else {
619 } else {
536 return {
620 return {
537 id: undefined,
621 id: undefined,
538 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
622 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
539 }
623 }
540 }
624 }
541
625
542
626
543 };
627 };
544
628
545 data.results.push({
629 data.results.push({
546 'text': _gettext('Specified expiration date'),
630 'text': _gettext('Specified expiration date'),
547 'children': [{
631 'children': [{
548 'id': parseQuery(query.term, now).id,
632 'id': parseQuery(query.term, now).id,
549 'text': parseQuery(query.term, now).text
633 'text': parseQuery(query.term, now).text
550 }]
634 }]
551 });
635 });
552 }
636 }
553
637
554 query.callback(data);
638 query.callback(data);
555 };
639 };
556
640
557
641
558 var storeUserSessionAttr = function (key, val) {
642 var storeUserSessionAttr = function (key, val) {
559
643
560 var postData = {
644 var postData = {
561 'key': key,
645 'key': key,
562 'val': val,
646 'val': val,
563 'csrf_token': CSRF_TOKEN
647 'csrf_token': CSRF_TOKEN
564 };
648 };
565
649
566 var success = function(o) {
650 var success = function(o) {
567 return true
651 return true
568 };
652 };
569
653
570 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
654 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
571 return false;
655 return false;
572 };
656 };
@@ -1,927 +1,929 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 var firefoxAnchorFix = function() {
19 var firefoxAnchorFix = function() {
20 // hack to make anchor links behave properly on firefox, in our inline
20 // hack to make anchor links behave properly on firefox, in our inline
21 // comments generation when comments are injected firefox is misbehaving
21 // comments generation when comments are injected firefox is misbehaving
22 // when jumping to anchor links
22 // when jumping to anchor links
23 if (location.href.indexOf('#') > -1) {
23 if (location.href.indexOf('#') > -1) {
24 location.href += '';
24 location.href += '';
25 }
25 }
26 };
26 };
27
27
28 var linkifyComments = function(comments) {
28 var linkifyComments = function(comments) {
29 var firstCommentId = null;
29 var firstCommentId = null;
30 if (comments) {
30 if (comments) {
31 firstCommentId = $(comments[0]).data('comment-id');
31 firstCommentId = $(comments[0]).data('comment-id');
32 }
32 }
33
33
34 if (firstCommentId){
34 if (firstCommentId){
35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
36 }
36 }
37 };
37 };
38
38
39 var bindToggleButtons = function() {
39 var bindToggleButtons = function() {
40 $('.comment-toggle').on('click', function() {
40 $('.comment-toggle').on('click', function() {
41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
42 });
42 });
43 };
43 };
44
44
45
45
46
46
47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
48 failHandler = failHandler || function() {};
48 failHandler = failHandler || function() {};
49 postData = toQueryString(postData);
49 postData = toQueryString(postData);
50 var request = $.ajax({
50 var request = $.ajax({
51 url: url,
51 url: url,
52 type: 'POST',
52 type: 'POST',
53 data: postData,
53 data: postData,
54 headers: {'X-PARTIAL-XHR': true}
54 headers: {'X-PARTIAL-XHR': true}
55 })
55 })
56 .done(function (data) {
56 .done(function (data) {
57 successHandler(data);
57 successHandler(data);
58 })
58 })
59 .fail(function (data, textStatus, errorThrown) {
59 .fail(function (data, textStatus, errorThrown) {
60 failHandler(data, textStatus, errorThrown)
60 failHandler(data, textStatus, errorThrown)
61 });
61 });
62 return request;
62 return request;
63 };
63 };
64
64
65
65
66
66
67
67
68 /* Comment form for main and inline comments */
68 /* Comment form for main and inline comments */
69 (function(mod) {
69 (function(mod) {
70
70
71 if (typeof exports == "object" && typeof module == "object") {
71 if (typeof exports == "object" && typeof module == "object") {
72 // CommonJS
72 // CommonJS
73 module.exports = mod();
73 module.exports = mod();
74 }
74 }
75 else {
75 else {
76 // Plain browser env
76 // Plain browser env
77 (this || window).CommentForm = mod();
77 (this || window).CommentForm = mod();
78 }
78 }
79
79
80 })(function() {
80 })(function() {
81 "use strict";
81 "use strict";
82
82
83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId) {
83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId) {
84 if (!(this instanceof CommentForm)) {
84 if (!(this instanceof CommentForm)) {
85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId);
85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId);
86 }
86 }
87
87
88 // bind the element instance to our Form
88 // bind the element instance to our Form
89 $(formElement).get(0).CommentForm = this;
89 $(formElement).get(0).CommentForm = this;
90
90
91 this.withLineNo = function(selector) {
91 this.withLineNo = function(selector) {
92 var lineNo = this.lineNo;
92 var lineNo = this.lineNo;
93 if (lineNo === undefined) {
93 if (lineNo === undefined) {
94 return selector
94 return selector
95 } else {
95 } else {
96 return selector + '_' + lineNo;
96 return selector + '_' + lineNo;
97 }
97 }
98 };
98 };
99
99
100 this.commitId = commitId;
100 this.commitId = commitId;
101 this.pullRequestId = pullRequestId;
101 this.pullRequestId = pullRequestId;
102 this.lineNo = lineNo;
102 this.lineNo = lineNo;
103 this.initAutocompleteActions = initAutocompleteActions;
103 this.initAutocompleteActions = initAutocompleteActions;
104
104
105 this.previewButton = this.withLineNo('#preview-btn');
105 this.previewButton = this.withLineNo('#preview-btn');
106 this.previewContainer = this.withLineNo('#preview-container');
106 this.previewContainer = this.withLineNo('#preview-container');
107
107
108 this.previewBoxSelector = this.withLineNo('#preview-box');
108 this.previewBoxSelector = this.withLineNo('#preview-box');
109
109
110 this.editButton = this.withLineNo('#edit-btn');
110 this.editButton = this.withLineNo('#edit-btn');
111 this.editContainer = this.withLineNo('#edit-container');
111 this.editContainer = this.withLineNo('#edit-container');
112 this.cancelButton = this.withLineNo('#cancel-btn');
112 this.cancelButton = this.withLineNo('#cancel-btn');
113 this.commentType = this.withLineNo('#comment_type');
113 this.commentType = this.withLineNo('#comment_type');
114
114
115 this.resolvesId = null;
115 this.resolvesId = null;
116 this.resolvesActionId = null;
116 this.resolvesActionId = null;
117
117
118 this.closesPr = '#close_pull_request';
118 this.closesPr = '#close_pull_request';
119
119
120 this.cmBox = this.withLineNo('#text');
120 this.cmBox = this.withLineNo('#text');
121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
122
122
123 this.statusChange = this.withLineNo('#change_status');
123 this.statusChange = this.withLineNo('#change_status');
124
124
125 this.submitForm = formElement;
125 this.submitForm = formElement;
126 this.submitButton = $(this.submitForm).find('input[type="submit"]');
126 this.submitButton = $(this.submitForm).find('input[type="submit"]');
127 this.submitButtonText = this.submitButton.val();
127 this.submitButtonText = this.submitButton.val();
128
128
129 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
129 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
130 {'repo_name': templateContext.repo_name,
130 {'repo_name': templateContext.repo_name,
131 'commit_id': templateContext.commit_data.commit_id});
131 'commit_id': templateContext.commit_data.commit_id});
132
132
133 if (resolvesCommentId){
133 if (resolvesCommentId){
134 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
134 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
135 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
135 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
136 $(this.commentType).prop('disabled', true);
136 $(this.commentType).prop('disabled', true);
137 $(this.commentType).addClass('disabled');
137 $(this.commentType).addClass('disabled');
138
138
139 // disable select
139 // disable select
140 setTimeout(function() {
140 setTimeout(function() {
141 $(self.statusChange).select2('readonly', true);
141 $(self.statusChange).select2('readonly', true);
142 }, 10);
142 }, 10);
143
143
144 var resolvedInfo = (
144 var resolvedInfo = (
145 '<li class="resolve-action">' +
145 '<li class="resolve-action">' +
146 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
146 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
147 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
147 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
148 '</li>'
148 '</li>'
149 ).format(resolvesCommentId, _gettext('resolve comment'));
149 ).format(resolvesCommentId, _gettext('resolve comment'));
150 $(resolvedInfo).insertAfter($(this.commentType).parent());
150 $(resolvedInfo).insertAfter($(this.commentType).parent());
151 }
151 }
152
152
153 // based on commitId, or pullRequestId decide where do we submit
153 // based on commitId, or pullRequestId decide where do we submit
154 // out data
154 // out data
155 if (this.commitId){
155 if (this.commitId){
156 this.submitUrl = pyroutes.url('repo_commit_comment_create',
156 this.submitUrl = pyroutes.url('repo_commit_comment_create',
157 {'repo_name': templateContext.repo_name,
157 {'repo_name': templateContext.repo_name,
158 'commit_id': this.commitId});
158 'commit_id': this.commitId});
159 this.selfUrl = pyroutes.url('repo_commit',
159 this.selfUrl = pyroutes.url('repo_commit',
160 {'repo_name': templateContext.repo_name,
160 {'repo_name': templateContext.repo_name,
161 'commit_id': this.commitId});
161 'commit_id': this.commitId});
162
162
163 } else if (this.pullRequestId) {
163 } else if (this.pullRequestId) {
164 this.submitUrl = pyroutes.url('pullrequest_comment_create',
164 this.submitUrl = pyroutes.url('pullrequest_comment_create',
165 {'repo_name': templateContext.repo_name,
165 {'repo_name': templateContext.repo_name,
166 'pull_request_id': this.pullRequestId});
166 'pull_request_id': this.pullRequestId});
167 this.selfUrl = pyroutes.url('pullrequest_show',
167 this.selfUrl = pyroutes.url('pullrequest_show',
168 {'repo_name': templateContext.repo_name,
168 {'repo_name': templateContext.repo_name,
169 'pull_request_id': this.pullRequestId});
169 'pull_request_id': this.pullRequestId});
170
170
171 } else {
171 } else {
172 throw new Error(
172 throw new Error(
173 'CommentForm requires pullRequestId, or commitId to be specified.')
173 'CommentForm requires pullRequestId, or commitId to be specified.')
174 }
174 }
175
175
176 // FUNCTIONS and helpers
176 // FUNCTIONS and helpers
177 var self = this;
177 var self = this;
178
178
179 this.isInline = function(){
179 this.isInline = function(){
180 return this.lineNo && this.lineNo != 'general';
180 return this.lineNo && this.lineNo != 'general';
181 };
181 };
182
182
183 this.getCmInstance = function(){
183 this.getCmInstance = function(){
184 return this.cm
184 return this.cm
185 };
185 };
186
186
187 this.setPlaceholder = function(placeholder) {
187 this.setPlaceholder = function(placeholder) {
188 var cm = this.getCmInstance();
188 var cm = this.getCmInstance();
189 if (cm){
189 if (cm){
190 cm.setOption('placeholder', placeholder);
190 cm.setOption('placeholder', placeholder);
191 }
191 }
192 };
192 };
193
193
194 this.getCommentStatus = function() {
194 this.getCommentStatus = function() {
195 return $(this.submitForm).find(this.statusChange).val();
195 return $(this.submitForm).find(this.statusChange).val();
196 };
196 };
197 this.getCommentType = function() {
197 this.getCommentType = function() {
198 return $(this.submitForm).find(this.commentType).val();
198 return $(this.submitForm).find(this.commentType).val();
199 };
199 };
200
200
201 this.getResolvesId = function() {
201 this.getResolvesId = function() {
202 return $(this.submitForm).find(this.resolvesId).val() || null;
202 return $(this.submitForm).find(this.resolvesId).val() || null;
203 };
203 };
204
204
205 this.getClosePr = function() {
205 this.getClosePr = function() {
206 return $(this.submitForm).find(this.closesPr).val() || null;
206 return $(this.submitForm).find(this.closesPr).val() || null;
207 };
207 };
208
208
209 this.markCommentResolved = function(resolvedCommentId){
209 this.markCommentResolved = function(resolvedCommentId){
210 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
210 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
211 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
211 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
212 };
212 };
213
213
214 this.isAllowedToSubmit = function() {
214 this.isAllowedToSubmit = function() {
215 return !$(this.submitButton).prop('disabled');
215 return !$(this.submitButton).prop('disabled');
216 };
216 };
217
217
218 this.initStatusChangeSelector = function(){
218 this.initStatusChangeSelector = function(){
219 var formatChangeStatus = function(state, escapeMarkup) {
219 var formatChangeStatus = function(state, escapeMarkup) {
220 var originalOption = state.element;
220 var originalOption = state.element;
221 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
221 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
222 return tmpl
222 return tmpl
223 };
223 };
224 var formatResult = function(result, container, query, escapeMarkup) {
224 var formatResult = function(result, container, query, escapeMarkup) {
225 return formatChangeStatus(result, escapeMarkup);
225 return formatChangeStatus(result, escapeMarkup);
226 };
226 };
227
227
228 var formatSelection = function(data, container, escapeMarkup) {
228 var formatSelection = function(data, container, escapeMarkup) {
229 return formatChangeStatus(data, escapeMarkup);
229 return formatChangeStatus(data, escapeMarkup);
230 };
230 };
231
231
232 $(this.submitForm).find(this.statusChange).select2({
232 $(this.submitForm).find(this.statusChange).select2({
233 placeholder: _gettext('Status Review'),
233 placeholder: _gettext('Status Review'),
234 formatResult: formatResult,
234 formatResult: formatResult,
235 formatSelection: formatSelection,
235 formatSelection: formatSelection,
236 containerCssClass: "drop-menu status_box_menu",
236 containerCssClass: "drop-menu status_box_menu",
237 dropdownCssClass: "drop-menu-dropdown",
237 dropdownCssClass: "drop-menu-dropdown",
238 dropdownAutoWidth: true,
238 dropdownAutoWidth: true,
239 minimumResultsForSearch: -1
239 minimumResultsForSearch: -1
240 });
240 });
241 $(this.submitForm).find(this.statusChange).on('change', function() {
241 $(this.submitForm).find(this.statusChange).on('change', function() {
242 var status = self.getCommentStatus();
242 var status = self.getCommentStatus();
243
243
244 if (status && !self.isInline()) {
244 if (status && !self.isInline()) {
245 $(self.submitButton).prop('disabled', false);
245 $(self.submitButton).prop('disabled', false);
246 }
246 }
247
247
248 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
248 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
249 self.setPlaceholder(placeholderText)
249 self.setPlaceholder(placeholderText)
250 })
250 })
251 };
251 };
252
252
253 // reset the comment form into it's original state
253 // reset the comment form into it's original state
254 this.resetCommentFormState = function(content) {
254 this.resetCommentFormState = function(content) {
255 content = content || '';
255 content = content || '';
256
256
257 $(this.editContainer).show();
257 $(this.editContainer).show();
258 $(this.editButton).parent().addClass('active');
258 $(this.editButton).parent().addClass('active');
259
259
260 $(this.previewContainer).hide();
260 $(this.previewContainer).hide();
261 $(this.previewButton).parent().removeClass('active');
261 $(this.previewButton).parent().removeClass('active');
262
262
263 this.setActionButtonsDisabled(true);
263 this.setActionButtonsDisabled(true);
264 self.cm.setValue(content);
264 self.cm.setValue(content);
265 self.cm.setOption("readOnly", false);
265 self.cm.setOption("readOnly", false);
266
266
267 if (this.resolvesId) {
267 if (this.resolvesId) {
268 // destroy the resolve action
268 // destroy the resolve action
269 $(this.resolvesId).parent().remove();
269 $(this.resolvesId).parent().remove();
270 }
270 }
271 // reset closingPR flag
271 // reset closingPR flag
272 $('.close-pr-input').remove();
272 $('.close-pr-input').remove();
273
273
274 $(this.statusChange).select2('readonly', false);
274 $(this.statusChange).select2('readonly', false);
275 };
275 };
276
276
277 this.globalSubmitSuccessCallback = function(){
277 this.globalSubmitSuccessCallback = function(){
278 // default behaviour is to call GLOBAL hook, if it's registered.
278 // default behaviour is to call GLOBAL hook, if it's registered.
279 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
279 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
280 commentFormGlobalSubmitSuccessCallback()
280 commentFormGlobalSubmitSuccessCallback()
281 }
281 }
282 };
282 };
283
283
284 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
284 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
285 return _submitAjaxPOST(url, postData, successHandler, failHandler);
285 return _submitAjaxPOST(url, postData, successHandler, failHandler);
286 };
286 };
287
287
288 // overwrite a submitHandler, we need to do it for inline comments
288 // overwrite a submitHandler, we need to do it for inline comments
289 this.setHandleFormSubmit = function(callback) {
289 this.setHandleFormSubmit = function(callback) {
290 this.handleFormSubmit = callback;
290 this.handleFormSubmit = callback;
291 };
291 };
292
292
293 // overwrite a submitSuccessHandler
293 // overwrite a submitSuccessHandler
294 this.setGlobalSubmitSuccessCallback = function(callback) {
294 this.setGlobalSubmitSuccessCallback = function(callback) {
295 this.globalSubmitSuccessCallback = callback;
295 this.globalSubmitSuccessCallback = callback;
296 };
296 };
297
297
298 // default handler for for submit for main comments
298 // default handler for for submit for main comments
299 this.handleFormSubmit = function() {
299 this.handleFormSubmit = function() {
300 var text = self.cm.getValue();
300 var text = self.cm.getValue();
301 var status = self.getCommentStatus();
301 var status = self.getCommentStatus();
302 var commentType = self.getCommentType();
302 var commentType = self.getCommentType();
303 var resolvesCommentId = self.getResolvesId();
303 var resolvesCommentId = self.getResolvesId();
304 var closePullRequest = self.getClosePr();
304 var closePullRequest = self.getClosePr();
305
305
306 if (text === "" && !status) {
306 if (text === "" && !status) {
307 return;
307 return;
308 }
308 }
309
309
310 var excludeCancelBtn = false;
310 var excludeCancelBtn = false;
311 var submitEvent = true;
311 var submitEvent = true;
312 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
312 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
313 self.cm.setOption("readOnly", true);
313 self.cm.setOption("readOnly", true);
314
314
315 var postData = {
315 var postData = {
316 'text': text,
316 'text': text,
317 'changeset_status': status,
317 'changeset_status': status,
318 'comment_type': commentType,
318 'comment_type': commentType,
319 'csrf_token': CSRF_TOKEN
319 'csrf_token': CSRF_TOKEN
320 };
320 };
321
321
322 if (resolvesCommentId) {
322 if (resolvesCommentId) {
323 postData['resolves_comment_id'] = resolvesCommentId;
323 postData['resolves_comment_id'] = resolvesCommentId;
324 }
324 }
325
325
326 if (closePullRequest) {
326 if (closePullRequest) {
327 postData['close_pull_request'] = true;
327 postData['close_pull_request'] = true;
328 }
328 }
329
329
330 var submitSuccessCallback = function(o) {
330 var submitSuccessCallback = function(o) {
331 // reload page if we change status for single commit.
331 // reload page if we change status for single commit.
332 if (status && self.commitId) {
332 if (status && self.commitId) {
333 location.reload(true);
333 location.reload(true);
334 } else {
334 } else {
335 $('#injected_page_comments').append(o.rendered_text);
335 $('#injected_page_comments').append(o.rendered_text);
336 self.resetCommentFormState();
336 self.resetCommentFormState();
337 timeagoActivate();
337 timeagoActivate();
338 tooltipActivate();
338
339
339 // mark visually which comment was resolved
340 // mark visually which comment was resolved
340 if (resolvesCommentId) {
341 if (resolvesCommentId) {
341 self.markCommentResolved(resolvesCommentId);
342 self.markCommentResolved(resolvesCommentId);
342 }
343 }
343 }
344 }
344
345
345 // run global callback on submit
346 // run global callback on submit
346 self.globalSubmitSuccessCallback();
347 self.globalSubmitSuccessCallback();
347
348
348 };
349 };
349 var submitFailCallback = function(data) {
350 var submitFailCallback = function(data) {
350 alert(
351 alert(
351 "Error while submitting comment.\n" +
352 "Error while submitting comment.\n" +
352 "Error code {0} ({1}).".format(data.status, data.statusText)
353 "Error code {0} ({1}).".format(data.status, data.statusText)
353 );
354 );
354 self.resetCommentFormState(text);
355 self.resetCommentFormState(text);
355 };
356 };
356 self.submitAjaxPOST(
357 self.submitAjaxPOST(
357 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
358 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
358 };
359 };
359
360
360 this.previewSuccessCallback = function(o) {
361 this.previewSuccessCallback = function(o) {
361 $(self.previewBoxSelector).html(o);
362 $(self.previewBoxSelector).html(o);
362 $(self.previewBoxSelector).removeClass('unloaded');
363 $(self.previewBoxSelector).removeClass('unloaded');
363
364
364 // swap buttons, making preview active
365 // swap buttons, making preview active
365 $(self.previewButton).parent().addClass('active');
366 $(self.previewButton).parent().addClass('active');
366 $(self.editButton).parent().removeClass('active');
367 $(self.editButton).parent().removeClass('active');
367
368
368 // unlock buttons
369 // unlock buttons
369 self.setActionButtonsDisabled(false);
370 self.setActionButtonsDisabled(false);
370 };
371 };
371
372
372 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
373 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
373 excludeCancelBtn = excludeCancelBtn || false;
374 excludeCancelBtn = excludeCancelBtn || false;
374 submitEvent = submitEvent || false;
375 submitEvent = submitEvent || false;
375
376
376 $(this.editButton).prop('disabled', state);
377 $(this.editButton).prop('disabled', state);
377 $(this.previewButton).prop('disabled', state);
378 $(this.previewButton).prop('disabled', state);
378
379
379 if (!excludeCancelBtn) {
380 if (!excludeCancelBtn) {
380 $(this.cancelButton).prop('disabled', state);
381 $(this.cancelButton).prop('disabled', state);
381 }
382 }
382
383
383 var submitState = state;
384 var submitState = state;
384 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
385 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
385 // if the value of commit review status is set, we allow
386 // if the value of commit review status is set, we allow
386 // submit button, but only on Main form, isInline means inline
387 // submit button, but only on Main form, isInline means inline
387 submitState = false
388 submitState = false
388 }
389 }
389
390
390 $(this.submitButton).prop('disabled', submitState);
391 $(this.submitButton).prop('disabled', submitState);
391 if (submitEvent) {
392 if (submitEvent) {
392 $(this.submitButton).val(_gettext('Submitting...'));
393 $(this.submitButton).val(_gettext('Submitting...'));
393 } else {
394 } else {
394 $(this.submitButton).val(this.submitButtonText);
395 $(this.submitButton).val(this.submitButtonText);
395 }
396 }
396
397
397 };
398 };
398
399
399 // lock preview/edit/submit buttons on load, but exclude cancel button
400 // lock preview/edit/submit buttons on load, but exclude cancel button
400 var excludeCancelBtn = true;
401 var excludeCancelBtn = true;
401 this.setActionButtonsDisabled(true, excludeCancelBtn);
402 this.setActionButtonsDisabled(true, excludeCancelBtn);
402
403
403 // anonymous users don't have access to initialized CM instance
404 // anonymous users don't have access to initialized CM instance
404 if (this.cm !== undefined){
405 if (this.cm !== undefined){
405 this.cm.on('change', function(cMirror) {
406 this.cm.on('change', function(cMirror) {
406 if (cMirror.getValue() === "") {
407 if (cMirror.getValue() === "") {
407 self.setActionButtonsDisabled(true, excludeCancelBtn)
408 self.setActionButtonsDisabled(true, excludeCancelBtn)
408 } else {
409 } else {
409 self.setActionButtonsDisabled(false, excludeCancelBtn)
410 self.setActionButtonsDisabled(false, excludeCancelBtn)
410 }
411 }
411 });
412 });
412 }
413 }
413
414
414 $(this.editButton).on('click', function(e) {
415 $(this.editButton).on('click', function(e) {
415 e.preventDefault();
416 e.preventDefault();
416
417
417 $(self.previewButton).parent().removeClass('active');
418 $(self.previewButton).parent().removeClass('active');
418 $(self.previewContainer).hide();
419 $(self.previewContainer).hide();
419
420
420 $(self.editButton).parent().addClass('active');
421 $(self.editButton).parent().addClass('active');
421 $(self.editContainer).show();
422 $(self.editContainer).show();
422
423
423 });
424 });
424
425
425 $(this.previewButton).on('click', function(e) {
426 $(this.previewButton).on('click', function(e) {
426 e.preventDefault();
427 e.preventDefault();
427 var text = self.cm.getValue();
428 var text = self.cm.getValue();
428
429
429 if (text === "") {
430 if (text === "") {
430 return;
431 return;
431 }
432 }
432
433
433 var postData = {
434 var postData = {
434 'text': text,
435 'text': text,
435 'renderer': templateContext.visual.default_renderer,
436 'renderer': templateContext.visual.default_renderer,
436 'csrf_token': CSRF_TOKEN
437 'csrf_token': CSRF_TOKEN
437 };
438 };
438
439
439 // lock ALL buttons on preview
440 // lock ALL buttons on preview
440 self.setActionButtonsDisabled(true);
441 self.setActionButtonsDisabled(true);
441
442
442 $(self.previewBoxSelector).addClass('unloaded');
443 $(self.previewBoxSelector).addClass('unloaded');
443 $(self.previewBoxSelector).html(_gettext('Loading ...'));
444 $(self.previewBoxSelector).html(_gettext('Loading ...'));
444
445
445 $(self.editContainer).hide();
446 $(self.editContainer).hide();
446 $(self.previewContainer).show();
447 $(self.previewContainer).show();
447
448
448 // by default we reset state of comment preserving the text
449 // by default we reset state of comment preserving the text
449 var previewFailCallback = function(data){
450 var previewFailCallback = function(data){
450 alert(
451 alert(
451 "Error while preview of comment.\n" +
452 "Error while preview of comment.\n" +
452 "Error code {0} ({1}).".format(data.status, data.statusText)
453 "Error code {0} ({1}).".format(data.status, data.statusText)
453 );
454 );
454 self.resetCommentFormState(text)
455 self.resetCommentFormState(text)
455 };
456 };
456 self.submitAjaxPOST(
457 self.submitAjaxPOST(
457 self.previewUrl, postData, self.previewSuccessCallback,
458 self.previewUrl, postData, self.previewSuccessCallback,
458 previewFailCallback);
459 previewFailCallback);
459
460
460 $(self.previewButton).parent().addClass('active');
461 $(self.previewButton).parent().addClass('active');
461 $(self.editButton).parent().removeClass('active');
462 $(self.editButton).parent().removeClass('active');
462 });
463 });
463
464
464 $(this.submitForm).submit(function(e) {
465 $(this.submitForm).submit(function(e) {
465 e.preventDefault();
466 e.preventDefault();
466 var allowedToSubmit = self.isAllowedToSubmit();
467 var allowedToSubmit = self.isAllowedToSubmit();
467 if (!allowedToSubmit){
468 if (!allowedToSubmit){
468 return false;
469 return false;
469 }
470 }
470 self.handleFormSubmit();
471 self.handleFormSubmit();
471 });
472 });
472
473
473 }
474 }
474
475
475 return CommentForm;
476 return CommentForm;
476 });
477 });
477
478
478 /* comments controller */
479 /* comments controller */
479 var CommentsController = function() {
480 var CommentsController = function() {
480 var mainComment = '#text';
481 var mainComment = '#text';
481 var self = this;
482 var self = this;
482
483
483 this.cancelComment = function(node) {
484 this.cancelComment = function(node) {
484 var $node = $(node);
485 var $node = $(node);
485 var $td = $node.closest('td');
486 var $td = $node.closest('td');
486 $node.closest('.comment-inline-form').remove();
487 $node.closest('.comment-inline-form').remove();
487 return false;
488 return false;
488 };
489 };
489
490
490 this.getLineNumber = function(node) {
491 this.getLineNumber = function(node) {
491 var $node = $(node);
492 var $node = $(node);
492 var lineNo = $node.closest('td').attr('data-line-no');
493 var lineNo = $node.closest('td').attr('data-line-no');
493 if (lineNo === undefined && $node.data('commentInline')){
494 if (lineNo === undefined && $node.data('commentInline')){
494 lineNo = $node.data('commentLineNo')
495 lineNo = $node.data('commentLineNo')
495 }
496 }
496
497
497 return lineNo
498 return lineNo
498 };
499 };
499
500
500 this.scrollToComment = function(node, offset, outdated) {
501 this.scrollToComment = function(node, offset, outdated) {
501 if (offset === undefined) {
502 if (offset === undefined) {
502 offset = 0;
503 offset = 0;
503 }
504 }
504 var outdated = outdated || false;
505 var outdated = outdated || false;
505 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
506 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
506
507
507 if (!node) {
508 if (!node) {
508 node = $('.comment-selected');
509 node = $('.comment-selected');
509 if (!node.length) {
510 if (!node.length) {
510 node = $('comment-current')
511 node = $('comment-current')
511 }
512 }
512 }
513 }
513 $wrapper = $(node).closest('div.comment');
514 $wrapper = $(node).closest('div.comment');
514 $comment = $(node).closest(klass);
515 $comment = $(node).closest(klass);
515 $comments = $(klass);
516 $comments = $(klass);
516
517
517 // show hidden comment when referenced.
518 // show hidden comment when referenced.
518 if (!$wrapper.is(':visible')){
519 if (!$wrapper.is(':visible')){
519 $wrapper.show();
520 $wrapper.show();
520 }
521 }
521
522
522 $('.comment-selected').removeClass('comment-selected');
523 $('.comment-selected').removeClass('comment-selected');
523
524
524 var nextIdx = $(klass).index($comment) + offset;
525 var nextIdx = $(klass).index($comment) + offset;
525 if (nextIdx >= $comments.length) {
526 if (nextIdx >= $comments.length) {
526 nextIdx = 0;
527 nextIdx = 0;
527 }
528 }
528 var $next = $(klass).eq(nextIdx);
529 var $next = $(klass).eq(nextIdx);
529
530
530 var $cb = $next.closest('.cb');
531 var $cb = $next.closest('.cb');
531 $cb.removeClass('cb-collapsed');
532 $cb.removeClass('cb-collapsed');
532
533
533 var $filediffCollapseState = $cb.closest('.filediff').prev();
534 var $filediffCollapseState = $cb.closest('.filediff').prev();
534 $filediffCollapseState.prop('checked', false);
535 $filediffCollapseState.prop('checked', false);
535 $next.addClass('comment-selected');
536 $next.addClass('comment-selected');
536 scrollToElement($next);
537 scrollToElement($next);
537 return false;
538 return false;
538 };
539 };
539
540
540 this.nextComment = function(node) {
541 this.nextComment = function(node) {
541 return self.scrollToComment(node, 1);
542 return self.scrollToComment(node, 1);
542 };
543 };
543
544
544 this.prevComment = function(node) {
545 this.prevComment = function(node) {
545 return self.scrollToComment(node, -1);
546 return self.scrollToComment(node, -1);
546 };
547 };
547
548
548 this.nextOutdatedComment = function(node) {
549 this.nextOutdatedComment = function(node) {
549 return self.scrollToComment(node, 1, true);
550 return self.scrollToComment(node, 1, true);
550 };
551 };
551
552
552 this.prevOutdatedComment = function(node) {
553 this.prevOutdatedComment = function(node) {
553 return self.scrollToComment(node, -1, true);
554 return self.scrollToComment(node, -1, true);
554 };
555 };
555
556
556 this.deleteComment = function(node) {
557 this.deleteComment = function(node) {
557 if (!confirm(_gettext('Delete this comment?'))) {
558 if (!confirm(_gettext('Delete this comment?'))) {
558 return false;
559 return false;
559 }
560 }
560 var $node = $(node);
561 var $node = $(node);
561 var $td = $node.closest('td');
562 var $td = $node.closest('td');
562 var $comment = $node.closest('.comment');
563 var $comment = $node.closest('.comment');
563 var comment_id = $comment.attr('data-comment-id');
564 var comment_id = $comment.attr('data-comment-id');
564 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
565 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
565 var postData = {
566 var postData = {
566 'csrf_token': CSRF_TOKEN
567 'csrf_token': CSRF_TOKEN
567 };
568 };
568
569
569 $comment.addClass('comment-deleting');
570 $comment.addClass('comment-deleting');
570 $comment.hide('fast');
571 $comment.hide('fast');
571
572
572 var success = function(response) {
573 var success = function(response) {
573 $comment.remove();
574 $comment.remove();
574 return false;
575 return false;
575 };
576 };
576 var failure = function(data, textStatus, xhr) {
577 var failure = function(data, textStatus, xhr) {
577 alert("error processing request: " + textStatus);
578 alert("error processing request: " + textStatus);
578 $comment.show('fast');
579 $comment.show('fast');
579 $comment.removeClass('comment-deleting');
580 $comment.removeClass('comment-deleting');
580 return false;
581 return false;
581 };
582 };
582 ajaxPOST(url, postData, success, failure);
583 ajaxPOST(url, postData, success, failure);
583 };
584 };
584
585
585 this.toggleWideMode = function (node) {
586 this.toggleWideMode = function (node) {
586 if ($('#content').hasClass('wrapper')) {
587 if ($('#content').hasClass('wrapper')) {
587 $('#content').removeClass("wrapper");
588 $('#content').removeClass("wrapper");
588 $('#content').addClass("wide-mode-wrapper");
589 $('#content').addClass("wide-mode-wrapper");
589 $(node).addClass('btn-success');
590 $(node).addClass('btn-success');
590 return true
591 return true
591 } else {
592 } else {
592 $('#content').removeClass("wide-mode-wrapper");
593 $('#content').removeClass("wide-mode-wrapper");
593 $('#content').addClass("wrapper");
594 $('#content').addClass("wrapper");
594 $(node).removeClass('btn-success');
595 $(node).removeClass('btn-success');
595 return false
596 return false
596 }
597 }
597
598
598 };
599 };
599
600
600 this.toggleComments = function(node, show) {
601 this.toggleComments = function(node, show) {
601 var $filediff = $(node).closest('.filediff');
602 var $filediff = $(node).closest('.filediff');
602 if (show === true) {
603 if (show === true) {
603 $filediff.removeClass('hide-comments');
604 $filediff.removeClass('hide-comments');
604 } else if (show === false) {
605 } else if (show === false) {
605 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
606 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
606 $filediff.addClass('hide-comments');
607 $filediff.addClass('hide-comments');
607 } else {
608 } else {
608 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
609 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
609 $filediff.toggleClass('hide-comments');
610 $filediff.toggleClass('hide-comments');
610 }
611 }
611 return false;
612 return false;
612 };
613 };
613
614
614 this.toggleLineComments = function(node) {
615 this.toggleLineComments = function(node) {
615 self.toggleComments(node, true);
616 self.toggleComments(node, true);
616 var $node = $(node);
617 var $node = $(node);
617 // mark outdated comments as visible before the toggle;
618 // mark outdated comments as visible before the toggle;
618 $(node.closest('tr')).find('.comment-outdated').show();
619 $(node.closest('tr')).find('.comment-outdated').show();
619 $node.closest('tr').toggleClass('hide-line-comments');
620 $node.closest('tr').toggleClass('hide-line-comments');
620 };
621 };
621
622
622 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){
623 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){
623 var pullRequestId = templateContext.pull_request_data.pull_request_id;
624 var pullRequestId = templateContext.pull_request_data.pull_request_id;
624 var commitId = templateContext.commit_data.commit_id;
625 var commitId = templateContext.commit_data.commit_id;
625
626
626 var commentForm = new CommentForm(
627 var commentForm = new CommentForm(
627 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId);
628 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId);
628 var cm = commentForm.getCmInstance();
629 var cm = commentForm.getCmInstance();
629
630
630 if (resolvesCommentId){
631 if (resolvesCommentId){
631 var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
632 var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
632 }
633 }
633
634
634 setTimeout(function() {
635 setTimeout(function() {
635 // callbacks
636 // callbacks
636 if (cm !== undefined) {
637 if (cm !== undefined) {
637 commentForm.setPlaceholder(placeholderText);
638 commentForm.setPlaceholder(placeholderText);
638 if (commentForm.isInline()) {
639 if (commentForm.isInline()) {
639 cm.focus();
640 cm.focus();
640 cm.refresh();
641 cm.refresh();
641 }
642 }
642 }
643 }
643 }, 10);
644 }, 10);
644
645
645 // trigger scrolldown to the resolve comment, since it might be away
646 // trigger scrolldown to the resolve comment, since it might be away
646 // from the clicked
647 // from the clicked
647 if (resolvesCommentId){
648 if (resolvesCommentId){
648 var actionNode = $(commentForm.resolvesActionId).offset();
649 var actionNode = $(commentForm.resolvesActionId).offset();
649
650
650 setTimeout(function() {
651 setTimeout(function() {
651 if (actionNode) {
652 if (actionNode) {
652 $('body, html').animate({scrollTop: actionNode.top}, 10);
653 $('body, html').animate({scrollTop: actionNode.top}, 10);
653 }
654 }
654 }, 100);
655 }, 100);
655 }
656 }
656
657
657 // add dropzone support
658 // add dropzone support
658 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
659 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
659 var renderer = templateContext.visual.default_renderer;
660 var renderer = templateContext.visual.default_renderer;
660 if (renderer == 'rst') {
661 if (renderer == 'rst') {
661 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
662 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
662 if (isRendered){
663 if (isRendered){
663 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
664 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
664 }
665 }
665 } else if (renderer == 'markdown') {
666 } else if (renderer == 'markdown') {
666 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
667 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
667 if (isRendered){
668 if (isRendered){
668 attachmentUrl = '!' + attachmentUrl;
669 attachmentUrl = '!' + attachmentUrl;
669 }
670 }
670 } else {
671 } else {
671 var attachmentUrl = '{}'.format(attachmentStoreUrl);
672 var attachmentUrl = '{}'.format(attachmentStoreUrl);
672 }
673 }
673 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
674 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
674
675
675 return false;
676 return false;
676 };
677 };
677
678
678 //see: https://www.dropzonejs.com/#configuration
679 //see: https://www.dropzonejs.com/#configuration
679 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
680 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
680 {'repo_name': templateContext.repo_name,
681 {'repo_name': templateContext.repo_name,
681 'commit_id': templateContext.commit_data.commit_id})
682 'commit_id': templateContext.commit_data.commit_id})
682
683
683 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
684 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
684 if (previewTmpl !== undefined){
685 if (previewTmpl !== undefined){
685 var selectLink = $(formElement).find('.pick-attachment').get(0);
686 var selectLink = $(formElement).find('.pick-attachment').get(0);
686 $(formElement).find('.comment-attachment-uploader').dropzone({
687 $(formElement).find('.comment-attachment-uploader').dropzone({
687 url: storeUrl,
688 url: storeUrl,
688 headers: {"X-CSRF-Token": CSRF_TOKEN},
689 headers: {"X-CSRF-Token": CSRF_TOKEN},
689 paramName: function () {
690 paramName: function () {
690 return "attachment"
691 return "attachment"
691 }, // The name that will be used to transfer the file
692 }, // The name that will be used to transfer the file
692 clickable: selectLink,
693 clickable: selectLink,
693 parallelUploads: 1,
694 parallelUploads: 1,
694 maxFiles: 10,
695 maxFiles: 10,
695 maxFilesize: templateContext.attachment_store.max_file_size_mb,
696 maxFilesize: templateContext.attachment_store.max_file_size_mb,
696 uploadMultiple: false,
697 uploadMultiple: false,
697 autoProcessQueue: true, // if false queue will not be processed automatically.
698 autoProcessQueue: true, // if false queue will not be processed automatically.
698 createImageThumbnails: false,
699 createImageThumbnails: false,
699 previewTemplate: previewTmpl.innerHTML,
700 previewTemplate: previewTmpl.innerHTML,
700
701
701 accept: function (file, done) {
702 accept: function (file, done) {
702 done();
703 done();
703 },
704 },
704 init: function () {
705 init: function () {
705
706
706 this.on("sending", function (file, xhr, formData) {
707 this.on("sending", function (file, xhr, formData) {
707 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
708 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
708 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
709 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
709 });
710 });
710
711
711 this.on("success", function (file, response) {
712 this.on("success", function (file, response) {
712 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
713 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
713 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
714 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
714
715
715 var isRendered = false;
716 var isRendered = false;
716 var ext = file.name.split('.').pop();
717 var ext = file.name.split('.').pop();
717 var imageExts = templateContext.attachment_store.image_ext;
718 var imageExts = templateContext.attachment_store.image_ext;
718 if (imageExts.indexOf(ext) !== -1){
719 if (imageExts.indexOf(ext) !== -1){
719 isRendered = true;
720 isRendered = true;
720 }
721 }
721
722
722 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
723 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
723 });
724 });
724
725
725 this.on("error", function (file, errorMessage, xhr) {
726 this.on("error", function (file, errorMessage, xhr) {
726 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
727 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
727
728
728 var error = null;
729 var error = null;
729
730
730 if (xhr !== undefined){
731 if (xhr !== undefined){
731 var httpStatus = xhr.status + " " + xhr.statusText;
732 var httpStatus = xhr.status + " " + xhr.statusText;
732 if (xhr !== undefined && xhr.status >= 500) {
733 if (xhr !== undefined && xhr.status >= 500) {
733 error = httpStatus;
734 error = httpStatus;
734 }
735 }
735 }
736 }
736
737
737 if (error === null) {
738 if (error === null) {
738 error = errorMessage.error || errorMessage || httpStatus;
739 error = errorMessage.error || errorMessage || httpStatus;
739 }
740 }
740 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
741 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
741
742
742 });
743 });
743 }
744 }
744 });
745 });
745 }
746 }
746 return commentForm;
747 return commentForm;
747 };
748 };
748
749
749 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
750 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
750
751
751 var tmpl = $('#cb-comment-general-form-template').html();
752 var tmpl = $('#cb-comment-general-form-template').html();
752 tmpl = tmpl.format(null, 'general');
753 tmpl = tmpl.format(null, 'general');
753 var $form = $(tmpl);
754 var $form = $(tmpl);
754
755
755 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
756 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
756 var curForm = $formPlaceholder.find('form');
757 var curForm = $formPlaceholder.find('form');
757 if (curForm){
758 if (curForm){
758 curForm.remove();
759 curForm.remove();
759 }
760 }
760 $formPlaceholder.append($form);
761 $formPlaceholder.append($form);
761
762
762 var _form = $($form[0]);
763 var _form = $($form[0]);
763 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
764 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
764 var commentForm = this.createCommentForm(
765 var commentForm = this.createCommentForm(
765 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId);
766 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId);
766 commentForm.initStatusChangeSelector();
767 commentForm.initStatusChangeSelector();
767
768
768 return commentForm;
769 return commentForm;
769 };
770 };
770
771
771 this.createComment = function(node, resolutionComment) {
772 this.createComment = function(node, resolutionComment) {
772 var resolvesCommentId = resolutionComment || null;
773 var resolvesCommentId = resolutionComment || null;
773 var $node = $(node);
774 var $node = $(node);
774 var $td = $node.closest('td');
775 var $td = $node.closest('td');
775 var $form = $td.find('.comment-inline-form');
776 var $form = $td.find('.comment-inline-form');
776
777
777 if (!$form.length) {
778 if (!$form.length) {
778
779
779 var $filediff = $node.closest('.filediff');
780 var $filediff = $node.closest('.filediff');
780 $filediff.removeClass('hide-comments');
781 $filediff.removeClass('hide-comments');
781 var f_path = $filediff.attr('data-f-path');
782 var f_path = $filediff.attr('data-f-path');
782 var lineno = self.getLineNumber(node);
783 var lineno = self.getLineNumber(node);
783 // create a new HTML from template
784 // create a new HTML from template
784 var tmpl = $('#cb-comment-inline-form-template').html();
785 var tmpl = $('#cb-comment-inline-form-template').html();
785 tmpl = tmpl.format(escapeHtml(f_path), lineno);
786 tmpl = tmpl.format(escapeHtml(f_path), lineno);
786 $form = $(tmpl);
787 $form = $(tmpl);
787
788
788 var $comments = $td.find('.inline-comments');
789 var $comments = $td.find('.inline-comments');
789 if (!$comments.length) {
790 if (!$comments.length) {
790 $comments = $(
791 $comments = $(
791 $('#cb-comments-inline-container-template').html());
792 $('#cb-comments-inline-container-template').html());
792 $td.append($comments);
793 $td.append($comments);
793 }
794 }
794
795
795 $td.find('.cb-comment-add-button').before($form);
796 $td.find('.cb-comment-add-button').before($form);
796
797
797 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
798 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
798 var _form = $($form[0]).find('form');
799 var _form = $($form[0]).find('form');
799 var autocompleteActions = ['as_note', 'as_todo'];
800 var autocompleteActions = ['as_note', 'as_todo'];
800 var commentForm = this.createCommentForm(
801 var commentForm = this.createCommentForm(
801 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId);
802 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId);
802
803
803 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
804 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
804 form: _form,
805 form: _form,
805 parent: $td[0],
806 parent: $td[0],
806 lineno: lineno,
807 lineno: lineno,
807 f_path: f_path}
808 f_path: f_path}
808 );
809 );
809
810
810 // set a CUSTOM submit handler for inline comments.
811 // set a CUSTOM submit handler for inline comments.
811 commentForm.setHandleFormSubmit(function(o) {
812 commentForm.setHandleFormSubmit(function(o) {
812 var text = commentForm.cm.getValue();
813 var text = commentForm.cm.getValue();
813 var commentType = commentForm.getCommentType();
814 var commentType = commentForm.getCommentType();
814 var resolvesCommentId = commentForm.getResolvesId();
815 var resolvesCommentId = commentForm.getResolvesId();
815
816
816 if (text === "") {
817 if (text === "") {
817 return;
818 return;
818 }
819 }
819
820
820 if (lineno === undefined) {
821 if (lineno === undefined) {
821 alert('missing line !');
822 alert('missing line !');
822 return;
823 return;
823 }
824 }
824 if (f_path === undefined) {
825 if (f_path === undefined) {
825 alert('missing file path !');
826 alert('missing file path !');
826 return;
827 return;
827 }
828 }
828
829
829 var excludeCancelBtn = false;
830 var excludeCancelBtn = false;
830 var submitEvent = true;
831 var submitEvent = true;
831 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
832 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
832 commentForm.cm.setOption("readOnly", true);
833 commentForm.cm.setOption("readOnly", true);
833 var postData = {
834 var postData = {
834 'text': text,
835 'text': text,
835 'f_path': f_path,
836 'f_path': f_path,
836 'line': lineno,
837 'line': lineno,
837 'comment_type': commentType,
838 'comment_type': commentType,
838 'csrf_token': CSRF_TOKEN
839 'csrf_token': CSRF_TOKEN
839 };
840 };
840 if (resolvesCommentId){
841 if (resolvesCommentId){
841 postData['resolves_comment_id'] = resolvesCommentId;
842 postData['resolves_comment_id'] = resolvesCommentId;
842 }
843 }
843
844
844 var submitSuccessCallback = function(json_data) {
845 var submitSuccessCallback = function(json_data) {
845 $form.remove();
846 $form.remove();
846 try {
847 try {
847 var html = json_data.rendered_text;
848 var html = json_data.rendered_text;
848 var lineno = json_data.line_no;
849 var lineno = json_data.line_no;
849 var target_id = json_data.target_id;
850 var target_id = json_data.target_id;
850
851
851 $comments.find('.cb-comment-add-button').before(html);
852 $comments.find('.cb-comment-add-button').before(html);
852
853
853 //mark visually which comment was resolved
854 //mark visually which comment was resolved
854 if (resolvesCommentId) {
855 if (resolvesCommentId) {
855 commentForm.markCommentResolved(resolvesCommentId);
856 commentForm.markCommentResolved(resolvesCommentId);
856 }
857 }
857
858
858 // run global callback on submit
859 // run global callback on submit
859 commentForm.globalSubmitSuccessCallback();
860 commentForm.globalSubmitSuccessCallback();
860
861
861 } catch (e) {
862 } catch (e) {
862 console.error(e);
863 console.error(e);
863 }
864 }
864
865
865 // re trigger the linkification of next/prev navigation
866 // re trigger the linkification of next/prev navigation
866 linkifyComments($('.inline-comment-injected'));
867 linkifyComments($('.inline-comment-injected'));
867 timeagoActivate();
868 timeagoActivate();
869 tooltipActivate();
868
870
869 if (window.updateSticky !== undefined) {
871 if (window.updateSticky !== undefined) {
870 // potentially our comments change the active window size, so we
872 // potentially our comments change the active window size, so we
871 // notify sticky elements
873 // notify sticky elements
872 updateSticky()
874 updateSticky()
873 }
875 }
874
876
875 commentForm.setActionButtonsDisabled(false);
877 commentForm.setActionButtonsDisabled(false);
876
878
877 };
879 };
878 var submitFailCallback = function(data){
880 var submitFailCallback = function(data){
879 alert(
881 alert(
880 "Error while submitting comment.\n" +
882 "Error while submitting comment.\n" +
881 "Error code {0} ({1}).".format(data.status, data.statusText)
883 "Error code {0} ({1}).".format(data.status, data.statusText)
882 );
884 );
883 commentForm.resetCommentFormState(text)
885 commentForm.resetCommentFormState(text)
884 };
886 };
885 commentForm.submitAjaxPOST(
887 commentForm.submitAjaxPOST(
886 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
888 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
887 });
889 });
888 }
890 }
889
891
890 $form.addClass('comment-inline-form-open');
892 $form.addClass('comment-inline-form-open');
891 };
893 };
892
894
893 this.createResolutionComment = function(commentId){
895 this.createResolutionComment = function(commentId){
894 // hide the trigger text
896 // hide the trigger text
895 $('#resolve-comment-{0}'.format(commentId)).hide();
897 $('#resolve-comment-{0}'.format(commentId)).hide();
896
898
897 var comment = $('#comment-'+commentId);
899 var comment = $('#comment-'+commentId);
898 var commentData = comment.data();
900 var commentData = comment.data();
899 if (commentData.commentInline) {
901 if (commentData.commentInline) {
900 this.createComment(comment, commentId)
902 this.createComment(comment, commentId)
901 } else {
903 } else {
902 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
904 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
903 }
905 }
904
906
905 return false;
907 return false;
906 };
908 };
907
909
908 this.submitResolution = function(commentId){
910 this.submitResolution = function(commentId){
909 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
911 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
910 var commentForm = form.get(0).CommentForm;
912 var commentForm = form.get(0).CommentForm;
911
913
912 var cm = commentForm.getCmInstance();
914 var cm = commentForm.getCmInstance();
913 var renderer = templateContext.visual.default_renderer;
915 var renderer = templateContext.visual.default_renderer;
914 if (renderer == 'rst'){
916 if (renderer == 'rst'){
915 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
917 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
916 } else if (renderer == 'markdown') {
918 } else if (renderer == 'markdown') {
917 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
919 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
918 } else {
920 } else {
919 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
921 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
920 }
922 }
921
923
922 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
924 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
923 form.submit();
925 form.submit();
924 return false;
926 return false;
925 };
927 };
926
928
927 };
929 };
@@ -1,517 +1,519 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 * Search file list
20 * Search file list
21 */
21 */
22
22
23 var NodeFilter = {};
23 var NodeFilter = {};
24
24
25 var fileBrowserListeners = function (node_list_url, url_base) {
25 var fileBrowserListeners = function (node_list_url, url_base) {
26 var $filterInput = $('#node_filter');
26 var $filterInput = $('#node_filter');
27 var n_filter = $filterInput.get(0);
27 var n_filter = $filterInput.get(0);
28
28
29 NodeFilter.filterTimeout = null;
29 NodeFilter.filterTimeout = null;
30 var nodes = null;
30 var nodes = null;
31
31
32 NodeFilter.focus = function () {
32 NodeFilter.focus = function () {
33 $filterInput.focus()
33 $filterInput.focus()
34 };
34 };
35
35
36 NodeFilter.fetchNodes = function (callback) {
36 NodeFilter.fetchNodes = function (callback) {
37 $.ajax(
37 $.ajax(
38 {url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
38 {url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
39 .done(function (data) {
39 .done(function (data) {
40 nodes = data.nodes;
40 nodes = data.nodes;
41 if (callback) {
41 if (callback) {
42 callback();
42 callback();
43 }
43 }
44 })
44 })
45 .fail(function (data) {
45 .fail(function (data) {
46 console.log('failed to load');
46 console.log('failed to load');
47 });
47 });
48 };
48 };
49
49
50 NodeFilter.initFilter = function (e) {
50 NodeFilter.initFilter = function (e) {
51 if ($filterInput.hasClass('loading')) {
51 if ($filterInput.hasClass('loading')) {
52 return
52 return
53 }
53 }
54
54
55 // in case we are already loaded, do nothing
55 // in case we are already loaded, do nothing
56 if (!$filterInput.hasClass('init')) {
56 if (!$filterInput.hasClass('init')) {
57 return NodeFilter.handleKey(e);
57 return NodeFilter.handleKey(e);
58 }
58 }
59 var iconLoading = 'icon-spin animate-spin';
59 var iconLoading = 'icon-spin animate-spin';
60 var iconSearch = 'icon-search';
60 var iconSearch = 'icon-search';
61 $('.files-filter-box-path i').removeClass(iconSearch).addClass(iconLoading);
61 $('.files-filter-box-path i').removeClass(iconSearch).addClass(iconLoading);
62 $filterInput.addClass('loading');
62 $filterInput.addClass('loading');
63
63
64 var callback = function (org) {
64 var callback = function (org) {
65 return function () {
65 return function () {
66 if ($filterInput.hasClass('init')) {
66 if ($filterInput.hasClass('init')) {
67 $filterInput.removeClass('init');
67 $filterInput.removeClass('init');
68 $filterInput.removeClass('loading');
68 $filterInput.removeClass('loading');
69 }
69 }
70 $('.files-filter-box-path i').removeClass(iconLoading).addClass(iconSearch);
70 $('.files-filter-box-path i').removeClass(iconLoading).addClass(iconSearch);
71
71
72 // auto re-filter if we filled in the input
72 // auto re-filter if we filled in the input
73 if (n_filter.value !== "") {
73 if (n_filter.value !== "") {
74 NodeFilter.updateFilter(n_filter, e)()
74 NodeFilter.updateFilter(n_filter, e)()
75 }
75 }
76
76
77 }
77 }
78 };
78 };
79 // load node data
79 // load node data
80 NodeFilter.fetchNodes(callback());
80 NodeFilter.fetchNodes(callback());
81
81
82 };
82 };
83
83
84 NodeFilter.resetFilter = function () {
84 NodeFilter.resetFilter = function () {
85 $('#tbody').show();
85 $('#tbody').show();
86 $('#tbody_filtered').hide();
86 $('#tbody_filtered').hide();
87 $filterInput.val('');
87 $filterInput.val('');
88 };
88 };
89
89
90 NodeFilter.handleKey = function (e) {
90 NodeFilter.handleKey = function (e) {
91 var scrollDown = function (element) {
91 var scrollDown = function (element) {
92 var elementBottom = element.offset().top + $(element).outerHeight();
92 var elementBottom = element.offset().top + $(element).outerHeight();
93 var windowBottom = window.innerHeight + $(window).scrollTop();
93 var windowBottom = window.innerHeight + $(window).scrollTop();
94 if (elementBottom > windowBottom) {
94 if (elementBottom > windowBottom) {
95 var offset = elementBottom - window.innerHeight;
95 var offset = elementBottom - window.innerHeight;
96 $('html,body').scrollTop(offset);
96 $('html,body').scrollTop(offset);
97 return false;
97 return false;
98 }
98 }
99 return true;
99 return true;
100 };
100 };
101
101
102 var scrollUp = function (element) {
102 var scrollUp = function (element) {
103 if (element.offset().top < $(window).scrollTop()) {
103 if (element.offset().top < $(window).scrollTop()) {
104 $('html,body').scrollTop(element.offset().top);
104 $('html,body').scrollTop(element.offset().top);
105 return false;
105 return false;
106 }
106 }
107 return true;
107 return true;
108 };
108 };
109 var $hlElem = $('.browser-highlight');
109 var $hlElem = $('.browser-highlight');
110
110
111 if (e.keyCode === 40) { // Down
111 if (e.keyCode === 40) { // Down
112 if ($hlElem.length === 0) {
112 if ($hlElem.length === 0) {
113 $('.browser-result').first().addClass('browser-highlight');
113 $('.browser-result').first().addClass('browser-highlight');
114 } else {
114 } else {
115 var next = $hlElem.next();
115 var next = $hlElem.next();
116 if (next.length !== 0) {
116 if (next.length !== 0) {
117 $hlElem.removeClass('browser-highlight');
117 $hlElem.removeClass('browser-highlight');
118 next.addClass('browser-highlight');
118 next.addClass('browser-highlight');
119 }
119 }
120 }
120 }
121
121
122 if ($hlElem.get(0) !== undefined){
122 if ($hlElem.get(0) !== undefined){
123 scrollDown($hlElem);
123 scrollDown($hlElem);
124 }
124 }
125 }
125 }
126 if (e.keyCode === 38) { // Up
126 if (e.keyCode === 38) { // Up
127 e.preventDefault();
127 e.preventDefault();
128 if ($hlElem.length !== 0) {
128 if ($hlElem.length !== 0) {
129 var next = $hlElem.prev();
129 var next = $hlElem.prev();
130 if (next.length !== 0) {
130 if (next.length !== 0) {
131 $('.browser-highlight').removeClass('browser-highlight');
131 $('.browser-highlight').removeClass('browser-highlight');
132 next.addClass('browser-highlight');
132 next.addClass('browser-highlight');
133 }
133 }
134 }
134 }
135
135
136 if ($hlElem.get(0) !== undefined){
136 if ($hlElem.get(0) !== undefined){
137 scrollUp($hlElem);
137 scrollUp($hlElem);
138 }
138 }
139
139
140 }
140 }
141 if (e.keyCode === 13) { // Enter
141 if (e.keyCode === 13) { // Enter
142 if ($('.browser-highlight').length !== 0) {
142 if ($('.browser-highlight').length !== 0) {
143 var url = $('.browser-highlight').find('.match-link').attr('href');
143 var url = $('.browser-highlight').find('.match-link').attr('href');
144 window.location = url;
144 window.location = url;
145 }
145 }
146 }
146 }
147 if (e.keyCode === 27) { // Esc
147 if (e.keyCode === 27) { // Esc
148 NodeFilter.resetFilter();
148 NodeFilter.resetFilter();
149 $('html,body').scrollTop(0);
149 $('html,body').scrollTop(0);
150 }
150 }
151
151
152 var capture_keys = [
152 var capture_keys = [
153 40, // ArrowDown
153 40, // ArrowDown
154 38, // ArrowUp
154 38, // ArrowUp
155 39, // ArrowRight
155 39, // ArrowRight
156 37, // ArrowLeft
156 37, // ArrowLeft
157 13, // Enter
157 13, // Enter
158 27 // Esc
158 27 // Esc
159 ];
159 ];
160
160
161 if ($.inArray(e.keyCode, capture_keys) === -1) {
161 if ($.inArray(e.keyCode, capture_keys) === -1) {
162 clearTimeout(NodeFilter.filterTimeout);
162 clearTimeout(NodeFilter.filterTimeout);
163 NodeFilter.filterTimeout = setTimeout(NodeFilter.updateFilter(n_filter, e), 200);
163 NodeFilter.filterTimeout = setTimeout(NodeFilter.updateFilter(n_filter, e), 200);
164 }
164 }
165
165
166 };
166 };
167
167
168 NodeFilter.fuzzy_match = function (filepath, query) {
168 NodeFilter.fuzzy_match = function (filepath, query) {
169 var highlight = [];
169 var highlight = [];
170 var order = 0;
170 var order = 0;
171 for (var i = 0; i < query.length; i++) {
171 for (var i = 0; i < query.length; i++) {
172 var match_position = filepath.indexOf(query[i]);
172 var match_position = filepath.indexOf(query[i]);
173 if (match_position !== -1) {
173 if (match_position !== -1) {
174 var prev_match_position = highlight[highlight.length - 1];
174 var prev_match_position = highlight[highlight.length - 1];
175 if (prev_match_position === undefined) {
175 if (prev_match_position === undefined) {
176 highlight.push(match_position);
176 highlight.push(match_position);
177 } else {
177 } else {
178 var current_match_position = prev_match_position + match_position + 1;
178 var current_match_position = prev_match_position + match_position + 1;
179 highlight.push(current_match_position);
179 highlight.push(current_match_position);
180 order = order + current_match_position - prev_match_position;
180 order = order + current_match_position - prev_match_position;
181 }
181 }
182 filepath = filepath.substring(match_position + 1);
182 filepath = filepath.substring(match_position + 1);
183 } else {
183 } else {
184 return false;
184 return false;
185 }
185 }
186 }
186 }
187 return {
187 return {
188 'order': order,
188 'order': order,
189 'highlight': highlight
189 'highlight': highlight
190 };
190 };
191 };
191 };
192
192
193 NodeFilter.sortPredicate = function (a, b) {
193 NodeFilter.sortPredicate = function (a, b) {
194 if (a.order < b.order) return -1;
194 if (a.order < b.order) return -1;
195 if (a.order > b.order) return 1;
195 if (a.order > b.order) return 1;
196 if (a.filepath < b.filepath) return -1;
196 if (a.filepath < b.filepath) return -1;
197 if (a.filepath > b.filepath) return 1;
197 if (a.filepath > b.filepath) return 1;
198 return 0;
198 return 0;
199 };
199 };
200
200
201 NodeFilter.updateFilter = function (elem, e) {
201 NodeFilter.updateFilter = function (elem, e) {
202 return function () {
202 return function () {
203 // Reset timeout
203 // Reset timeout
204 NodeFilter.filterTimeout = null;
204 NodeFilter.filterTimeout = null;
205 var query = elem.value.toLowerCase();
205 var query = elem.value.toLowerCase();
206 var match = [];
206 var match = [];
207 var matches_max = 20;
207 var matches_max = 20;
208 if (query !== "") {
208 if (query !== "") {
209 var results = [];
209 var results = [];
210 for (var k = 0; k < nodes.length; k++) {
210 for (var k = 0; k < nodes.length; k++) {
211 var result = NodeFilter.fuzzy_match(
211 var result = NodeFilter.fuzzy_match(
212 nodes[k].name.toLowerCase(), query);
212 nodes[k].name.toLowerCase(), query);
213 if (result) {
213 if (result) {
214 result.type = nodes[k].type;
214 result.type = nodes[k].type;
215 result.filepath = nodes[k].name;
215 result.filepath = nodes[k].name;
216 results.push(result);
216 results.push(result);
217 }
217 }
218 }
218 }
219 results = results.sort(NodeFilter.sortPredicate);
219 results = results.sort(NodeFilter.sortPredicate);
220 var limit = matches_max;
220 var limit = matches_max;
221 if (results.length < matches_max) {
221 if (results.length < matches_max) {
222 limit = results.length;
222 limit = results.length;
223 }
223 }
224 for (var i = 0; i < limit; i++) {
224 for (var i = 0; i < limit; i++) {
225 if (query && results.length > 0) {
225 if (query && results.length > 0) {
226 var n = results[i].filepath;
226 var n = results[i].filepath;
227 var t = results[i].type;
227 var t = results[i].type;
228 var n_hl = n.split("");
228 var n_hl = n.split("");
229 var pos = results[i].highlight;
229 var pos = results[i].highlight;
230 for (var j = 0; j < pos.length; j++) {
230 for (var j = 0; j < pos.length; j++) {
231 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
231 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
232 }
232 }
233 n_hl = n_hl.join("");
233 n_hl = n_hl.join("");
234 var new_url = url_base.replace('__FPATH__', n);
234 var new_url = url_base.replace('__FPATH__', n);
235
235
236 var typeObj = {
236 var typeObj = {
237 dir: 'icon-directory browser-dir',
237 dir: 'icon-directory browser-dir',
238 file: 'icon-file-text browser-file'
238 file: 'icon-file-text browser-file'
239 };
239 };
240
240
241 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
241 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
242 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url, typeIcon, n_hl));
242 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url, typeIcon, n_hl));
243 }
243 }
244 }
244 }
245 if (results.length > limit) {
245 if (results.length > limit) {
246 var truncated_count = results.length - matches_max;
246 var truncated_count = results.length - matches_max;
247 if (truncated_count === 1) {
247 if (truncated_count === 1) {
248 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
248 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
249 } else {
249 } else {
250 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
250 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
251 }
251 }
252 }
252 }
253 }
253 }
254 if (query !== "") {
254 if (query !== "") {
255 $('#tbody').hide();
255 $('#tbody').hide();
256 $('#tbody_filtered').show();
256 $('#tbody_filtered').show();
257
257
258 if (match.length === 0) {
258 if (match.length === 0) {
259 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
259 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
260 }
260 }
261 $('#tbody_filtered').html(match.join(""));
261 $('#tbody_filtered').html(match.join(""));
262 } else {
262 } else {
263 $('#tbody').show();
263 $('#tbody').show();
264 $('#tbody_filtered').hide();
264 $('#tbody_filtered').hide();
265 }
265 }
266
266
267 };
267 };
268 };
268 };
269
269
270 };
270 };
271
271
272 var getIdentNode = function(n){
272 var getIdentNode = function(n){
273 // iterate through nodes until matched interesting node
273 // iterate through nodes until matched interesting node
274 if (typeof n === 'undefined'){
274 if (typeof n === 'undefined'){
275 return -1;
275 return -1;
276 }
276 }
277 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
277 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
278 return n;
278 return n;
279 }
279 }
280 else{
280 else{
281 return getIdentNode(n.parentNode);
281 return getIdentNode(n.parentNode);
282 }
282 }
283 };
283 };
284
284
285 var getSelectionLink = function(e) {
285 var getSelectionLink = function(e) {
286 // get selection from start/to nodes
286 // get selection from start/to nodes
287 if (typeof window.getSelection !== "undefined") {
287 if (typeof window.getSelection !== "undefined") {
288 s = window.getSelection();
288 s = window.getSelection();
289
289
290 from = getIdentNode(s.anchorNode);
290 from = getIdentNode(s.anchorNode);
291 till = getIdentNode(s.focusNode);
291 till = getIdentNode(s.focusNode);
292
292
293 f_int = parseInt(from.id.replace('L',''));
293 f_int = parseInt(from.id.replace('L',''));
294 t_int = parseInt(till.id.replace('L',''));
294 t_int = parseInt(till.id.replace('L',''));
295
295
296 if (f_int > t_int){
296 if (f_int > t_int){
297 // highlight from bottom
297 // highlight from bottom
298 offset = -35;
298 offset = -35;
299 ranges = [t_int,f_int];
299 ranges = [t_int,f_int];
300 }
300 }
301 else{
301 else{
302 // highligth from top
302 // highligth from top
303 offset = 35;
303 offset = 35;
304 ranges = [f_int,t_int];
304 ranges = [f_int,t_int];
305 }
305 }
306 // if we select more than 2 lines
306 // if we select more than 2 lines
307 if (ranges[0] !== ranges[1]){
307 if (ranges[0] !== ranges[1]){
308 if($('#linktt').length === 0){
308 if($('#linktt').length === 0){
309 hl_div = document.createElement('div');
309 hl_div = document.createElement('div');
310 hl_div.id = 'linktt';
310 hl_div.id = 'linktt';
311 }
311 }
312 hl_div.innerHTML = '';
312 hl_div.innerHTML = '';
313
313
314 anchor = '#L'+ranges[0]+'-'+ranges[1];
314 anchor = '#L'+ranges[0]+'-'+ranges[1];
315 var link = document.createElement('a');
315 var link = document.createElement('a');
316 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
316 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
317 link.innerHTML = _gettext('Selection link');
317 link.innerHTML = _gettext('Selection link');
318 hl_div.appendChild(link);
318 hl_div.appendChild(link);
319 $('#codeblock').append(hl_div);
319 $('#codeblock').append(hl_div);
320
320
321 var xy = $(till).offset();
321 var xy = $(till).offset();
322 $('#linktt').addClass('hl-tip-box tip-box');
322 $('#linktt').addClass('hl-tip-box tip-box');
323 $('#linktt').offset({top: xy.top + offset, left: xy.left});
323 $('#linktt').offset({top: xy.top + offset, left: xy.left});
324 $('#linktt').css('visibility','visible');
324 $('#linktt').css('visibility','visible');
325 }
325 }
326 else{
326 else{
327 $('#linktt').css('visibility','hidden');
327 $('#linktt').css('visibility','hidden');
328 }
328 }
329 }
329 }
330 };
330 };
331
331
332 var getFileState = function() {
332 var getFileState = function() {
333 // relies on a global set filesUrlData
333 // relies on a global set filesUrlData
334 var f_path = filesUrlData['f_path'];
334 var f_path = filesUrlData['f_path'];
335 var commit_id = filesUrlData['commit_id'];
335 var commit_id = filesUrlData['commit_id'];
336
336
337 var url_params = {
337 var url_params = {
338 repo_name: templateContext.repo_name,
338 repo_name: templateContext.repo_name,
339 commit_id: commit_id,
339 commit_id: commit_id,
340 f_path:'__FPATH__'
340 f_path:'__FPATH__'
341 };
341 };
342 if (atRef !== '') {
342 if (atRef !== '') {
343 url_params['at'] = atRef
343 url_params['at'] = atRef
344 }
344 }
345
345
346 var _url_base = pyroutes.url('repo_files', url_params);
346 var _url_base = pyroutes.url('repo_files', url_params);
347 var _node_list_url = pyroutes.url('repo_files_nodelist',
347 var _node_list_url = pyroutes.url('repo_files_nodelist',
348 {repo_name: templateContext.repo_name,
348 {repo_name: templateContext.repo_name,
349 commit_id: commit_id, f_path: f_path});
349 commit_id: commit_id, f_path: f_path});
350
350
351 return {
351 return {
352 f_path: f_path,
352 f_path: f_path,
353 commit_id: commit_id,
353 commit_id: commit_id,
354 node_list_url: _node_list_url,
354 node_list_url: _node_list_url,
355 url_base: _url_base
355 url_base: _url_base
356 };
356 };
357 };
357 };
358
358
359 var getFilesMetadata = function() {
359 var getFilesMetadata = function() {
360 // relies on metadataRequest global state
360 // relies on metadataRequest global state
361 if (metadataRequest && metadataRequest.readyState != 4) {
361 if (metadataRequest && metadataRequest.readyState != 4) {
362 metadataRequest.abort();
362 metadataRequest.abort();
363 }
363 }
364
364
365 if ($('#file-tree-wrapper').hasClass('full-load')) {
365 if ($('#file-tree-wrapper').hasClass('full-load')) {
366 // in case our HTML wrapper has full-load class we don't
366 // in case our HTML wrapper has full-load class we don't
367 // trigger the async load of metadata
367 // trigger the async load of metadata
368 return false;
368 return false;
369 }
369 }
370
370
371 var state = getFileState();
371 var state = getFileState();
372 var url_data = {
372 var url_data = {
373 'repo_name': templateContext.repo_name,
373 'repo_name': templateContext.repo_name,
374 'commit_id': state.commit_id,
374 'commit_id': state.commit_id,
375 'f_path': state.f_path
375 'f_path': state.f_path
376 };
376 };
377
377
378 var url = pyroutes.url('repo_nodetree_full', url_data);
378 var url = pyroutes.url('repo_nodetree_full', url_data);
379
379
380 metadataRequest = $.ajax({url: url});
380 metadataRequest = $.ajax({url: url});
381
381
382 metadataRequest.done(function(data) {
382 metadataRequest.done(function(data) {
383 $('#file-tree').html(data);
383 $('#file-tree').html(data);
384 timeagoActivate();
384 timeagoActivate();
385 tooltipActivate();
385 });
386 });
386 metadataRequest.fail(function (data, textStatus, errorThrown) {
387 metadataRequest.fail(function (data, textStatus, errorThrown) {
387 if (data.status != 0) {
388 if (data.status != 0) {
388 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
389 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
389 }
390 }
390 });
391 });
391 };
392 };
392
393
393 // show more authors
394 // show more authors
394 var showAuthors = function(elem, annotate) {
395 var showAuthors = function(elem, annotate) {
395 var state = getFileState('callbacks');
396 var state = getFileState('callbacks');
396
397
397 var url = pyroutes.url('repo_file_authors',
398 var url = pyroutes.url('repo_file_authors',
398 {'repo_name': templateContext.repo_name,
399 {'repo_name': templateContext.repo_name,
399 'commit_id': state.commit_id, 'f_path': state.f_path});
400 'commit_id': state.commit_id, 'f_path': state.f_path});
400
401
401 $.pjax({
402 $.pjax({
402 url: url,
403 url: url,
403 data: 'annotate={0}'.format(annotate),
404 data: 'annotate={0}'.format(annotate),
404 container: '#file_authors',
405 container: '#file_authors',
405 push: false,
406 push: false,
406 timeout: 5000
407 timeout: 5000
407 }).complete(function(){
408 }).complete(function(){
408 $(elem).hide();
409 $(elem).hide();
409 $('#file_authors_title').html(_gettext('All Authors'))
410 $('#file_authors_title').html(_gettext('All Authors'));
411 tooltipActivate();
410 })
412 })
411 };
413 };
412
414
413
415
414 (function (mod) {
416 (function (mod) {
415
417
416 if (typeof exports == "object" && typeof module == "object") {
418 if (typeof exports == "object" && typeof module == "object") {
417 // CommonJS
419 // CommonJS
418 module.exports = mod();
420 module.exports = mod();
419 } else {
421 } else {
420 // Plain browser env
422 // Plain browser env
421 (this || window).FileEditor = mod();
423 (this || window).FileEditor = mod();
422 }
424 }
423
425
424 })(function () {
426 })(function () {
425 "use strict";
427 "use strict";
426
428
427 function FileEditor(textAreaElement, options) {
429 function FileEditor(textAreaElement, options) {
428 if (!(this instanceof FileEditor)) {
430 if (!(this instanceof FileEditor)) {
429 return new FileEditor(textAreaElement, options);
431 return new FileEditor(textAreaElement, options);
430 }
432 }
431 // bind the element instance to our Form
433 // bind the element instance to our Form
432 var te = $(textAreaElement).get(0);
434 var te = $(textAreaElement).get(0);
433 if (te !== undefined) {
435 if (te !== undefined) {
434 te.FileEditor = this;
436 te.FileEditor = this;
435 }
437 }
436
438
437 this.modes_select = '#set_mode';
439 this.modes_select = '#set_mode';
438 this.filename_selector = '#filename';
440 this.filename_selector = '#filename';
439 this.commit_btn_selector = '#commit_btn';
441 this.commit_btn_selector = '#commit_btn';
440 this.line_wrap_selector = '#line_wrap';
442 this.line_wrap_selector = '#line_wrap';
441 this.editor_preview_selector = '#editor_preview';
443 this.editor_preview_selector = '#editor_preview';
442
444
443 if (te !== undefined) {
445 if (te !== undefined) {
444 this.cm = initCodeMirror(textAreaElement, null, false);
446 this.cm = initCodeMirror(textAreaElement, null, false);
445 }
447 }
446
448
447 // FUNCTIONS and helpers
449 // FUNCTIONS and helpers
448 var self = this;
450 var self = this;
449
451
450 this.submitHandler = function() {
452 this.submitHandler = function() {
451 $(self.commit_btn_selector).on('click', function(e) {
453 $(self.commit_btn_selector).on('click', function(e) {
452
454
453 var filename = $(self.filename_selector).val();
455 var filename = $(self.filename_selector).val();
454 if (filename === "") {
456 if (filename === "") {
455 alert("Missing filename");
457 alert("Missing filename");
456 e.preventDefault();
458 e.preventDefault();
457 }
459 }
458
460
459 var button = $(this);
461 var button = $(this);
460 if (button.hasClass('clicked')) {
462 if (button.hasClass('clicked')) {
461 button.attr('disabled', true);
463 button.attr('disabled', true);
462 } else {
464 } else {
463 button.addClass('clicked');
465 button.addClass('clicked');
464 }
466 }
465 });
467 });
466 };
468 };
467 this.submitHandler();
469 this.submitHandler();
468
470
469 // on select line wraps change the editor
471 // on select line wraps change the editor
470 this.lineWrapHandler = function () {
472 this.lineWrapHandler = function () {
471 $(self.line_wrap_selector).on('change', function (e) {
473 $(self.line_wrap_selector).on('change', function (e) {
472 var selected = e.currentTarget;
474 var selected = e.currentTarget;
473 var line_wraps = {'on': true, 'off': false}[selected.value];
475 var line_wraps = {'on': true, 'off': false}[selected.value];
474 setCodeMirrorLineWrap(self.cm, line_wraps)
476 setCodeMirrorLineWrap(self.cm, line_wraps)
475 });
477 });
476 };
478 };
477 this.lineWrapHandler();
479 this.lineWrapHandler();
478
480
479
481
480 this.showPreview = function () {
482 this.showPreview = function () {
481
483
482 var _text = self.cm.getValue();
484 var _text = self.cm.getValue();
483 var _file_path = $(self.filename_selector).val();
485 var _file_path = $(self.filename_selector).val();
484 if (_text && _file_path) {
486 if (_text && _file_path) {
485 $('.show-preview').addClass('active');
487 $('.show-preview').addClass('active');
486 $('.show-editor').removeClass('active');
488 $('.show-editor').removeClass('active');
487
489
488 $(self.editor_preview_selector).show();
490 $(self.editor_preview_selector).show();
489 $(self.cm.getWrapperElement()).hide();
491 $(self.cm.getWrapperElement()).hide();
490
492
491
493
492 var post_data = {'text': _text, 'file_path': _file_path, 'csrf_token': CSRF_TOKEN};
494 var post_data = {'text': _text, 'file_path': _file_path, 'csrf_token': CSRF_TOKEN};
493 $(self.editor_preview_selector).html(_gettext('Loading ...'));
495 $(self.editor_preview_selector).html(_gettext('Loading ...'));
494
496
495 var url = pyroutes.url('file_preview');
497 var url = pyroutes.url('file_preview');
496
498
497 ajaxPOST(url, post_data, function (o) {
499 ajaxPOST(url, post_data, function (o) {
498 $(self.editor_preview_selector).html(o);
500 $(self.editor_preview_selector).html(o);
499 })
501 })
500 }
502 }
501
503
502 };
504 };
503
505
504 this.showEditor = function () {
506 this.showEditor = function () {
505 $(self.editor_preview_selector).hide();
507 $(self.editor_preview_selector).hide();
506 $('.show-editor').addClass('active');
508 $('.show-editor').addClass('active');
507 $('.show-preview').removeClass('active');
509 $('.show-preview').removeClass('active');
508
510
509 $(self.cm.getWrapperElement()).show();
511 $(self.cm.getWrapperElement()).show();
510 };
512 };
511
513
512
514
513 }
515 }
514
516
515 return FileEditor;
517 return FileEditor;
516 });
518 });
517
519
@@ -1,551 +1,552 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 var prButtonLockChecks = {
20 var prButtonLockChecks = {
21 'compare': false,
21 'compare': false,
22 'reviewers': false
22 'reviewers': false
23 };
23 };
24
24
25 /**
25 /**
26 * lock button until all checks and loads are made. E.g reviewer calculation
26 * lock button until all checks and loads are made. E.g reviewer calculation
27 * should prevent from submitting a PR
27 * should prevent from submitting a PR
28 * @param lockEnabled
28 * @param lockEnabled
29 * @param msg
29 * @param msg
30 * @param scope
30 * @param scope
31 */
31 */
32 var prButtonLock = function(lockEnabled, msg, scope) {
32 var prButtonLock = function(lockEnabled, msg, scope) {
33 scope = scope || 'all';
33 scope = scope || 'all';
34 if (scope == 'all'){
34 if (scope == 'all'){
35 prButtonLockChecks['compare'] = !lockEnabled;
35 prButtonLockChecks['compare'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 } else if (scope == 'compare') {
37 } else if (scope == 'compare') {
38 prButtonLockChecks['compare'] = !lockEnabled;
38 prButtonLockChecks['compare'] = !lockEnabled;
39 } else if (scope == 'reviewers'){
39 } else if (scope == 'reviewers'){
40 prButtonLockChecks['reviewers'] = !lockEnabled;
40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 }
41 }
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 if (lockEnabled) {
43 if (lockEnabled) {
44 $('#pr_submit').attr('disabled', 'disabled');
44 $('#pr_submit').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#pr_submit').removeAttr('disabled');
47 $('#pr_submit').removeAttr('disabled');
48 }
48 }
49
49
50 if (msg) {
50 if (msg) {
51 $('#pr_open_message').html(msg);
51 $('#pr_open_message').html(msg);
52 }
52 }
53 };
53 };
54
54
55
55
56 /**
56 /**
57 Generate Title and Description for a PullRequest.
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
60 and build description in a form
61 - commitN
61 - commitN
62 - commitN+1
62 - commitN+1
63 ...
63 ...
64
64
65 Title is then constructed from branch names, or other references,
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
66 replacing '-' and '_' into spaces
67
67
68 * @param sourceRef
68 * @param sourceRef
69 * @param elements
69 * @param elements
70 * @param limit
70 * @param limit
71 * @returns {*[]}
71 * @returns {*[]}
72 */
72 */
73 var getTitleAndDescription = function(sourceRef, elements, limit) {
73 var getTitleAndDescription = function(sourceRef, elements, limit) {
74 var title = '';
74 var title = '';
75 var desc = '';
75 var desc = '';
76
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
80 });
81 // only 1 commit, use commit message as title
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
82 if (elements.length === 1) {
83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
84 }
84 }
85 else {
85 else {
86 // use reference name
86 // use reference name
87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
88 }
88 }
89
89
90 return [title, desc]
90 return [title, desc]
91 };
91 };
92
92
93
93
94
94
95 ReviewersController = function () {
95 ReviewersController = function () {
96 var self = this;
96 var self = this;
97 this.$reviewRulesContainer = $('#review_rules');
97 this.$reviewRulesContainer = $('#review_rules');
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
99 this.forbidReviewUsers = undefined;
99 this.forbidReviewUsers = undefined;
100 this.$reviewMembers = $('#review_members');
100 this.$reviewMembers = $('#review_members');
101 this.currentRequest = null;
101 this.currentRequest = null;
102
102
103 this.defaultForbidReviewUsers = function() {
103 this.defaultForbidReviewUsers = function() {
104 return [
104 return [
105 {'username': 'default',
105 {'username': 'default',
106 'user_id': templateContext.default_user.user_id}
106 'user_id': templateContext.default_user.user_id}
107 ];
107 ];
108 };
108 };
109
109
110 this.hideReviewRules = function() {
110 this.hideReviewRules = function() {
111 self.$reviewRulesContainer.hide();
111 self.$reviewRulesContainer.hide();
112 };
112 };
113
113
114 this.showReviewRules = function() {
114 this.showReviewRules = function() {
115 self.$reviewRulesContainer.show();
115 self.$reviewRulesContainer.show();
116 };
116 };
117
117
118 this.addRule = function(ruleText) {
118 this.addRule = function(ruleText) {
119 self.showReviewRules();
119 self.showReviewRules();
120 return '<div>- {0}</div>'.format(ruleText)
120 return '<div>- {0}</div>'.format(ruleText)
121 };
121 };
122
122
123 this.loadReviewRules = function(data) {
123 this.loadReviewRules = function(data) {
124 // reset forbidden Users
124 // reset forbidden Users
125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
126
126
127 // reset state of review rules
127 // reset state of review rules
128 self.$rulesList.html('');
128 self.$rulesList.html('');
129
129
130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
131 // default rule, case for older repo that don't have any rules stored
131 // default rule, case for older repo that don't have any rules stored
132 self.$rulesList.append(
132 self.$rulesList.append(
133 self.addRule(
133 self.addRule(
134 _gettext('All reviewers must vote.'))
134 _gettext('All reviewers must vote.'))
135 );
135 );
136 return self.forbidReviewUsers
136 return self.forbidReviewUsers
137 }
137 }
138
138
139 if (data.rules.voting !== undefined) {
139 if (data.rules.voting !== undefined) {
140 if (data.rules.voting < 0) {
140 if (data.rules.voting < 0) {
141 self.$rulesList.append(
141 self.$rulesList.append(
142 self.addRule(
142 self.addRule(
143 _gettext('All individual reviewers must vote.'))
143 _gettext('All individual reviewers must vote.'))
144 )
144 )
145 } else if (data.rules.voting === 1) {
145 } else if (data.rules.voting === 1) {
146 self.$rulesList.append(
146 self.$rulesList.append(
147 self.addRule(
147 self.addRule(
148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
149 )
149 )
150
150
151 } else {
151 } else {
152 self.$rulesList.append(
152 self.$rulesList.append(
153 self.addRule(
153 self.addRule(
154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
155 )
155 )
156 }
156 }
157 }
157 }
158
158
159 if (data.rules.voting_groups !== undefined) {
159 if (data.rules.voting_groups !== undefined) {
160 $.each(data.rules.voting_groups, function(index, rule_data) {
160 $.each(data.rules.voting_groups, function(index, rule_data) {
161 self.$rulesList.append(
161 self.$rulesList.append(
162 self.addRule(rule_data.text)
162 self.addRule(rule_data.text)
163 )
163 )
164 });
164 });
165 }
165 }
166
166
167 if (data.rules.use_code_authors_for_review) {
167 if (data.rules.use_code_authors_for_review) {
168 self.$rulesList.append(
168 self.$rulesList.append(
169 self.addRule(
169 self.addRule(
170 _gettext('Reviewers picked from source code changes.'))
170 _gettext('Reviewers picked from source code changes.'))
171 )
171 )
172 }
172 }
173 if (data.rules.forbid_adding_reviewers) {
173 if (data.rules.forbid_adding_reviewers) {
174 $('#add_reviewer_input').remove();
174 $('#add_reviewer_input').remove();
175 self.$rulesList.append(
175 self.$rulesList.append(
176 self.addRule(
176 self.addRule(
177 _gettext('Adding new reviewers is forbidden.'))
177 _gettext('Adding new reviewers is forbidden.'))
178 )
178 )
179 }
179 }
180 if (data.rules.forbid_author_to_review) {
180 if (data.rules.forbid_author_to_review) {
181 self.forbidReviewUsers.push(data.rules_data.pr_author);
181 self.forbidReviewUsers.push(data.rules_data.pr_author);
182 self.$rulesList.append(
182 self.$rulesList.append(
183 self.addRule(
183 self.addRule(
184 _gettext('Author is not allowed to be a reviewer.'))
184 _gettext('Author is not allowed to be a reviewer.'))
185 )
185 )
186 }
186 }
187 if (data.rules.forbid_commit_author_to_review) {
187 if (data.rules.forbid_commit_author_to_review) {
188
188
189 if (data.rules_data.forbidden_users) {
189 if (data.rules_data.forbidden_users) {
190 $.each(data.rules_data.forbidden_users, function(index, member_data) {
190 $.each(data.rules_data.forbidden_users, function(index, member_data) {
191 self.forbidReviewUsers.push(member_data)
191 self.forbidReviewUsers.push(member_data)
192 });
192 });
193
193
194 }
194 }
195
195
196 self.$rulesList.append(
196 self.$rulesList.append(
197 self.addRule(
197 self.addRule(
198 _gettext('Commit Authors are not allowed to be a reviewer.'))
198 _gettext('Commit Authors are not allowed to be a reviewer.'))
199 )
199 )
200 }
200 }
201
201
202 return self.forbidReviewUsers
202 return self.forbidReviewUsers
203 };
203 };
204
204
205 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
205 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
206
206
207 if (self.currentRequest) {
207 if (self.currentRequest) {
208 // make sure we cleanup old running requests before triggering this
208 // make sure we cleanup old running requests before triggering this
209 // again
209 // again
210 self.currentRequest.abort();
210 self.currentRequest.abort();
211 }
211 }
212
212
213 $('.calculate-reviewers').show();
213 $('.calculate-reviewers').show();
214 // reset reviewer members
214 // reset reviewer members
215 self.$reviewMembers.empty();
215 self.$reviewMembers.empty();
216
216
217 prButtonLock(true, null, 'reviewers');
217 prButtonLock(true, null, 'reviewers');
218 $('#user').hide(); // hide user autocomplete before load
218 $('#user').hide(); // hide user autocomplete before load
219
219
220 if (sourceRef.length !== 3 || targetRef.length !== 3) {
220 if (sourceRef.length !== 3 || targetRef.length !== 3) {
221 // don't load defaults in case we're missing some refs...
221 // don't load defaults in case we're missing some refs...
222 $('.calculate-reviewers').hide();
222 $('.calculate-reviewers').hide();
223 return
223 return
224 }
224 }
225
225
226 var url = pyroutes.url('repo_default_reviewers_data',
226 var url = pyroutes.url('repo_default_reviewers_data',
227 {
227 {
228 'repo_name': templateContext.repo_name,
228 'repo_name': templateContext.repo_name,
229 'source_repo': sourceRepo,
229 'source_repo': sourceRepo,
230 'source_ref': sourceRef[2],
230 'source_ref': sourceRef[2],
231 'target_repo': targetRepo,
231 'target_repo': targetRepo,
232 'target_ref': targetRef[2]
232 'target_ref': targetRef[2]
233 });
233 });
234
234
235 self.currentRequest = $.get(url)
235 self.currentRequest = $.get(url)
236 .done(function(data) {
236 .done(function(data) {
237 self.currentRequest = null;
237 self.currentRequest = null;
238
238
239 // review rules
239 // review rules
240 self.loadReviewRules(data);
240 self.loadReviewRules(data);
241
241
242 for (var i = 0; i < data.reviewers.length; i++) {
242 for (var i = 0; i < data.reviewers.length; i++) {
243 var reviewer = data.reviewers[i];
243 var reviewer = data.reviewers[i];
244 self.addReviewMember(
244 self.addReviewMember(
245 reviewer, reviewer.reasons, reviewer.mandatory);
245 reviewer, reviewer.reasons, reviewer.mandatory);
246 }
246 }
247 $('.calculate-reviewers').hide();
247 $('.calculate-reviewers').hide();
248 prButtonLock(false, null, 'reviewers');
248 prButtonLock(false, null, 'reviewers');
249 $('#user').show(); // show user autocomplete after load
249 $('#user').show(); // show user autocomplete after load
250 });
250 });
251 };
251 };
252
252
253 // check those, refactor
253 // check those, refactor
254 this.removeReviewMember = function(reviewer_id, mark_delete) {
254 this.removeReviewMember = function(reviewer_id, mark_delete) {
255 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
255 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
256
256
257 if(typeof(mark_delete) === undefined){
257 if(typeof(mark_delete) === undefined){
258 mark_delete = false;
258 mark_delete = false;
259 }
259 }
260
260
261 if(mark_delete === true){
261 if(mark_delete === true){
262 if (reviewer){
262 if (reviewer){
263 // now delete the input
263 // now delete the input
264 $('#reviewer_{0} input'.format(reviewer_id)).remove();
264 $('#reviewer_{0} input'.format(reviewer_id)).remove();
265 // mark as to-delete
265 // mark as to-delete
266 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
266 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
267 obj.addClass('to-delete');
267 obj.addClass('to-delete');
268 obj.css({"text-decoration":"line-through", "opacity": 0.5});
268 obj.css({"text-decoration":"line-through", "opacity": 0.5});
269 }
269 }
270 }
270 }
271 else{
271 else{
272 $('#reviewer_{0}'.format(reviewer_id)).remove();
272 $('#reviewer_{0}'.format(reviewer_id)).remove();
273 }
273 }
274 };
274 };
275 this.reviewMemberEntry = function() {
275 this.reviewMemberEntry = function() {
276
276
277 };
277 };
278 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
278 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
279 var members = self.$reviewMembers.get(0);
279 var members = self.$reviewMembers.get(0);
280 var id = reviewer_obj.user_id;
280 var id = reviewer_obj.user_id;
281 var username = reviewer_obj.username;
281 var username = reviewer_obj.username;
282
282
283 var reasons = reasons || [];
283 var reasons = reasons || [];
284 var mandatory = mandatory || false;
284 var mandatory = mandatory || false;
285
285
286 // register IDS to check if we don't have this ID already in
286 // register IDS to check if we don't have this ID already in
287 var currentIds = [];
287 var currentIds = [];
288 var _els = self.$reviewMembers.find('li').toArray();
288 var _els = self.$reviewMembers.find('li').toArray();
289 for (el in _els){
289 for (el in _els){
290 currentIds.push(_els[el].id)
290 currentIds.push(_els[el].id)
291 }
291 }
292
292
293 var userAllowedReview = function(userId) {
293 var userAllowedReview = function(userId) {
294 var allowed = true;
294 var allowed = true;
295 $.each(self.forbidReviewUsers, function(index, member_data) {
295 $.each(self.forbidReviewUsers, function(index, member_data) {
296 if (parseInt(userId) === member_data['user_id']) {
296 if (parseInt(userId) === member_data['user_id']) {
297 allowed = false;
297 allowed = false;
298 return false // breaks the loop
298 return false // breaks the loop
299 }
299 }
300 });
300 });
301 return allowed
301 return allowed
302 };
302 };
303
303
304 var userAllowed = userAllowedReview(id);
304 var userAllowed = userAllowedReview(id);
305 if (!userAllowed){
305 if (!userAllowed){
306 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
306 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
307 } else {
307 } else {
308 // only add if it's not there
308 // only add if it's not there
309 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
309 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
310
310
311 if (alreadyReviewer) {
311 if (alreadyReviewer) {
312 alert(_gettext('User `{0}` already in reviewers').format(username));
312 alert(_gettext('User `{0}` already in reviewers').format(username));
313 } else {
313 } else {
314 members.innerHTML += renderTemplate('reviewMemberEntry', {
314 members.innerHTML += renderTemplate('reviewMemberEntry', {
315 'member': reviewer_obj,
315 'member': reviewer_obj,
316 'mandatory': mandatory,
316 'mandatory': mandatory,
317 'allowed_to_update': true,
317 'allowed_to_update': true,
318 'review_status': 'not_reviewed',
318 'review_status': 'not_reviewed',
319 'review_status_label': _gettext('Not Reviewed'),
319 'review_status_label': _gettext('Not Reviewed'),
320 'reasons': reasons,
320 'reasons': reasons,
321 'create': true
321 'create': true
322 });
322 });
323 tooltipActivate();
323 }
324 }
324 }
325 }
325
326
326 };
327 };
327
328
328 this.updateReviewers = function(repo_name, pull_request_id){
329 this.updateReviewers = function(repo_name, pull_request_id){
329 var postData = $('#reviewers input').serialize();
330 var postData = $('#reviewers input').serialize();
330 _updatePullRequest(repo_name, pull_request_id, postData);
331 _updatePullRequest(repo_name, pull_request_id, postData);
331 };
332 };
332
333
333 };
334 };
334
335
335
336
336 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
337 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
337 var url = pyroutes.url(
338 var url = pyroutes.url(
338 'pullrequest_update',
339 'pullrequest_update',
339 {"repo_name": repo_name, "pull_request_id": pull_request_id});
340 {"repo_name": repo_name, "pull_request_id": pull_request_id});
340 if (typeof postData === 'string' ) {
341 if (typeof postData === 'string' ) {
341 postData += '&csrf_token=' + CSRF_TOKEN;
342 postData += '&csrf_token=' + CSRF_TOKEN;
342 } else {
343 } else {
343 postData.csrf_token = CSRF_TOKEN;
344 postData.csrf_token = CSRF_TOKEN;
344 }
345 }
345 var success = function(o) {
346 var success = function(o) {
346 window.location.reload();
347 window.location.reload();
347 };
348 };
348 ajaxPOST(url, postData, success);
349 ajaxPOST(url, postData, success);
349 };
350 };
350
351
351 /**
352 /**
352 * PULL REQUEST update commits
353 * PULL REQUEST update commits
353 */
354 */
354 var updateCommits = function(repo_name, pull_request_id) {
355 var updateCommits = function(repo_name, pull_request_id) {
355 var postData = {
356 var postData = {
356 'update_commits': true};
357 'update_commits': true};
357 _updatePullRequest(repo_name, pull_request_id, postData);
358 _updatePullRequest(repo_name, pull_request_id, postData);
358 };
359 };
359
360
360
361
361 /**
362 /**
362 * PULL REQUEST edit info
363 * PULL REQUEST edit info
363 */
364 */
364 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
365 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
365 var url = pyroutes.url(
366 var url = pyroutes.url(
366 'pullrequest_update',
367 'pullrequest_update',
367 {"repo_name": repo_name, "pull_request_id": pull_request_id});
368 {"repo_name": repo_name, "pull_request_id": pull_request_id});
368
369
369 var postData = {
370 var postData = {
370 'title': title,
371 'title': title,
371 'description': description,
372 'description': description,
372 'description_renderer': renderer,
373 'description_renderer': renderer,
373 'edit_pull_request': true,
374 'edit_pull_request': true,
374 'csrf_token': CSRF_TOKEN
375 'csrf_token': CSRF_TOKEN
375 };
376 };
376 var success = function(o) {
377 var success = function(o) {
377 window.location.reload();
378 window.location.reload();
378 };
379 };
379 ajaxPOST(url, postData, success);
380 ajaxPOST(url, postData, success);
380 };
381 };
381
382
382
383
383 /**
384 /**
384 * Reviewer autocomplete
385 * Reviewer autocomplete
385 */
386 */
386 var ReviewerAutoComplete = function(inputId) {
387 var ReviewerAutoComplete = function(inputId) {
387 $(inputId).autocomplete({
388 $(inputId).autocomplete({
388 serviceUrl: pyroutes.url('user_autocomplete_data'),
389 serviceUrl: pyroutes.url('user_autocomplete_data'),
389 minChars:2,
390 minChars:2,
390 maxHeight:400,
391 maxHeight:400,
391 deferRequestBy: 300, //miliseconds
392 deferRequestBy: 300, //miliseconds
392 showNoSuggestionNotice: true,
393 showNoSuggestionNotice: true,
393 tabDisabled: true,
394 tabDisabled: true,
394 autoSelectFirst: true,
395 autoSelectFirst: true,
395 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
396 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
396 formatResult: autocompleteFormatResult,
397 formatResult: autocompleteFormatResult,
397 lookupFilter: autocompleteFilterResult,
398 lookupFilter: autocompleteFilterResult,
398 onSelect: function(element, data) {
399 onSelect: function(element, data) {
399 var mandatory = false;
400 var mandatory = false;
400 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
401 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
401
402
402 // add whole user groups
403 // add whole user groups
403 if (data.value_type == 'user_group') {
404 if (data.value_type == 'user_group') {
404 reasons.push(_gettext('member of "{0}"').format(data.value_display));
405 reasons.push(_gettext('member of "{0}"').format(data.value_display));
405
406
406 $.each(data.members, function(index, member_data) {
407 $.each(data.members, function(index, member_data) {
407 var reviewer = member_data;
408 var reviewer = member_data;
408 reviewer['user_id'] = member_data['id'];
409 reviewer['user_id'] = member_data['id'];
409 reviewer['gravatar_link'] = member_data['icon_link'];
410 reviewer['gravatar_link'] = member_data['icon_link'];
410 reviewer['user_link'] = member_data['profile_link'];
411 reviewer['user_link'] = member_data['profile_link'];
411 reviewer['rules'] = [];
412 reviewer['rules'] = [];
412 reviewersController.addReviewMember(reviewer, reasons, mandatory);
413 reviewersController.addReviewMember(reviewer, reasons, mandatory);
413 })
414 })
414 }
415 }
415 // add single user
416 // add single user
416 else {
417 else {
417 var reviewer = data;
418 var reviewer = data;
418 reviewer['user_id'] = data['id'];
419 reviewer['user_id'] = data['id'];
419 reviewer['gravatar_link'] = data['icon_link'];
420 reviewer['gravatar_link'] = data['icon_link'];
420 reviewer['user_link'] = data['profile_link'];
421 reviewer['user_link'] = data['profile_link'];
421 reviewer['rules'] = [];
422 reviewer['rules'] = [];
422 reviewersController.addReviewMember(reviewer, reasons, mandatory);
423 reviewersController.addReviewMember(reviewer, reasons, mandatory);
423 }
424 }
424
425
425 $(inputId).val('');
426 $(inputId).val('');
426 }
427 }
427 });
428 });
428 };
429 };
429
430
430
431
431 VersionController = function () {
432 VersionController = function () {
432 var self = this;
433 var self = this;
433 this.$verSource = $('input[name=ver_source]');
434 this.$verSource = $('input[name=ver_source]');
434 this.$verTarget = $('input[name=ver_target]');
435 this.$verTarget = $('input[name=ver_target]');
435 this.$showVersionDiff = $('#show-version-diff');
436 this.$showVersionDiff = $('#show-version-diff');
436
437
437 this.adjustRadioSelectors = function (curNode) {
438 this.adjustRadioSelectors = function (curNode) {
438 var getVal = function (item) {
439 var getVal = function (item) {
439 if (item == 'latest') {
440 if (item == 'latest') {
440 return Number.MAX_SAFE_INTEGER
441 return Number.MAX_SAFE_INTEGER
441 }
442 }
442 else {
443 else {
443 return parseInt(item)
444 return parseInt(item)
444 }
445 }
445 };
446 };
446
447
447 var curVal = getVal($(curNode).val());
448 var curVal = getVal($(curNode).val());
448 var cleared = false;
449 var cleared = false;
449
450
450 $.each(self.$verSource, function (index, value) {
451 $.each(self.$verSource, function (index, value) {
451 var elVal = getVal($(value).val());
452 var elVal = getVal($(value).val());
452
453
453 if (elVal > curVal) {
454 if (elVal > curVal) {
454 if ($(value).is(':checked')) {
455 if ($(value).is(':checked')) {
455 cleared = true;
456 cleared = true;
456 }
457 }
457 $(value).attr('disabled', 'disabled');
458 $(value).attr('disabled', 'disabled');
458 $(value).removeAttr('checked');
459 $(value).removeAttr('checked');
459 $(value).css({'opacity': 0.1});
460 $(value).css({'opacity': 0.1});
460 }
461 }
461 else {
462 else {
462 $(value).css({'opacity': 1});
463 $(value).css({'opacity': 1});
463 $(value).removeAttr('disabled');
464 $(value).removeAttr('disabled');
464 }
465 }
465 });
466 });
466
467
467 if (cleared) {
468 if (cleared) {
468 // if we unchecked an active, set the next one to same loc.
469 // if we unchecked an active, set the next one to same loc.
469 $(this.$verSource).filter('[value={0}]'.format(
470 $(this.$verSource).filter('[value={0}]'.format(
470 curVal)).attr('checked', 'checked');
471 curVal)).attr('checked', 'checked');
471 }
472 }
472
473
473 self.setLockAction(false,
474 self.setLockAction(false,
474 $(curNode).data('verPos'),
475 $(curNode).data('verPos'),
475 $(this.$verSource).filter(':checked').data('verPos')
476 $(this.$verSource).filter(':checked').data('verPos')
476 );
477 );
477 };
478 };
478
479
479
480
480 this.attachVersionListener = function () {
481 this.attachVersionListener = function () {
481 self.$verTarget.change(function (e) {
482 self.$verTarget.change(function (e) {
482 self.adjustRadioSelectors(this)
483 self.adjustRadioSelectors(this)
483 });
484 });
484 self.$verSource.change(function (e) {
485 self.$verSource.change(function (e) {
485 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
486 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
486 });
487 });
487 };
488 };
488
489
489 this.init = function () {
490 this.init = function () {
490
491
491 var curNode = self.$verTarget.filter(':checked');
492 var curNode = self.$verTarget.filter(':checked');
492 self.adjustRadioSelectors(curNode);
493 self.adjustRadioSelectors(curNode);
493 self.setLockAction(true);
494 self.setLockAction(true);
494 self.attachVersionListener();
495 self.attachVersionListener();
495
496
496 };
497 };
497
498
498 this.setLockAction = function (state, selectedVersion, otherVersion) {
499 this.setLockAction = function (state, selectedVersion, otherVersion) {
499 var $showVersionDiff = this.$showVersionDiff;
500 var $showVersionDiff = this.$showVersionDiff;
500
501
501 if (state) {
502 if (state) {
502 $showVersionDiff.attr('disabled', 'disabled');
503 $showVersionDiff.attr('disabled', 'disabled');
503 $showVersionDiff.addClass('disabled');
504 $showVersionDiff.addClass('disabled');
504 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
505 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
505 }
506 }
506 else {
507 else {
507 $showVersionDiff.removeAttr('disabled');
508 $showVersionDiff.removeAttr('disabled');
508 $showVersionDiff.removeClass('disabled');
509 $showVersionDiff.removeClass('disabled');
509
510
510 if (selectedVersion == otherVersion) {
511 if (selectedVersion == otherVersion) {
511 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
512 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
512 } else {
513 } else {
513 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
514 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
514 }
515 }
515 }
516 }
516
517
517 };
518 };
518
519
519 this.showVersionDiff = function () {
520 this.showVersionDiff = function () {
520 var target = self.$verTarget.filter(':checked');
521 var target = self.$verTarget.filter(':checked');
521 var source = self.$verSource.filter(':checked');
522 var source = self.$verSource.filter(':checked');
522
523
523 if (target.val() && source.val()) {
524 if (target.val() && source.val()) {
524 var params = {
525 var params = {
525 'pull_request_id': templateContext.pull_request_data.pull_request_id,
526 'pull_request_id': templateContext.pull_request_data.pull_request_id,
526 'repo_name': templateContext.repo_name,
527 'repo_name': templateContext.repo_name,
527 'version': target.val(),
528 'version': target.val(),
528 'from_version': source.val()
529 'from_version': source.val()
529 };
530 };
530 window.location = pyroutes.url('pullrequest_show', params)
531 window.location = pyroutes.url('pullrequest_show', params)
531 }
532 }
532
533
533 return false;
534 return false;
534 };
535 };
535
536
536 this.toggleVersionView = function (elem) {
537 this.toggleVersionView = function (elem) {
537
538
538 if (this.$showVersionDiff.is(':visible')) {
539 if (this.$showVersionDiff.is(':visible')) {
539 $('.version-pr').hide();
540 $('.version-pr').hide();
540 this.$showVersionDiff.hide();
541 this.$showVersionDiff.hide();
541 $(elem).html($(elem).data('toggleOn'))
542 $(elem).html($(elem).data('toggleOn'))
542 } else {
543 } else {
543 $('.version-pr').show();
544 $('.version-pr').show();
544 this.$showVersionDiff.show();
545 this.$showVersionDiff.show();
545 $(elem).html($(elem).data('toggleOff'))
546 $(elem).html($(elem).data('toggleOff'))
546 }
547 }
547
548
548 return false
549 return false
549 }
550 }
550
551
551 }; No newline at end of file
552 };
This diff has been collapsed as it changes many lines, (4369 lines changed) Show them Hide them
@@ -1,94 +1,4283 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 /**
2 // #
2 * tooltipster http://iamceege.github.io/tooltipster/
3 // # This program is free software: you can redistribute it and/or modify
3 * A rockin' custom tooltip jQuery plugin
4 // # it under the terms of the GNU Affero General Public License, version 3
4 * Developed by Caleb Jacob and Louis Ameline
5 // # (only), as published by the Free Software Foundation.
5 * MIT license
6 // #
6 */
7 // # This program is distributed in the hope that it will be useful,
7 (function (root, factory) {
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 if (typeof define === 'function' && define.amd) {
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // AMD. Register as an anonymous module unless amdModuleId is set
10 // # GNU General Public License for more details.
10 define(["jquery"], function (a0) {
11 // #
11 return (factory(a0));
12 // # You should have received a copy of the GNU Affero General Public License
12 });
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 } else if (typeof exports === 'object') {
14 // #
14 // Node. Does not work with strict CommonJS, but
15 // # This program is dual-licensed. If you wish to learn more about the
15 // only CommonJS-like environments that support module.exports,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // like Node.
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 module.exports = factory(require("jquery"));
18 } else {
19 factory(jQuery);
20 }
21 }(this, function ($) {
22
23 // This file will be UMDified by a build task.
24
25 var defaults = {
26 animation: 'fade',
27 animationDuration: 350,
28 content: null,
29 contentAsHTML: false,
30 contentCloning: false,
31 debug: true,
32 delay: 300,
33 delayTouch: [300, 500],
34 functionInit: null,
35 functionBefore: null,
36 functionReady: null,
37 functionAfter: null,
38 functionFormat: null,
39 IEmin: 6,
40 interactive: false,
41 multiple: false,
42 // will default to document.body, or must be an element positioned at (0, 0)
43 // in the document, typically like the very top views of an app.
44 parent: null,
45 plugins: ['sideTip'],
46 repositionOnScroll: false,
47 restoration: 'none',
48 selfDestruction: true,
49 theme: [],
50 timer: 0,
51 trackerInterval: 500,
52 trackOrigin: false,
53 trackTooltip: false,
54 trigger: 'hover',
55 triggerClose: {
56 click: false,
57 mouseleave: false,
58 originClick: false,
59 scroll: false,
60 tap: false,
61 touchleave: false
62 },
63 triggerOpen: {
64 click: false,
65 mouseenter: false,
66 tap: false,
67 touchstart: false
68 },
69 updateAnimation: 'rotate',
70 zIndex: 9999999
71 },
72 // we'll avoid using the 'window' global as a good practice but npm's
73 // jquery@<2.1.0 package actually requires a 'window' global, so not sure
74 // it's useful at all
75 win = (typeof window != 'undefined') ? window : null,
76 // env will be proxied by the core for plugins to have access its properties
77 env = {
78 // detect if this device can trigger touch events. Better have a false
79 // positive (unused listeners, that's ok) than a false negative.
80 // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
81 // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
82 hasTouchCapability: !!(
83 win
84 && ( 'ontouchstart' in win
85 || (win.DocumentTouch && win.document instanceof win.DocumentTouch)
86 || win.navigator.maxTouchPoints
87 )
88 ),
89 hasTransitions: transitionSupport(),
90 IE: false,
91 // don't set manually, it will be updated by a build task after the manifest
92 semVer: '4.2.7',
93 window: win
94 },
95 core = function() {
96
97 // core variables
98
99 // the core emitters
100 this.__$emitterPrivate = $({});
101 this.__$emitterPublic = $({});
102 this.__instancesLatestArr = [];
103 // collects plugin constructors
104 this.__plugins = {};
105 // proxy env variables for plugins who might use them
106 this._env = env;
107 };
108
109 // core methods
110 core.prototype = {
111
112 /**
113 * A function to proxy the public methods of an object onto another
114 *
115 * @param {object} constructor The constructor to bridge
116 * @param {object} obj The object that will get new methods (an instance or the core)
117 * @param {string} pluginName A plugin name for the console log message
118 * @return {core}
119 * @private
120 */
121 __bridge: function(constructor, obj, pluginName) {
122
123 // if it's not already bridged
124 if (!obj[pluginName]) {
125
126 var fn = function() {};
127 fn.prototype = constructor;
128
129 var pluginInstance = new fn();
130
131 // the _init method has to exist in instance constructors but might be missing
132 // in core constructors
133 if (pluginInstance.__init) {
134 pluginInstance.__init(obj);
135 }
136
137 $.each(constructor, function(methodName, fn) {
138
139 // don't proxy "private" methods, only "protected" and public ones
140 if (methodName.indexOf('__') != 0) {
141
142 // if the method does not exist yet
143 if (!obj[methodName]) {
144
145 obj[methodName] = function() {
146 return pluginInstance[methodName].apply(pluginInstance, Array.prototype.slice.apply(arguments));
147 };
148
149 // remember to which plugin this method corresponds (several plugins may
150 // have methods of the same name, we need to be sure)
151 obj[methodName].bridged = pluginInstance;
152 }
153 else if (defaults.debug) {
154
155 console.log('The '+ methodName +' method of the '+ pluginName
156 +' plugin conflicts with another plugin or native methods');
157 }
158 }
159 });
160
161 obj[pluginName] = pluginInstance;
162 }
163
164 return this;
165 },
166
167 /**
168 * For mockup in Node env if need be, for testing purposes
169 *
170 * @return {core}
171 * @private
172 */
173 __setWindow: function(window) {
174 env.window = window;
175 return this;
176 },
177
178 /**
179 * Returns a ruler, a tool to help measure the size of a tooltip under
180 * various settings. Meant for plugins
181 *
182 * @see Ruler
183 * @return {object} A Ruler instance
184 * @protected
185 */
186 _getRuler: function($tooltip) {
187 return new Ruler($tooltip);
188 },
189
190 /**
191 * For internal use by plugins, if needed
192 *
193 * @return {core}
194 * @protected
195 */
196 _off: function() {
197 this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
198 return this;
199 },
200
201 /**
202 * For internal use by plugins, if needed
203 *
204 * @return {core}
205 * @protected
206 */
207 _on: function() {
208 this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
209 return this;
210 },
211
212 /**
213 * For internal use by plugins, if needed
214 *
215 * @return {core}
216 * @protected
217 */
218 _one: function() {
219 this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
220 return this;
221 },
222
223 /**
224 * Returns (getter) or adds (setter) a plugin
225 *
226 * @param {string|object} plugin Provide a string (in the full form
227 * "namespace.name") to use as as getter, an object to use as a setter
228 * @return {object|core}
229 * @protected
230 */
231 _plugin: function(plugin) {
232
233 var self = this;
234
235 // getter
236 if (typeof plugin == 'string') {
237
238 var pluginName = plugin,
239 p = null;
240
241 // if the namespace is provided, it's easy to search
242 if (pluginName.indexOf('.') > 0) {
243 p = self.__plugins[pluginName];
244 }
245 // otherwise, return the first name that matches
246 else {
247 $.each(self.__plugins, function(i, plugin) {
248
249 if (plugin.name.substring(plugin.name.length - pluginName.length - 1) == '.'+ pluginName) {
250 p = plugin;
251 return false;
252 }
253 });
254 }
255
256 return p;
257 }
258 // setter
259 else {
260
261 // force namespaces
262 if (plugin.name.indexOf('.') < 0) {
263 throw new Error('Plugins must be namespaced');
264 }
265
266 self.__plugins[plugin.name] = plugin;
267
268 // if the plugin has core features
269 if (plugin.core) {
270
271 // bridge non-private methods onto the core to allow new core methods
272 self.__bridge(plugin.core, self, plugin.name);
273 }
274
275 return this;
276 }
277 },
278
279 /**
280 * Trigger events on the core emitters
281 *
282 * @returns {core}
283 * @protected
284 */
285 _trigger: function() {
286
287 var args = Array.prototype.slice.apply(arguments);
288
289 if (typeof args[0] == 'string') {
290 args[0] = { type: args[0] };
291 }
292
293 // note: the order of emitters matters
294 this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
295 this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
296
297 return this;
298 },
299
300 /**
301 * Returns instances of all tooltips in the page or an a given element
302 *
303 * @param {string|HTML object collection} selector optional Use this
304 * parameter to restrict the set of objects that will be inspected
305 * for the retrieval of instances. By default, all instances in the
306 * page are returned.
307 * @return {array} An array of instance objects
308 * @public
309 */
310 instances: function(selector) {
311
312 var instances = [],
313 sel = selector || '.tooltipstered';
314
315 $(sel).each(function() {
316
317 var $this = $(this),
318 ns = $this.data('tooltipster-ns');
319
320 if (ns) {
321
322 $.each(ns, function(i, namespace) {
323 instances.push($this.data(namespace));
324 });
325 }
326 });
327
328 return instances;
329 },
330
331 /**
332 * Returns the Tooltipster objects generated by the last initializing call
333 *
334 * @return {array} An array of instance objects
335 * @public
336 */
337 instancesLatest: function() {
338 return this.__instancesLatestArr;
339 },
340
341 /**
342 * For public use only, not to be used by plugins (use ::_off() instead)
343 *
344 * @return {core}
345 * @public
346 */
347 off: function() {
348 this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
349 return this;
350 },
351
352 /**
353 * For public use only, not to be used by plugins (use ::_on() instead)
354 *
355 * @return {core}
356 * @public
357 */
358 on: function() {
359 this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
360 return this;
361 },
362
363 /**
364 * For public use only, not to be used by plugins (use ::_one() instead)
365 *
366 * @return {core}
367 * @public
368 */
369 one: function() {
370 this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
371 return this;
372 },
373
374 /**
375 * Returns all HTML elements which have one or more tooltips
376 *
377 * @param {string} selector optional Use this to restrict the results
378 * to the descendants of an element
379 * @return {array} An array of HTML elements
380 * @public
381 */
382 origins: function(selector) {
383
384 var sel = selector ?
385 selector +' ' :
386 '';
387
388 return $(sel +'.tooltipstered').toArray();
389 },
390
391 /**
392 * Change default options for all future instances
393 *
394 * @param {object} d The options that should be made defaults
395 * @return {core}
396 * @public
397 */
398 setDefaults: function(d) {
399 $.extend(defaults, d);
400 return this;
401 },
402
403 /**
404 * For users to trigger their handlers on the public emitter
405 *
406 * @returns {core}
407 * @public
408 */
409 triggerHandler: function() {
410 this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
411 return this;
412 }
413 };
414
415 // $.tooltipster will be used to call core methods
416 $.tooltipster = new core();
417
418 // the Tooltipster instance class (mind the capital T)
419 $.Tooltipster = function(element, options) {
420
421 // list of instance variables
422
423 // stack of custom callbacks provided as parameters to API methods
424 this.__callbacks = {
425 close: [],
426 open: []
427 };
428 // the schedule time of DOM removal
429 this.__closingTime;
430 // this will be the user content shown in the tooltip. A capital "C" is used
431 // because there is also a method called content()
432 this.__Content;
433 // for the size tracker
434 this.__contentBcr;
435 // to disable the tooltip after destruction
436 this.__destroyed = false;
437 // we can't emit directly on the instance because if a method with the same
438 // name as the event exists, it will be called by jQuery. Se we use a plain
439 // object as emitter. This emitter is for internal use by plugins,
440 // if needed.
441 this.__$emitterPrivate = $({});
442 // this emitter is for the user to listen to events without risking to mess
443 // with our internal listeners
444 this.__$emitterPublic = $({});
445 this.__enabled = true;
446 // the reference to the gc interval
447 this.__garbageCollector;
448 // various position and size data recomputed before each repositioning
449 this.__Geometry;
450 // the tooltip position, saved after each repositioning by a plugin
451 this.__lastPosition;
452 // a unique namespace per instance
453 this.__namespace = 'tooltipster-'+ Math.round(Math.random()*1000000);
454 this.__options;
455 // will be used to support origins in scrollable areas
456 this.__$originParents;
457 this.__pointerIsOverOrigin = false;
458 // to remove themes if needed
459 this.__previousThemes = [];
460 // the state can be either: appearing, stable, disappearing, closed
461 this.__state = 'closed';
462 // timeout references
463 this.__timeouts = {
464 close: [],
465 open: null
466 };
467 // store touch events to be able to detect emulated mouse events
468 this.__touchEvents = [];
469 // the reference to the tracker interval
470 this.__tracker = null;
471 // the element to which this tooltip is associated
472 this._$origin;
473 // this will be the tooltip element (jQuery wrapped HTML element).
474 // It's the job of a plugin to create it and append it to the DOM
475 this._$tooltip;
476
477 // launch
478 this.__init(element, options);
479 };
480
481 $.Tooltipster.prototype = {
482
483 /**
484 * @param origin
485 * @param options
486 * @private
487 */
488 __init: function(origin, options) {
489
490 var self = this;
491
492 self._$origin = $(origin);
493 self.__options = $.extend(true, {}, defaults, options);
494
495 // some options may need to be reformatted
496 self.__optionsFormat();
497
498 // don't run on old IE if asked no to
499 if ( !env.IE
500 || env.IE >= self.__options.IEmin
501 ) {
502
503 // note: the content is null (empty) by default and can stay that
504 // way if the plugin remains initialized but not fed any content. The
505 // tooltip will just not appear.
506
507 // let's save the initial value of the title attribute for later
508 // restoration if need be.
509 var initialTitle = null;
510
511 // it will already have been saved in case of multiple tooltips
512 if (self._$origin.data('tooltipster-initialTitle') === undefined) {
513
514 initialTitle = self._$origin.attr('title');
515
516 // we do not want initialTitle to be "undefined" because
517 // of how jQuery's .data() method works
518 if (initialTitle === undefined) initialTitle = null;
519
520 self._$origin.data('tooltipster-initialTitle', initialTitle);
521 }
522
523 // If content is provided in the options, it has precedence over the
524 // title attribute.
525 // Note: an empty string is considered content, only 'null' represents
526 // the absence of content.
527 // Also, an existing title="" attribute will result in an empty string
528 // content
529 if (self.__options.content !== null) {
530 self.__contentSet(self.__options.content);
531 }
532 else {
533
534 var selector = self._$origin.attr('data-tooltip-content'),
535 $el;
536
537 if (selector){
538 $el = $(selector);
539 }
540
541 if ($el && $el[0]) {
542 self.__contentSet($el.first());
543 }
544 else {
545 self.__contentSet(initialTitle);
546 }
547 }
548
549 self._$origin
550 // strip the title off of the element to prevent the default tooltips
551 // from popping up
552 .removeAttr('title')
553 // to be able to find all instances on the page later (upon window
554 // events in particular)
555 .addClass('tooltipstered');
556
557 // set listeners on the origin
558 self.__prepareOrigin();
559
560 // set the garbage collector
561 self.__prepareGC();
562
563 // init plugins
564 $.each(self.__options.plugins, function(i, pluginName) {
565 self._plug(pluginName);
566 });
567
568 // to detect swiping
569 if (env.hasTouchCapability) {
570 $(env.window.document.body).on('touchmove.'+ self.__namespace +'-triggerOpen', function(event) {
571 self._touchRecordEvent(event);
572 });
573 }
574
575 self
576 // prepare the tooltip when it gets created. This event must
577 // be fired by a plugin
578 ._on('created', function() {
579 self.__prepareTooltip();
580 })
581 // save position information when it's sent by a plugin
582 ._on('repositioned', function(e) {
583 self.__lastPosition = e.position;
584 });
585 }
586 else {
587 self.__options.disabled = true;
588 }
589 },
590
591 /**
592 * Insert the content into the appropriate HTML element of the tooltip
593 *
594 * @returns {self}
595 * @private
596 */
597 __contentInsert: function() {
598
599 var self = this,
600 $el = self._$tooltip.find('.tooltipster-content'),
601 formattedContent = self.__Content,
602 format = function(content) {
603 formattedContent = content;
604 };
605
606 self._trigger({
607 type: 'format',
608 content: self.__Content,
609 format: format
610 });
611
612 if (self.__options.functionFormat) {
613
614 formattedContent = self.__options.functionFormat.call(
615 self,
616 self,
617 { origin: self._$origin[0] },
618 self.__Content
619 );
620 }
621
622 if (typeof formattedContent === 'string' && !self.__options.contentAsHTML) {
623 $el.text(formattedContent);
624 }
625 else {
626 $el
627 .empty()
628 .append(formattedContent);
629 }
630
631 return self;
632 },
633
634 /**
635 * Save the content, cloning it beforehand if need be
636 *
637 * @param content
638 * @returns {self}
639 * @private
640 */
641 __contentSet: function(content) {
642
643 // clone if asked. Cloning the object makes sure that each instance has its
644 // own version of the content (in case a same object were provided for several
645 // instances)
646 // reminder: typeof null === object
647 if (content instanceof $ && this.__options.contentCloning) {
648 content = content.clone(true);
649 }
650
651 this.__Content = content;
652
653 this._trigger({
654 type: 'updated',
655 content: content
656 });
657
658 return this;
659 },
660
661 /**
662 * Error message about a method call made after destruction
663 *
664 * @private
665 */
666 __destroyError: function() {
667 throw new Error('This tooltip has been destroyed and cannot execute your method call.');
668 },
669
670 /**
671 * Gather all information about dimensions and available space,
672 * called before every repositioning
673 *
674 * @private
675 * @returns {object}
676 */
677 __geometry: function() {
678
679 var self = this,
680 $target = self._$origin,
681 originIsArea = self._$origin.is('area');
682
683 // if this._$origin is a map area, the target we'll need
684 // the dimensions of is actually the image using the map,
685 // not the area itself
686 if (originIsArea) {
687
688 var mapName = self._$origin.parent().attr('name');
689
690 $target = $('img[usemap="#'+ mapName +'"]');
691 }
692
693 var bcr = $target[0].getBoundingClientRect(),
694 $document = $(env.window.document),
695 $window = $(env.window),
696 $parent = $target,
697 // some useful properties of important elements
698 geo = {
699 // available space for the tooltip, see down below
700 available: {
701 document: null,
702 window: null
703 },
704 document: {
705 size: {
706 height: $document.height(),
707 width: $document.width()
708 }
709 },
710 window: {
711 scroll: {
712 // the second ones are for IE compatibility
713 left: env.window.scrollX || env.window.document.documentElement.scrollLeft,
714 top: env.window.scrollY || env.window.document.documentElement.scrollTop
715 },
716 size: {
717 height: $window.height(),
718 width: $window.width()
719 }
720 },
721 origin: {
722 // the origin has a fixed lineage if itself or one of its
723 // ancestors has a fixed position
724 fixedLineage: false,
725 // relative to the document
726 offset: {},
727 size: {
728 height: bcr.bottom - bcr.top,
729 width: bcr.right - bcr.left
730 },
731 usemapImage: originIsArea ? $target[0] : null,
732 // relative to the window
733 windowOffset: {
734 bottom: bcr.bottom,
735 left: bcr.left,
736 right: bcr.right,
737 top: bcr.top
738 }
739 }
740 },
741 geoFixed = false;
742
743 // if the element is a map area, some properties may need
744 // to be recalculated
745 if (originIsArea) {
746
747 var shape = self._$origin.attr('shape'),
748 coords = self._$origin.attr('coords');
749
750 if (coords) {
751
752 coords = coords.split(',');
753
754 $.map(coords, function(val, i) {
755 coords[i] = parseInt(val);
756 });
757 }
758
759 // if the image itself is the area, nothing more to do
760 if (shape != 'default') {
761
762 switch(shape) {
763
764 case 'circle':
765
766 var circleCenterLeft = coords[0],
767 circleCenterTop = coords[1],
768 circleRadius = coords[2],
769 areaTopOffset = circleCenterTop - circleRadius,
770 areaLeftOffset = circleCenterLeft - circleRadius;
771
772 geo.origin.size.height = circleRadius * 2;
773 geo.origin.size.width = geo.origin.size.height;
774
775 geo.origin.windowOffset.left += areaLeftOffset;
776 geo.origin.windowOffset.top += areaTopOffset;
777
778 break;
779
780 case 'rect':
781
782 var areaLeft = coords[0],
783 areaTop = coords[1],
784 areaRight = coords[2],
785 areaBottom = coords[3];
786
787 geo.origin.size.height = areaBottom - areaTop;
788 geo.origin.size.width = areaRight - areaLeft;
789
790 geo.origin.windowOffset.left += areaLeft;
791 geo.origin.windowOffset.top += areaTop;
792
793 break;
794
795 case 'poly':
796
797 var areaSmallestX = 0,
798 areaSmallestY = 0,
799 areaGreatestX = 0,
800 areaGreatestY = 0,
801 arrayAlternate = 'even';
802
803 for (var i = 0; i < coords.length; i++) {
804
805 var areaNumber = coords[i];
806
807 if (arrayAlternate == 'even') {
808
809 if (areaNumber > areaGreatestX) {
810
811 areaGreatestX = areaNumber;
812
813 if (i === 0) {
814 areaSmallestX = areaGreatestX;
815 }
816 }
817
818 if (areaNumber < areaSmallestX) {
819 areaSmallestX = areaNumber;
820 }
821
822 arrayAlternate = 'odd';
823 }
824 else {
825 if (areaNumber > areaGreatestY) {
826
827 areaGreatestY = areaNumber;
828
829 if (i == 1) {
830 areaSmallestY = areaGreatestY;
831 }
832 }
833
834 if (areaNumber < areaSmallestY) {
835 areaSmallestY = areaNumber;
836 }
837
838 arrayAlternate = 'even';
839 }
840 }
841
842 geo.origin.size.height = areaGreatestY - areaSmallestY;
843 geo.origin.size.width = areaGreatestX - areaSmallestX;
844
845 geo.origin.windowOffset.left += areaSmallestX;
846 geo.origin.windowOffset.top += areaSmallestY;
847
848 break;
849 }
850 }
851 }
852
853 // user callback through an event
854 var edit = function(r) {
855 geo.origin.size.height = r.height,
856 geo.origin.windowOffset.left = r.left,
857 geo.origin.windowOffset.top = r.top,
858 geo.origin.size.width = r.width
859 };
860
861 self._trigger({
862 type: 'geometry',
863 edit: edit,
864 geometry: {
865 height: geo.origin.size.height,
866 left: geo.origin.windowOffset.left,
867 top: geo.origin.windowOffset.top,
868 width: geo.origin.size.width
869 }
870 });
871
872 // calculate the remaining properties with what we got
873
874 geo.origin.windowOffset.right = geo.origin.windowOffset.left + geo.origin.size.width;
875 geo.origin.windowOffset.bottom = geo.origin.windowOffset.top + geo.origin.size.height;
876
877 geo.origin.offset.left = geo.origin.windowOffset.left + geo.window.scroll.left;
878 geo.origin.offset.top = geo.origin.windowOffset.top + geo.window.scroll.top;
879 geo.origin.offset.bottom = geo.origin.offset.top + geo.origin.size.height;
880 geo.origin.offset.right = geo.origin.offset.left + geo.origin.size.width;
881
882 // the space that is available to display the tooltip relatively to the document
883 geo.available.document = {
884 bottom: {
885 height: geo.document.size.height - geo.origin.offset.bottom,
886 width: geo.document.size.width
887 },
888 left: {
889 height: geo.document.size.height,
890 width: geo.origin.offset.left
891 },
892 right: {
893 height: geo.document.size.height,
894 width: geo.document.size.width - geo.origin.offset.right
895 },
896 top: {
897 height: geo.origin.offset.top,
898 width: geo.document.size.width
899 }
900 };
901
902 // the space that is available to display the tooltip relatively to the viewport
903 // (the resulting values may be negative if the origin overflows the viewport)
904 geo.available.window = {
905 bottom: {
906 // the inner max is here to make sure the available height is no bigger
907 // than the viewport height (when the origin is off screen at the top).
908 // The outer max just makes sure that the height is not negative (when
909 // the origin overflows at the bottom).
910 height: Math.max(geo.window.size.height - Math.max(geo.origin.windowOffset.bottom, 0), 0),
911 width: geo.window.size.width
912 },
913 left: {
914 height: geo.window.size.height,
915 width: Math.max(geo.origin.windowOffset.left, 0)
916 },
917 right: {
918 height: geo.window.size.height,
919 width: Math.max(geo.window.size.width - Math.max(geo.origin.windowOffset.right, 0), 0)
920 },
921 top: {
922 height: Math.max(geo.origin.windowOffset.top, 0),
923 width: geo.window.size.width
924 }
925 };
926
927 while ($parent[0].tagName.toLowerCase() != 'html') {
928
929 if ($parent.css('position') == 'fixed') {
930 geo.origin.fixedLineage = true;
931 break;
932 }
933
934 $parent = $parent.parent();
935 }
936
937 return geo;
938 },
939
940 /**
941 * Some options may need to be formated before being used
942 *
943 * @returns {self}
944 * @private
945 */
946 __optionsFormat: function() {
947
948 if (typeof this.__options.animationDuration == 'number') {
949 this.__options.animationDuration = [this.__options.animationDuration, this.__options.animationDuration];
950 }
951
952 if (typeof this.__options.delay == 'number') {
953 this.__options.delay = [this.__options.delay, this.__options.delay];
954 }
955
956 if (typeof this.__options.delayTouch == 'number') {
957 this.__options.delayTouch = [this.__options.delayTouch, this.__options.delayTouch];
958 }
959
960 if (typeof this.__options.theme == 'string') {
961 this.__options.theme = [this.__options.theme];
962 }
963
964 // determine the future parent
965 if (this.__options.parent === null) {
966 this.__options.parent = $(env.window.document.body);
967 }
968 else if (typeof this.__options.parent == 'string') {
969 this.__options.parent = $(this.__options.parent);
970 }
971
972 if (this.__options.trigger == 'hover') {
973
974 this.__options.triggerOpen = {
975 mouseenter: true,
976 touchstart: true
977 };
978
979 this.__options.triggerClose = {
980 mouseleave: true,
981 originClick: true,
982 touchleave: true
983 };
984 }
985 else if (this.__options.trigger == 'click') {
986
987 this.__options.triggerOpen = {
988 click: true,
989 tap: true
990 };
991
992 this.__options.triggerClose = {
993 click: true,
994 tap: true
995 };
996 }
997
998 // for the plugins
999 this._trigger('options');
1000
1001 return this;
1002 },
1003
1004 /**
1005 * Schedules or cancels the garbage collector task
1006 *
1007 * @returns {self}
1008 * @private
1009 */
1010 __prepareGC: function() {
1011
1012 var self = this;
1013
1014 // in case the selfDestruction option has been changed by a method call
1015 if (self.__options.selfDestruction) {
1016
1017 // the GC task
1018 self.__garbageCollector = setInterval(function() {
1019
1020 var now = new Date().getTime();
1021
1022 // forget the old events
1023 self.__touchEvents = $.grep(self.__touchEvents, function(event, i) {
1024 // 1 minute
1025 return now - event.time > 60000;
1026 });
1027
1028 // auto-destruct if the origin is gone
1029 if (!bodyContains(self._$origin)) {
1030
1031 self.close(function(){
1032 self.destroy();
1033 });
1034 }
1035 }, 20000);
1036 }
1037 else {
1038 clearInterval(self.__garbageCollector);
1039 }
1040
1041 return self;
1042 },
1043
1044 /**
1045 * Sets listeners on the origin if the open triggers require them.
1046 * Unlike the listeners set at opening time, these ones
1047 * remain even when the tooltip is closed. It has been made a
1048 * separate method so it can be called when the triggers are
1049 * changed in the options. Closing is handled in _open()
1050 * because of the bindings that may be needed on the tooltip
1051 * itself
1052 *
1053 * @returns {self}
1054 * @private
1055 */
1056 __prepareOrigin: function() {
1057
1058 var self = this;
1059
1060 // in case we're resetting the triggers
1061 self._$origin.off('.'+ self.__namespace +'-triggerOpen');
1062
1063 // if the device is touch capable, even if only mouse triggers
1064 // are asked, we need to listen to touch events to know if the mouse
1065 // events are actually emulated (so we can ignore them)
1066 if (env.hasTouchCapability) {
1067
1068 self._$origin.on(
1069 'touchstart.'+ self.__namespace +'-triggerOpen ' +
1070 'touchend.'+ self.__namespace +'-triggerOpen ' +
1071 'touchcancel.'+ self.__namespace +'-triggerOpen',
1072 function(event){
1073 self._touchRecordEvent(event);
1074 }
1075 );
1076 }
1077
1078 // mouse click and touch tap work the same way
1079 if ( self.__options.triggerOpen.click
1080 || (self.__options.triggerOpen.tap && env.hasTouchCapability)
1081 ) {
1082
1083 var eventNames = '';
1084 if (self.__options.triggerOpen.click) {
1085 eventNames += 'click.'+ self.__namespace +'-triggerOpen ';
1086 }
1087 if (self.__options.triggerOpen.tap && env.hasTouchCapability) {
1088 eventNames += 'touchend.'+ self.__namespace +'-triggerOpen';
1089 }
1090
1091 self._$origin.on(eventNames, function(event) {
1092 if (self._touchIsMeaningfulEvent(event)) {
1093 self._open(event);
1094 }
1095 });
1096 }
1097
1098 // mouseenter and touch start work the same way
1099 if ( self.__options.triggerOpen.mouseenter
1100 || (self.__options.triggerOpen.touchstart && env.hasTouchCapability)
1101 ) {
1102
1103 var eventNames = '';
1104 if (self.__options.triggerOpen.mouseenter) {
1105 eventNames += 'mouseenter.'+ self.__namespace +'-triggerOpen ';
1106 }
1107 if (self.__options.triggerOpen.touchstart && env.hasTouchCapability) {
1108 eventNames += 'touchstart.'+ self.__namespace +'-triggerOpen';
1109 }
1110
1111 self._$origin.on(eventNames, function(event) {
1112 if ( self._touchIsTouchEvent(event)
1113 || !self._touchIsEmulatedEvent(event)
1114 ) {
1115 self.__pointerIsOverOrigin = true;
1116 self._openShortly(event);
1117 }
1118 });
1119 }
1120
1121 // info for the mouseleave/touchleave close triggers when they use a delay
1122 if ( self.__options.triggerClose.mouseleave
1123 || (self.__options.triggerClose.touchleave && env.hasTouchCapability)
1124 ) {
1125
1126 var eventNames = '';
1127 if (self.__options.triggerClose.mouseleave) {
1128 eventNames += 'mouseleave.'+ self.__namespace +'-triggerOpen ';
1129 }
1130 if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1131 eventNames += 'touchend.'+ self.__namespace +'-triggerOpen touchcancel.'+ self.__namespace +'-triggerOpen';
1132 }
1133
1134 self._$origin.on(eventNames, function(event) {
1135
1136 if (self._touchIsMeaningfulEvent(event)) {
1137 self.__pointerIsOverOrigin = false;
1138 }
1139 });
1140 }
1141
1142 return self;
1143 },
1144
1145 /**
1146 * Do the things that need to be done only once after the tooltip
1147 * HTML element it has been created. It has been made a separate
1148 * method so it can be called when options are changed. Remember
1149 * that the tooltip may actually exist in the DOM before it is
1150 * opened, and present after it has been closed: it's the display
1151 * plugin that takes care of handling it.
1152 *
1153 * @returns {self}
1154 * @private
1155 */
1156 __prepareTooltip: function() {
1157
1158 var self = this,
1159 p = self.__options.interactive ? 'auto' : '';
1160
1161 // this will be useful to know quickly if the tooltip is in
1162 // the DOM or not
1163 self._$tooltip
1164 .attr('id', self.__namespace)
1165 .css({
1166 // pointer events
1167 'pointer-events': p,
1168 zIndex: self.__options.zIndex
1169 });
1170
1171 // themes
1172 // remove the old ones and add the new ones
1173 $.each(self.__previousThemes, function(i, theme) {
1174 self._$tooltip.removeClass(theme);
1175 });
1176 $.each(self.__options.theme, function(i, theme) {
1177 self._$tooltip.addClass(theme);
1178 });
1179
1180 self.__previousThemes = $.merge([], self.__options.theme);
1181
1182 return self;
1183 },
1184
1185 /**
1186 * Handles the scroll on any of the parents of the origin (when the
1187 * tooltip is open)
1188 *
1189 * @param {object} event
1190 * @returns {self}
1191 * @private
1192 */
1193 __scrollHandler: function(event) {
1194
1195 var self = this;
1196
1197 if (self.__options.triggerClose.scroll) {
1198 self._close(event);
1199 }
1200 else {
1201
1202 // if the origin or tooltip have been removed: do nothing, the tracker will
1203 // take care of it later
1204 if (bodyContains(self._$origin) && bodyContains(self._$tooltip)) {
1205
1206 var geo = null;
1207
1208 // if the scroll happened on the window
1209 if (event.target === env.window.document) {
1210
1211 // if the origin has a fixed lineage, window scroll will have no
1212 // effect on its position nor on the position of the tooltip
1213 if (!self.__Geometry.origin.fixedLineage) {
1214
1215 // we don't need to do anything unless repositionOnScroll is true
1216 // because the tooltip will already have moved with the window
1217 // (and of course with the origin)
1218 if (self.__options.repositionOnScroll) {
1219 self.reposition(event);
1220 }
1221 }
1222 }
1223 // if the scroll happened on another parent of the tooltip, it means
1224 // that it's in a scrollable area and now needs to have its position
1225 // adjusted or recomputed, depending ont the repositionOnScroll
1226 // option. Also, if the origin is partly hidden due to a parent that
1227 // hides its overflow, we'll just hide (not close) the tooltip.
1228 else {
1229
1230 geo = self.__geometry();
1231
1232 var overflows = false;
1233
1234 // a fixed position origin is not affected by the overflow hiding
1235 // of a parent
1236 if (self._$origin.css('position') != 'fixed') {
1237
1238 self.__$originParents.each(function(i, el) {
1239
1240 var $el = $(el),
1241 overflowX = $el.css('overflow-x'),
1242 overflowY = $el.css('overflow-y');
1243
1244 if (overflowX != 'visible' || overflowY != 'visible') {
1245
1246 var bcr = el.getBoundingClientRect();
1247
1248 if (overflowX != 'visible') {
1249
1250 if ( geo.origin.windowOffset.left < bcr.left
1251 || geo.origin.windowOffset.right > bcr.right
1252 ) {
1253 overflows = true;
1254 return false;
1255 }
1256 }
1257
1258 if (overflowY != 'visible') {
1259
1260 if ( geo.origin.windowOffset.top < bcr.top
1261 || geo.origin.windowOffset.bottom > bcr.bottom
1262 ) {
1263 overflows = true;
1264 return false;
1265 }
1266 }
1267 }
1268
1269 // no need to go further if fixed, for the same reason as above
1270 if ($el.css('position') == 'fixed') {
1271 return false;
1272 }
1273 });
1274 }
1275
1276 if (overflows) {
1277 self._$tooltip.css('visibility', 'hidden');
1278 }
1279 else {
1280
1281 self._$tooltip.css('visibility', 'visible');
1282
1283 // reposition
1284 if (self.__options.repositionOnScroll) {
1285 self.reposition(event);
1286 }
1287 // or just adjust offset
1288 else {
1289
1290 // we have to use offset and not windowOffset because this way,
1291 // only the scroll distance of the scrollable areas are taken into
1292 // account (the scrolltop value of the main window must be
1293 // ignored since the tooltip already moves with it)
1294 var offsetLeft = geo.origin.offset.left - self.__Geometry.origin.offset.left,
1295 offsetTop = geo.origin.offset.top - self.__Geometry.origin.offset.top;
1296
1297 // add the offset to the position initially computed by the display plugin
1298 self._$tooltip.css({
1299 left: self.__lastPosition.coord.left + offsetLeft,
1300 top: self.__lastPosition.coord.top + offsetTop
1301 });
1302 }
1303 }
1304 }
1305
1306 self._trigger({
1307 type: 'scroll',
1308 event: event,
1309 geo: geo
1310 });
1311 }
1312 }
1313
1314 return self;
1315 },
1316
1317 /**
1318 * Changes the state of the tooltip
1319 *
1320 * @param {string} state
1321 * @returns {self}
1322 * @private
1323 */
1324 __stateSet: function(state) {
1325
1326 this.__state = state;
1327
1328 this._trigger({
1329 type: 'state',
1330 state: state
1331 });
1332
1333 return this;
1334 },
1335
1336 /**
1337 * Clear appearance timeouts
1338 *
1339 * @returns {self}
1340 * @private
1341 */
1342 __timeoutsClear: function() {
1343
1344 // there is only one possible open timeout: the delayed opening
1345 // when the mouseenter/touchstart open triggers are used
1346 clearTimeout(this.__timeouts.open);
1347 this.__timeouts.open = null;
1348
1349 // ... but several close timeouts: the delayed closing when the
1350 // mouseleave close trigger is used and the timer option
1351 $.each(this.__timeouts.close, function(i, timeout) {
1352 clearTimeout(timeout);
1353 });
1354 this.__timeouts.close = [];
1355
1356 return this;
1357 },
1358
1359 /**
1360 * Start the tracker that will make checks at regular intervals
1361 *
1362 * @returns {self}
1363 * @private
1364 */
1365 __trackerStart: function() {
1366
1367 var self = this,
1368 $content = self._$tooltip.find('.tooltipster-content');
1369
1370 // get the initial content size
1371 if (self.__options.trackTooltip) {
1372 self.__contentBcr = $content[0].getBoundingClientRect();
1373 }
1374
1375 self.__tracker = setInterval(function() {
1376
1377 // if the origin or tooltip elements have been removed.
1378 // Note: we could destroy the instance now if the origin has
1379 // been removed but we'll leave that task to our garbage collector
1380 if (!bodyContains(self._$origin) || !bodyContains(self._$tooltip)) {
1381 self._close();
1382 }
1383 // if everything is alright
1384 else {
1385
1386 // compare the former and current positions of the origin to reposition
1387 // the tooltip if need be
1388 if (self.__options.trackOrigin) {
1389
1390 var g = self.__geometry(),
1391 identical = false;
1392
1393 // compare size first (a change requires repositioning too)
1394 if (areEqual(g.origin.size, self.__Geometry.origin.size)) {
1395
1396 // for elements that have a fixed lineage (see __geometry()), we track the
1397 // top and left properties (relative to window)
1398 if (self.__Geometry.origin.fixedLineage) {
1399 if (areEqual(g.origin.windowOffset, self.__Geometry.origin.windowOffset)) {
1400 identical = true;
1401 }
1402 }
1403 // otherwise, track total offset (relative to document)
1404 else {
1405 if (areEqual(g.origin.offset, self.__Geometry.origin.offset)) {
1406 identical = true;
1407 }
1408 }
1409 }
1410
1411 if (!identical) {
1412
1413 // close the tooltip when using the mouseleave close trigger
1414 // (see https://github.com/iamceege/tooltipster/pull/253)
1415 if (self.__options.triggerClose.mouseleave) {
1416 self._close();
1417 }
1418 else {
1419 self.reposition();
1420 }
1421 }
1422 }
1423
1424 if (self.__options.trackTooltip) {
1425
1426 var currentBcr = $content[0].getBoundingClientRect();
1427
1428 if ( currentBcr.height !== self.__contentBcr.height
1429 || currentBcr.width !== self.__contentBcr.width
1430 ) {
1431 self.reposition();
1432 self.__contentBcr = currentBcr;
1433 }
1434 }
1435 }
1436 }, self.__options.trackerInterval);
1437
1438 return self;
1439 },
1440
1441 /**
1442 * Closes the tooltip (after the closing delay)
1443 *
1444 * @param event
1445 * @param callback
1446 * @param force Set to true to override a potential refusal of the user's function
1447 * @returns {self}
1448 * @protected
1449 */
1450 _close: function(event, callback, force) {
1451
1452 var self = this,
1453 ok = true;
1454
1455 self._trigger({
1456 type: 'close',
1457 event: event,
1458 stop: function() {
1459 ok = false;
1460 }
1461 });
1462
1463 // a destroying tooltip (force == true) may not refuse to close
1464 if (ok || force) {
1465
1466 // save the method custom callback and cancel any open method custom callbacks
1467 if (callback) self.__callbacks.close.push(callback);
1468 self.__callbacks.open = [];
1469
1470 // clear open/close timeouts
1471 self.__timeoutsClear();
1472
1473 var finishCallbacks = function() {
1474
1475 // trigger any close method custom callbacks and reset them
1476 $.each(self.__callbacks.close, function(i,c) {
1477 c.call(self, self, {
1478 event: event,
1479 origin: self._$origin[0]
1480 });
1481 });
1482
1483 self.__callbacks.close = [];
1484 };
1485
1486 if (self.__state != 'closed') {
1487
1488 var necessary = true,
1489 d = new Date(),
1490 now = d.getTime(),
1491 newClosingTime = now + self.__options.animationDuration[1];
1492
1493 // the tooltip may already already be disappearing, but if a new
1494 // call to close() is made after the animationDuration was changed
1495 // to 0 (for example), we ought to actually close it sooner than
1496 // previously scheduled. In that case it should be noted that the
1497 // browser will not adapt the animation duration to the new
1498 // animationDuration that was set after the start of the closing
1499 // animation.
1500 // Note: the same thing could be considered at opening, but is not
1501 // really useful since the tooltip is actually opened immediately
1502 // upon a call to _open(). Since it would not make the opening
1503 // animation finish sooner, its sole impact would be to trigger the
1504 // state event and the open callbacks sooner than the actual end of
1505 // the opening animation, which is not great.
1506 if (self.__state == 'disappearing') {
1507
1508 if ( newClosingTime > self.__closingTime
1509 // in case closing is actually overdue because the script
1510 // execution was suspended. See #679
1511 && self.__options.animationDuration[1] > 0
1512 ) {
1513 necessary = false;
1514 }
1515 }
1516
1517 if (necessary) {
1518
1519 self.__closingTime = newClosingTime;
1520
1521 if (self.__state != 'disappearing') {
1522 self.__stateSet('disappearing');
1523 }
1524
1525 var finish = function() {
1526
1527 // stop the tracker
1528 clearInterval(self.__tracker);
1529
1530 // a "beforeClose" option has been asked several times but would
1531 // probably useless since the content element is still accessible
1532 // via ::content(), and because people can always use listeners
1533 // inside their content to track what's going on. For the sake of
1534 // simplicity, this has been denied. Bur for the rare people who
1535 // really need the option (for old browsers or for the case where
1536 // detaching the content is actually destructive, for file or
1537 // password inputs for example), this event will do the work.
1538 self._trigger({
1539 type: 'closing',
1540 event: event
1541 });
1542
1543 // unbind listeners which are no longer needed
1544
1545 self._$tooltip
1546 .off('.'+ self.__namespace +'-triggerClose')
1547 .removeClass('tooltipster-dying');
1548
1549 // orientationchange, scroll and resize listeners
1550 $(env.window).off('.'+ self.__namespace +'-triggerClose');
1551
1552 // scroll listeners
1553 self.__$originParents.each(function(i, el) {
1554 $(el).off('scroll.'+ self.__namespace +'-triggerClose');
1555 });
1556 // clear the array to prevent memory leaks
1557 self.__$originParents = null;
1558
1559 $(env.window.document.body).off('.'+ self.__namespace +'-triggerClose');
1560
1561 self._$origin.off('.'+ self.__namespace +'-triggerClose');
1562
1563 self._off('dismissable');
1564
1565 // a plugin that would like to remove the tooltip from the
1566 // DOM when closed should bind on this
1567 self.__stateSet('closed');
1568
1569 // trigger event
1570 self._trigger({
1571 type: 'after',
1572 event: event
1573 });
1574
1575 // call our constructor custom callback function
1576 if (self.__options.functionAfter) {
1577 self.__options.functionAfter.call(self, self, {
1578 event: event,
1579 origin: self._$origin[0]
1580 });
1581 }
1582
1583 // call our method custom callbacks functions
1584 finishCallbacks();
1585 };
1586
1587 if (env.hasTransitions) {
1588
1589 self._$tooltip.css({
1590 '-moz-animation-duration': self.__options.animationDuration[1] + 'ms',
1591 '-ms-animation-duration': self.__options.animationDuration[1] + 'ms',
1592 '-o-animation-duration': self.__options.animationDuration[1] + 'ms',
1593 '-webkit-animation-duration': self.__options.animationDuration[1] + 'ms',
1594 'animation-duration': self.__options.animationDuration[1] + 'ms',
1595 'transition-duration': self.__options.animationDuration[1] + 'ms'
1596 });
1597
1598 self._$tooltip
1599 // clear both potential open and close tasks
1600 .clearQueue()
1601 .removeClass('tooltipster-show')
1602 // for transitions only
1603 .addClass('tooltipster-dying');
1604
1605 if (self.__options.animationDuration[1] > 0) {
1606 self._$tooltip.delay(self.__options.animationDuration[1]);
1607 }
1608
1609 self._$tooltip.queue(finish);
1610 }
1611 else {
1612
1613 self._$tooltip
1614 .stop()
1615 .fadeOut(self.__options.animationDuration[1], finish);
1616 }
1617 }
1618 }
1619 // if the tooltip is already closed, we still need to trigger
1620 // the method custom callbacks
1621 else {
1622 finishCallbacks();
1623 }
1624 }
1625
1626 return self;
1627 },
1628
1629 /**
1630 * For internal use by plugins, if needed
1631 *
1632 * @returns {self}
1633 * @protected
1634 */
1635 _off: function() {
1636 this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1637 return this;
1638 },
1639
1640 /**
1641 * For internal use by plugins, if needed
1642 *
1643 * @returns {self}
1644 * @protected
1645 */
1646 _on: function() {
1647 this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1648 return this;
1649 },
1650
1651 /**
1652 * For internal use by plugins, if needed
1653 *
1654 * @returns {self}
1655 * @protected
1656 */
1657 _one: function() {
1658 this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1659 return this;
1660 },
1661
1662 /**
1663 * Opens the tooltip right away.
1664 *
1665 * @param event
1666 * @param callback Will be called when the opening animation is over
1667 * @returns {self}
1668 * @protected
1669 */
1670 _open: function(event, callback) {
1671
1672 var self = this;
1673
1674 // if the destruction process has not begun and if this was not
1675 // triggered by an unwanted emulated click event
1676 if (!self.__destroying) {
1677
1678 // check that the origin is still in the DOM
1679 if ( bodyContains(self._$origin)
1680 // if the tooltip is enabled
1681 && self.__enabled
1682 ) {
1683
1684 var ok = true;
1685
1686 // if the tooltip is not open yet, we need to call functionBefore.
1687 // otherwise we can jst go on
1688 if (self.__state == 'closed') {
1689
1690 // trigger an event. The event.stop function allows the callback
1691 // to prevent the opening of the tooltip
1692 self._trigger({
1693 type: 'before',
1694 event: event,
1695 stop: function() {
1696 ok = false;
1697 }
1698 });
1699
1700 if (ok && self.__options.functionBefore) {
1701
1702 // call our custom function before continuing
1703 ok = self.__options.functionBefore.call(self, self, {
1704 event: event,
1705 origin: self._$origin[0]
1706 });
1707 }
1708 }
1709
1710 if (ok !== false) {
1711
1712 // if there is some content
1713 if (self.__Content !== null) {
1714
1715 // save the method callback and cancel close method callbacks
1716 if (callback) {
1717 self.__callbacks.open.push(callback);
1718 }
1719 self.__callbacks.close = [];
1720
1721 // get rid of any appearance timeouts
1722 self.__timeoutsClear();
1723
1724 var extraTime,
1725 finish = function() {
1726
1727 if (self.__state != 'stable') {
1728 self.__stateSet('stable');
1729 }
1730
1731 // trigger any open method custom callbacks and reset them
1732 $.each(self.__callbacks.open, function(i,c) {
1733 c.call(self, self, {
1734 origin: self._$origin[0],
1735 tooltip: self._$tooltip[0]
1736 });
1737 });
1738
1739 self.__callbacks.open = [];
1740 };
1741
1742 // if the tooltip is already open
1743 if (self.__state !== 'closed') {
1744
1745 // the timer (if any) will start (or restart) right now
1746 extraTime = 0;
1747
1748 // if it was disappearing, cancel that
1749 if (self.__state === 'disappearing') {
1750
1751 self.__stateSet('appearing');
1752
1753 if (env.hasTransitions) {
1754
1755 self._$tooltip
1756 .clearQueue()
1757 .removeClass('tooltipster-dying')
1758 .addClass('tooltipster-show');
1759
1760 if (self.__options.animationDuration[0] > 0) {
1761 self._$tooltip.delay(self.__options.animationDuration[0]);
1762 }
1763
1764 self._$tooltip.queue(finish);
1765 }
1766 else {
1767 // in case the tooltip was currently fading out, bring it back
1768 // to life
1769 self._$tooltip
1770 .stop()
1771 .fadeIn(finish);
1772 }
1773 }
1774 // if the tooltip is already open, we still need to trigger the method
1775 // custom callback
1776 else if (self.__state == 'stable') {
1777 finish();
1778 }
1779 }
1780 // if the tooltip isn't already open, open it
1781 else {
1782
1783 // a plugin must bind on this and store the tooltip in this._$tooltip
1784 self.__stateSet('appearing');
1785
1786 // the timer (if any) will start when the tooltip has fully appeared
1787 // after its transition
1788 extraTime = self.__options.animationDuration[0];
1789
1790 // insert the content inside the tooltip
1791 self.__contentInsert();
1792
1793 // reposition the tooltip and attach to the DOM
1794 self.reposition(event, true);
1795
1796 // animate in the tooltip. If the display plugin wants no css
1797 // animations, it may override the animation option with a
1798 // dummy value that will produce no effect
1799 if (env.hasTransitions) {
1800
1801 // note: there seems to be an issue with start animations which
1802 // are randomly not played on fast devices in both Chrome and FF,
1803 // couldn't find a way to solve it yet. It seems that applying
1804 // the classes before appending to the DOM helps a little, but
1805 // it messes up some CSS transitions. The issue almost never
1806 // happens when delay[0]==0 though
1807 self._$tooltip
1808 .addClass('tooltipster-'+ self.__options.animation)
1809 .addClass('tooltipster-initial')
1810 .css({
1811 '-moz-animation-duration': self.__options.animationDuration[0] + 'ms',
1812 '-ms-animation-duration': self.__options.animationDuration[0] + 'ms',
1813 '-o-animation-duration': self.__options.animationDuration[0] + 'ms',
1814 '-webkit-animation-duration': self.__options.animationDuration[0] + 'ms',
1815 'animation-duration': self.__options.animationDuration[0] + 'ms',
1816 'transition-duration': self.__options.animationDuration[0] + 'ms'
1817 });
1818
1819 setTimeout(
1820 function() {
1821
1822 // a quick hover may have already triggered a mouseleave
1823 if (self.__state != 'closed') {
1824
1825 self._$tooltip
1826 .addClass('tooltipster-show')
1827 .removeClass('tooltipster-initial');
1828
1829 if (self.__options.animationDuration[0] > 0) {
1830 self._$tooltip.delay(self.__options.animationDuration[0]);
1831 }
1832
1833 self._$tooltip.queue(finish);
1834 }
1835 },
1836 0
1837 );
1838 }
1839 else {
1840
1841 // old browsers will have to live with this
1842 self._$tooltip
1843 .css('display', 'none')
1844 .fadeIn(self.__options.animationDuration[0], finish);
1845 }
1846
1847 // checks if the origin is removed while the tooltip is open
1848 self.__trackerStart();
1849
1850 // NOTE: the listeners below have a '-triggerClose' namespace
1851 // because we'll remove them when the tooltip closes (unlike
1852 // the '-triggerOpen' listeners). So some of them are actually
1853 // not about close triggers, rather about positioning.
1854
1855 $(env.window)
1856 // reposition on resize
1857 .on('resize.'+ self.__namespace +'-triggerClose', function(e) {
1858
1859 var $ae = $(document.activeElement);
1860
1861 // reposition only if the resize event was not triggered upon the opening
1862 // of a virtual keyboard due to an input field being focused within the tooltip
1863 // (otherwise the repositioning would lose the focus)
1864 if ( (!$ae.is('input') && !$ae.is('textarea'))
1865 || !$.contains(self._$tooltip[0], $ae[0])
1866 ) {
1867 self.reposition(e);
1868 }
1869 })
1870 // same as below for parents
1871 .on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1872 self.__scrollHandler(e);
1873 });
1874
1875 self.__$originParents = self._$origin.parents();
1876
1877 // scrolling may require the tooltip to be moved or even
1878 // repositioned in some cases
1879 self.__$originParents.each(function(i, parent) {
1880
1881 $(parent).on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1882 self.__scrollHandler(e);
1883 });
1884 });
1885
1886 if ( self.__options.triggerClose.mouseleave
1887 || (self.__options.triggerClose.touchleave && env.hasTouchCapability)
1888 ) {
1889
1890 // we use an event to allow users/plugins to control when the mouseleave/touchleave
1891 // close triggers will come to action. It allows to have more triggering elements
1892 // than just the origin and the tooltip for example, or to cancel/delay the closing,
1893 // or to make the tooltip interactive even if it wasn't when it was open, etc.
1894 self._on('dismissable', function(event) {
1895
1896 if (event.dismissable) {
1897
1898 if (event.delay) {
1899
1900 timeout = setTimeout(function() {
1901 // event.event may be undefined
1902 self._close(event.event);
1903 }, event.delay);
1904
1905 self.__timeouts.close.push(timeout);
1906 }
1907 else {
1908 self._close(event);
1909 }
1910 }
1911 else {
1912 clearTimeout(timeout);
1913 }
1914 });
1915
1916 // now set the listeners that will trigger 'dismissable' events
1917 var $elements = self._$origin,
1918 eventNamesIn = '',
1919 eventNamesOut = '',
1920 timeout = null;
1921
1922 // if we have to allow interaction, bind on the tooltip too
1923 if (self.__options.interactive) {
1924 $elements = $elements.add(self._$tooltip);
1925 }
1926
1927 if (self.__options.triggerClose.mouseleave) {
1928 eventNamesIn += 'mouseenter.'+ self.__namespace +'-triggerClose ';
1929 eventNamesOut += 'mouseleave.'+ self.__namespace +'-triggerClose ';
1930 }
1931 if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1932 eventNamesIn += 'touchstart.'+ self.__namespace +'-triggerClose';
1933 eventNamesOut += 'touchend.'+ self.__namespace +'-triggerClose touchcancel.'+ self.__namespace +'-triggerClose';
1934 }
1935
1936 $elements
1937 // close after some time spent outside of the elements
1938 .on(eventNamesOut, function(event) {
1939
1940 // it's ok if the touch gesture ended up to be a swipe,
1941 // it's still a "touch leave" situation
1942 if ( self._touchIsTouchEvent(event)
1943 || !self._touchIsEmulatedEvent(event)
1944 ) {
1945
1946 var delay = (event.type == 'mouseleave') ?
1947 self.__options.delay :
1948 self.__options.delayTouch;
1949
1950 self._trigger({
1951 delay: delay[1],
1952 dismissable: true,
1953 event: event,
1954 type: 'dismissable'
1955 });
1956 }
1957 })
1958 // suspend the mouseleave timeout when the pointer comes back
1959 // over the elements
1960 .on(eventNamesIn, function(event) {
1961
1962 // it's also ok if the touch event is a swipe gesture
1963 if ( self._touchIsTouchEvent(event)
1964 || !self._touchIsEmulatedEvent(event)
1965 ) {
1966 self._trigger({
1967 dismissable: false,
1968 event: event,
1969 type: 'dismissable'
1970 });
1971 }
1972 });
1973 }
1974
1975 // close the tooltip when the origin gets a mouse click (common behavior of
1976 // native tooltips)
1977 if (self.__options.triggerClose.originClick) {
1978
1979 self._$origin.on('click.'+ self.__namespace + '-triggerClose', function(event) {
1980
1981 // we could actually let a tap trigger this but this feature just
1982 // does not make sense on touch devices
1983 if ( !self._touchIsTouchEvent(event)
1984 && !self._touchIsEmulatedEvent(event)
1985 ) {
1986 self._close(event);
1987 }
1988 });
1989 }
1990
1991 // set the same bindings for click and touch on the body to close the tooltip
1992 if ( self.__options.triggerClose.click
1993 || (self.__options.triggerClose.tap && env.hasTouchCapability)
1994 ) {
1995
1996 // don't set right away since the click/tap event which triggered this method
1997 // (if it was a click/tap) is going to bubble up to the body, we don't want it
1998 // to close the tooltip immediately after it opened
1999 setTimeout(function() {
2000
2001 if (self.__state != 'closed') {
2002
2003 var eventNames = '',
2004 $body = $(env.window.document.body);
2005
2006 if (self.__options.triggerClose.click) {
2007 eventNames += 'click.'+ self.__namespace +'-triggerClose ';
2008 }
2009 if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2010 eventNames += 'touchend.'+ self.__namespace +'-triggerClose';
2011 }
2012
2013 $body.on(eventNames, function(event) {
2014
2015 if (self._touchIsMeaningfulEvent(event)) {
2016
2017 self._touchRecordEvent(event);
2018
2019 if (!self.__options.interactive || !$.contains(self._$tooltip[0], event.target)) {
2020 self._close(event);
2021 }
2022 }
2023 });
2024
2025 // needed to detect and ignore swiping
2026 if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2027
2028 $body.on('touchstart.'+ self.__namespace +'-triggerClose', function(event) {
2029 self._touchRecordEvent(event);
2030 });
2031 }
2032 }
2033 }, 0);
2034 }
2035
2036 self._trigger('ready');
2037
2038 // call our custom callback
2039 if (self.__options.functionReady) {
2040 self.__options.functionReady.call(self, self, {
2041 origin: self._$origin[0],
2042 tooltip: self._$tooltip[0]
2043 });
2044 }
2045 }
2046
2047 // if we have a timer set, let the countdown begin
2048 if (self.__options.timer > 0) {
2049
2050 var timeout = setTimeout(function() {
2051 self._close();
2052 }, self.__options.timer + extraTime);
2053
2054 self.__timeouts.close.push(timeout);
2055 }
2056 }
2057 }
2058 }
2059 }
2060
2061 return self;
2062 },
2063
2064 /**
2065 * When using the mouseenter/touchstart open triggers, this function will
2066 * schedule the opening of the tooltip after the delay, if there is one
2067 *
2068 * @param event
2069 * @returns {self}
2070 * @protected
2071 */
2072 _openShortly: function(event) {
2073
2074 var self = this,
2075 ok = true;
2076
2077 if (self.__state != 'stable' && self.__state != 'appearing') {
2078
2079 // if a timeout is not already running
2080 if (!self.__timeouts.open) {
2081
2082 self._trigger({
2083 type: 'start',
2084 event: event,
2085 stop: function() {
2086 ok = false;
2087 }
2088 });
2089
2090 if (ok) {
2091
2092 var delay = (event.type.indexOf('touch') == 0) ?
2093 self.__options.delayTouch :
2094 self.__options.delay;
2095
2096 if (delay[0]) {
2097
2098 self.__timeouts.open = setTimeout(function() {
2099
2100 self.__timeouts.open = null;
2101
2102 // open only if the pointer (mouse or touch) is still over the origin.
2103 // The check on the "meaningful event" can only be made here, after some
2104 // time has passed (to know if the touch was a swipe or not)
2105 if (self.__pointerIsOverOrigin && self._touchIsMeaningfulEvent(event)) {
2106
2107 // signal that we go on
2108 self._trigger('startend');
2109
2110 self._open(event);
2111 }
2112 else {
2113 // signal that we cancel
2114 self._trigger('startcancel');
2115 }
2116 }, delay[0]);
2117 }
2118 else {
2119 // signal that we go on
2120 self._trigger('startend');
2121
2122 self._open(event);
2123 }
2124 }
2125 }
2126 }
2127
2128 return self;
2129 },
2130
2131 /**
2132 * Meant for plugins to get their options
2133 *
2134 * @param {string} pluginName The name of the plugin that asks for its options
2135 * @param {object} defaultOptions The default options of the plugin
2136 * @returns {object} The options
2137 * @protected
2138 */
2139 _optionsExtract: function(pluginName, defaultOptions) {
2140
2141 var self = this,
2142 options = $.extend(true, {}, defaultOptions);
2143
2144 // if the plugin options were isolated in a property named after the
2145 // plugin, use them (prevents conflicts with other plugins)
2146 var pluginOptions = self.__options[pluginName];
2147
2148 // if not, try to get them as regular options
2149 if (!pluginOptions){
2150
2151 pluginOptions = {};
2152
2153 $.each(defaultOptions, function(optionName, value) {
2154
2155 var o = self.__options[optionName];
2156
2157 if (o !== undefined) {
2158 pluginOptions[optionName] = o;
2159 }
2160 });
2161 }
2162
2163 // let's merge the default options and the ones that were provided. We'd want
2164 // to do a deep copy but not let jQuery merge arrays, so we'll do a shallow
2165 // extend on two levels, that will be enough if options are not more than 1
2166 // level deep
2167 $.each(options, function(optionName, value) {
2168
2169 if (pluginOptions[optionName] !== undefined) {
2170
2171 if (( typeof value == 'object'
2172 && !(value instanceof Array)
2173 && value != null
2174 )
2175 &&
2176 ( typeof pluginOptions[optionName] == 'object'
2177 && !(pluginOptions[optionName] instanceof Array)
2178 && pluginOptions[optionName] != null
2179 )
2180 ) {
2181 $.extend(options[optionName], pluginOptions[optionName]);
2182 }
2183 else {
2184 options[optionName] = pluginOptions[optionName];
2185 }
2186 }
2187 });
2188
2189 return options;
2190 },
2191
2192 /**
2193 * Used at instantiation of the plugin, or afterwards by plugins that activate themselves
2194 * on existing instances
2195 *
2196 * @param {object} pluginName
2197 * @returns {self}
2198 * @protected
2199 */
2200 _plug: function(pluginName) {
2201
2202 var plugin = $.tooltipster._plugin(pluginName);
2203
2204 if (plugin) {
2205
2206 // if there is a constructor for instances
2207 if (plugin.instance) {
2208
2209 // proxy non-private methods on the instance to allow new instance methods
2210 $.tooltipster.__bridge(plugin.instance, this, plugin.name);
2211 }
2212 }
2213 else {
2214 throw new Error('The "'+ pluginName +'" plugin is not defined');
2215 }
2216
2217 return this;
2218 },
2219
2220 /**
2221 * This will return true if the event is a mouse event which was
2222 * emulated by the browser after a touch event. This allows us to
2223 * really dissociate mouse and touch triggers.
2224 *
2225 * There is a margin of error if a real mouse event is fired right
2226 * after (within the delay shown below) a touch event on the same
2227 * element, but hopefully it should not happen often.
2228 *
2229 * @returns {boolean}
2230 * @protected
2231 */
2232 _touchIsEmulatedEvent: function(event) {
2233
2234 var isEmulated = false,
2235 now = new Date().getTime();
2236
2237 for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2238
2239 var e = this.__touchEvents[i];
2240
2241 // delay, in milliseconds. It's supposed to be 300ms in
2242 // most browsers (350ms on iOS) to allow a double tap but
2243 // can be less (check out FastClick for more info)
2244 if (now - e.time < 500) {
2245
2246 if (e.target === event.target) {
2247 isEmulated = true;
2248 }
2249 }
2250 else {
2251 break;
2252 }
2253 }
2254
2255 return isEmulated;
2256 },
2257
2258 /**
2259 * Returns false if the event was an emulated mouse event or
2260 * a touch event involved in a swipe gesture.
2261 *
2262 * @param {object} event
2263 * @returns {boolean}
2264 * @protected
2265 */
2266 _touchIsMeaningfulEvent: function(event) {
2267 return (
2268 (this._touchIsTouchEvent(event) && !this._touchSwiped(event.target))
2269 || (!this._touchIsTouchEvent(event) && !this._touchIsEmulatedEvent(event))
2270 );
2271 },
2272
2273 /**
2274 * Checks if an event is a touch event
2275 *
2276 * @param {object} event
2277 * @returns {boolean}
2278 * @protected
2279 */
2280 _touchIsTouchEvent: function(event){
2281 return event.type.indexOf('touch') == 0;
2282 },
2283
2284 /**
2285 * Store touch events for a while to detect swiping and emulated mouse events
2286 *
2287 * @param {object} event
2288 * @returns {self}
2289 * @protected
2290 */
2291 _touchRecordEvent: function(event) {
2292
2293 if (this._touchIsTouchEvent(event)) {
2294 event.time = new Date().getTime();
2295 this.__touchEvents.push(event);
2296 }
2297
2298 return this;
2299 },
2300
2301 /**
2302 * Returns true if a swipe happened after the last touchstart event fired on
2303 * event.target.
2304 *
2305 * We need to differentiate a swipe from a tap before we let the event open
2306 * or close the tooltip. A swipe is when a touchmove (scroll) event happens
2307 * on the body between the touchstart and the touchend events of an element.
2308 *
2309 * @param {object} target The HTML element that may have triggered the swipe
2310 * @returns {boolean}
2311 * @protected
2312 */
2313 _touchSwiped: function(target) {
2314
2315 var swiped = false;
2316
2317 for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2318
2319 var e = this.__touchEvents[i];
2320
2321 if (e.type == 'touchmove') {
2322 swiped = true;
2323 break;
2324 }
2325 else if (
2326 e.type == 'touchstart'
2327 && target === e.target
2328 ) {
2329 break;
2330 }
2331 }
2332
2333 return swiped;
2334 },
2335
2336 /**
2337 * Triggers an event on the instance emitters
2338 *
2339 * @returns {self}
2340 * @protected
2341 */
2342 _trigger: function() {
2343
2344 var args = Array.prototype.slice.apply(arguments);
2345
2346 if (typeof args[0] == 'string') {
2347 args[0] = { type: args[0] };
2348 }
2349
2350 // add properties to the event
2351 args[0].instance = this;
2352 args[0].origin = this._$origin ? this._$origin[0] : null;
2353 args[0].tooltip = this._$tooltip ? this._$tooltip[0] : null;
2354
2355 // note: the order of emitters matters
2356 this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
2357 $.tooltipster._trigger.apply($.tooltipster, args);
2358 this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
2359
2360 return this;
2361 },
2362
2363 /**
2364 * Deactivate a plugin on this instance
2365 *
2366 * @returns {self}
2367 * @protected
2368 */
2369 _unplug: function(pluginName) {
2370
2371 var self = this;
2372
2373 // if the plugin has been activated on this instance
2374 if (self[pluginName]) {
2375
2376 var plugin = $.tooltipster._plugin(pluginName);
2377
2378 // if there is a constructor for instances
2379 if (plugin.instance) {
2380
2381 // unbridge
2382 $.each(plugin.instance, function(methodName, fn) {
2383
2384 // if the method exists (privates methods do not) and comes indeed from
2385 // this plugin (may be missing or come from a conflicting plugin).
2386 if ( self[methodName]
2387 && self[methodName].bridged === self[pluginName]
2388 ) {
2389 delete self[methodName];
2390 }
2391 });
2392 }
2393
2394 // destroy the plugin
2395 if (self[pluginName].__destroy) {
2396 self[pluginName].__destroy();
2397 }
2398
2399 // remove the reference to the plugin instance
2400 delete self[pluginName];
2401 }
2402
2403 return self;
2404 },
2405
2406 /**
2407 * @see self::_close
2408 * @returns {self}
2409 * @public
2410 */
2411 close: function(callback) {
2412
2413 if (!this.__destroyed) {
2414 this._close(null, callback);
2415 }
2416 else {
2417 this.__destroyError();
2418 }
2419
2420 return this;
2421 },
2422
2423 /**
2424 * Sets or gets the content of the tooltip
2425 *
2426 * @returns {mixed|self}
2427 * @public
2428 */
2429 content: function(content) {
2430
2431 var self = this;
2432
2433 // getter method
2434 if (content === undefined) {
2435 return self.__Content;
2436 }
2437 // setter method
2438 else {
2439
2440 if (!self.__destroyed) {
2441
2442 // change the content
2443 self.__contentSet(content);
2444
2445 if (self.__Content !== null) {
2446
2447 // update the tooltip if it is open
2448 if (self.__state !== 'closed') {
2449
2450 // reset the content in the tooltip
2451 self.__contentInsert();
2452
2453 // reposition and resize the tooltip
2454 self.reposition();
2455
2456 // if we want to play a little animation showing the content changed
2457 if (self.__options.updateAnimation) {
2458
2459 if (env.hasTransitions) {
2460
2461 // keep the reference in the local scope
2462 var animation = self.__options.updateAnimation;
2463
2464 self._$tooltip.addClass('tooltipster-update-'+ animation);
2465
2466 // remove the class after a while. The actual duration of the
2467 // update animation may be shorter, it's set in the CSS rules
2468 setTimeout(function() {
2469
2470 if (self.__state != 'closed') {
2471
2472 self._$tooltip.removeClass('tooltipster-update-'+ animation);
2473 }
2474 }, 1000);
2475 }
2476 else {
2477 self._$tooltip.fadeTo(200, 0.5, function() {
2478 if (self.__state != 'closed') {
2479 self._$tooltip.fadeTo(200, 1);
2480 }
2481 });
2482 }
2483 }
2484 }
2485 }
2486 else {
2487 self._close();
2488 }
2489 }
2490 else {
2491 self.__destroyError();
2492 }
2493
2494 return self;
2495 }
2496 },
2497
2498 /**
2499 * Destroys the tooltip
2500 *
2501 * @returns {self}
2502 * @public
2503 */
2504 destroy: function() {
2505
2506 var self = this;
2507
2508 if (!self.__destroyed) {
2509
2510 if(self.__state != 'closed'){
2511
2512 // no closing delay
2513 self.option('animationDuration', 0)
2514 // force closing
2515 ._close(null, null, true);
2516 }
2517 else {
2518 // there might be an open timeout still running
2519 self.__timeoutsClear();
2520 }
2521
2522 // send event
2523 self._trigger('destroy');
2524
2525 self.__destroyed = true;
2526
2527 self._$origin
2528 .removeData(self.__namespace)
2529 // remove the open trigger listeners
2530 .off('.'+ self.__namespace +'-triggerOpen');
2531
2532 // remove the touch listener
2533 $(env.window.document.body).off('.' + self.__namespace +'-triggerOpen');
2534
2535 var ns = self._$origin.data('tooltipster-ns');
2536
2537 // if the origin has been removed from DOM, its data may
2538 // well have been destroyed in the process and there would
2539 // be nothing to clean up or restore
2540 if (ns) {
2541
2542 // if there are no more tooltips on this element
2543 if (ns.length === 1) {
2544
2545 // optional restoration of a title attribute
2546 var title = null;
2547 if (self.__options.restoration == 'previous') {
2548 title = self._$origin.data('tooltipster-initialTitle');
2549 }
2550 else if (self.__options.restoration == 'current') {
2551
2552 // old school technique to stringify when outerHTML is not supported
2553 title = (typeof self.__Content == 'string') ?
2554 self.__Content :
2555 $('<div></div>').append(self.__Content).html();
2556 }
2557
2558 if (title) {
2559 self._$origin.attr('title', title);
2560 }
2561
2562 // final cleaning
2563
2564 self._$origin.removeClass('tooltipstered');
2565
2566 self._$origin
2567 .removeData('tooltipster-ns')
2568 .removeData('tooltipster-initialTitle');
2569 }
2570 else {
2571 // remove the instance namespace from the list of namespaces of
2572 // tooltips present on the element
2573 ns = $.grep(ns, function(el, i) {
2574 return el !== self.__namespace;
2575 });
2576 self._$origin.data('tooltipster-ns', ns);
2577 }
2578 }
2579
2580 // last event
2581 self._trigger('destroyed');
2582
2583 // unbind private and public event listeners
2584 self._off();
2585 self.off();
2586
2587 // remove external references, just in case
2588 self.__Content = null;
2589 self.__$emitterPrivate = null;
2590 self.__$emitterPublic = null;
2591 self.__options.parent = null;
2592 self._$origin = null;
2593 self._$tooltip = null;
2594
2595 // make sure the object is no longer referenced in there to prevent
2596 // memory leaks
2597 $.tooltipster.__instancesLatestArr = $.grep($.tooltipster.__instancesLatestArr, function(el, i) {
2598 return self !== el;
2599 });
2600
2601 clearInterval(self.__garbageCollector);
2602 }
2603 else {
2604 self.__destroyError();
2605 }
2606
2607 // we return the scope rather than true so that the call to
2608 // .tooltipster('destroy') actually returns the matched elements
2609 // and applies to all of them
2610 return self;
2611 },
2612
2613 /**
2614 * Disables the tooltip
2615 *
2616 * @returns {self}
2617 * @public
2618 */
2619 disable: function() {
2620
2621 if (!this.__destroyed) {
2622
2623 // close first, in case the tooltip would not disappear on
2624 // its own (no close trigger)
2625 this._close();
2626 this.__enabled = false;
2627
2628 return this;
2629 }
2630 else {
2631 this.__destroyError();
2632 }
2633
2634 return this;
2635 },
2636
2637 /**
2638 * Returns the HTML element of the origin
2639 *
2640 * @returns {self}
2641 * @public
2642 */
2643 elementOrigin: function() {
2644
2645 if (!this.__destroyed) {
2646 return this._$origin[0];
2647 }
2648 else {
2649 this.__destroyError();
2650 }
2651 },
2652
2653 /**
2654 * Returns the HTML element of the tooltip
2655 *
2656 * @returns {self}
2657 * @public
2658 */
2659 elementTooltip: function() {
2660 return this._$tooltip ? this._$tooltip[0] : null;
2661 },
2662
2663 /**
2664 * Enables the tooltip
2665 *
2666 * @returns {self}
2667 * @public
2668 */
2669 enable: function() {
2670 this.__enabled = true;
2671 return this;
2672 },
2673
2674 /**
2675 * Alias, deprecated in 4.0.0
2676 *
2677 * @param {function} callback
2678 * @returns {self}
2679 * @public
2680 */
2681 hide: function(callback) {
2682 return this.close(callback);
2683 },
2684
2685 /**
2686 * Returns the instance
2687 *
2688 * @returns {self}
2689 * @public
2690 */
2691 instance: function() {
2692 return this;
2693 },
2694
2695 /**
2696 * For public use only, not to be used by plugins (use ::_off() instead)
2697 *
2698 * @returns {self}
2699 * @public
2700 */
2701 off: function() {
2702
2703 if (!this.__destroyed) {
2704 this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2705 }
2706
2707 return this;
2708 },
2709
2710 /**
2711 * For public use only, not to be used by plugins (use ::_on() instead)
2712 *
2713 * @returns {self}
2714 * @public
2715 */
2716 on: function() {
2717
2718 if (!this.__destroyed) {
2719 this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2720 }
2721 else {
2722 this.__destroyError();
2723 }
2724
2725 return this;
2726 },
2727
2728 /**
2729 * For public use only, not to be used by plugins
2730 *
2731 * @returns {self}
2732 * @public
2733 */
2734 one: function() {
2735
2736 if (!this.__destroyed) {
2737 this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2738 }
2739 else {
2740 this.__destroyError();
2741 }
2742
2743 return this;
2744 },
2745
2746 /**
2747 * @see self::_open
2748 * @returns {self}
2749 * @public
2750 */
2751 open: function(callback) {
2752
2753 if (!this.__destroyed) {
2754 this._open(null, callback);
2755 }
2756 else {
2757 this.__destroyError();
2758 }
2759
2760 return this;
2761 },
2762
2763 /**
2764 * Get or set options. For internal use and advanced users only.
2765 *
2766 * @param {string} o Option name
2767 * @param {mixed} val optional A new value for the option
2768 * @return {mixed|self} If val is omitted, the value of the option
2769 * is returned, otherwise the instance itself is returned
2770 * @public
2771 */
2772 option: function(o, val) {
2773
2774 // getter
2775 if (val === undefined) {
2776 return this.__options[o];
2777 }
2778 // setter
2779 else {
2780
2781 if (!this.__destroyed) {
2782
2783 // change value
2784 this.__options[o] = val;
2785
2786 // format
2787 this.__optionsFormat();
2788
2789 // re-prepare the triggers if needed
2790 if ($.inArray(o, ['trigger', 'triggerClose', 'triggerOpen']) >= 0) {
2791 this.__prepareOrigin();
2792 }
2793
2794 if (o === 'selfDestruction') {
2795 this.__prepareGC();
2796 }
2797 }
2798 else {
2799 this.__destroyError();
2800 }
2801
2802 return this;
2803 }
2804 },
2805
2806 /**
2807 * This method is in charge of setting the position and size properties of the tooltip.
2808 * All the hard work is delegated to the display plugin.
2809 * Note: The tooltip may be detached from the DOM at the moment the method is called
2810 * but must be attached by the end of the method call.
2811 *
2812 * @param {object} event For internal use only. Defined if an event such as
2813 * window resizing triggered the repositioning
2814 * @param {boolean} tooltipIsDetached For internal use only. Set this to true if you
2815 * know that the tooltip not being in the DOM is not an issue (typically when the
2816 * tooltip element has just been created but has not been added to the DOM yet).
2817 * @returns {self}
2818 * @public
2819 */
2820 reposition: function(event, tooltipIsDetached) {
2821
2822 var self = this;
2823
2824 if (!self.__destroyed) {
2825
2826 // if the tooltip is still open and the origin is still in the DOM
2827 if (self.__state != 'closed' && bodyContains(self._$origin)) {
2828
2829 // if the tooltip has not been removed from DOM manually (or if it
2830 // has been detached on purpose)
2831 if (tooltipIsDetached || bodyContains(self._$tooltip)) {
2832
2833 if (!tooltipIsDetached) {
2834 // detach in case the tooltip overflows the window and adds
2835 // scrollbars to it, so __geometry can be accurate
2836 self._$tooltip.detach();
2837 }
2838
2839 // refresh the geometry object before passing it as a helper
2840 self.__Geometry = self.__geometry();
2841
2842 // let a plugin fo the rest
2843 self._trigger({
2844 type: 'reposition',
2845 event: event,
2846 helper: {
2847 geo: self.__Geometry
2848 }
2849 });
2850 }
2851 }
2852 }
2853 else {
2854 self.__destroyError();
2855 }
2856
2857 return self;
2858 },
2859
2860 /**
2861 * Alias, deprecated in 4.0.0
2862 *
2863 * @param callback
2864 * @returns {self}
2865 * @public
2866 */
2867 show: function(callback) {
2868 return this.open(callback);
2869 },
2870
2871 /**
2872 * Returns some properties about the instance
2873 *
2874 * @returns {object}
2875 * @public
2876 */
2877 status: function() {
2878
2879 return {
2880 destroyed: this.__destroyed,
2881 enabled: this.__enabled,
2882 open: this.__state !== 'closed',
2883 state: this.__state
2884 };
2885 },
2886
2887 /**
2888 * For public use only, not to be used by plugins
2889 *
2890 * @returns {self}
2891 * @public
2892 */
2893 triggerHandler: function() {
2894
2895 if (!this.__destroyed) {
2896 this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2897 }
2898 else {
2899 this.__destroyError();
2900 }
2901
2902 return this;
2903 }
2904 };
2905
2906 $.fn.tooltipster = function() {
2907
2908 // for using in closures
2909 var args = Array.prototype.slice.apply(arguments),
2910 // common mistake: an HTML element can't be in several tooltips at the same time
2911 contentCloningWarning = 'You are using a single HTML element as content for several tooltips. You probably want to set the contentCloning option to TRUE.';
2912
2913 // this happens with $(sel).tooltipster(...) when $(sel) does not match anything
2914 if (this.length === 0) {
2915
2916 // still chainable
2917 return this;
2918 }
2919 // this happens when calling $(sel).tooltipster('methodName or options')
2920 // where $(sel) matches one or more elements
2921 else {
2922
2923 // method calls
2924 if (typeof args[0] === 'string') {
2925
2926 var v = '#*$~&';
2927
2928 this.each(function() {
2929
2930 // retrieve the namepaces of the tooltip(s) that exist on that element.
2931 // We will interact with the first tooltip only.
2932 var ns = $(this).data('tooltipster-ns'),
2933 // self represents the instance of the first tooltipster plugin
2934 // associated to the current HTML object of the loop
2935 self = ns ? $(this).data(ns[0]) : null;
2936
2937 // if the current element holds a tooltipster instance
2938 if (self) {
2939
2940 if (typeof self[args[0]] === 'function') {
2941
2942 if ( this.length > 1
2943 && args[0] == 'content'
2944 && ( args[1] instanceof $
2945 || (typeof args[1] == 'object' && args[1] != null && args[1].tagName)
2946 )
2947 && !self.__options.contentCloning
2948 && self.__options.debug
2949 ) {
2950 console.log(contentCloningWarning);
2951 }
2952
2953 // note : args[1] and args[2] may not be defined
2954 var resp = self[args[0]](args[1], args[2]);
2955 }
2956 else {
2957 throw new Error('Unknown method "'+ args[0] +'"');
2958 }
2959
2960 // if the function returned anything other than the instance
2961 // itself (which implies chaining, except for the `instance` method)
2962 if (resp !== self || args[0] === 'instance') {
2963
2964 v = resp;
2965
2966 // return false to stop .each iteration on the first element
2967 // matched by the selector
2968 return false;
2969 }
2970 }
2971 else {
2972 throw new Error('You called Tooltipster\'s "'+ args[0] +'" method on an uninitialized element');
2973 }
2974 });
2975
2976 return (v !== '#*$~&') ? v : this;
2977 }
2978 // first argument is undefined or an object: the tooltip is initializing
2979 else {
2980
2981 // reset the array of last initialized objects
2982 $.tooltipster.__instancesLatestArr = [];
2983
2984 // is there a defined value for the multiple option in the options object ?
2985 var multipleIsSet = args[0] && args[0].multiple !== undefined,
2986 // if the multiple option is set to true, or if it's not defined but
2987 // set to true in the defaults
2988 multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
2989 // same for content
2990 contentIsSet = args[0] && args[0].content !== undefined,
2991 content = (contentIsSet && args[0].content) || (!contentIsSet && defaults.content),
2992 // same for contentCloning
2993 contentCloningIsSet = args[0] && args[0].contentCloning !== undefined,
2994 contentCloning =
2995 (contentCloningIsSet && args[0].contentCloning)
2996 || (!contentCloningIsSet && defaults.contentCloning),
2997 // same for debug
2998 debugIsSet = args[0] && args[0].debug !== undefined,
2999 debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
3000
3001 if ( this.length > 1
3002 && ( content instanceof $
3003 || (typeof content == 'object' && content != null && content.tagName)
3004 )
3005 && !contentCloning
3006 && debug
3007 ) {
3008 console.log(contentCloningWarning);
3009 }
3010
3011 // create a tooltipster instance for each element if it doesn't
3012 // already have one or if the multiple option is set, and attach the
3013 // object to it
3014 this.each(function() {
3015
3016 var go = false,
3017 $this = $(this),
3018 ns = $this.data('tooltipster-ns'),
3019 obj = null;
3020
3021 if (!ns) {
3022 go = true;
3023 }
3024 else if (multiple) {
3025 go = true;
3026 }
3027 else if (debug) {
3028 console.log('Tooltipster: one or more tooltips are already attached to the element below. Ignoring.');
3029 console.log(this);
3030 }
3031
3032 if (go) {
3033 obj = new $.Tooltipster(this, args[0]);
3034
3035 // save the reference of the new instance
3036 if (!ns) ns = [];
3037 ns.push(obj.__namespace);
3038 $this.data('tooltipster-ns', ns);
3039
3040 // save the instance itself
3041 $this.data(obj.__namespace, obj);
3042
3043 // call our constructor custom function.
3044 // we do this here and not in ::init() because we wanted
3045 // the object to be saved in $this.data before triggering
3046 // it
3047 if (obj.__options.functionInit) {
3048 obj.__options.functionInit.call(obj, obj, {
3049 origin: this
3050 });
3051 }
3052
3053 // and now the event, for the plugins and core emitter
3054 obj._trigger('init');
3055 }
3056
3057 $.tooltipster.__instancesLatestArr.push(obj);
3058 });
3059
3060 return this;
3061 }
3062 }
3063 };
3064
3065 // Utilities
18
3066
19 /**
3067 /**
20 * TOOLTIP IMPL.
3068 * A class to check if a tooltip can fit in given dimensions
3069 *
3070 * @param {object} $tooltip The jQuery wrapped tooltip element, or a clone of it
3071 */
3072 function Ruler($tooltip) {
3073
3074 // list of instance variables
3075
3076 this.$container;
3077 this.constraints = null;
3078 this.__$tooltip;
3079
3080 this.__init($tooltip);
3081 }
3082
3083 Ruler.prototype = {
3084
3085 /**
3086 * Move the tooltip into an invisible div that does not allow overflow to make
3087 * size tests. Note: the tooltip may or may not be attached to the DOM at the
3088 * moment this method is called, it does not matter.
3089 *
3090 * @param {object} $tooltip The object to test. May be just a clone of the
3091 * actual tooltip.
3092 * @private
3093 */
3094 __init: function($tooltip) {
3095
3096 this.__$tooltip = $tooltip;
3097
3098 this.__$tooltip
3099 .css({
3100 // for some reason we have to specify top and left 0
3101 left: 0,
3102 // any overflow will be ignored while measuring
3103 overflow: 'hidden',
3104 // positions at (0,0) without the div using 100% of the available width
3105 position: 'absolute',
3106 top: 0
3107 })
3108 // overflow must be auto during the test. We re-set this in case
3109 // it were modified by the user
3110 .find('.tooltipster-content')
3111 .css('overflow', 'auto');
3112
3113 this.$container = $('<div class="tooltipster-ruler"></div>')
3114 .append(this.__$tooltip)
3115 .appendTo(env.window.document.body);
3116 },
3117
3118 /**
3119 * Force the browser to redraw (re-render) the tooltip immediately. This is required
3120 * when you changed some CSS properties and need to make something with it
3121 * immediately, without waiting for the browser to redraw at the end of instructions.
3122 *
3123 * @see http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes
3124 * @private
3125 */
3126 __forceRedraw: function() {
3127
3128 // note: this would work but for Webkit only
3129 //this.__$tooltip.close();
3130 //this.__$tooltip[0].offsetHeight;
3131 //this.__$tooltip.open();
3132
3133 // works in FF too
3134 var $p = this.__$tooltip.parent();
3135 this.__$tooltip.detach();
3136 this.__$tooltip.appendTo($p);
3137 },
3138
3139 /**
3140 * Set maximum dimensions for the tooltip. A call to ::measure afterwards
3141 * will tell us if the content overflows or if it's ok
3142 *
3143 * @param {int} width
3144 * @param {int} height
3145 * @return {Ruler}
3146 * @public
3147 */
3148 constrain: function(width, height) {
3149
3150 this.constraints = {
3151 width: width,
3152 height: height
3153 };
3154
3155 this.__$tooltip.css({
3156 // we disable display:flex, otherwise the content would overflow without
3157 // creating horizontal scrolling (which we need to detect).
3158 display: 'block',
3159 // reset any previous height
3160 height: '',
3161 // we'll check if horizontal scrolling occurs
3162 overflow: 'auto',
3163 // we'll set the width and see what height is generated and if there
3164 // is horizontal overflow
3165 width: width
3166 });
3167
3168 return this;
3169 },
3170
3171 /**
3172 * Reset the tooltip content overflow and remove the test container
3173 *
3174 * @returns {Ruler}
3175 * @public
3176 */
3177 destroy: function() {
3178
3179 // in case the element was not a clone
3180 this.__$tooltip
3181 .detach()
3182 .find('.tooltipster-content')
3183 .css({
3184 // reset to CSS value
3185 display: '',
3186 overflow: ''
3187 });
3188
3189 this.$container.remove();
3190 },
3191
3192 /**
3193 * Removes any constraints
3194 *
3195 * @returns {Ruler}
3196 * @public
3197 */
3198 free: function() {
3199
3200 this.constraints = null;
3201
3202 // reset to natural size
3203 this.__$tooltip.css({
3204 display: '',
3205 height: '',
3206 overflow: 'visible',
3207 width: ''
3208 });
3209
3210 return this;
3211 },
3212
3213 /**
3214 * Returns the size of the tooltip. When constraints are applied, also returns
3215 * whether the tooltip fits in the provided dimensions.
3216 * The idea is to see if the new height is small enough and if the content does
3217 * not overflow horizontally.
3218 *
3219 * @param {int} width
3220 * @param {int} height
3221 * @returns {object} An object with a bool `fits` property and a `size` property
3222 * @public
3223 */
3224 measure: function() {
3225
3226 this.__forceRedraw();
3227
3228 var tooltipBcr = this.__$tooltip[0].getBoundingClientRect(),
3229 result = { size: {
3230 // bcr.width/height are not defined in IE8- but in this
3231 // case, bcr.right/bottom will have the same value
3232 // except in iOS 8+ where tooltipBcr.bottom/right are wrong
3233 // after scrolling for reasons yet to be determined.
3234 // tooltipBcr.top/left might not be 0, see issue #514
3235 height: tooltipBcr.height || (tooltipBcr.bottom - tooltipBcr.top),
3236 width: tooltipBcr.width || (tooltipBcr.right - tooltipBcr.left)
3237 }};
3238
3239 if (this.constraints) {
3240
3241 // note: we used to use offsetWidth instead of boundingRectClient but
3242 // it returned rounded values, causing issues with sub-pixel layouts.
3243
3244 // note2: noticed that the bcrWidth of text content of a div was once
3245 // greater than the bcrWidth of its container by 1px, causing the final
3246 // tooltip box to be too small for its content. However, evaluating
3247 // their widths one against the other (below) surprisingly returned
3248 // equality. Happened only once in Chrome 48, was not able to reproduce
3249 // => just having fun with float position values...
3250
3251 var $content = this.__$tooltip.find('.tooltipster-content'),
3252 height = this.__$tooltip.outerHeight(),
3253 contentBcr = $content[0].getBoundingClientRect(),
3254 fits = {
3255 height: height <= this.constraints.height,
3256 width: (
3257 // this condition accounts for min-width property that
3258 // may apply
3259 tooltipBcr.width <= this.constraints.width
3260 // the -1 is here because scrollWidth actually returns
3261 // a rounded value, and may be greater than bcr.width if
3262 // it was rounded up. This may cause an issue for contents
3263 // which actually really overflow by 1px or so, but that
3264 // should be rare. Not sure how to solve this efficiently.
3265 // See http://blogs.msdn.com/b/ie/archive/2012/02/17/sub-pixel-rendering-and-the-css-object-model.aspx
3266 && contentBcr.width >= $content[0].scrollWidth - 1
3267 )
3268 };
3269
3270 result.fits = fits.height && fits.width;
3271 }
3272
3273 // old versions of IE get the width wrong for some reason and it causes
3274 // the text to be broken to a new line, so we round it up. If the width
3275 // is the width of the screen though, we can assume it is accurate.
3276 if ( env.IE
3277 && env.IE <= 11
3278 && result.size.width !== env.window.document.documentElement.clientWidth
3279 ) {
3280 result.size.width = Math.ceil(result.size.width) + 1;
3281 }
3282
3283 return result;
3284 }
3285 };
3286
3287 // quick & dirty compare function, not bijective nor multidimensional
3288 function areEqual(a,b) {
3289 var same = true;
3290 $.each(a, function(i, _) {
3291 if (b[i] === undefined || a[i] !== b[i]) {
3292 same = false;
3293 return false;
3294 }
3295 });
3296 return same;
3297 }
3298
3299 /**
3300 * A fast function to check if an element is still in the DOM. It
3301 * tries to use an id as ids are indexed by the browser, or falls
3302 * back to jQuery's `contains` method. May fail if two elements
3303 * have the same id, but so be it
3304 *
3305 * @param {object} $obj A jQuery-wrapped HTML element
3306 * @return {boolean}
21 */
3307 */
22
3308 function bodyContains($obj) {
23 var TTIP = {};
3309 var id = $obj.attr('id'),
24
3310 el = id ? env.window.document.getElementById(id) : null;
25 TTIP.main = {
3311 // must also check that the element with the id is the one we want
26 offset: [15,15],
3312 return el ? el === $obj[0] : $.contains(env.window.document.body, $obj[0]);
27 maxWidth: 600,
3313 }
28
3314
29 setDeferredListeners: function(){
3315 // detect IE versions for dirty fixes
30 $('body').on('mouseover', '.tooltip', yt.show_tip);
3316 var uA = navigator.userAgent.toLowerCase();
31 $('body').on('mousemove', '.tooltip', yt.move_tip);
3317 if (uA.indexOf('msie') != -1) env.IE = parseInt(uA.split('msie')[1]);
32 $('body').on('mouseout', '.tooltip', yt.close_tip);
3318 else if (uA.toLowerCase().indexOf('trident') !== -1 && uA.indexOf(' rv:11') !== -1) env.IE = 11;
33 },
3319 else if (uA.toLowerCase().indexOf('edge/') != -1) env.IE = parseInt(uA.toLowerCase().split('edge/')[1]);
34
3320
35 init: function(){
3321 // detecting support for CSS transitions
36 $('#tip-box').remove();
3322 function transitionSupport() {
37 yt.tipBox = document.createElement('div');
3323
38 document.body.appendChild(yt.tipBox);
3324 // env.window is not defined yet when this is called
39 yt.tipBox.id = 'tip-box';
3325 if (!win) return false;
40
3326
41 $(yt.tipBox).hide();
3327 var b = win.document.body || win.document.documentElement,
42 $(yt.tipBox).css('position', 'absolute');
3328 s = b.style,
43 if(yt.maxWidth !== null){
3329 p = 'transition',
44 $(yt.tipBox).css('max-width', yt.maxWidth+'px');
3330 v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
45 }
3331
46 yt.setDeferredListeners();
3332 if (typeof s[p] == 'string') { return true; }
47 },
3333
48
3334 p = p.charAt(0).toUpperCase() + p.substr(1);
49 show_tip: function(e, el){
3335 for (var i=0; i<v.length; i++) {
50 e.stopPropagation();
3336 if (typeof s[v[i] + p] == 'string') { return true; }
51 e.preventDefault();
3337 }
52 var el = e.data || e.currentTarget || el;
3338 return false;
53 if(el.tagName.toLowerCase() === 'img'){
54 yt.tipText = el.alt ? el.alt : '';
55 } else {
56 yt.tipText = el.title ? el.title : '';
57 }
58
59 if(yt.tipText !== ''){
60 // save org title
61 $(el).attr('tt_title', yt.tipText);
62 // reset title to not show org tooltips
63 $(el).attr('title', '');
64
65 yt.tipBox.innerHTML = yt.tipText;
66 $(yt.tipBox).show();
67 }
68 },
69
70 move_tip: function(e, el){
71 e.stopPropagation();
72 e.preventDefault();
73 var el = e.data || e.currentTarget || el;
74 var movePos = [e.pageX, e.pageY];
75 $(yt.tipBox).css('top', (movePos[1] + yt.offset[1]) + 'px')
76 $(yt.tipBox).css('left', (movePos[0] + yt.offset[0]) + 'px')
77 },
78
79 close_tip: function(e, el){
80 e.stopPropagation();
81 e.preventDefault();
82 var el = e.data || e.currentTarget || el;
83 $(yt.tipBox).hide();
84 $(el).attr('title', $(el).attr('tt_title'));
85 $('#tip-box').hide();
86 }
87 };
88
89 // activate tooltips
90 yt = TTIP.main;
91 if ($(document).data('activated-tooltips') !== '1'){
92 $(document).ready(yt.init);
93 $(document).data('activated-tooltips', '1');
94 }
3339 }
3340
3341 // we'll return jQuery for plugins not to have to declare it as a dependency,
3342 // but it's done by a build task since it should be included only once at the
3343 // end when we concatenate the main file with a plugin
3344 // sideTip is Tooltipster's default plugin.
3345 // This file will be UMDified by a build task.
3346
3347 var pluginName = 'tooltipster.sideTip';
3348
3349 $.tooltipster._plugin({
3350 name: pluginName,
3351 instance: {
3352 /**
3353 * Defaults are provided as a function for an easy override by inheritance
3354 *
3355 * @return {object} An object with the defaults options
3356 * @private
3357 */
3358 __defaults: function() {
3359
3360 return {
3361 // if the tooltip should display an arrow that points to the origin
3362 arrow: true,
3363 // the distance in pixels between the tooltip and the origin
3364 distance: 6,
3365 // allows to easily change the position of the tooltip
3366 functionPosition: null,
3367 maxWidth: null,
3368 // used to accomodate the arrow of tooltip if there is one.
3369 // First to make sure that the arrow target is not too close
3370 // to the edge of the tooltip, so the arrow does not overflow
3371 // the tooltip. Secondly when we reposition the tooltip to
3372 // make sure that it's positioned in such a way that the arrow is
3373 // still pointing at the target (and not a few pixels beyond it).
3374 // It should be equal to or greater than half the width of
3375 // the arrow (by width we mean the size of the side which touches
3376 // the side of the tooltip).
3377 minIntersection: 16,
3378 minWidth: 0,
3379 // deprecated in 4.0.0. Listed for _optionsExtract to pick it up
3380 position: null,
3381 side: 'top',
3382 // set to false to position the tooltip relatively to the document rather
3383 // than the window when we open it
3384 viewportAware: true
3385 };
3386 },
3387
3388 /**
3389 * Run once: at instantiation of the plugin
3390 *
3391 * @param {object} instance The tooltipster object that instantiated this plugin
3392 * @private
3393 */
3394 __init: function(instance) {
3395
3396 var self = this;
3397
3398 // list of instance variables
3399
3400 self.__instance = instance;
3401 self.__namespace = 'tooltipster-sideTip-'+ Math.round(Math.random()*1000000);
3402 self.__previousState = 'closed';
3403 self.__options;
3404
3405 // initial formatting
3406 self.__optionsFormat();
3407
3408 self.__instance._on('state.'+ self.__namespace, function(event) {
3409
3410 if (event.state == 'closed') {
3411 self.__close();
3412 }
3413 else if (event.state == 'appearing' && self.__previousState == 'closed') {
3414 self.__create();
3415 }
3416
3417 self.__previousState = event.state;
3418 });
3419
3420 // reformat every time the options are changed
3421 self.__instance._on('options.'+ self.__namespace, function() {
3422 self.__optionsFormat();
3423 });
3424
3425 self.__instance._on('reposition.'+ self.__namespace, function(e) {
3426 self.__reposition(e.event, e.helper);
3427 });
3428 },
3429
3430 /**
3431 * Called when the tooltip has closed
3432 *
3433 * @private
3434 */
3435 __close: function() {
3436
3437 // detach our content object first, so the next jQuery's remove()
3438 // call does not unbind its event handlers
3439 if (this.__instance.content() instanceof $) {
3440 this.__instance.content().detach();
3441 }
3442
3443 // remove the tooltip from the DOM
3444 this.__instance._$tooltip.remove();
3445 this.__instance._$tooltip = null;
3446 },
3447
3448 /**
3449 * Creates the HTML element of the tooltip.
3450 *
3451 * @private
3452 */
3453 __create: function() {
3454
3455 // note: we wrap with a .tooltipster-box div to be able to set a margin on it
3456 // (.tooltipster-base must not have one)
3457 var $html = $(
3458 '<div class="tooltipster-base tooltipster-sidetip">' +
3459 '<div class="tooltipster-box">' +
3460 '<div class="tooltipster-content"></div>' +
3461 '</div>' +
3462 '<div class="tooltipster-arrow">' +
3463 '<div class="tooltipster-arrow-uncropped">' +
3464 '<div class="tooltipster-arrow-border"></div>' +
3465 '<div class="tooltipster-arrow-background"></div>' +
3466 '</div>' +
3467 '</div>' +
3468 '</div>'
3469 );
3470
3471 // hide arrow if asked
3472 if (!this.__options.arrow) {
3473 $html
3474 .find('.tooltipster-box')
3475 .css('margin', 0)
3476 .end()
3477 .find('.tooltipster-arrow')
3478 .hide();
3479 }
3480
3481 // apply min/max width if asked
3482 if (this.__options.minWidth) {
3483 $html.css('min-width', this.__options.minWidth + 'px');
3484 }
3485 if (this.__options.maxWidth) {
3486 $html.css('max-width', this.__options.maxWidth + 'px');
3487 }
3488
3489 this.__instance._$tooltip = $html;
3490
3491 // tell the instance that the tooltip element has been created
3492 this.__instance._trigger('created');
3493 },
3494
3495 /**
3496 * Used when the plugin is to be unplugged
3497 *
3498 * @private
3499 */
3500 __destroy: function() {
3501 this.__instance._off('.'+ self.__namespace);
3502 },
3503
3504 /**
3505 * (Re)compute this.__options from the options declared to the instance
3506 *
3507 * @private
3508 */
3509 __optionsFormat: function() {
3510
3511 var self = this;
3512
3513 // get the options
3514 self.__options = self.__instance._optionsExtract(pluginName, self.__defaults());
3515
3516 // for backward compatibility, deprecated in v4.0.0
3517 if (self.__options.position) {
3518 self.__options.side = self.__options.position;
3519 }
3520
3521 // options formatting
3522
3523 // format distance as a four-cell array if it ain't one yet and then make
3524 // it an object with top/bottom/left/right properties
3525 if (typeof self.__options.distance != 'object') {
3526 self.__options.distance = [self.__options.distance];
3527 }
3528 if (self.__options.distance.length < 4) {
3529
3530 if (self.__options.distance[1] === undefined) self.__options.distance[1] = self.__options.distance[0];
3531 if (self.__options.distance[2] === undefined) self.__options.distance[2] = self.__options.distance[0];
3532 if (self.__options.distance[3] === undefined) self.__options.distance[3] = self.__options.distance[1];
3533
3534 self.__options.distance = {
3535 top: self.__options.distance[0],
3536 right: self.__options.distance[1],
3537 bottom: self.__options.distance[2],
3538 left: self.__options.distance[3]
3539 };
3540 }
3541
3542 // let's transform:
3543 // 'top' into ['top', 'bottom', 'right', 'left']
3544 // 'right' into ['right', 'left', 'top', 'bottom']
3545 // 'bottom' into ['bottom', 'top', 'right', 'left']
3546 // 'left' into ['left', 'right', 'top', 'bottom']
3547 if (typeof self.__options.side == 'string') {
3548
3549 var opposites = {
3550 'top': 'bottom',
3551 'right': 'left',
3552 'bottom': 'top',
3553 'left': 'right'
3554 };
3555
3556 self.__options.side = [self.__options.side, opposites[self.__options.side]];
3557
3558 if (self.__options.side[0] == 'left' || self.__options.side[0] == 'right') {
3559 self.__options.side.push('top', 'bottom');
3560 }
3561 else {
3562 self.__options.side.push('right', 'left');
3563 }
3564 }
3565
3566 // misc
3567 // disable the arrow in IE6 unless the arrow option was explicitly set to true
3568 if ( $.tooltipster._env.IE === 6
3569 && self.__options.arrow !== true
3570 ) {
3571 self.__options.arrow = false;
3572 }
3573 },
3574
3575 /**
3576 * This method must compute and set the positioning properties of the
3577 * tooltip (left, top, width, height, etc.). It must also make sure the
3578 * tooltip is eventually appended to its parent (since the element may be
3579 * detached from the DOM at the moment the method is called).
3580 *
3581 * We'll evaluate positioning scenarios to find which side can contain the
3582 * tooltip in the best way. We'll consider things relatively to the window
3583 * (unless the user asks not to), then to the document (if need be, or if the
3584 * user explicitly requires the tests to run on the document). For each
3585 * scenario, measures are taken, allowing us to know how well the tooltip
3586 * is going to fit. After that, a sorting function will let us know what
3587 * the best scenario is (we also allow the user to choose his favorite
3588 * scenario by using an event).
3589 *
3590 * @param {object} helper An object that contains variables that plugin
3591 * creators may find useful (see below)
3592 * @param {object} helper.geo An object with many layout properties
3593 * about objects of interest (window, document, origin). This should help
3594 * plugin users compute the optimal position of the tooltip
3595 * @private
3596 */
3597 __reposition: function(event, helper) {
3598
3599 var self = this,
3600 finalResult,
3601 // to know where to put the tooltip, we need to know on which point
3602 // of the x or y axis we should center it. That coordinate is the target
3603 targets = self.__targetFind(helper),
3604 testResults = [];
3605
3606 // make sure the tooltip is detached while we make tests on a clone
3607 self.__instance._$tooltip.detach();
3608
3609 // we could actually provide the original element to the Ruler and
3610 // not a clone, but it just feels right to keep it out of the
3611 // machinery.
3612 var $clone = self.__instance._$tooltip.clone(),
3613 // start position tests session
3614 ruler = $.tooltipster._getRuler($clone),
3615 satisfied = false,
3616 animation = self.__instance.option('animation');
3617
3618 // an animation class could contain properties that distort the size
3619 if (animation) {
3620 $clone.removeClass('tooltipster-'+ animation);
3621 }
3622
3623 // start evaluating scenarios
3624 $.each(['window', 'document'], function(i, container) {
3625
3626 var takeTest = null;
3627
3628 // let the user decide to keep on testing or not
3629 self.__instance._trigger({
3630 container: container,
3631 helper: helper,
3632 satisfied: satisfied,
3633 takeTest: function(bool) {
3634 takeTest = bool;
3635 },
3636 results: testResults,
3637 type: 'positionTest'
3638 });
3639
3640 if ( takeTest == true
3641 || ( takeTest != false
3642 && satisfied == false
3643 // skip the window scenarios if asked. If they are reintegrated by
3644 // the callback of the positionTest event, they will have to be
3645 // excluded using the callback of positionTested
3646 && (container != 'window' || self.__options.viewportAware)
3647 )
3648 ) {
3649
3650 // for each allowed side
3651 for (var i=0; i < self.__options.side.length; i++) {
3652
3653 var distance = {
3654 horizontal: 0,
3655 vertical: 0
3656 },
3657 side = self.__options.side[i];
3658
3659 if (side == 'top' || side == 'bottom') {
3660 distance.vertical = self.__options.distance[side];
3661 }
3662 else {
3663 distance.horizontal = self.__options.distance[side];
3664 }
3665
3666 // this may have an effect on the size of the tooltip if there are css
3667 // rules for the arrow or something else
3668 self.__sideChange($clone, side);
3669
3670 $.each(['natural', 'constrained'], function(i, mode) {
3671
3672 takeTest = null;
3673
3674 // emit an event on the instance
3675 self.__instance._trigger({
3676 container: container,
3677 event: event,
3678 helper: helper,
3679 mode: mode,
3680 results: testResults,
3681 satisfied: satisfied,
3682 side: side,
3683 takeTest: function(bool) {
3684 takeTest = bool;
3685 },
3686 type: 'positionTest'
3687 });
3688
3689 if ( takeTest == true
3690 || ( takeTest != false
3691 && satisfied == false
3692 )
3693 ) {
3694
3695 var testResult = {
3696 container: container,
3697 // we let the distance as an object here, it can make things a little easier
3698 // during the user's calculations at positionTest/positionTested
3699 distance: distance,
3700 // whether the tooltip can fit in the size of the viewport (does not mean
3701 // that we'll be able to make it initially entirely visible, see 'whole')
3702 fits: null,
3703 mode: mode,
3704 outerSize: null,
3705 side: side,
3706 size: null,
3707 target: targets[side],
3708 // check if the origin has enough surface on screen for the tooltip to
3709 // aim at it without overflowing the viewport (this is due to the thickness
3710 // of the arrow represented by the minIntersection length).
3711 // If not, the tooltip will have to be partly or entirely off screen in
3712 // order to stay docked to the origin. This value will stay null when the
3713 // container is the document, as it is not relevant
3714 whole: null
3715 };
3716
3717 // get the size of the tooltip with or without size constraints
3718 var rulerConfigured = (mode == 'natural') ?
3719 ruler.free() :
3720 ruler.constrain(
3721 helper.geo.available[container][side].width - distance.horizontal,
3722 helper.geo.available[container][side].height - distance.vertical
3723 ),
3724 rulerResults = rulerConfigured.measure();
3725
3726 testResult.size = rulerResults.size;
3727 testResult.outerSize = {
3728 height: rulerResults.size.height + distance.vertical,
3729 width: rulerResults.size.width + distance.horizontal
3730 };
3731
3732 if (mode == 'natural') {
3733
3734 if( helper.geo.available[container][side].width >= testResult.outerSize.width
3735 && helper.geo.available[container][side].height >= testResult.outerSize.height
3736 ) {
3737 testResult.fits = true;
3738 }
3739 else {
3740 testResult.fits = false;
3741 }
3742 }
3743 else {
3744 testResult.fits = rulerResults.fits;
3745 }
3746
3747 if (container == 'window') {
3748
3749 if (!testResult.fits) {
3750 testResult.whole = false;
3751 }
3752 else {
3753 if (side == 'top' || side == 'bottom') {
3754
3755 testResult.whole = (
3756 helper.geo.origin.windowOffset.right >= self.__options.minIntersection
3757 && helper.geo.window.size.width - helper.geo.origin.windowOffset.left >= self.__options.minIntersection
3758 );
3759 }
3760 else {
3761 testResult.whole = (
3762 helper.geo.origin.windowOffset.bottom >= self.__options.minIntersection
3763 && helper.geo.window.size.height - helper.geo.origin.windowOffset.top >= self.__options.minIntersection
3764 );
3765 }
3766 }
3767 }
3768
3769 testResults.push(testResult);
3770
3771 // we don't need to compute more positions if we have one fully on screen
3772 if (testResult.whole) {
3773 satisfied = true;
3774 }
3775 else {
3776 // don't run the constrained test unless the natural width was greater
3777 // than the available width, otherwise it's pointless as we know it
3778 // wouldn't fit either
3779 if ( testResult.mode == 'natural'
3780 && ( testResult.fits
3781 || testResult.size.width <= helper.geo.available[container][side].width
3782 )
3783 ) {
3784 return false;
3785 }
3786 }
3787 }
3788 });
3789 }
3790 }
3791 });
3792
3793 // the user may eliminate the unwanted scenarios from testResults, but he's
3794 // not supposed to alter them at this point. functionPosition and the
3795 // position event serve that purpose.
3796 self.__instance._trigger({
3797 edit: function(r) {
3798 testResults = r;
3799 },
3800 event: event,
3801 helper: helper,
3802 results: testResults,
3803 type: 'positionTested'
3804 });
3805
3806 /**
3807 * Sort the scenarios to find the favorite one.
3808 *
3809 * The favorite scenario is when we can fully display the tooltip on screen,
3810 * even if it means that the middle of the tooltip is no longer centered on
3811 * the middle of the origin (when the origin is near the edge of the screen
3812 * or even partly off screen). We want the tooltip on the preferred side,
3813 * even if it means that we have to use a constrained size rather than a
3814 * natural one (as long as it fits). When the origin is off screen at the top
3815 * the tooltip will be positioned at the bottom (if allowed), if the origin
3816 * is off screen on the right, it will be positioned on the left, etc.
3817 * If there are no scenarios where the tooltip can fit on screen, or if the
3818 * user does not want the tooltip to fit on screen (viewportAware == false),
3819 * we fall back to the scenarios relative to the document.
3820 *
3821 * When the tooltip is bigger than the viewport in either dimension, we stop
3822 * looking at the window scenarios and consider the document scenarios only,
3823 * with the same logic to find on which side it would fit best.
3824 *
3825 * If the tooltip cannot fit the document on any side, we force it at the
3826 * bottom, so at least the user can scroll to see it.
3827 */
3828 testResults.sort(function(a, b) {
3829
3830 // best if it's whole (the tooltip fits and adapts to the viewport)
3831 if (a.whole && !b.whole) {
3832 return -1;
3833 }
3834 else if (!a.whole && b.whole) {
3835 return 1;
3836 }
3837 else if (a.whole && b.whole) {
3838
3839 var ai = self.__options.side.indexOf(a.side),
3840 bi = self.__options.side.indexOf(b.side);
3841
3842 // use the user's sides fallback array
3843 if (ai < bi) {
3844 return -1;
3845 }
3846 else if (ai > bi) {
3847 return 1;
3848 }
3849 else {
3850 // will be used if the user forced the tests to continue
3851 return a.mode == 'natural' ? -1 : 1;
3852 }
3853 }
3854 else {
3855
3856 // better if it fits
3857 if (a.fits && !b.fits) {
3858 return -1;
3859 }
3860 else if (!a.fits && b.fits) {
3861 return 1;
3862 }
3863 else if (a.fits && b.fits) {
3864
3865 var ai = self.__options.side.indexOf(a.side),
3866 bi = self.__options.side.indexOf(b.side);
3867
3868 // use the user's sides fallback array
3869 if (ai < bi) {
3870 return -1;
3871 }
3872 else if (ai > bi) {
3873 return 1;
3874 }
3875 else {
3876 // will be used if the user forced the tests to continue
3877 return a.mode == 'natural' ? -1 : 1;
3878 }
3879 }
3880 else {
3881
3882 // if everything failed, this will give a preference to the case where
3883 // the tooltip overflows the document at the bottom
3884 if ( a.container == 'document'
3885 && a.side == 'bottom'
3886 && a.mode == 'natural'
3887 ) {
3888 return -1;
3889 }
3890 else {
3891 return 1;
3892 }
3893 }
3894 }
3895 });
3896
3897 finalResult = testResults[0];
3898
3899
3900 // now let's find the coordinates of the tooltip relatively to the window
3901 finalResult.coord = {};
3902
3903 switch (finalResult.side) {
3904
3905 case 'left':
3906 case 'right':
3907 finalResult.coord.top = Math.floor(finalResult.target - finalResult.size.height / 2);
3908 break;
3909
3910 case 'bottom':
3911 case 'top':
3912 finalResult.coord.left = Math.floor(finalResult.target - finalResult.size.width / 2);
3913 break;
3914 }
3915
3916 switch (finalResult.side) {
3917
3918 case 'left':
3919 finalResult.coord.left = helper.geo.origin.windowOffset.left - finalResult.outerSize.width;
3920 break;
3921
3922 case 'right':
3923 finalResult.coord.left = helper.geo.origin.windowOffset.right + finalResult.distance.horizontal;
3924 break;
3925
3926 case 'top':
3927 finalResult.coord.top = helper.geo.origin.windowOffset.top - finalResult.outerSize.height;
3928 break;
3929
3930 case 'bottom':
3931 finalResult.coord.top = helper.geo.origin.windowOffset.bottom + finalResult.distance.vertical;
3932 break;
3933 }
3934
3935 // if the tooltip can potentially be contained within the viewport dimensions
3936 // and that we are asked to make it fit on screen
3937 if (finalResult.container == 'window') {
3938
3939 // if the tooltip overflows the viewport, we'll move it accordingly (then it will
3940 // not be centered on the middle of the origin anymore). We only move horizontally
3941 // for top and bottom tooltips and vice versa.
3942 if (finalResult.side == 'top' || finalResult.side == 'bottom') {
3943
3944 // if there is an overflow on the left
3945 if (finalResult.coord.left < 0) {
3946
3947 // prevent the overflow unless the origin itself gets off screen (minus the
3948 // margin needed to keep the arrow pointing at the target)
3949 if (helper.geo.origin.windowOffset.right - this.__options.minIntersection >= 0) {
3950 finalResult.coord.left = 0;
3951 }
3952 else {
3953 finalResult.coord.left = helper.geo.origin.windowOffset.right - this.__options.minIntersection - 1;
3954 }
3955 }
3956 // or an overflow on the right
3957 else if (finalResult.coord.left > helper.geo.window.size.width - finalResult.size.width) {
3958
3959 if (helper.geo.origin.windowOffset.left + this.__options.minIntersection <= helper.geo.window.size.width) {
3960 finalResult.coord.left = helper.geo.window.size.width - finalResult.size.width;
3961 }
3962 else {
3963 finalResult.coord.left = helper.geo.origin.windowOffset.left + this.__options.minIntersection + 1 - finalResult.size.width;
3964 }
3965 }
3966 }
3967 else {
3968
3969 // overflow at the top
3970 if (finalResult.coord.top < 0) {
3971
3972 if (helper.geo.origin.windowOffset.bottom - this.__options.minIntersection >= 0) {
3973 finalResult.coord.top = 0;
3974 }
3975 else {
3976 finalResult.coord.top = helper.geo.origin.windowOffset.bottom - this.__options.minIntersection - 1;
3977 }
3978 }
3979 // or at the bottom
3980 else if (finalResult.coord.top > helper.geo.window.size.height - finalResult.size.height) {
3981
3982 if (helper.geo.origin.windowOffset.top + this.__options.minIntersection <= helper.geo.window.size.height) {
3983 finalResult.coord.top = helper.geo.window.size.height - finalResult.size.height;
3984 }
3985 else {
3986 finalResult.coord.top = helper.geo.origin.windowOffset.top + this.__options.minIntersection + 1 - finalResult.size.height;
3987 }
3988 }
3989 }
3990 }
3991 else {
3992
3993 // there might be overflow here too but it's easier to handle. If there has
3994 // to be an overflow, we'll make sure it's on the right side of the screen
3995 // (because the browser will extend the document size if there is an overflow
3996 // on the right, but not on the left). The sort function above has already
3997 // made sure that a bottom document overflow is preferred to a top overflow,
3998 // so we don't have to care about it.
3999
4000 // if there is an overflow on the right
4001 if (finalResult.coord.left > helper.geo.window.size.width - finalResult.size.width) {
4002
4003 // this may actually create on overflow on the left but we'll fix it in a sec
4004 finalResult.coord.left = helper.geo.window.size.width - finalResult.size.width;
4005 }
4006
4007 // if there is an overflow on the left
4008 if (finalResult.coord.left < 0) {
4009
4010 // don't care if it overflows the right after that, we made our best
4011 finalResult.coord.left = 0;
4012 }
4013 }
4014
4015
4016 // submit the positioning proposal to the user function which may choose to change
4017 // the side, size and/or the coordinates
4018
4019 // first, set the rules that corresponds to the proposed side: it may change
4020 // the size of the tooltip, and the custom functionPosition may want to detect the
4021 // size of something before making a decision. So let's make things easier for the
4022 // implementor
4023 self.__sideChange($clone, finalResult.side);
4024
4025 // add some variables to the helper
4026 helper.tooltipClone = $clone[0];
4027 helper.tooltipParent = self.__instance.option('parent').parent[0];
4028 // move informative values to the helper
4029 helper.mode = finalResult.mode;
4030 helper.whole = finalResult.whole;
4031 // add some variables to the helper for the functionPosition callback (these
4032 // will also be added to the event fired by self.__instance._trigger but that's
4033 // ok, we're just being consistent)
4034 helper.origin = self.__instance._$origin[0];
4035 helper.tooltip = self.__instance._$tooltip[0];
4036
4037 // leave only the actionable values in there for functionPosition
4038 delete finalResult.container;
4039 delete finalResult.fits;
4040 delete finalResult.mode;
4041 delete finalResult.outerSize;
4042 delete finalResult.whole;
4043
4044 // keep only the distance on the relevant side, for clarity
4045 finalResult.distance = finalResult.distance.horizontal || finalResult.distance.vertical;
4046
4047 // beginners may not be comfortable with the concept of editing the object
4048 // passed by reference, so we provide an edit function and pass a clone
4049 var finalResultClone = $.extend(true, {}, finalResult);
4050
4051 // emit an event on the instance
4052 self.__instance._trigger({
4053 edit: function(result) {
4054 finalResult = result;
4055 },
4056 event: event,
4057 helper: helper,
4058 position: finalResultClone,
4059 type: 'position'
4060 });
4061
4062 if (self.__options.functionPosition) {
4063
4064 var result = self.__options.functionPosition.call(self, self.__instance, helper, finalResultClone);
4065
4066 if (result) finalResult = result;
4067 }
4068
4069 // end the positioning tests session (the user might have had a
4070 // use for it during the position event, now it's over)
4071 ruler.destroy();
4072
4073 // compute the position of the target relatively to the tooltip root
4074 // element so we can place the arrow and make the needed adjustments
4075 var arrowCoord,
4076 maxVal;
4077
4078 if (finalResult.side == 'top' || finalResult.side == 'bottom') {
4079
4080 arrowCoord = {
4081 prop: 'left',
4082 val: finalResult.target - finalResult.coord.left
4083 };
4084 maxVal = finalResult.size.width - this.__options.minIntersection;
4085 }
4086 else {
4087
4088 arrowCoord = {
4089 prop: 'top',
4090 val: finalResult.target - finalResult.coord.top
4091 };
4092 maxVal = finalResult.size.height - this.__options.minIntersection;
4093 }
4094
4095 // cannot lie beyond the boundaries of the tooltip, minus the
4096 // arrow margin
4097 if (arrowCoord.val < this.__options.minIntersection) {
4098 arrowCoord.val = this.__options.minIntersection;
4099 }
4100 else if (arrowCoord.val > maxVal) {
4101 arrowCoord.val = maxVal;
4102 }
4103
4104 var originParentOffset;
4105
4106 // let's convert the window-relative coordinates into coordinates relative to the
4107 // future positioned parent that the tooltip will be appended to
4108 if (helper.geo.origin.fixedLineage) {
4109
4110 // same as windowOffset when the position is fixed
4111 originParentOffset = helper.geo.origin.windowOffset;
4112 }
4113 else {
4114
4115 // this assumes that the parent of the tooltip is located at
4116 // (0, 0) in the document, typically like when the parent is
4117 // <body>.
4118 // If we ever allow other types of parent, .tooltipster-ruler
4119 // will have to be appended to the parent to inherit css style
4120 // values that affect the display of the text and such.
4121 originParentOffset = {
4122 left: helper.geo.origin.windowOffset.left + helper.geo.window.scroll.left,
4123 top: helper.geo.origin.windowOffset.top + helper.geo.window.scroll.top
4124 };
4125 }
4126
4127 finalResult.coord = {
4128 left: originParentOffset.left + (finalResult.coord.left - helper.geo.origin.windowOffset.left),
4129 top: originParentOffset.top + (finalResult.coord.top - helper.geo.origin.windowOffset.top)
4130 };
4131
4132 // set position values on the original tooltip element
4133
4134 self.__sideChange(self.__instance._$tooltip, finalResult.side);
4135
4136 if (helper.geo.origin.fixedLineage) {
4137 self.__instance._$tooltip
4138 .css('position', 'fixed');
4139 }
4140 else {
4141 // CSS default
4142 self.__instance._$tooltip
4143 .css('position', '');
4144 }
4145
4146 self.__instance._$tooltip
4147 .css({
4148 left: finalResult.coord.left,
4149 top: finalResult.coord.top,
4150 // we need to set a size even if the tooltip is in its natural size
4151 // because when the tooltip is positioned beyond the width of the body
4152 // (which is by default the width of the window; it will happen when
4153 // you scroll the window horizontally to get to the origin), its text
4154 // content will otherwise break lines at each word to keep up with the
4155 // body overflow strategy.
4156 height: finalResult.size.height,
4157 width: finalResult.size.width
4158 })
4159 .find('.tooltipster-arrow')
4160 .css({
4161 'left': '',
4162 'top': ''
4163 })
4164 .css(arrowCoord.prop, arrowCoord.val);
4165
4166 // append the tooltip HTML element to its parent
4167 self.__instance._$tooltip.appendTo(self.__instance.option('parent'));
4168
4169 self.__instance._trigger({
4170 type: 'repositioned',
4171 event: event,
4172 position: finalResult
4173 });
4174 },
4175
4176 /**
4177 * Make whatever modifications are needed when the side is changed. This has
4178 * been made an independant method for easy inheritance in custom plugins based
4179 * on this default plugin.
4180 *
4181 * @param {object} $obj
4182 * @param {string} side
4183 * @private
4184 */
4185 __sideChange: function($obj, side) {
4186
4187 $obj
4188 .removeClass('tooltipster-bottom')
4189 .removeClass('tooltipster-left')
4190 .removeClass('tooltipster-right')
4191 .removeClass('tooltipster-top')
4192 .addClass('tooltipster-'+ side);
4193 },
4194
4195 /**
4196 * Returns the target that the tooltip should aim at for a given side.
4197 * The calculated value is a distance from the edge of the window
4198 * (left edge for top/bottom sides, top edge for left/right side). The
4199 * tooltip will be centered on that position and the arrow will be
4200 * positioned there (as much as possible).
4201 *
4202 * @param {object} helper
4203 * @return {integer}
4204 * @private
4205 */
4206 __targetFind: function(helper) {
4207
4208 var target = {},
4209 rects = this.__instance._$origin[0].getClientRects();
4210
4211 // these lines fix a Chrome bug (issue #491)
4212 if (rects.length > 1) {
4213 var opacity = this.__instance._$origin.css('opacity');
4214 if(opacity == 1) {
4215 this.__instance._$origin.css('opacity', 0.99);
4216 rects = this.__instance._$origin[0].getClientRects();
4217 this.__instance._$origin.css('opacity', 1);
4218 }
4219 }
4220
4221 // by default, the target will be the middle of the origin
4222 if (rects.length < 2) {
4223
4224 target.top = Math.floor(helper.geo.origin.windowOffset.left + (helper.geo.origin.size.width / 2));
4225 target.bottom = target.top;
4226
4227 target.left = Math.floor(helper.geo.origin.windowOffset.top + (helper.geo.origin.size.height / 2));
4228 target.right = target.left;
4229 }
4230 // if multiple client rects exist, the element may be text split
4231 // up into multiple lines and the middle of the origin may not be
4232 // best option anymore. We need to choose the best target client rect
4233 else {
4234
4235 // top: the first
4236 var targetRect = rects[0];
4237 target.top = Math.floor(targetRect.left + (targetRect.right - targetRect.left) / 2);
4238
4239 // right: the middle line, rounded down in case there is an even
4240 // number of lines (looks more centered => check out the
4241 // demo with 4 split lines)
4242 if (rects.length > 2) {
4243 targetRect = rects[Math.ceil(rects.length / 2) - 1];
4244 }
4245 else {
4246 targetRect = rects[0];
4247 }
4248 target.right = Math.floor(targetRect.top + (targetRect.bottom - targetRect.top) / 2);
4249
4250 // bottom: the last
4251 targetRect = rects[rects.length - 1];
4252 target.bottom = Math.floor(targetRect.left + (targetRect.right - targetRect.left) / 2);
4253
4254 // left: the middle line, rounded up
4255 if (rects.length > 2) {
4256 targetRect = rects[Math.ceil((rects.length + 1) / 2) - 1];
4257 }
4258 else {
4259 targetRect = rects[rects.length - 1];
4260 }
4261
4262 target.left = Math.floor(targetRect.top + (targetRect.bottom - targetRect.top) / 2);
4263 }
4264
4265 return target;
4266 }
4267 }
4268 });
4269
4270 /* a build task will add "return $;" here */
4271 return $;
4272
4273 }));
4274
4275 //
4276 // $(document).ready(function() {
4277 // $('.tooltip-hovercard').tooltipster({
4278 // theme: 'tooltipster-shadow',
4279 // animation: 'fade',
4280 // delay: 100,
4281 // contentCloning: true,
4282 //
4283 // }); No newline at end of file
@@ -1,137 +1,139 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 ${('active' if c.active=='all' else '')}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
30 <li class="btn ${('active' if c.active=='all' else '')}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
31 %endif
31 %endif
32 <li class="btn ${('active' if c.active=='public' else '')}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li>
32 <li class="btn ${('active' if c.active=='public' else '')}"><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 ${('active' if c.active=='my_all' else '')}"><a href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
34 <li class="btn ${('active' if c.active=='my_all' else '')}"><a href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
35 <li class="btn ${('active' if c.active=='my_private' else '')}"><a href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
35 <li class="btn ${('active' if c.active=='my_private' else '')}"><a href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
36 <li class="btn ${('active' if c.active=='my_public' else '')}"><a href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
36 <li class="btn ${('active' if c.active=='my_public' else '')}"><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 <div class="grid-quick-filter">
40 <div class="grid-quick-filter">
41 <ul class="grid-filter-box">
41 <ul class="grid-filter-box">
42 <li class="grid-filter-box-icon">
42 <li class="grid-filter-box-icon">
43 <i class="icon-search"></i>
43 <i class="icon-search"></i>
44 </li>
44 </li>
45 <li class="grid-filter-box-input">
45 <li class="grid-filter-box-input">
46 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
46 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
47 </li>
47 </li>
48 </ul>
48 </ul>
49 </div>
49 </div>
50
50
51 </div>
51 </div>
52
52
53 <div class="main-content-full-width">
53 <div class="main-content-full-width">
54 <div id="repos_list_wrap">
54 <div id="repos_list_wrap">
55 <table id="gist_list_table" class="display"></table>
55 <table id="gist_list_table" class="display"></table>
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59 </div>
59 </div>
60
60
61 <script type="text/javascript">
61 <script type="text/javascript">
62 $(document).ready(function() {
62 $(document).ready(function() {
63
63
64 var get_datatable_count = function(){
64 var get_datatable_count = function(){
65 var api = $('#gist_list_table').dataTable().api();
65 var api = $('#gist_list_table').dataTable().api();
66 $('#gists_count').text(api.page.info().recordsDisplay);
66 $('#gists_count').text(api.page.info().recordsDisplay);
67 };
67 };
68
68
69
69
70 // custom filter that filters by access_id, description or author
70 // custom filter that filters by access_id, description or author
71 $.fn.dataTable.ext.search.push(
71 $.fn.dataTable.ext.search.push(
72 function( settings, data, dataIndex ) {
72 function( settings, data, dataIndex ) {
73 var query = $('#q_filter').val();
73 var query = $('#q_filter').val();
74 var author = data[0].strip();
74 var author = data[0].strip();
75 var access_id = data[2].strip();
75 var access_id = data[2].strip();
76 var description = data[3].strip();
76 var description = data[3].strip();
77
77
78 var query_str = (access_id + " " + author + " " + description).toLowerCase();
78 var query_str = (access_id + " " + author + " " + description).toLowerCase();
79
79
80 if(query_str.indexOf(query.toLowerCase()) !== -1){
80 if(query_str.indexOf(query.toLowerCase()) !== -1){
81 return true;
81 return true;
82 }
82 }
83 return false;
83 return false;
84 }
84 }
85 );
85 );
86
86
87 // gists list
87 // gists list
88 $('#gist_list_table').DataTable({
88 $('#gist_list_table').DataTable({
89 data: ${c.data|n},
89 data: ${c.data|n},
90 dom: 'rtp',
90 dom: 'rtp',
91 pageLength: ${c.visual.dashboard_items},
91 pageLength: ${c.visual.dashboard_items},
92 order: [[ 4, "desc" ]],
92 order: [[ 4, "desc" ]],
93 columns: [
93 columns: [
94 { data: {"_": "author",
94 { data: {"_": "author",
95 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
95 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
96 { data: {"_": "type",
96 { data: {"_": "type",
97 "sort": "type"}, title: "${_("Type")}", width: "70px", className: "td-tags" },
97 "sort": "type"}, title: "${_("Type")}", width: "70px", className: "td-tags" },
98 { data: {"_": "access_id",
98 { data: {"_": "access_id",
99 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
99 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
100 { data: {"_": "description",
100 { data: {"_": "description",
101 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
101 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
102 { data: {"_": "created_on",
102 { data: {"_": "created_on",
103 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
103 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
104 { data: {"_": "expires",
104 { data: {"_": "expires",
105 "sort": "expires"}, title: "${_("Expires")}", className: "td-exp" }
105 "sort": "expires"}, title: "${_("Expires")}", className: "td-exp" }
106 ],
106 ],
107 language: {
107 language: {
108 paginate: DEFAULT_GRID_PAGINATION,
108 paginate: DEFAULT_GRID_PAGINATION,
109 emptyTable: _gettext("No gists available yet.")
109 emptyTable: _gettext("No gists available yet.")
110 },
110 },
111 "initComplete": function( settings, json ) {
111 "initComplete": function( settings, json ) {
112 timeagoActivate();
112 timeagoActivate();
113 tooltipActivate();
113 get_datatable_count();
114 get_datatable_count();
114 }
115 }
115 });
116 });
116
117
117 // update the counter when things change
118 // update the counter when things change
118 $('#gist_list_table').on('draw.dt', function() {
119 $('#gist_list_table').on('draw.dt', function() {
119 timeagoActivate();
120 timeagoActivate();
121 tooltipActivate();
120 get_datatable_count();
122 get_datatable_count();
121 });
123 });
122
124
123 // filter, filter both grids
125 // filter, filter both grids
124 $('#q_filter').on( 'keyup', function () {
126 $('#q_filter').on( 'keyup', function () {
125 var repo_api = $('#gist_list_table').dataTable().api();
127 var repo_api = $('#gist_list_table').dataTable().api();
126 repo_api
128 repo_api
127 .draw();
129 .draw();
128 });
130 });
129
131
130 // refilter table if page load via back button
132 // refilter table if page load via back button
131 $("#q_filter").trigger('keyup');
133 $("#q_filter").trigger('keyup');
132
134
133 });
135 });
134
136
135 </script>
137 </script>
136 </%def>
138 </%def>
137
139
@@ -1,111 +1,111 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="robots()">
4 <%def name="robots()">
5 %if c.gist.gist_type != 'public':
5 %if c.gist.gist_type != 'public':
6 <meta name="robots" content="noindex, nofollow">
6 <meta name="robots" content="noindex, nofollow">
7 %else:
7 %else:
8 ${parent.robots()}
8 ${parent.robots()}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="title()">
12 <%def name="title()">
13 ${_('Gist')} &middot; ${c.gist.gist_access_id}
13 ${_('Gist')} &middot; ${c.gist.gist_access_id}
14 %if c.rhodecode_name:
14 %if c.rhodecode_name:
15 &middot; ${h.branding(c.rhodecode_name)}
15 &middot; ${h.branding(c.rhodecode_name)}
16 %endif
16 %endif
17 </%def>
17 </%def>
18
18
19 <%def name="breadcrumbs_links()">
19 <%def name="breadcrumbs_links()">
20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='gists')}
24 ${self.menu_items(active='gists')}
25 </%def>
25 </%def>
26
26
27 <%def name="main()">
27 <%def name="main()">
28 <div class="box">
28 <div class="box">
29 <!-- box / title -->
29 <!-- box / title -->
30 <div class="title">
30 <div class="title">
31 ${self.breadcrumbs()}
31 ${self.breadcrumbs()}
32 </div>
32 </div>
33
33
34 <div class="table">
34 <div class="table">
35 <div id="files_data">
35 <div id="files_data">
36 <div id="codeblock" class="codeblock">
36 <div id="codeblock" class="codeblock">
37 <div class="code-header">
37 <div class="code-header">
38 <div class="gist_url">
38 <div class="gist_url">
39 <code>
39 <code>
40 ${c.gist.gist_url()} <span class="icon-clipboard clipboard-action" data-clipboard-text="${c.gist.gist_url()}" title="${_('Copy the url')}"></span>
40 ${c.gist.gist_url()} <span class="icon-clipboard clipboard-action" data-clipboard-text="${c.gist.gist_url()}" title="${_('Copy the url')}"></span>
41 </code>
41 </code>
42 </div>
42 </div>
43 <div class="stats">
43 <div class="stats">
44 %if c.is_super_admin or c.gist.gist_owner == c.rhodecode_user.user_id:
44 %if c.is_super_admin or c.gist.gist_owner == c.rhodecode_user.user_id:
45 <div class="remove_gist">
45 <div class="remove_gist">
46 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), request=request)}
46 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), request=request)}
47 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
47 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
48 ${h.end_form()}
48 ${h.end_form()}
49 </div>
49 </div>
50 %endif
50 %endif
51 <div class="buttons">
51 <div class="buttons">
52 ## only owner should see that
52 ## only owner should see that
53 <a href="#copySource" onclick="return false;" class="btn btn-mini icon-clipboard clipboard-action" data-clipboard-text="${c.files[0].content}">${_('Copy content')}</a>
53 <a href="#copySource" onclick="return false;" class="btn btn-mini icon-clipboard clipboard-action" data-clipboard-text="${c.files[0].content}">${_('Copy content')}</a>
54
54
55 %if c.is_super_admin or c.gist.gist_owner == c.rhodecode_user.user_id:
55 %if c.is_super_admin or c.gist.gist_owner == c.rhodecode_user.user_id:
56 ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")}
56 ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")}
57 %endif
57 %endif
58 ${h.link_to(_('Show as Raw'), h.route_path('gist_show_formatted', gist_id=c.gist.gist_access_id, revision='tip', format='raw'), class_="btn btn-mini")}
58 ${h.link_to(_('Show as Raw'), h.route_path('gist_show_formatted', gist_id=c.gist.gist_access_id, revision='tip', format='raw'), class_="btn btn-mini")}
59 </div>
59 </div>
60 <div class="left" >
60 <div class="left" >
61 %if c.gist.gist_type != 'public':
61 %if c.gist.gist_type != 'public':
62 <span class="tag tag-ok disabled">${_('Private Gist')}</span>
62 <span class="tag tag-ok disabled">${_('Private Gist')}</span>
63 %endif
63 %endif
64 <span> ${c.gist.gist_description}</span>
64 <span> ${c.gist.gist_description}</span>
65 <span>${_('Expires')}:
65 <span>${_('Expires')}:
66 %if c.gist.gist_expires == -1:
66 %if c.gist.gist_expires == -1:
67 ${_('never')}
67 ${_('never')}
68 %else:
68 %else:
69 ${h.age_component(h.time_to_utcdatetime(c.gist.gist_expires))}
69 ${h.age_component(h.time_to_utcdatetime(c.gist.gist_expires))}
70 %endif
70 %endif
71 </span>
71 </span>
72
72
73 </div>
73 </div>
74 </div>
74 </div>
75
75
76 <div class="author">
76 <div class="author">
77 <div title="${h.tooltip(c.file_last_commit.author)}">
77 <div title="${h.tooltip(c.file_last_commit.author)}">
78 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
78 ${self.gravatar_with_user(c.file_last_commit.author, 16, tooltip=True)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
79 </div>
79 </div>
80
80
81 </div>
81 </div>
82 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div>
82 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div>
83 </div>
83 </div>
84
84
85 ## iterate over the files
85 ## iterate over the files
86 % for file in c.files:
86 % for file in c.files:
87 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
87 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
88 <!--
88 <!--
89 <div id="${h.FID('G', file.path)}" class="stats" >
89 <div id="${h.FID('G', file.path)}" class="stats" >
90 <a href="${c.gist.gist_url()}">¶</a>
90 <a href="${c.gist.gist_url()}">¶</a>
91 <b >${file.path}</b>
91 <b >${file.path}</b>
92 <div>
92 <div>
93 ${h.link_to(_('Show as raw'), h.route_path('gist_show_formatted_path', gist_id=c.gist.gist_access_id, revision=file.commit.raw_id, format='raw', f_path=file.path), class_="btn btn-mini")}
93 ${h.link_to(_('Show as raw'), h.route_path('gist_show_formatted_path', gist_id=c.gist.gist_access_id, revision=file.commit.raw_id, format='raw', f_path=file.path), class_="btn btn-mini")}
94 </div>
94 </div>
95 </div>
95 </div>
96 -->
96 -->
97 <div class="code-body textarea text-area editor">
97 <div class="code-body textarea text-area editor">
98 %if renderer:
98 %if renderer:
99 ${h.render(file.content, renderer=renderer)}
99 ${h.render(file.content, renderer=renderer)}
100 %else:
100 %else:
101 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
101 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
102 %endif
102 %endif
103 </div>
103 </div>
104 %endfor
104 %endfor
105 </div>
105 </div>
106 </div>
106 </div>
107 </div>
107 </div>
108
108
109
109
110 </div>
110 </div>
111 </%def>
111 </%def>
@@ -1,93 +1,94 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-body">
4 <div class="panel-body">
5 %if c.closed:
5 %if c.closed:
6 ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))}
6 ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))}
7 %else:
7 %else:
8 ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))}
8 ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))}
9 %endif
9 %endif
10 </div>
10 </div>
11 </div>
11 </div>
12
12
13 <div class="panel panel-default">
13 <div class="panel panel-default">
14 <div class="panel-heading">
14 <div class="panel-heading">
15 <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3>
15 <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3>
16 </div>
16 </div>
17 <div class="panel-body panel-body-min-height">
17 <div class="panel-body panel-body-min-height">
18 <table id="pull_request_list_table" class="display"></table>
18 <table id="pull_request_list_table" class="display"></table>
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <script type="text/javascript">
22 <script type="text/javascript">
23 $(document).ready(function() {
23 $(document).ready(function() {
24
24
25 $('#show_closed').on('click', function(e){
25 $('#show_closed').on('click', function(e){
26 if($(this).is(":checked")){
26 if($(this).is(":checked")){
27 window.location = "${h.route_path('my_account_pullrequests', _query={'pr_show_closed':1})}";
27 window.location = "${h.route_path('my_account_pullrequests', _query={'pr_show_closed':1})}";
28 }
28 }
29 else{
29 else{
30 window.location = "${h.route_path('my_account_pullrequests')}";
30 window.location = "${h.route_path('my_account_pullrequests')}";
31 }
31 }
32 });
32 });
33
33
34 var $pullRequestListTable = $('#pull_request_list_table');
34 var $pullRequestListTable = $('#pull_request_list_table');
35
35
36 // participating object list
36 // participating object list
37 $pullRequestListTable.DataTable({
37 $pullRequestListTable.DataTable({
38 processing: true,
38 processing: true,
39 serverSide: true,
39 serverSide: true,
40 ajax: {
40 ajax: {
41 "url": "${h.route_path('my_account_pullrequests_data')}",
41 "url": "${h.route_path('my_account_pullrequests_data')}",
42 "data": function (d) {
42 "data": function (d) {
43 d.closed = "${c.closed}";
43 d.closed = "${c.closed}";
44 }
44 }
45 },
45 },
46 dom: 'rtp',
46 dom: 'rtp',
47 pageLength: ${c.visual.dashboard_items},
47 pageLength: ${c.visual.dashboard_items},
48 order: [[ 2, "desc" ]],
48 order: [[ 2, "desc" ]],
49 columns: [
49 columns: [
50 { data: {"_": "status",
50 { data: {"_": "status",
51 "sort": "status"}, title: "", className: "td-status", orderable: false},
51 "sort": "status"}, title: "", className: "td-status", orderable: false},
52 { data: {"_": "target_repo",
52 { data: {"_": "target_repo",
53 "sort": "target_repo"}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false},
53 "sort": "target_repo"}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false},
54 { data: {"_": "name",
54 { data: {"_": "name",
55 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname", "type": "num" },
55 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname", "type": "num" },
56 { data: {"_": "author",
56 { data: {"_": "author",
57 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
57 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
58 { data: {"_": "title",
58 { data: {"_": "title",
59 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
59 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
60 { data: {"_": "comments",
60 { data: {"_": "comments",
61 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
61 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
62 { data: {"_": "updated_on",
62 { data: {"_": "updated_on",
63 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
63 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
64 ],
64 ],
65 language: {
65 language: {
66 paginate: DEFAULT_GRID_PAGINATION,
66 paginate: DEFAULT_GRID_PAGINATION,
67 sProcessing: _gettext('loading...'),
67 sProcessing: _gettext('loading...'),
68 emptyTable: _gettext("There are currently no open pull requests requiring your participation.")
68 emptyTable: _gettext("There are currently no open pull requests requiring your participation.")
69 },
69 },
70 "drawCallback": function( settings, json ) {
70 "drawCallback": function( settings, json ) {
71 timeagoActivate();
71 timeagoActivate();
72 tooltipActivate();
72 },
73 },
73 "createdRow": function ( row, data, index ) {
74 "createdRow": function ( row, data, index ) {
74 if (data['closed']) {
75 if (data['closed']) {
75 $(row).addClass('closed');
76 $(row).addClass('closed');
76 }
77 }
77 if (data['owned']) {
78 if (data['owned']) {
78 $(row).addClass('owned');
79 $(row).addClass('owned');
79 }
80 }
80 if (data['state'] !== 'created') {
81 if (data['state'] !== 'created') {
81 $(row).addClass('state-' + data['state']);
82 $(row).addClass('state-' + data['state']);
82 }
83 }
83 }
84 }
84 });
85 });
85 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
86 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
86 $pullRequestListTable.css('opacity', 1);
87 $pullRequestListTable.css('opacity', 1);
87 });
88 });
88
89
89 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
90 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
90 $pullRequestListTable.css('opacity', 0.3);
91 $pullRequestListTable.css('opacity', 0.3);
91 });
92 });
92 });
93 });
93 </script>
94 </script>
@@ -1,71 +1,71 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 source_repo_id = c.repo_group.changeset_cache.get('source_repo_id')
4 source_repo_id = c.repo_group.changeset_cache.get('source_repo_id')
5
5
6 elems = [
6 elems = [
7 (_('Repository Group ID'), c.repo_group.group_id, '', ''),
7 (_('Repository Group ID'), c.repo_group.group_id, '', ''),
8 (_('Owner'), lambda:base.gravatar_with_user(c.repo_group.user.email), '', ''),
8 (_('Owner'), lambda:base.gravatar_with_user(c.repo_group.user.email, tooltip=True), '', ''),
9 (_('Created on'), h.format_date(c.repo_group.created_on), '', ''),
9 (_('Created on'), h.format_date(c.repo_group.created_on), '', ''),
10 (_('Updated on'), h.format_date(c.repo_group.updated_on), '', ''),
10 (_('Updated on'), h.format_date(c.repo_group.updated_on), '', ''),
11 (_('Cached Commit date'), (c.repo_group.changeset_cache.get('date')), '', ''),
11 (_('Cached Commit date'), (c.repo_group.changeset_cache.get('date')), '', ''),
12 (_('Cached Commit repo_id'), (h.link_to_if(source_repo_id, source_repo_id, h.route_path('repo_summary', repo_name='_{}'.format(source_repo_id)))), '', ''),
12 (_('Cached Commit repo_id'), (h.link_to_if(source_repo_id, source_repo_id, h.route_path('repo_summary', repo_name='_{}'.format(source_repo_id)))), '', ''),
13
13
14 (_('Is Personal Group'), c.repo_group.personal or False, '', ''),
14 (_('Is Personal Group'), c.repo_group.personal or False, '', ''),
15
15
16 (_('Total repositories'), c.repo_group.repositories_recursive_count, '', ''),
16 (_('Total repositories'), c.repo_group.repositories_recursive_count, '', ''),
17 (_('Top level repositories'), c.repo_group.repositories.count(), '', c.repo_group.repositories.all()),
17 (_('Top level repositories'), c.repo_group.repositories.count(), '', c.repo_group.repositories.all()),
18
18
19 (_('Children groups'), c.repo_group.children.count(), '', c.repo_group.children.all()),
19 (_('Children groups'), c.repo_group.children.count(), '', c.repo_group.children.all()),
20 ]
20 ]
21 %>
21 %>
22
22
23 <div class="panel panel-default">
23 <div class="panel panel-default">
24 <div class="panel-heading">
24 <div class="panel-heading">
25 <h3 class="panel-title">${_('Repository Group Advanced: {}').format(c.repo_group.name)}</h3>
25 <h3 class="panel-title">${_('Repository Group Advanced: {}').format(c.repo_group.name)}</h3>
26 </div>
26 </div>
27 <div class="panel-body">
27 <div class="panel-body">
28 ${base.dt_info_panel(elems)}
28 ${base.dt_info_panel(elems)}
29 </div>
29 </div>
30
30
31 </div>
31 </div>
32
32
33 <div class="panel panel-danger">
33 <div class="panel panel-danger">
34 <div class="panel-heading">
34 <div class="panel-heading">
35 <h3 class="panel-title">${_('Delete repository group')}</h3>
35 <h3 class="panel-title">${_('Delete repository group')}</h3>
36 </div>
36 </div>
37 <div class="panel-body">
37 <div class="panel-body">
38 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=c.repo_group.group_name), request=request)}
38 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=c.repo_group.group_name), request=request)}
39 <table class="display">
39 <table class="display">
40
40
41 <tr>
41 <tr>
42 <td>
42 <td>
43 ${_ungettext('This repository group includes %s children repository group.', 'This repository group includes %s children repository groups.', c.repo_group.children.count()) % c.repo_group.children.count()}
43 ${_ungettext('This repository group includes %s children repository group.', 'This repository group includes %s children repository groups.', c.repo_group.children.count()) % c.repo_group.children.count()}
44 </td>
44 </td>
45 <td>
45 <td>
46 </td>
46 </td>
47 <td>
47 <td>
48 </td>
48 </td>
49 </tr>
49 </tr>
50 <tr>
50 <tr>
51 <td>
51 <td>
52 ${_ungettext('This repository group includes %s repository.', 'This repository group includes %s repositories.', c.repo_group.repositories_recursive_count) % c.repo_group.repositories_recursive_count}
52 ${_ungettext('This repository group includes %s repository.', 'This repository group includes %s repositories.', c.repo_group.repositories_recursive_count) % c.repo_group.repositories_recursive_count}
53 </td>
53 </td>
54 <td>
54 <td>
55 </td>
55 </td>
56 <td>
56 <td>
57 </td>
57 </td>
58 </tr>
58 </tr>
59
59
60 </table>
60 </table>
61 <div style="margin: 0 0 20px 0" class="fake-space"></div>
61 <div style="margin: 0 0 20px 0" class="fake-space"></div>
62
62
63 <button class="btn btn-small btn-danger" type="submit"
63 <button class="btn btn-small btn-danger" type="submit"
64 onclick="return confirm('${_('Confirm to delete this group: %s') % (c.repo_group.group_name)}');">
64 onclick="return confirm('${_('Confirm to delete this group: %s') % (c.repo_group.group_name)}');">
65 ${_('Delete this repository group')}
65 ${_('Delete this repository group')}
66 </button>
66 </button>
67 ${h.end_form()}
67 ${h.end_form()}
68 </div>
68 </div>
69 </div>
69 </div>
70
70
71
71
@@ -1,219 +1,220 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Group Permissions: {}').format(c.repo_group.name)}</h3>
5 <h3 class="panel-title">${_('Repository Group Permissions: {}').format(c.repo_group.name)}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.repo_group.permissions():
20 %for _user in c.repo_group.permissions():
21 ## super admin/owner row
21 ## super admin/owner row
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
23 <tr class="perm_admin_row">
23 <tr class="perm_admin_row">
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
28 <td class="td-user">
28 <td class="td-user">
29 ${base.gravatar(_user.email, 16)}
29 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
30 ${h.link_to_user(_user.username)}
30 ${h.link_to_user(_user.username)}
31 %if getattr(_user, 'admin_row', None):
31 %if getattr(_user, 'admin_row', None):
32 (${_('super admin')})
32 (${_('super admin')})
33 %endif
33 %endif
34 %if getattr(_user, 'owner_row', None):
34 %if getattr(_user, 'owner_row', None):
35 (${_('owner')})
35 (${_('owner')})
36 %endif
36 %endif
37 </td>
37 </td>
38 <td></td>
38 <td></td>
39 <td class="quick_repo_menu">
39 <td class="quick_repo_menu">
40 % if c.rhodecode_user.is_admin:
40 % if c.rhodecode_user.is_admin:
41 <i class="icon-more"></i>
41 <i class="icon-more"></i>
42 <div class="menu_items_container" style="display: none;">
42 <div class="menu_items_container" style="display: none;">
43 <ul class="menu_items">
43 <ul class="menu_items">
44 <li>
44 <li>
45 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
45 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
46 </li>
46 </li>
47 </ul>
47 </ul>
48 </div>
48 </div>
49 % endif
49 % endif
50 </td>
50 </td>
51 </tr>
51 </tr>
52 %else:
52 %else:
53 <tr>
53 <tr>
54 ##forbid revoking permission from yourself, except if you're an super admin
54 ##forbid revoking permission from yourself, except if you're an super admin
55 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
55 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
57 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
57 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
60 <td class="td-user">
60 <td class="td-user">
61 ${base.gravatar(_user.email, 16)}
61 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
62 <span class="user">
62 <span class="user">
63 % if _user.username == h.DEFAULT_USER:
63 % if _user.username == h.DEFAULT_USER:
64 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
64 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
65 % else:
65 % else:
66 ${h.link_to_user(_user.username)}
66 ${h.link_to_user(_user.username)}
67 %if getattr(_user, 'duplicate_perm', None):
67 %if getattr(_user, 'duplicate_perm', None):
68 (${_('inactive duplicate')})
68 (${_('inactive duplicate')})
69 %endif
69 %endif
70 % endif
70 % endif
71 </span>
71 </span>
72 </td>
72 </td>
73 <td class="td-action">
73 <td class="td-action">
74 %if _user.username != h.DEFAULT_USER:
74 %if _user.username != h.DEFAULT_USER:
75 <span class="btn btn-link btn-danger revoke_perm"
75 <span class="btn btn-link btn-danger revoke_perm"
76 member="${_user.user_id}" member_type="user">
76 member="${_user.user_id}" member_type="user">
77 ${_('Remove')}
77 ${_('Remove')}
78 </span>
78 </span>
79 %endif
79 %endif
80 </td>
80 </td>
81 <td class="quick_repo_menu">
81 <td class="quick_repo_menu">
82 % if c.rhodecode_user.is_admin:
82 % if c.rhodecode_user.is_admin:
83 <i class="icon-more"></i>
83 <i class="icon-more"></i>
84 <div class="menu_items_container" style="display: none;">
84 <div class="menu_items_container" style="display: none;">
85 <ul class="menu_items">
85 <ul class="menu_items">
86 <li>
86 <li>
87 % if _user.username == h.DEFAULT_USER:
87 % if _user.username == h.DEFAULT_USER:
88 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-groups-permissions'))}
88 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-groups-permissions'))}
89 % else:
89 % else:
90 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
90 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
91 % endif
91 % endif
92 </li>
92 </li>
93 </ul>
93 </ul>
94 </div>
94 </div>
95 % endif
95 % endif
96 </td>
96 </td>
97 %else:
97 %else:
98 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
98 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
99 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
99 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
100 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
100 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
103 <td class="td-user">
103 <td class="td-user">
104 ${base.gravatar(_user.email, 16)}
104 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
105 <span class="user">
105 <span class="user">
106 % if _user.username == h.DEFAULT_USER:
106 % if _user.username == h.DEFAULT_USER:
107 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
107 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
108 % else:
108 % else:
109 ${h.link_to_user(_user.username)}
109 ${h.link_to_user(_user.username)}
110 %if getattr(_user, 'duplicate_perm', None):
110 %if getattr(_user, 'duplicate_perm', None):
111 (${_('inactive duplicate')})
111 (${_('inactive duplicate')})
112 %endif
112 %endif
113 % endif
113 % endif
114 <span class="user-perm-help-text">(${_('delegated admin')})</span>
114 <span class="user-perm-help-text">(${_('delegated admin')})</span>
115 </span>
115 </span>
116 </td>
116 </td>
117 <td></td>
117 <td></td>
118 <td class="quick_repo_menu">
118 <td class="quick_repo_menu">
119 % if c.rhodecode_user.is_admin:
119 % if c.rhodecode_user.is_admin:
120 <i class="icon-more"></i>
120 <i class="icon-more"></i>
121 <div class="menu_items_container" style="display: none;">
121 <div class="menu_items_container" style="display: none;">
122 <ul class="menu_items">
122 <ul class="menu_items">
123 <li>
123 <li>
124 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
124 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
125 </li>
125 </li>
126 </ul>
126 </ul>
127 </div>
127 </div>
128 % endif
128 % endif
129 </td>
129 </td>
130 %endif
130 %endif
131 </tr>
131 </tr>
132 %endif
132 %endif
133 %endfor
133 %endfor
134
134
135 ## USER GROUPS
135 ## USER GROUPS
136 %for _user_group in c.repo_group.permission_user_groups(with_members=True):
136 %for _user_group in c.repo_group.permission_user_groups(with_members=True):
137 <tr id="id${id(_user_group.users_group_name)}">
137 <tr id="id${id(_user_group.users_group_name)}">
138 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
138 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
139 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
139 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
142 <td class="td-componentname">
142 <td class="td-componentname">
143 <i class="icon-user-group"></i>
143 ${base.user_group_icon(_user_group, tooltip=True)}
144
144 %if c.is_super_admin:
145 %if c.is_super_admin:
145 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
146 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
146 ${_user_group.users_group_name}
147 ${_user_group.users_group_name}
147 </a>
148 </a>
148 %else:
149 %else:
149 ${h.link_to_group(_user_group.users_group_name)}
150 ${h.link_to_group(_user_group.users_group_name)}
150 %endif
151 %endif
151 (${_('members')}: ${len(_user_group.members)})
152 (${_('members')}: ${len(_user_group.members)})
152 </td>
153 </td>
153 <td class="td-action">
154 <td class="td-action">
154 <span class="btn btn-link btn-danger revoke_perm"
155 <span class="btn btn-link btn-danger revoke_perm"
155 member="${_user_group.users_group_id}" member_type="user_group">
156 member="${_user_group.users_group_id}" member_type="user_group">
156 ${_('Remove')}
157 ${_('Remove')}
157 </span>
158 </span>
158 </td>
159 </td>
159 <td class="quick_repo_menu">
160 <td class="quick_repo_menu">
160 % if c.rhodecode_user.is_admin:
161 % if c.rhodecode_user.is_admin:
161 <i class="icon-more"></i>
162 <i class="icon-more"></i>
162 <div class="menu_items_container" style="display: none;">
163 <div class="menu_items_container" style="display: none;">
163 <ul class="menu_items">
164 <ul class="menu_items">
164 <li>
165 <li>
165 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-groups-permissions'))}
166 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-groups-permissions'))}
166 </li>
167 </li>
167 </ul>
168 </ul>
168 </div>
169 </div>
169 % endif
170 % endif
170 </td>
171 </td>
171 </tr>
172 </tr>
172 %endfor
173 %endfor
173
174
174 <tr class="new_members" id="add_perm_input"></tr>
175 <tr class="new_members" id="add_perm_input"></tr>
175 <tr>
176 <tr>
176 <td></td>
177 <td></td>
177 <td></td>
178 <td></td>
178 <td></td>
179 <td></td>
179 <td></td>
180 <td></td>
180 <td></td>
181 <td></td>
181 <td>
182 <td>
182 <span id="add_perm" class="link">
183 <span id="add_perm" class="link">
183 ${_('Add user/user group')}
184 ${_('Add user/user group')}
184 </span>
185 </span>
185 </td>
186 </td>
186 <td></td>
187 <td></td>
187 </tr>
188 </tr>
188 </table>
189 </table>
189
190
190 <div class="fields">
191 <div class="fields">
191 <div class="field">
192 <div class="field">
192 <div class="label label-radio">
193 <div class="label label-radio">
193 ${_('Apply to children')}:
194 ${_('Apply to children')}:
194 </div>
195 </div>
195 <div class="radios">
196 <div class="radios">
196 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
197 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
197 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
198 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
198 ${h.radio('recursive', 'repos', label=_('Repositories'))}
199 ${h.radio('recursive', 'repos', label=_('Repositories'))}
199 ${h.radio('recursive', 'all', label=_('Both'))}
200 ${h.radio('recursive', 'all', label=_('Both'))}
200 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
201 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
201 </div>
202 </div>
202 </div>
203 </div>
203 </div>
204 </div>
204 <div class="buttons">
205 <div class="buttons">
205 ${h.submit('save',_('Save'),class_="btn btn-primary")}
206 ${h.submit('save',_('Save'),class_="btn btn-primary")}
206 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
207 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
207 </div>
208 </div>
208 ${h.end_form()}
209 ${h.end_form()}
209 </div>
210 </div>
210 </div>
211 </div>
211 <script type="text/javascript">
212 <script type="text/javascript">
212 $('#add_perm').on('click', function(e){
213 $('#add_perm').on('click', function(e){
213 addNewPermInput($(this), 'group');
214 addNewPermInput($(this), 'group');
214 });
215 });
215 $('.revoke_perm').on('click', function(e){
216 $('.revoke_perm').on('click', function(e){
216 markRevokePermInput($(this), 'group');
217 markRevokePermInput($(this), 'group');
217 });
218 });
218 quick_repo_menu();
219 quick_repo_menu();
219 </script>
220 </script>
@@ -1,291 +1,291 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Repository ID'), c.rhodecode_db_repo.repo_id, '', ''),
5 (_('Repository ID'), c.rhodecode_db_repo.repo_id, '', ''),
6 (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email), '', ''),
6 (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email, tooltip=True), '', ''),
7 (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''),
7 (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''),
8 (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''),
8 (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''),
9 (_('Cached Commit id'), lambda: h.link_to(c.rhodecode_db_repo.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.changeset_cache.get('raw_id'))), '', ''),
9 (_('Cached Commit id'), lambda: h.link_to(c.rhodecode_db_repo.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.changeset_cache.get('raw_id'))), '', ''),
10 (_('Cached Commit date'), c.rhodecode_db_repo.changeset_cache.get('date'), '', ''),
10 (_('Cached Commit date'), c.rhodecode_db_repo.changeset_cache.get('date'), '', ''),
11 (_('Attached scoped tokens'), len(c.rhodecode_db_repo.scoped_tokens), '', [x.user for x in c.rhodecode_db_repo.scoped_tokens]),
11 (_('Attached scoped tokens'), len(c.rhodecode_db_repo.scoped_tokens), '', [x.user for x in c.rhodecode_db_repo.scoped_tokens]),
12 (_('Pull requests source'), len(c.rhodecode_db_repo.pull_requests_source), '', ['pr_id:{}, repo:{}'.format(x.pull_request_id,x.source_repo.repo_name) for x in c.rhodecode_db_repo.pull_requests_source]),
12 (_('Pull requests source'), len(c.rhodecode_db_repo.pull_requests_source), '', ['pr_id:{}, repo:{}'.format(x.pull_request_id,x.source_repo.repo_name) for x in c.rhodecode_db_repo.pull_requests_source]),
13 (_('Pull requests target'), len(c.rhodecode_db_repo.pull_requests_target), '', ['pr_id:{}, repo:{}'.format(x.pull_request_id,x.target_repo.repo_name) for x in c.rhodecode_db_repo.pull_requests_target]),
13 (_('Pull requests target'), len(c.rhodecode_db_repo.pull_requests_target), '', ['pr_id:{}, repo:{}'.format(x.pull_request_id,x.target_repo.repo_name) for x in c.rhodecode_db_repo.pull_requests_target]),
14 (_('Attached Artifacts'), len(c.rhodecode_db_repo.artifacts), '', ''),
14 (_('Attached Artifacts'), len(c.rhodecode_db_repo.artifacts), '', ''),
15 ]
15 ]
16 %>
16 %>
17
17
18 <div class="panel panel-default">
18 <div class="panel panel-default">
19 <div class="panel-heading" id="advanced-info" >
19 <div class="panel-heading" id="advanced-info" >
20 <h3 class="panel-title">${_('Repository: %s') % c.rhodecode_db_repo.repo_name} <a class="permalink" href="#advanced-info"></a></h3>
20 <h3 class="panel-title">${_('Repository: %s') % c.rhodecode_db_repo.repo_name} <a class="permalink" href="#advanced-info"></a></h3>
21 </div>
21 </div>
22 <div class="panel-body">
22 <div class="panel-body">
23 ${base.dt_info_panel(elems)}
23 ${base.dt_info_panel(elems)}
24 </div>
24 </div>
25 </div>
25 </div>
26
26
27
27
28 <div class="panel panel-default">
28 <div class="panel panel-default">
29 <div class="panel-heading" id="advanced-fork">
29 <div class="panel-heading" id="advanced-fork">
30 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"></a></h3>
30 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"></a></h3>
31 </div>
31 </div>
32 <div class="panel-body">
32 <div class="panel-body">
33 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
33 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
34
34
35 % if c.rhodecode_db_repo.fork:
35 % if c.rhodecode_db_repo.fork:
36 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.rhodecode_db_repo.fork.repo_name, h.route_path('repo_summary', repo_name=c.rhodecode_db_repo.fork.repo_name))})}
36 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.rhodecode_db_repo.fork.repo_name, h.route_path('repo_summary', repo_name=c.rhodecode_db_repo.fork.repo_name))})}
37 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
37 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
38 % endif
38 % endif
39
39
40 <div class="field">
40 <div class="field">
41 ${h.hidden('id_fork_of')}
41 ${h.hidden('id_fork_of')}
42 ${h.submit('set_as_fork_%s' % c.rhodecode_db_repo.repo_name,_('Set'),class_="btn btn-small",)}
42 ${h.submit('set_as_fork_%s' % c.rhodecode_db_repo.repo_name,_('Set'),class_="btn btn-small",)}
43 </div>
43 </div>
44 <div class="field">
44 <div class="field">
45 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
45 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
46 </div>
46 </div>
47 ${h.end_form()}
47 ${h.end_form()}
48 </div>
48 </div>
49 </div>
49 </div>
50
50
51
51
52 <div class="panel panel-default">
52 <div class="panel panel-default">
53 <div class="panel-heading" id="advanced-journal">
53 <div class="panel-heading" id="advanced-journal">
54 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"></a></h3>
54 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"></a></h3>
55 </div>
55 </div>
56 <div class="panel-body">
56 <div class="panel-body">
57 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
57 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
58 <div class="field">
58 <div class="field">
59 %if c.in_public_journal:
59 %if c.in_public_journal:
60 <button class="btn btn-small" type="submit">
60 <button class="btn btn-small" type="submit">
61 ${_('Remove from Public Journal')}
61 ${_('Remove from Public Journal')}
62 </button>
62 </button>
63 %else:
63 %else:
64 <button class="btn btn-small" type="submit">
64 <button class="btn btn-small" type="submit">
65 ${_('Add to Public Journal')}
65 ${_('Add to Public Journal')}
66 </button>
66 </button>
67 %endif
67 %endif
68 </div>
68 </div>
69 <div class="field" >
69 <div class="field" >
70 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
70 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
71 </div>
71 </div>
72 ${h.end_form()}
72 ${h.end_form()}
73 </div>
73 </div>
74 </div>
74 </div>
75
75
76
76
77 <div class="panel panel-default">
77 <div class="panel panel-default">
78 <div class="panel-heading" id="advanced-locking">
78 <div class="panel-heading" id="advanced-locking">
79 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"></a></h3>
79 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"></a></h3>
80 </div>
80 </div>
81 <div class="panel-body">
81 <div class="panel-body">
82 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
82 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
83
83
84 %if c.rhodecode_db_repo.locked[0]:
84 %if c.rhodecode_db_repo.locked[0]:
85 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.rhodecode_db_repo.locked[0]),
85 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.rhodecode_db_repo.locked[0]),
86 h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div>
86 h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div>
87 %else:
87 %else:
88 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
88 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
89 %endif
89 %endif
90
90
91 <div class="field" >
91 <div class="field" >
92 %if c.rhodecode_db_repo.locked[0]:
92 %if c.rhodecode_db_repo.locked[0]:
93 ${h.hidden('set_unlock', '1')}
93 ${h.hidden('set_unlock', '1')}
94 <button class="btn btn-small" type="submit"
94 <button class="btn btn-small" type="submit"
95 onclick="return confirm('${_('Confirm to unlock repository.')}');">
95 onclick="return confirm('${_('Confirm to unlock repository.')}');">
96 <i class="icon-unlock"></i>
96 <i class="icon-unlock"></i>
97 ${_('Unlock repository')}
97 ${_('Unlock repository')}
98 </button>
98 </button>
99 %else:
99 %else:
100 ${h.hidden('set_lock', '1')}
100 ${h.hidden('set_lock', '1')}
101 <button class="btn btn-small" type="submit"
101 <button class="btn btn-small" type="submit"
102 onclick="return confirm('${_('Confirm to lock repository.')}');">
102 onclick="return confirm('${_('Confirm to lock repository.')}');">
103 <i class="icon-lock"></i>
103 <i class="icon-lock"></i>
104 ${_('Lock repository')}
104 ${_('Lock repository')}
105 </button>
105 </button>
106 %endif
106 %endif
107 </div>
107 </div>
108 <div class="field" >
108 <div class="field" >
109 <span class="help-block">
109 <span class="help-block">
110 ${_('Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again.')}
110 ${_('Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again.')}
111 </span>
111 </span>
112 </div>
112 </div>
113 ${h.end_form()}
113 ${h.end_form()}
114 </div>
114 </div>
115 </div>
115 </div>
116
116
117
117
118 <div class="panel panel-default">
118 <div class="panel panel-default">
119 <div class="panel-heading" id="advanced-hooks">
119 <div class="panel-heading" id="advanced-hooks">
120 <h3 class="panel-title">${_('Hooks')} <a class="permalink" href="#advanced-hooks"></a></h3>
120 <h3 class="panel-title">${_('Hooks')} <a class="permalink" href="#advanced-hooks"></a></h3>
121 </div>
121 </div>
122 <div class="panel-body">
122 <div class="panel-body">
123 <table class="rctable">
123 <table class="rctable">
124 <th>${_('Hook type')}</th>
124 <th>${_('Hook type')}</th>
125 <th>${_('Hook version')}</th>
125 <th>${_('Hook version')}</th>
126 <th>${_('Current version')}</th>
126 <th>${_('Current version')}</th>
127 % if c.ver_info_dict:
127 % if c.ver_info_dict:
128 <tr>
128 <tr>
129 <td>${_('PRE HOOK')}</td>
129 <td>${_('PRE HOOK')}</td>
130 <td>${c.ver_info_dict['pre_version']}</td>
130 <td>${c.ver_info_dict['pre_version']}</td>
131 <td>${c.rhodecode_version}</td>
131 <td>${c.rhodecode_version}</td>
132 </tr>
132 </tr>
133 <tr>
133 <tr>
134 <td>${_('POST HOOK')}</td>
134 <td>${_('POST HOOK')}</td>
135 <td>${c.ver_info_dict['post_version']}</td>
135 <td>${c.ver_info_dict['post_version']}</td>
136 <td>${c.rhodecode_version}</td>
136 <td>${c.rhodecode_version}</td>
137 </tr>
137 </tr>
138 % else:
138 % else:
139 <tr>
139 <tr>
140 <td>${_('Unable to read hook information from VCS Server')}</td>
140 <td>${_('Unable to read hook information from VCS Server')}</td>
141 </tr>
141 </tr>
142 % endif
142 % endif
143 </table>
143 </table>
144
144
145 <a href="${h.route_path('edit_repo_advanced_hooks', repo_name=c.repo_name)}"
145 <a href="${h.route_path('edit_repo_advanced_hooks', repo_name=c.repo_name)}"
146 onclick="return confirm('${_('Confirm to reinstall hooks for this repository.')}');">
146 onclick="return confirm('${_('Confirm to reinstall hooks for this repository.')}');">
147 ${_('Update Hooks')}
147 ${_('Update Hooks')}
148 </a>
148 </a>
149 </div>
149 </div>
150 </div>
150 </div>
151
151
152 <div class="panel panel-warning">
152 <div class="panel panel-warning">
153 <div class="panel-heading" id="advanced-archive">
153 <div class="panel-heading" id="advanced-archive">
154 <h3 class="panel-title">${_('Archive repository')} <a class="permalink" href="#advanced-archive"></a></h3>
154 <h3 class="panel-title">${_('Archive repository')} <a class="permalink" href="#advanced-archive"></a></h3>
155 </div>
155 </div>
156 <div class="panel-body">
156 <div class="panel-body">
157 ${h.secure_form(h.route_path('edit_repo_advanced_archive', repo_name=c.repo_name), request=request)}
157 ${h.secure_form(h.route_path('edit_repo_advanced_archive', repo_name=c.repo_name), request=request)}
158
158
159 <div style="margin: 0 0 20px 0" class="fake-space"></div>
159 <div style="margin: 0 0 20px 0" class="fake-space"></div>
160
160
161 <div class="field">
161 <div class="field">
162 <button class="btn btn-small btn-danger" type="submit"
162 <button class="btn btn-small btn-danger" type="submit"
163 onclick="return confirm('${_('Confirm to archive this repository: %s') % c.repo_name}');">
163 onclick="return confirm('${_('Confirm to archive this repository: %s') % c.repo_name}');">
164 <i class="icon-remove"></i>
164 <i class="icon-remove"></i>
165 ${_('Archive this repository')}
165 ${_('Archive this repository')}
166 </button>
166 </button>
167 </div>
167 </div>
168 <div class="field">
168 <div class="field">
169 <span class="help-block">
169 <span class="help-block">
170 ${_('Archiving the repository will make it entirely read-only. The repository cannot be committed to.'
170 ${_('Archiving the repository will make it entirely read-only. The repository cannot be committed to.'
171 'It is hidden from the search results and dashboard. ')}
171 'It is hidden from the search results and dashboard. ')}
172 </span>
172 </span>
173 </div>
173 </div>
174
174
175 ${h.end_form()}
175 ${h.end_form()}
176 </div>
176 </div>
177 </div>
177 </div>
178
178
179
179
180 <div class="panel panel-danger">
180 <div class="panel panel-danger">
181 <div class="panel-heading" id="advanced-delete">
181 <div class="panel-heading" id="advanced-delete">
182 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"></a></h3>
182 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"></a></h3>
183 </div>
183 </div>
184 <div class="panel-body">
184 <div class="panel-body">
185 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)}
185 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)}
186 <table class="display">
186 <table class="display">
187 <tr>
187 <tr>
188 <td>
188 <td>
189 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()}
189 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()}
190 </td>
190 </td>
191 <td>
191 <td>
192 %if c.rhodecode_db_repo.forks.count():
192 %if c.rhodecode_db_repo.forks.count():
193 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
193 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
194 %endif
194 %endif
195 </td>
195 </td>
196 <td>
196 <td>
197 %if c.rhodecode_db_repo.forks.count():
197 %if c.rhodecode_db_repo.forks.count():
198 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
198 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
199 %endif
199 %endif
200 </td>
200 </td>
201 </tr>
201 </tr>
202 <% attached_prs = len(c.rhodecode_db_repo.pull_requests_source + c.rhodecode_db_repo.pull_requests_target) %>
202 <% attached_prs = len(c.rhodecode_db_repo.pull_requests_source + c.rhodecode_db_repo.pull_requests_target) %>
203 % if c.rhodecode_db_repo.pull_requests_source or c.rhodecode_db_repo.pull_requests_target:
203 % if c.rhodecode_db_repo.pull_requests_source or c.rhodecode_db_repo.pull_requests_target:
204 <tr>
204 <tr>
205 <td>
205 <td>
206 ${_ungettext('This repository has %s attached pull request.', 'This repository has %s attached pull requests.', attached_prs) % attached_prs}
206 ${_ungettext('This repository has %s attached pull request.', 'This repository has %s attached pull requests.', attached_prs) % attached_prs}
207 <br/>
207 <br/>
208 ${_('Consider to archive this repository instead.')}
208 ${_('Consider to archive this repository instead.')}
209 </td>
209 </td>
210 <td></td>
210 <td></td>
211 <td></td>
211 <td></td>
212 </tr>
212 </tr>
213 % endif
213 % endif
214 </table>
214 </table>
215 <div style="margin: 0 0 20px 0" class="fake-space"></div>
215 <div style="margin: 0 0 20px 0" class="fake-space"></div>
216
216
217 <div class="field">
217 <div class="field">
218 <button class="btn btn-small btn-danger" type="submit"
218 <button class="btn btn-small btn-danger" type="submit"
219 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
219 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
220 <i class="icon-remove"></i>
220 <i class="icon-remove"></i>
221 ${_('Delete this repository')}
221 ${_('Delete this repository')}
222 </button>
222 </button>
223 </div>
223 </div>
224 <div class="field">
224 <div class="field">
225 <span class="help-block">
225 <span class="help-block">
226 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
226 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
227 </span>
227 </span>
228 </div>
228 </div>
229
229
230 ${h.end_form()}
230 ${h.end_form()}
231 </div>
231 </div>
232 </div>
232 </div>
233
233
234
234
235 <script>
235 <script>
236
236
237 var currentRepoId = ${c.rhodecode_db_repo.repo_id};
237 var currentRepoId = ${c.rhodecode_db_repo.repo_id};
238
238
239 var repoTypeFilter = function(data) {
239 var repoTypeFilter = function(data) {
240 var results = [];
240 var results = [];
241
241
242 if (!data.results[0]) {
242 if (!data.results[0]) {
243 return data
243 return data
244 }
244 }
245
245
246 $.each(data.results[0].children, function() {
246 $.each(data.results[0].children, function() {
247 // filter out the SAME repo, it cannot be used as fork of itself
247 // filter out the SAME repo, it cannot be used as fork of itself
248 if (this.repo_id != currentRepoId) {
248 if (this.repo_id != currentRepoId) {
249 this.id = this.repo_id;
249 this.id = this.repo_id;
250 results.push(this)
250 results.push(this)
251 }
251 }
252 });
252 });
253 data.results[0].children = results;
253 data.results[0].children = results;
254 return data;
254 return data;
255 };
255 };
256
256
257 $("#id_fork_of").select2({
257 $("#id_fork_of").select2({
258 cachedDataSource: {},
258 cachedDataSource: {},
259 minimumInputLength: 2,
259 minimumInputLength: 2,
260 placeholder: "${_('Change repository') if c.rhodecode_db_repo.fork else _('Pick repository')}",
260 placeholder: "${_('Change repository') if c.rhodecode_db_repo.fork else _('Pick repository')}",
261 dropdownAutoWidth: true,
261 dropdownAutoWidth: true,
262 containerCssClass: "drop-menu",
262 containerCssClass: "drop-menu",
263 dropdownCssClass: "drop-menu-dropdown",
263 dropdownCssClass: "drop-menu-dropdown",
264 formatResult: formatRepoResult,
264 formatResult: formatRepoResult,
265 query: $.debounce(250, function(query){
265 query: $.debounce(250, function(query){
266 self = this;
266 self = this;
267 var cacheKey = query.term;
267 var cacheKey = query.term;
268 var cachedData = self.cachedDataSource[cacheKey];
268 var cachedData = self.cachedDataSource[cacheKey];
269
269
270 if (cachedData) {
270 if (cachedData) {
271 query.callback({results: cachedData.results});
271 query.callback({results: cachedData.results});
272 } else {
272 } else {
273 $.ajax({
273 $.ajax({
274 url: pyroutes.url('repo_list_data'),
274 url: pyroutes.url('repo_list_data'),
275 data: {'query': query.term, repo_type: '${c.rhodecode_db_repo.repo_type}'},
275 data: {'query': query.term, repo_type: '${c.rhodecode_db_repo.repo_type}'},
276 dataType: 'json',
276 dataType: 'json',
277 type: 'GET',
277 type: 'GET',
278 success: function(data) {
278 success: function(data) {
279 data = repoTypeFilter(data);
279 data = repoTypeFilter(data);
280 self.cachedDataSource[cacheKey] = data;
280 self.cachedDataSource[cacheKey] = data;
281 query.callback({results: data.results});
281 query.callback({results: data.results});
282 },
282 },
283 error: function(data, textStatus, errorThrown) {
283 error: function(data, textStatus, errorThrown) {
284 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
284 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
285 }
285 }
286 })
286 })
287 }
287 }
288 })
288 })
289 });
289 });
290 </script>
290 </script>
291
291
@@ -1,222 +1,222 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Access Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Access Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.rhodecode_db_repo.permissions():
20 %for _user in c.rhodecode_db_repo.permissions():
21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 <tr class="perm_admin_row">
22 <tr class="perm_admin_row">
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-user">
27 <td class="td-user">
28 ${base.gravatar(_user.email, 16)}
28 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
29 ${h.link_to_user(_user.username)}
29 ${h.link_to_user(_user.username)}
30 %if getattr(_user, 'admin_row', None):
30 %if getattr(_user, 'admin_row', None):
31 (${_('super admin')})
31 (${_('super admin')})
32 %endif
32 %endif
33 %if getattr(_user, 'owner_row', None):
33 %if getattr(_user, 'owner_row', None):
34 (${_('owner')})
34 (${_('owner')})
35 %endif
35 %endif
36 </td>
36 </td>
37 <td></td>
37 <td></td>
38 <td class="quick_repo_menu">
38 <td class="quick_repo_menu">
39 % if c.rhodecode_user.is_admin:
39 % if c.rhodecode_user.is_admin:
40 <i class="icon-more"></i>
40 <i class="icon-more"></i>
41 <div class="menu_items_container" style="display: none;">
41 <div class="menu_items_container" style="display: none;">
42 <ul class="menu_items">
42 <ul class="menu_items">
43 <li>
43 <li>
44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
45 </li>
45 </li>
46 </ul>
46 </ul>
47 </div>
47 </div>
48 % endif
48 % endif
49 </td>
49 </td>
50 </tr>
50 </tr>
51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
52 <tr>
52 <tr>
53 <td colspan="4">
53 <td colspan="4">
54 <span class="private_repo_msg">
54 <span class="private_repo_msg">
55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
56 </span>
56 </span>
57 </td>
57 </td>
58 <td class="private_repo_msg">
58 <td class="private_repo_msg">
59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
61 <td></td>
61 <td></td>
62 <td class="quick_repo_menu">
62 <td class="quick_repo_menu">
63 % if c.rhodecode_user.is_admin:
63 % if c.rhodecode_user.is_admin:
64 <i class="icon-more"></i>
64 <i class="icon-more"></i>
65 <div class="menu_items_container" style="display: none;">
65 <div class="menu_items_container" style="display: none;">
66 <ul class="menu_items">
66 <ul class="menu_items">
67 <li>
67 <li>
68 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
68 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
69 </li>
69 </li>
70 </ul>
70 </ul>
71 </div>
71 </div>
72 % endif
72 % endif
73 </td>
73 </td>
74 </tr>
74 </tr>
75 %else:
75 %else:
76 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
76 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
77 <tr>
77 <tr>
78 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
78 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
79 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
79 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
80 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
80 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
81 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
81 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
82 <td class="td-user">
82 <td class="td-user">
83 ${base.gravatar(_user.email, 16)}
83 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
84 <span class="user">
84 <span class="user">
85 % if _user.username == h.DEFAULT_USER:
85 % if _user.username == h.DEFAULT_USER:
86 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
86 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
87 % else:
87 % else:
88 ${h.link_to_user(_user.username)}
88 ${h.link_to_user(_user.username)}
89 %if getattr(_user, 'duplicate_perm', None):
89 %if getattr(_user, 'duplicate_perm', None):
90 (${_('inactive duplicate')})
90 (${_('inactive duplicate')})
91 %endif
91 %endif
92 %if getattr(_user, 'branch_rules', None):
92 %if getattr(_user, 'branch_rules', None):
93 % if used_by_n_rules == 1:
93 % if used_by_n_rules == 1:
94 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
94 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
95 % else:
95 % else:
96 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
96 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
97 % endif
97 % endif
98 %endif
98 %endif
99 % endif
99 % endif
100 </span>
100 </span>
101 </td>
101 </td>
102 <td class="td-action">
102 <td class="td-action">
103 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
103 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
104 <span class="btn btn-link btn-danger revoke_perm"
104 <span class="btn btn-link btn-danger revoke_perm"
105 member="${_user.user_id}" member_type="user">
105 member="${_user.user_id}" member_type="user">
106 ${_('Remove')}
106 ${_('Remove')}
107 </span>
107 </span>
108 %elif _user.username == h.DEFAULT_USER:
108 %elif _user.username == h.DEFAULT_USER:
109 <span class="tooltip btn btn-link btn-default" onclick="enablePrivateRepo(); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators.')}">
109 <span class="tooltip btn btn-link btn-default" onclick="enablePrivateRepo(); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators.')}">
110 ${_('set private mode')}
110 ${_('set private mode')}
111 </span>
111 </span>
112 %endif
112 %endif
113 </td>
113 </td>
114 <td class="quick_repo_menu">
114 <td class="quick_repo_menu">
115 % if c.rhodecode_user.is_admin:
115 % if c.rhodecode_user.is_admin:
116 <i class="icon-more"></i>
116 <i class="icon-more"></i>
117 <div class="menu_items_container" style="display: none;">
117 <div class="menu_items_container" style="display: none;">
118 <ul class="menu_items">
118 <ul class="menu_items">
119 <li>
119 <li>
120 % if _user.username == h.DEFAULT_USER:
120 % if _user.username == h.DEFAULT_USER:
121 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
121 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
122 % else:
122 % else:
123 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
123 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
124 % endif
124 % endif
125 </li>
125 </li>
126 </ul>
126 </ul>
127 </div>
127 </div>
128 % endif
128 % endif
129 </td>
129 </td>
130 </tr>
130 </tr>
131 %endif
131 %endif
132 %endfor
132 %endfor
133
133
134 ## USER GROUPS
134 ## USER GROUPS
135 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
135 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
136 <tr>
136 <tr>
137 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
137 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
138 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
138 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
139 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
139 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
141 <td class="td-componentname">
141 <td class="td-componentname">
142 <i class="icon-user-group"></i>
142 ${base.user_group_icon(_user_group, tooltip=True)}
143 %if c.is_super_admin:
143 %if c.is_super_admin:
144 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
144 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
145 ${_user_group.users_group_name}
145 ${_user_group.users_group_name}
146 </a>
146 </a>
147 %else:
147 %else:
148 ${h.link_to_group(_user_group.users_group_name)}
148 ${h.link_to_group(_user_group.users_group_name)}
149 %endif
149 %endif
150 (${_('members')}: ${len(_user_group.members)})
150 (${_('members')}: ${len(_user_group.members)})
151 </td>
151 </td>
152 <td class="td-action">
152 <td class="td-action">
153 <span class="btn btn-link btn-danger revoke_perm"
153 <span class="btn btn-link btn-danger revoke_perm"
154 member="${_user_group.users_group_id}" member_type="user_group">
154 member="${_user_group.users_group_id}" member_type="user_group">
155 ${_('Remove')}
155 ${_('Remove')}
156 </span>
156 </span>
157 </td>
157 </td>
158 <td class="quick_repo_menu">
158 <td class="quick_repo_menu">
159 % if c.rhodecode_user.is_admin:
159 % if c.rhodecode_user.is_admin:
160 <i class="icon-more"></i>
160 <i class="icon-more"></i>
161 <div class="menu_items_container" style="display: none;">
161 <div class="menu_items_container" style="display: none;">
162 <ul class="menu_items">
162 <ul class="menu_items">
163 <li>
163 <li>
164 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-permissions'))}
164 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-permissions'))}
165 </li>
165 </li>
166 </ul>
166 </ul>
167 </div>
167 </div>
168 % endif
168 % endif
169 </td>
169 </td>
170 </tr>
170 </tr>
171 %endfor
171 %endfor
172 <tr class="new_members" id="add_perm_input"></tr>
172 <tr class="new_members" id="add_perm_input"></tr>
173
173
174 <tr>
174 <tr>
175 <td></td>
175 <td></td>
176 <td></td>
176 <td></td>
177 <td></td>
177 <td></td>
178 <td></td>
178 <td></td>
179 <td></td>
179 <td></td>
180 <td>
180 <td>
181 <span id="add_perm" class="link">
181 <span id="add_perm" class="link">
182 ${_('Add user/user group')}
182 ${_('Add user/user group')}
183 </span>
183 </span>
184 </td>
184 </td>
185 <td></td>
185 <td></td>
186 </tr>
186 </tr>
187
187
188 </table>
188 </table>
189
189
190 <div class="buttons">
190 <div class="buttons">
191 ${h.submit('save',_('Save'),class_="btn btn-primary")}
191 ${h.submit('save',_('Save'),class_="btn btn-primary")}
192 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
192 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
193 </div>
193 </div>
194 ${h.end_form()}
194 ${h.end_form()}
195 </div>
195 </div>
196 </div>
196 </div>
197
197
198 <script type="text/javascript">
198 <script type="text/javascript">
199 $('#add_perm').on('click', function(e){
199 $('#add_perm').on('click', function(e){
200 addNewPermInput($(this), 'repository');
200 addNewPermInput($(this), 'repository');
201 });
201 });
202 $('.revoke_perm').on('click', function(e){
202 $('.revoke_perm').on('click', function(e){
203 markRevokePermInput($(this), 'repository');
203 markRevokePermInput($(this), 'repository');
204 });
204 });
205 quick_repo_menu();
205 quick_repo_menu();
206
206
207 var enablePrivateRepo = function () {
207 var enablePrivateRepo = function () {
208 var postData = {
208 var postData = {
209 'csrf_token': CSRF_TOKEN
209 'csrf_token': CSRF_TOKEN
210 };
210 };
211
211
212 var success = function(o) {
212 var success = function(o) {
213 var defaultUrl = pyroutes.url('edit_repo_perms', {"repo_name": templateContext.repo_name});
213 var defaultUrl = pyroutes.url('edit_repo_perms', {"repo_name": templateContext.repo_name});
214 window.location = o.redirect_url || defaultUrl;
214 window.location = o.redirect_url || defaultUrl;
215 };
215 };
216
216
217 ajaxPOST(
217 ajaxPOST(
218 pyroutes.url('edit_repo_perms_set_private', {"repo_name": templateContext.repo_name}),
218 pyroutes.url('edit_repo_perms_set_private', {"repo_name": templateContext.repo_name}),
219 postData,
219 postData,
220 success);
220 success);
221 }
221 }
222 </script>
222 </script>
@@ -1,207 +1,207 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), request=request)}
8 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th>${_('User/User Group')}</th>
15 <th>${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.user_group.permissions():
20 %for _user in c.user_group.permissions():
21 ## super admin/owner row
21 ## super admin/owner row
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
23 <tr class="perm_admin_row">
23 <tr class="perm_admin_row">
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
28 <td class="td-user">
28 <td class="td-user">
29 ${base.gravatar(_user.email, 16)}
29 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
30 <span class="user">
30 <span class="user">
31 ${h.link_to_user(_user.username)}
31 ${h.link_to_user(_user.username)}
32 %if getattr(_user, 'admin_row', None):
32 %if getattr(_user, 'admin_row', None):
33 (${_('super admin')})
33 (${_('super admin')})
34 %endif
34 %endif
35 %if getattr(_user, 'owner_row', None):
35 %if getattr(_user, 'owner_row', None):
36 (${_('owner')})
36 (${_('owner')})
37 %endif
37 %endif
38 </span>
38 </span>
39 </td>
39 </td>
40 <td></td>
40 <td></td>
41 <td class="quick_repo_menu">
41 <td class="quick_repo_menu">
42 % if c.rhodecode_user.is_admin:
42 % if c.rhodecode_user.is_admin:
43 <i class="icon-more"></i>
43 <i class="icon-more"></i>
44 <div class="menu_items_container" style="display: none;">
44 <div class="menu_items_container" style="display: none;">
45 <ul class="menu_items">
45 <ul class="menu_items">
46 <li>
46 <li>
47 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
47 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
48 </li>
48 </li>
49 </ul>
49 </ul>
50 </div>
50 </div>
51 % endif
51 % endif
52 </td>
52 </td>
53 </tr>
53 </tr>
54 %else:
54 %else:
55 ##forbid revoking permission from yourself, except if you're an super admin
55 ##forbid revoking permission from yourself, except if you're an super admin
56 <tr>
56 <tr>
57 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
57 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
60 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
60 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
61 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
61 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
62 <td class="td-user">
62 <td class="td-user">
63 ${base.gravatar(_user.email, 16)}
63 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
64 <span class="user">
64 <span class="user">
65 % if _user.username == h.DEFAULT_USER:
65 % if _user.username == h.DEFAULT_USER:
66 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
66 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
67 % else:
67 % else:
68 ${h.link_to_user(_user.username)}
68 ${h.link_to_user(_user.username)}
69 %if getattr(_user, 'duplicate_perm', None):
69 %if getattr(_user, 'duplicate_perm', None):
70 (${_('inactive duplicate')})
70 (${_('inactive duplicate')})
71 %endif
71 %endif
72 % endif
72 % endif
73 </span>
73 </span>
74 </td>
74 </td>
75 <td class="td-action">
75 <td class="td-action">
76 %if _user.username != h.DEFAULT_USER:
76 %if _user.username != h.DEFAULT_USER:
77 <span class="btn btn-link btn-danger revoke_perm"
77 <span class="btn btn-link btn-danger revoke_perm"
78 member="${_user.user_id}" member_type="user">
78 member="${_user.user_id}" member_type="user">
79 ${_('Remove')}
79 ${_('Remove')}
80 </span>
80 </span>
81 %endif
81 %endif
82 </td>
82 </td>
83 <td class="quick_repo_menu">
83 <td class="quick_repo_menu">
84 % if c.rhodecode_user.is_admin:
84 % if c.rhodecode_user.is_admin:
85 <i class="icon-more"></i>
85 <i class="icon-more"></i>
86 <div class="menu_items_container" style="display: none;">
86 <div class="menu_items_container" style="display: none;">
87 <ul class="menu_items">
87 <ul class="menu_items">
88 <li>
88 <li>
89 % if _user.username == h.DEFAULT_USER:
89 % if _user.username == h.DEFAULT_USER:
90 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='user-groups-permissions'))}
90 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='user-groups-permissions'))}
91 % else:
91 % else:
92 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
92 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
93 % endif
93 % endif
94 </li>
94 </li>
95 </ul>
95 </ul>
96 </div>
96 </div>
97 % endif
97 % endif
98 </td>
98 </td>
99 %else:
99 %else:
100 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
100 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
103 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
103 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
104 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
104 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
105 <td class="td-user">
105 <td class="td-user">
106 ${base.gravatar(_user.email, 16)}
106 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
107 <span class="user">
107 <span class="user">
108 % if _user.username == h.DEFAULT_USER:
108 % if _user.username == h.DEFAULT_USER:
109 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
109 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
110 % else:
110 % else:
111 ${h.link_to_user(_user.username)}
111 ${h.link_to_user(_user.username)}
112 %if getattr(_user, 'duplicate_perm', None):
112 %if getattr(_user, 'duplicate_perm', None):
113 (${_('inactive duplicate')})
113 (${_('inactive duplicate')})
114 %endif
114 %endif
115 % endif
115 % endif
116 <span class="user-perm-help-text">(${_('delegated admin')})</span>
116 <span class="user-perm-help-text">(${_('delegated admin')})</span>
117 </span>
117 </span>
118 </td>
118 </td>
119 <td></td>
119 <td></td>
120 <td class="quick_repo_menu">
120 <td class="quick_repo_menu">
121 % if c.rhodecode_user.is_admin:
121 % if c.rhodecode_user.is_admin:
122 <i class="icon-more"></i>
122 <i class="icon-more"></i>
123 <div class="menu_items_container" style="display: none;">
123 <div class="menu_items_container" style="display: none;">
124 <ul class="menu_items">
124 <ul class="menu_items">
125 <li>
125 <li>
126 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
126 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
127 </li>
127 </li>
128 </ul>
128 </ul>
129 </div>
129 </div>
130 % endif
130 % endif
131 </td>
131 </td>
132 %endif
132 %endif
133 </tr>
133 </tr>
134 %endif
134 %endif
135 %endfor
135 %endfor
136
136
137 ## USER GROUPS
137 ## USER GROUPS
138 %for _user_group in c.user_group.permission_user_groups(with_members=True):
138 %for _user_group in c.user_group.permission_user_groups(with_members=True):
139 <tr>
139 <tr>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
142 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
142 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
143 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
143 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
144 <td class="td-user">
144 <td class="td-user">
145 <i class="icon-user-group"></i>
145 <i class="icon-user-group"></i>
146 %if c.is_super_admin:
146 %if c.is_super_admin:
147 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
147 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
148 ${_user_group.users_group_name}
148 ${_user_group.users_group_name}
149 </a>
149 </a>
150 %else:
150 %else:
151 ${h.link_to_group(_user_group.users_group_name)}
151 ${h.link_to_group(_user_group.users_group_name)}
152 %endif
152 %endif
153 (${_('members')}: ${len(_user_group.members)})
153 (${_('members')}: ${len(_user_group.members)})
154 </td>
154 </td>
155 <td class="td-action">
155 <td class="td-action">
156 <span class="btn btn-link btn-danger revoke_perm"
156 <span class="btn btn-link btn-danger revoke_perm"
157 member="${_user_group.users_group_id}" member_type="user_group">
157 member="${_user_group.users_group_id}" member_type="user_group">
158 ${_('Remove')}
158 ${_('Remove')}
159 </span>
159 </span>
160 </td>
160 </td>
161 <td class="quick_repo_menu">
161 <td class="quick_repo_menu">
162 % if c.rhodecode_user.is_admin:
162 % if c.rhodecode_user.is_admin:
163 <i class="icon-more"></i>
163 <i class="icon-more"></i>
164 <div class="menu_items_container" style="display: none;">
164 <div class="menu_items_container" style="display: none;">
165 <ul class="menu_items">
165 <ul class="menu_items">
166 <li>
166 <li>
167 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='user-groups-permissions'))}
167 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='user-groups-permissions'))}
168 </li>
168 </li>
169 </ul>
169 </ul>
170 </div>
170 </div>
171 % endif
171 % endif
172 </td>
172 </td>
173 </tr>
173 </tr>
174 %endfor
174 %endfor
175 <tr class="new_members" id="add_perm_input"></tr>
175 <tr class="new_members" id="add_perm_input"></tr>
176 <tr>
176 <tr>
177 <td></td>
177 <td></td>
178 <td></td>
178 <td></td>
179 <td></td>
179 <td></td>
180 <td></td>
180 <td></td>
181 <td></td>
181 <td></td>
182 <td>
182 <td>
183 <span id="add_perm" class="link">
183 <span id="add_perm" class="link">
184 ${_('Add user/user group')}
184 ${_('Add user/user group')}
185 </span>
185 </span>
186 </td>
186 </td>
187 <td></td>
187 <td></td>
188 </tr>
188 </tr>
189 </table>
189 </table>
190
190
191 <div class="buttons">
191 <div class="buttons">
192 ${h.submit('save',_('Save'),class_="btn btn-primary")}
192 ${h.submit('save',_('Save'),class_="btn btn-primary")}
193 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
193 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
194 </div>
194 </div>
195 ${h.end_form()}
195 ${h.end_form()}
196 </div>
196 </div>
197 </div>
197 </div>
198
198
199 <script type="text/javascript">
199 <script type="text/javascript">
200 $('#add_perm').on('click', function(e){
200 $('#add_perm').on('click', function(e){
201 addNewPermInput($(this), 'usergroup');
201 addNewPermInput($(this), 'usergroup');
202 });
202 });
203 $('.revoke_perm').on('click', function(e){
203 $('.revoke_perm').on('click', function(e){
204 markRevokePermInput($(this), 'usergroup');
204 markRevokePermInput($(this), 'usergroup');
205 });
205 });
206 quick_repo_menu()
206 quick_repo_menu()
207 </script>
207 </script>
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now