Show More
The requested changes are too big and content was truncated. Show full diff
@@ -1,231 +1,240 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2019 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import os |
|
22 | 22 | import time |
|
23 | 23 | import shutil |
|
24 | 24 | import hashlib |
|
25 | 25 | |
|
26 | 26 | from rhodecode.lib.ext_json import json |
|
27 | 27 | from rhodecode.apps.file_store import utils |
|
28 | 28 | from rhodecode.apps.file_store.extensions import resolve_extensions |
|
29 |
from rhodecode.apps.file_store.exceptions import |
|
|
29 | from rhodecode.apps.file_store.exceptions import ( | |
|
30 | FileNotAllowedException, FileOverSizeException) | |
|
30 | 31 | |
|
31 | 32 | METADATA_VER = 'v1' |
|
32 | 33 | |
|
33 | 34 | |
|
34 | 35 | class LocalFileStorage(object): |
|
35 | 36 | |
|
36 | 37 | @classmethod |
|
37 | 38 | def resolve_name(cls, name, directory): |
|
38 | 39 | """ |
|
39 | 40 | Resolves a unique name and the correct path. If a filename |
|
40 | 41 | for that path already exists then a numeric prefix with values > 0 will be |
|
41 | 42 | added, for example test.jpg -> 1-test.jpg etc. initially file would have 0 prefix. |
|
42 | 43 | |
|
43 | 44 | :param name: base name of file |
|
44 | 45 | :param directory: absolute directory path |
|
45 | 46 | """ |
|
46 | 47 | |
|
47 | 48 | counter = 0 |
|
48 | 49 | while True: |
|
49 | 50 | name = '%d-%s' % (counter, name) |
|
50 | 51 | |
|
51 | 52 | # sub_store prefix to optimize disk usage, e.g some_path/ab/final_file |
|
52 | 53 | sub_store = cls._sub_store_from_filename(name) |
|
53 | 54 | sub_store_path = os.path.join(directory, sub_store) |
|
54 | 55 | if not os.path.exists(sub_store_path): |
|
55 | 56 | os.makedirs(sub_store_path) |
|
56 | 57 | |
|
57 | 58 | path = os.path.join(sub_store_path, name) |
|
58 | 59 | if not os.path.exists(path): |
|
59 | 60 | return name, path |
|
60 | 61 | counter += 1 |
|
61 | 62 | |
|
62 | 63 | @classmethod |
|
63 | 64 | def _sub_store_from_filename(cls, filename): |
|
64 | 65 | return filename[:2] |
|
65 | 66 | |
|
66 | 67 | @classmethod |
|
67 | 68 | def calculate_path_hash(cls, file_path): |
|
68 | 69 | """ |
|
69 | 70 | Efficient calculation of file_path sha256 sum |
|
70 | 71 | |
|
71 | 72 | :param file_path: |
|
72 | 73 | :return: sha256sum |
|
73 | 74 | """ |
|
74 | 75 | digest = hashlib.sha256() |
|
75 | 76 | with open(file_path, 'rb') as f: |
|
76 | 77 | for chunk in iter(lambda: f.read(1024 * 100), b""): |
|
77 | 78 | digest.update(chunk) |
|
78 | 79 | |
|
79 | 80 | return digest.hexdigest() |
|
80 | 81 | |
|
81 | 82 | def __init__(self, base_path, extension_groups=None): |
|
82 | 83 | |
|
83 | 84 | """ |
|
84 | 85 | Local file storage |
|
85 | 86 | |
|
86 | 87 | :param base_path: the absolute base path where uploads are stored |
|
87 | 88 | :param extension_groups: extensions string |
|
88 | 89 | """ |
|
89 | 90 | |
|
90 | 91 | extension_groups = extension_groups or ['any'] |
|
91 | 92 | self.base_path = base_path |
|
92 | 93 | self.extensions = resolve_extensions([], groups=extension_groups) |
|
93 | 94 | |
|
94 | 95 | def __repr__(self): |
|
95 | 96 | return '{}@{}'.format(self.__class__, self.base_path) |
|
96 | 97 | |
|
97 | 98 | def store_path(self, filename): |
|
98 | 99 | """ |
|
99 | 100 | Returns absolute file path of the filename, joined to the |
|
100 | 101 | base_path. |
|
101 | 102 | |
|
102 | 103 | :param filename: base name of file |
|
103 | 104 | """ |
|
104 | 105 | sub_store = self._sub_store_from_filename(filename) |
|
105 | 106 | return os.path.join(self.base_path, sub_store, filename) |
|
106 | 107 | |
|
107 | 108 | def delete(self, filename): |
|
108 | 109 | """ |
|
109 | 110 | Deletes the filename. Filename is resolved with the |
|
110 | 111 | absolute path based on base_path. If file does not exist, |
|
111 | 112 | returns **False**, otherwise **True** |
|
112 | 113 | |
|
113 | 114 | :param filename: base name of file |
|
114 | 115 | """ |
|
115 | 116 | if self.exists(filename): |
|
116 | 117 | os.remove(self.store_path(filename)) |
|
117 | 118 | return True |
|
118 | 119 | return False |
|
119 | 120 | |
|
120 | 121 | def exists(self, filename): |
|
121 | 122 | """ |
|
122 | 123 | Checks if file exists. Resolves filename's absolute |
|
123 | 124 | path based on base_path. |
|
124 | 125 | |
|
125 | 126 | :param filename: base name of file |
|
126 | 127 | """ |
|
127 | 128 | return os.path.exists(self.store_path(filename)) |
|
128 | 129 | |
|
129 | 130 | def filename_allowed(self, filename, extensions=None): |
|
130 | 131 | """Checks if a filename has an allowed extension |
|
131 | 132 | |
|
132 | 133 | :param filename: base name of file |
|
133 | 134 | :param extensions: iterable of extensions (or self.extensions) |
|
134 | 135 | """ |
|
135 | 136 | _, ext = os.path.splitext(filename) |
|
136 | 137 | return self.extension_allowed(ext, extensions) |
|
137 | 138 | |
|
138 | 139 | def extension_allowed(self, ext, extensions=None): |
|
139 | 140 | """ |
|
140 | 141 | Checks if an extension is permitted. Both e.g. ".jpg" and |
|
141 | 142 | "jpg" can be passed in. Extension lookup is case-insensitive. |
|
142 | 143 | |
|
143 | 144 | :param ext: extension to check |
|
144 | 145 | :param extensions: iterable of extensions to validate against (or self.extensions) |
|
145 | 146 | """ |
|
146 | 147 | def normalize_ext(_ext): |
|
147 | 148 | if _ext.startswith('.'): |
|
148 | 149 | _ext = _ext[1:] |
|
149 | 150 | return _ext.lower() |
|
150 | 151 | |
|
151 | 152 | extensions = extensions or self.extensions |
|
152 | 153 | if not extensions: |
|
153 | 154 | return True |
|
154 | 155 | |
|
155 | 156 | ext = normalize_ext(ext) |
|
156 | 157 | |
|
157 | 158 | return ext in [normalize_ext(x) for x in extensions] |
|
158 | 159 | |
|
159 | 160 | def save_file(self, file_obj, filename, directory=None, extensions=None, |
|
160 | extra_metadata=None, **kwargs): | |
|
161 | extra_metadata=None, max_filesize=None, **kwargs): | |
|
161 | 162 | """ |
|
162 | 163 | Saves a file object to the uploads location. |
|
163 | 164 | Returns the resolved filename, i.e. the directory + |
|
164 | 165 | the (randomized/incremented) base name. |
|
165 | 166 | |
|
166 | 167 | :param file_obj: **cgi.FieldStorage** object (or similar) |
|
167 | 168 | :param filename: original filename |
|
168 | 169 | :param directory: relative path of sub-directory |
|
169 | 170 | :param extensions: iterable of allowed extensions, if not default |
|
171 | :param max_filesize: maximum size of file that should be allowed | |
|
170 | 172 | :param extra_metadata: extra JSON metadata to store next to the file with .meta suffix |
|
173 | ||
|
171 | 174 | """ |
|
172 | 175 | |
|
173 | 176 | extensions = extensions or self.extensions |
|
174 | 177 | |
|
175 | 178 | if not self.filename_allowed(filename, extensions): |
|
176 | 179 | raise FileNotAllowedException() |
|
177 | 180 | |
|
178 | 181 | if directory: |
|
179 | 182 | dest_directory = os.path.join(self.base_path, directory) |
|
180 | 183 | else: |
|
181 | 184 | dest_directory = self.base_path |
|
182 | 185 | |
|
183 | 186 | if not os.path.exists(dest_directory): |
|
184 | 187 | os.makedirs(dest_directory) |
|
185 | 188 | |
|
186 | 189 | filename = utils.uid_filename(filename) |
|
187 | 190 | |
|
188 | 191 | # resolve also produces special sub-dir for file optimized store |
|
189 | 192 | filename, path = self.resolve_name(filename, dest_directory) |
|
190 | 193 | stored_file_dir = os.path.dirname(path) |
|
191 | 194 | |
|
192 | 195 | file_obj.seek(0) |
|
193 | 196 | |
|
194 | 197 | with open(path, "wb") as dest: |
|
195 | 198 | shutil.copyfileobj(file_obj, dest) |
|
196 | 199 | |
|
197 | 200 | metadata = {} |
|
198 | 201 | if extra_metadata: |
|
199 | 202 | metadata = extra_metadata |
|
200 | 203 | |
|
201 | 204 | size = os.stat(path).st_size |
|
205 | ||
|
206 | if max_filesize and size > max_filesize: | |
|
207 | # free up the copied file, and raise exc | |
|
208 | os.remove(path) | |
|
209 | raise FileOverSizeException() | |
|
210 | ||
|
202 | 211 | file_hash = self.calculate_path_hash(path) |
|
203 | 212 | |
|
204 | 213 | metadata.update( |
|
205 | 214 | {"filename": filename, |
|
206 | 215 | "size": size, |
|
207 | 216 | "time": time.time(), |
|
208 | 217 | "sha256": file_hash, |
|
209 | 218 | "meta_ver": METADATA_VER}) |
|
210 | 219 | |
|
211 | 220 | filename_meta = filename + '.meta' |
|
212 | 221 | with open(os.path.join(stored_file_dir, filename_meta), "wb") as dest_meta: |
|
213 | 222 | dest_meta.write(json.dumps(metadata)) |
|
214 | 223 | |
|
215 | 224 | if directory: |
|
216 | 225 | filename = os.path.join(directory, filename) |
|
217 | 226 | |
|
218 | 227 | return filename, metadata |
|
219 | 228 | |
|
220 | 229 | def get_metadata(self, filename): |
|
221 | 230 | """ |
|
222 | 231 | Reads JSON stored metadata for a file |
|
223 | 232 | |
|
224 | 233 | :param filename: |
|
225 | 234 | :return: |
|
226 | 235 | """ |
|
227 | 236 | filename = self.store_path(filename) |
|
228 | 237 | filename_meta = filename + '.meta' |
|
229 | 238 | |
|
230 | 239 | with open(filename_meta, "rb") as source_meta: |
|
231 | 240 | return json.loads(source_meta.read()) |
@@ -1,512 +1,516 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2019 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | from rhodecode.apps._base import add_route_with_slash |
|
21 | 21 | |
|
22 | 22 | |
|
23 | 23 | def includeme(config): |
|
24 | 24 | |
|
25 | 25 | # repo creating checks, special cases that aren't repo routes |
|
26 | 26 | config.add_route( |
|
27 | 27 | name='repo_creating', |
|
28 | 28 | pattern='/{repo_name:.*?[^/]}/repo_creating') |
|
29 | 29 | |
|
30 | 30 | config.add_route( |
|
31 | 31 | name='repo_creating_check', |
|
32 | 32 | pattern='/{repo_name:.*?[^/]}/repo_creating_check') |
|
33 | 33 | |
|
34 | 34 | # Summary |
|
35 | 35 | # NOTE(marcink): one additional route is defined in very bottom, catch |
|
36 | 36 | # all pattern |
|
37 | 37 | config.add_route( |
|
38 | 38 | name='repo_summary_explicit', |
|
39 | 39 | pattern='/{repo_name:.*?[^/]}/summary', repo_route=True) |
|
40 | 40 | config.add_route( |
|
41 | 41 | name='repo_summary_commits', |
|
42 | 42 | pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) |
|
43 | 43 | |
|
44 | 44 | # Commits |
|
45 | 45 | config.add_route( |
|
46 | 46 | name='repo_commit', |
|
47 | 47 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True) |
|
48 | 48 | |
|
49 | 49 | config.add_route( |
|
50 | 50 | name='repo_commit_children', |
|
51 | 51 | pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True) |
|
52 | 52 | |
|
53 | 53 | config.add_route( |
|
54 | 54 | name='repo_commit_parents', |
|
55 | 55 | pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True) |
|
56 | 56 | |
|
57 | 57 | config.add_route( |
|
58 | 58 | name='repo_commit_raw', |
|
59 | 59 | pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True) |
|
60 | 60 | |
|
61 | 61 | config.add_route( |
|
62 | 62 | name='repo_commit_patch', |
|
63 | 63 | pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True) |
|
64 | 64 | |
|
65 | 65 | config.add_route( |
|
66 | 66 | name='repo_commit_download', |
|
67 | 67 | pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True) |
|
68 | 68 | |
|
69 | 69 | config.add_route( |
|
70 | 70 | name='repo_commit_data', |
|
71 | 71 | pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True) |
|
72 | 72 | |
|
73 | 73 | config.add_route( |
|
74 | 74 | name='repo_commit_comment_create', |
|
75 | 75 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True) |
|
76 | 76 | |
|
77 | 77 | config.add_route( |
|
78 | 78 | name='repo_commit_comment_preview', |
|
79 | 79 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True) |
|
80 | 80 | |
|
81 | 81 | config.add_route( |
|
82 | name='repo_commit_comment_attachment_upload', | |
|
83 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True) | |
|
84 | ||
|
85 | config.add_route( | |
|
82 | 86 | name='repo_commit_comment_delete', |
|
83 | 87 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True) |
|
84 | 88 | |
|
85 | 89 | # still working url for backward compat. |
|
86 | 90 | config.add_route( |
|
87 | 91 | name='repo_commit_raw_deprecated', |
|
88 | 92 | pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True) |
|
89 | 93 | |
|
90 | 94 | # Files |
|
91 | 95 | config.add_route( |
|
92 | 96 | name='repo_archivefile', |
|
93 | 97 | pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True) |
|
94 | 98 | |
|
95 | 99 | config.add_route( |
|
96 | 100 | name='repo_files_diff', |
|
97 | 101 | pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True) |
|
98 | 102 | config.add_route( # legacy route to make old links work |
|
99 | 103 | name='repo_files_diff_2way_redirect', |
|
100 | 104 | pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True) |
|
101 | 105 | |
|
102 | 106 | config.add_route( |
|
103 | 107 | name='repo_files', |
|
104 | 108 | pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True) |
|
105 | 109 | config.add_route( |
|
106 | 110 | name='repo_files:default_path', |
|
107 | 111 | pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True) |
|
108 | 112 | config.add_route( |
|
109 | 113 | name='repo_files:default_commit', |
|
110 | 114 | pattern='/{repo_name:.*?[^/]}/files', repo_route=True) |
|
111 | 115 | |
|
112 | 116 | config.add_route( |
|
113 | 117 | name='repo_files:rendered', |
|
114 | 118 | pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True) |
|
115 | 119 | |
|
116 | 120 | config.add_route( |
|
117 | 121 | name='repo_files:annotated', |
|
118 | 122 | pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True) |
|
119 | 123 | config.add_route( |
|
120 | 124 | name='repo_files:annotated_previous', |
|
121 | 125 | pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True) |
|
122 | 126 | |
|
123 | 127 | config.add_route( |
|
124 | 128 | name='repo_nodetree_full', |
|
125 | 129 | pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True) |
|
126 | 130 | config.add_route( |
|
127 | 131 | name='repo_nodetree_full:default_path', |
|
128 | 132 | pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True) |
|
129 | 133 | |
|
130 | 134 | config.add_route( |
|
131 | 135 | name='repo_files_nodelist', |
|
132 | 136 | pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True) |
|
133 | 137 | |
|
134 | 138 | config.add_route( |
|
135 | 139 | name='repo_file_raw', |
|
136 | 140 | pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True) |
|
137 | 141 | |
|
138 | 142 | config.add_route( |
|
139 | 143 | name='repo_file_download', |
|
140 | 144 | pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True) |
|
141 | 145 | config.add_route( # backward compat to keep old links working |
|
142 | 146 | name='repo_file_download:legacy', |
|
143 | 147 | pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}', |
|
144 | 148 | repo_route=True) |
|
145 | 149 | |
|
146 | 150 | config.add_route( |
|
147 | 151 | name='repo_file_history', |
|
148 | 152 | pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True) |
|
149 | 153 | |
|
150 | 154 | config.add_route( |
|
151 | 155 | name='repo_file_authors', |
|
152 | 156 | pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True) |
|
153 | 157 | |
|
154 | 158 | config.add_route( |
|
155 | 159 | name='repo_files_remove_file', |
|
156 | 160 | pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}', |
|
157 | 161 | repo_route=True) |
|
158 | 162 | config.add_route( |
|
159 | 163 | name='repo_files_delete_file', |
|
160 | 164 | pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}', |
|
161 | 165 | repo_route=True) |
|
162 | 166 | config.add_route( |
|
163 | 167 | name='repo_files_edit_file', |
|
164 | 168 | pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}', |
|
165 | 169 | repo_route=True) |
|
166 | 170 | config.add_route( |
|
167 | 171 | name='repo_files_update_file', |
|
168 | 172 | pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}', |
|
169 | 173 | repo_route=True) |
|
170 | 174 | config.add_route( |
|
171 | 175 | name='repo_files_add_file', |
|
172 | 176 | pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}', |
|
173 | 177 | repo_route=True) |
|
174 | 178 | config.add_route( |
|
175 | 179 | name='repo_files_upload_file', |
|
176 | 180 | pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}', |
|
177 | 181 | repo_route=True) |
|
178 | 182 | config.add_route( |
|
179 | 183 | name='repo_files_create_file', |
|
180 | 184 | pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}', |
|
181 | 185 | repo_route=True) |
|
182 | 186 | |
|
183 | 187 | # Refs data |
|
184 | 188 | config.add_route( |
|
185 | 189 | name='repo_refs_data', |
|
186 | 190 | pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True) |
|
187 | 191 | |
|
188 | 192 | config.add_route( |
|
189 | 193 | name='repo_refs_changelog_data', |
|
190 | 194 | pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True) |
|
191 | 195 | |
|
192 | 196 | config.add_route( |
|
193 | 197 | name='repo_stats', |
|
194 | 198 | pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True) |
|
195 | 199 | |
|
196 | 200 | # Commits |
|
197 | 201 | config.add_route( |
|
198 | 202 | name='repo_commits', |
|
199 | 203 | pattern='/{repo_name:.*?[^/]}/commits', repo_route=True) |
|
200 | 204 | config.add_route( |
|
201 | 205 | name='repo_commits_file', |
|
202 | 206 | pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True) |
|
203 | 207 | config.add_route( |
|
204 | 208 | name='repo_commits_elements', |
|
205 | 209 | pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True) |
|
206 | 210 | config.add_route( |
|
207 | 211 | name='repo_commits_elements_file', |
|
208 | 212 | pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True) |
|
209 | 213 | |
|
210 | 214 | # Changelog (old deprecated name for commits page) |
|
211 | 215 | config.add_route( |
|
212 | 216 | name='repo_changelog', |
|
213 | 217 | pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True) |
|
214 | 218 | config.add_route( |
|
215 | 219 | name='repo_changelog_file', |
|
216 | 220 | pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True) |
|
217 | 221 | |
|
218 | 222 | # Compare |
|
219 | 223 | config.add_route( |
|
220 | 224 | name='repo_compare_select', |
|
221 | 225 | pattern='/{repo_name:.*?[^/]}/compare', repo_route=True) |
|
222 | 226 | |
|
223 | 227 | config.add_route( |
|
224 | 228 | name='repo_compare', |
|
225 | 229 | pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True) |
|
226 | 230 | |
|
227 | 231 | # Tags |
|
228 | 232 | config.add_route( |
|
229 | 233 | name='tags_home', |
|
230 | 234 | pattern='/{repo_name:.*?[^/]}/tags', repo_route=True) |
|
231 | 235 | |
|
232 | 236 | # Branches |
|
233 | 237 | config.add_route( |
|
234 | 238 | name='branches_home', |
|
235 | 239 | pattern='/{repo_name:.*?[^/]}/branches', repo_route=True) |
|
236 | 240 | |
|
237 | 241 | # Bookmarks |
|
238 | 242 | config.add_route( |
|
239 | 243 | name='bookmarks_home', |
|
240 | 244 | pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True) |
|
241 | 245 | |
|
242 | 246 | # Forks |
|
243 | 247 | config.add_route( |
|
244 | 248 | name='repo_fork_new', |
|
245 | 249 | pattern='/{repo_name:.*?[^/]}/fork', repo_route=True, |
|
246 | 250 | repo_forbid_when_archived=True, |
|
247 | 251 | repo_accepted_types=['hg', 'git']) |
|
248 | 252 | |
|
249 | 253 | config.add_route( |
|
250 | 254 | name='repo_fork_create', |
|
251 | 255 | pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True, |
|
252 | 256 | repo_forbid_when_archived=True, |
|
253 | 257 | repo_accepted_types=['hg', 'git']) |
|
254 | 258 | |
|
255 | 259 | config.add_route( |
|
256 | 260 | name='repo_forks_show_all', |
|
257 | 261 | pattern='/{repo_name:.*?[^/]}/forks', repo_route=True, |
|
258 | 262 | repo_accepted_types=['hg', 'git']) |
|
259 | 263 | config.add_route( |
|
260 | 264 | name='repo_forks_data', |
|
261 | 265 | pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True, |
|
262 | 266 | repo_accepted_types=['hg', 'git']) |
|
263 | 267 | |
|
264 | 268 | # Pull Requests |
|
265 | 269 | config.add_route( |
|
266 | 270 | name='pullrequest_show', |
|
267 | 271 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}', |
|
268 | 272 | repo_route=True) |
|
269 | 273 | |
|
270 | 274 | config.add_route( |
|
271 | 275 | name='pullrequest_show_all', |
|
272 | 276 | pattern='/{repo_name:.*?[^/]}/pull-request', |
|
273 | 277 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
274 | 278 | |
|
275 | 279 | config.add_route( |
|
276 | 280 | name='pullrequest_show_all_data', |
|
277 | 281 | pattern='/{repo_name:.*?[^/]}/pull-request-data', |
|
278 | 282 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
279 | 283 | |
|
280 | 284 | config.add_route( |
|
281 | 285 | name='pullrequest_repo_refs', |
|
282 | 286 | pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}', |
|
283 | 287 | repo_route=True) |
|
284 | 288 | |
|
285 | 289 | config.add_route( |
|
286 | 290 | name='pullrequest_repo_targets', |
|
287 | 291 | pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets', |
|
288 | 292 | repo_route=True) |
|
289 | 293 | |
|
290 | 294 | config.add_route( |
|
291 | 295 | name='pullrequest_new', |
|
292 | 296 | pattern='/{repo_name:.*?[^/]}/pull-request/new', |
|
293 | 297 | repo_route=True, repo_accepted_types=['hg', 'git'], |
|
294 | 298 | repo_forbid_when_archived=True) |
|
295 | 299 | |
|
296 | 300 | config.add_route( |
|
297 | 301 | name='pullrequest_create', |
|
298 | 302 | pattern='/{repo_name:.*?[^/]}/pull-request/create', |
|
299 | 303 | repo_route=True, repo_accepted_types=['hg', 'git'], |
|
300 | 304 | repo_forbid_when_archived=True) |
|
301 | 305 | |
|
302 | 306 | config.add_route( |
|
303 | 307 | name='pullrequest_update', |
|
304 | 308 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update', |
|
305 | 309 | repo_route=True, repo_forbid_when_archived=True) |
|
306 | 310 | |
|
307 | 311 | config.add_route( |
|
308 | 312 | name='pullrequest_merge', |
|
309 | 313 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge', |
|
310 | 314 | repo_route=True, repo_forbid_when_archived=True) |
|
311 | 315 | |
|
312 | 316 | config.add_route( |
|
313 | 317 | name='pullrequest_delete', |
|
314 | 318 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete', |
|
315 | 319 | repo_route=True, repo_forbid_when_archived=True) |
|
316 | 320 | |
|
317 | 321 | config.add_route( |
|
318 | 322 | name='pullrequest_comment_create', |
|
319 | 323 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment', |
|
320 | 324 | repo_route=True) |
|
321 | 325 | |
|
322 | 326 | config.add_route( |
|
323 | 327 | name='pullrequest_comment_delete', |
|
324 | 328 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete', |
|
325 | 329 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
326 | 330 | |
|
327 | 331 | # Artifacts, (EE feature) |
|
328 | 332 | config.add_route( |
|
329 | 333 | name='repo_artifacts_list', |
|
330 | 334 | pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True) |
|
331 | 335 | |
|
332 | 336 | # Settings |
|
333 | 337 | config.add_route( |
|
334 | 338 | name='edit_repo', |
|
335 | 339 | pattern='/{repo_name:.*?[^/]}/settings', repo_route=True) |
|
336 | 340 | # update is POST on edit_repo |
|
337 | 341 | |
|
338 | 342 | # Settings advanced |
|
339 | 343 | config.add_route( |
|
340 | 344 | name='edit_repo_advanced', |
|
341 | 345 | pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True) |
|
342 | 346 | config.add_route( |
|
343 | 347 | name='edit_repo_advanced_archive', |
|
344 | 348 | pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True) |
|
345 | 349 | config.add_route( |
|
346 | 350 | name='edit_repo_advanced_delete', |
|
347 | 351 | pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True) |
|
348 | 352 | config.add_route( |
|
349 | 353 | name='edit_repo_advanced_locking', |
|
350 | 354 | pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True) |
|
351 | 355 | config.add_route( |
|
352 | 356 | name='edit_repo_advanced_journal', |
|
353 | 357 | pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True) |
|
354 | 358 | config.add_route( |
|
355 | 359 | name='edit_repo_advanced_fork', |
|
356 | 360 | pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True) |
|
357 | 361 | |
|
358 | 362 | config.add_route( |
|
359 | 363 | name='edit_repo_advanced_hooks', |
|
360 | 364 | pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True) |
|
361 | 365 | |
|
362 | 366 | # Caches |
|
363 | 367 | config.add_route( |
|
364 | 368 | name='edit_repo_caches', |
|
365 | 369 | pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True) |
|
366 | 370 | |
|
367 | 371 | # Permissions |
|
368 | 372 | config.add_route( |
|
369 | 373 | name='edit_repo_perms', |
|
370 | 374 | pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True) |
|
371 | 375 | |
|
372 | 376 | config.add_route( |
|
373 | 377 | name='edit_repo_perms_set_private', |
|
374 | 378 | pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True) |
|
375 | 379 | |
|
376 | 380 | # Permissions Branch (EE feature) |
|
377 | 381 | config.add_route( |
|
378 | 382 | name='edit_repo_perms_branch', |
|
379 | 383 | pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True) |
|
380 | 384 | config.add_route( |
|
381 | 385 | name='edit_repo_perms_branch_delete', |
|
382 | 386 | pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete', |
|
383 | 387 | repo_route=True) |
|
384 | 388 | |
|
385 | 389 | # Maintenance |
|
386 | 390 | config.add_route( |
|
387 | 391 | name='edit_repo_maintenance', |
|
388 | 392 | pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True) |
|
389 | 393 | |
|
390 | 394 | config.add_route( |
|
391 | 395 | name='edit_repo_maintenance_execute', |
|
392 | 396 | pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True) |
|
393 | 397 | |
|
394 | 398 | # Fields |
|
395 | 399 | config.add_route( |
|
396 | 400 | name='edit_repo_fields', |
|
397 | 401 | pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True) |
|
398 | 402 | config.add_route( |
|
399 | 403 | name='edit_repo_fields_create', |
|
400 | 404 | pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True) |
|
401 | 405 | config.add_route( |
|
402 | 406 | name='edit_repo_fields_delete', |
|
403 | 407 | pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True) |
|
404 | 408 | |
|
405 | 409 | # Locking |
|
406 | 410 | config.add_route( |
|
407 | 411 | name='repo_edit_toggle_locking', |
|
408 | 412 | pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True) |
|
409 | 413 | |
|
410 | 414 | # Remote |
|
411 | 415 | config.add_route( |
|
412 | 416 | name='edit_repo_remote', |
|
413 | 417 | pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True) |
|
414 | 418 | config.add_route( |
|
415 | 419 | name='edit_repo_remote_pull', |
|
416 | 420 | pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True) |
|
417 | 421 | config.add_route( |
|
418 | 422 | name='edit_repo_remote_push', |
|
419 | 423 | pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True) |
|
420 | 424 | |
|
421 | 425 | # Statistics |
|
422 | 426 | config.add_route( |
|
423 | 427 | name='edit_repo_statistics', |
|
424 | 428 | pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True) |
|
425 | 429 | config.add_route( |
|
426 | 430 | name='edit_repo_statistics_reset', |
|
427 | 431 | pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True) |
|
428 | 432 | |
|
429 | 433 | # Issue trackers |
|
430 | 434 | config.add_route( |
|
431 | 435 | name='edit_repo_issuetracker', |
|
432 | 436 | pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True) |
|
433 | 437 | config.add_route( |
|
434 | 438 | name='edit_repo_issuetracker_test', |
|
435 | 439 | pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True) |
|
436 | 440 | config.add_route( |
|
437 | 441 | name='edit_repo_issuetracker_delete', |
|
438 | 442 | pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True) |
|
439 | 443 | config.add_route( |
|
440 | 444 | name='edit_repo_issuetracker_update', |
|
441 | 445 | pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True) |
|
442 | 446 | |
|
443 | 447 | # VCS Settings |
|
444 | 448 | config.add_route( |
|
445 | 449 | name='edit_repo_vcs', |
|
446 | 450 | pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True) |
|
447 | 451 | config.add_route( |
|
448 | 452 | name='edit_repo_vcs_update', |
|
449 | 453 | pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True) |
|
450 | 454 | |
|
451 | 455 | # svn pattern |
|
452 | 456 | config.add_route( |
|
453 | 457 | name='edit_repo_vcs_svn_pattern_delete', |
|
454 | 458 | pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True) |
|
455 | 459 | |
|
456 | 460 | # Repo Review Rules (EE feature) |
|
457 | 461 | config.add_route( |
|
458 | 462 | name='repo_reviewers', |
|
459 | 463 | pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True) |
|
460 | 464 | |
|
461 | 465 | config.add_route( |
|
462 | 466 | name='repo_default_reviewers_data', |
|
463 | 467 | pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True) |
|
464 | 468 | |
|
465 | 469 | # Repo Automation (EE feature) |
|
466 | 470 | config.add_route( |
|
467 | 471 | name='repo_automation', |
|
468 | 472 | pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True) |
|
469 | 473 | |
|
470 | 474 | # Strip |
|
471 | 475 | config.add_route( |
|
472 | 476 | name='edit_repo_strip', |
|
473 | 477 | pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True) |
|
474 | 478 | |
|
475 | 479 | config.add_route( |
|
476 | 480 | name='strip_check', |
|
477 | 481 | pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True) |
|
478 | 482 | |
|
479 | 483 | config.add_route( |
|
480 | 484 | name='strip_execute', |
|
481 | 485 | pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) |
|
482 | 486 | |
|
483 | 487 | # Audit logs |
|
484 | 488 | config.add_route( |
|
485 | 489 | name='edit_repo_audit_logs', |
|
486 | 490 | pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True) |
|
487 | 491 | |
|
488 | 492 | # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility |
|
489 | 493 | config.add_route( |
|
490 | 494 | name='rss_feed_home', |
|
491 | 495 | pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True) |
|
492 | 496 | |
|
493 | 497 | config.add_route( |
|
494 | 498 | name='atom_feed_home', |
|
495 | 499 | pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True) |
|
496 | 500 | |
|
497 | 501 | config.add_route( |
|
498 | 502 | name='rss_feed_home_old', |
|
499 | 503 | pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True) |
|
500 | 504 | |
|
501 | 505 | config.add_route( |
|
502 | 506 | name='atom_feed_home_old', |
|
503 | 507 | pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True) |
|
504 | 508 | |
|
505 | 509 | # NOTE(marcink): needs to be at the end for catch-all |
|
506 | 510 | add_route_with_slash( |
|
507 | 511 | config, |
|
508 | 512 | name='repo_summary', |
|
509 | 513 | pattern='/{repo_name:.*?[^/]}', repo_route=True) |
|
510 | 514 | |
|
511 | 515 | # Scan module for configuration decorators. |
|
512 | 516 | config.scan('.views', ignore='.tests') |
@@ -1,503 +1,600 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2019 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | import logging |
|
23 | 23 | import collections |
|
24 | 24 | |
|
25 | 25 | from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound |
|
26 | 26 | from pyramid.view import view_config |
|
27 | 27 | from pyramid.renderers import render |
|
28 | 28 | from pyramid.response import Response |
|
29 | 29 | |
|
30 | 30 | from rhodecode.apps._base import RepoAppView |
|
31 | from rhodecode.apps.file_store import utils as store_utils | |
|
32 | from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException | |
|
31 | 33 | |
|
32 | 34 | from rhodecode.lib import diffs, codeblocks |
|
33 | 35 | from rhodecode.lib.auth import ( |
|
34 | 36 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) |
|
35 | 37 | |
|
36 | 38 | from rhodecode.lib.compat import OrderedDict |
|
37 | 39 | from rhodecode.lib.diffs import ( |
|
38 | 40 | cache_diff, load_cached_diff, diff_cache_exist, get_diff_context, |
|
39 | 41 | get_diff_whitespace_flag) |
|
40 | 42 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError |
|
41 | 43 | import rhodecode.lib.helpers as h |
|
42 | 44 | from rhodecode.lib.utils2 import safe_unicode, str2bool |
|
43 | 45 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
44 | 46 | from rhodecode.lib.vcs.exceptions import ( |
|
45 | 47 | RepositoryError, CommitDoesNotExistError) |
|
46 | from rhodecode.model.db import ChangesetComment, ChangesetStatus | |
|
48 | from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore | |
|
47 | 49 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
48 | 50 | from rhodecode.model.comment import CommentsModel |
|
49 | 51 | from rhodecode.model.meta import Session |
|
50 | 52 | from rhodecode.model.settings import VcsSettingsModel |
|
51 | 53 | |
|
52 | 54 | log = logging.getLogger(__name__) |
|
53 | 55 | |
|
54 | 56 | |
|
55 | 57 | def _update_with_GET(params, request): |
|
56 | 58 | for k in ['diff1', 'diff2', 'diff']: |
|
57 | 59 | params[k] += request.GET.getall(k) |
|
58 | 60 | |
|
59 | 61 | |
|
60 | 62 | class RepoCommitsView(RepoAppView): |
|
61 | 63 | def load_default_context(self): |
|
62 | 64 | c = self._get_local_tmpl_context(include_app_defaults=True) |
|
63 | 65 | c.rhodecode_repo = self.rhodecode_vcs_repo |
|
64 | 66 | |
|
65 | 67 | return c |
|
66 | 68 | |
|
67 | 69 | def _is_diff_cache_enabled(self, target_repo): |
|
68 | 70 | caching_enabled = self._get_general_setting( |
|
69 | 71 | target_repo, 'rhodecode_diff_cache') |
|
70 | 72 | log.debug('Diff caching enabled: %s', caching_enabled) |
|
71 | 73 | return caching_enabled |
|
72 | 74 | |
|
73 | 75 | def _commit(self, commit_id_range, method): |
|
74 | 76 | _ = self.request.translate |
|
75 | 77 | c = self.load_default_context() |
|
76 | 78 | c.fulldiff = self.request.GET.get('fulldiff') |
|
77 | 79 | |
|
78 | 80 | # fetch global flags of ignore ws or context lines |
|
79 | 81 | diff_context = get_diff_context(self.request) |
|
80 | 82 | hide_whitespace_changes = get_diff_whitespace_flag(self.request) |
|
81 | 83 | |
|
82 | 84 | # diff_limit will cut off the whole diff if the limit is applied |
|
83 | 85 | # otherwise it will just hide the big files from the front-end |
|
84 | 86 | diff_limit = c.visual.cut_off_limit_diff |
|
85 | 87 | file_limit = c.visual.cut_off_limit_file |
|
86 | 88 | |
|
87 | 89 | # get ranges of commit ids if preset |
|
88 | 90 | commit_range = commit_id_range.split('...')[:2] |
|
89 | 91 | |
|
90 | 92 | try: |
|
91 | 93 | pre_load = ['affected_files', 'author', 'branch', 'date', |
|
92 | 94 | 'message', 'parents'] |
|
93 | 95 | if self.rhodecode_vcs_repo.alias == 'hg': |
|
94 | 96 | pre_load += ['hidden', 'obsolete', 'phase'] |
|
95 | 97 | |
|
96 | 98 | if len(commit_range) == 2: |
|
97 | 99 | commits = self.rhodecode_vcs_repo.get_commits( |
|
98 | 100 | start_id=commit_range[0], end_id=commit_range[1], |
|
99 | 101 | pre_load=pre_load, translate_tags=False) |
|
100 | 102 | commits = list(commits) |
|
101 | 103 | else: |
|
102 | 104 | commits = [self.rhodecode_vcs_repo.get_commit( |
|
103 | 105 | commit_id=commit_id_range, pre_load=pre_load)] |
|
104 | 106 | |
|
105 | 107 | c.commit_ranges = commits |
|
106 | 108 | if not c.commit_ranges: |
|
107 | 109 | raise RepositoryError('The commit range returned an empty result') |
|
108 | 110 | except CommitDoesNotExistError as e: |
|
109 | 111 | msg = _('No such commit exists. Org exception: `{}`').format(e) |
|
110 | 112 | h.flash(msg, category='error') |
|
111 | 113 | raise HTTPNotFound() |
|
112 | 114 | except Exception: |
|
113 | 115 | log.exception("General failure") |
|
114 | 116 | raise HTTPNotFound() |
|
115 | 117 | |
|
116 | 118 | c.changes = OrderedDict() |
|
117 | 119 | c.lines_added = 0 |
|
118 | 120 | c.lines_deleted = 0 |
|
119 | 121 | |
|
120 | 122 | # auto collapse if we have more than limit |
|
121 | 123 | collapse_limit = diffs.DiffProcessor._collapse_commits_over |
|
122 | 124 | c.collapse_all_commits = len(c.commit_ranges) > collapse_limit |
|
123 | 125 | |
|
124 | 126 | c.commit_statuses = ChangesetStatus.STATUSES |
|
125 | 127 | c.inline_comments = [] |
|
126 | 128 | c.files = [] |
|
127 | 129 | |
|
128 | 130 | c.statuses = [] |
|
129 | 131 | c.comments = [] |
|
130 | 132 | c.unresolved_comments = [] |
|
131 | 133 | c.resolved_comments = [] |
|
132 | 134 | if len(c.commit_ranges) == 1: |
|
133 | 135 | commit = c.commit_ranges[0] |
|
134 | 136 | c.comments = CommentsModel().get_comments( |
|
135 | 137 | self.db_repo.repo_id, |
|
136 | 138 | revision=commit.raw_id) |
|
137 | 139 | c.statuses.append(ChangesetStatusModel().get_status( |
|
138 | 140 | self.db_repo.repo_id, commit.raw_id)) |
|
139 | 141 | # comments from PR |
|
140 | 142 | statuses = ChangesetStatusModel().get_statuses( |
|
141 | 143 | self.db_repo.repo_id, commit.raw_id, |
|
142 | 144 | with_revisions=True) |
|
143 | 145 | prs = set(st.pull_request for st in statuses |
|
144 | 146 | if st.pull_request is not None) |
|
145 | 147 | # from associated statuses, check the pull requests, and |
|
146 | 148 | # show comments from them |
|
147 | 149 | for pr in prs: |
|
148 | 150 | c.comments.extend(pr.comments) |
|
149 | 151 | |
|
150 | 152 | c.unresolved_comments = CommentsModel()\ |
|
151 | 153 | .get_commit_unresolved_todos(commit.raw_id) |
|
152 | 154 | c.resolved_comments = CommentsModel()\ |
|
153 | 155 | .get_commit_resolved_todos(commit.raw_id) |
|
154 | 156 | |
|
155 | 157 | diff = None |
|
156 | 158 | # Iterate over ranges (default commit view is always one commit) |
|
157 | 159 | for commit in c.commit_ranges: |
|
158 | 160 | c.changes[commit.raw_id] = [] |
|
159 | 161 | |
|
160 | 162 | commit2 = commit |
|
161 | 163 | commit1 = commit.first_parent |
|
162 | 164 | |
|
163 | 165 | if method == 'show': |
|
164 | 166 | inline_comments = CommentsModel().get_inline_comments( |
|
165 | 167 | self.db_repo.repo_id, revision=commit.raw_id) |
|
166 | 168 | c.inline_cnt = CommentsModel().get_inline_comments_count( |
|
167 | 169 | inline_comments) |
|
168 | 170 | c.inline_comments = inline_comments |
|
169 | 171 | |
|
170 | 172 | cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path( |
|
171 | 173 | self.db_repo) |
|
172 | 174 | cache_file_path = diff_cache_exist( |
|
173 | 175 | cache_path, 'diff', commit.raw_id, |
|
174 | 176 | hide_whitespace_changes, diff_context, c.fulldiff) |
|
175 | 177 | |
|
176 | 178 | caching_enabled = self._is_diff_cache_enabled(self.db_repo) |
|
177 | 179 | force_recache = str2bool(self.request.GET.get('force_recache')) |
|
178 | 180 | |
|
179 | 181 | cached_diff = None |
|
180 | 182 | if caching_enabled: |
|
181 | 183 | cached_diff = load_cached_diff(cache_file_path) |
|
182 | 184 | |
|
183 | 185 | has_proper_diff_cache = cached_diff and cached_diff.get('diff') |
|
184 | 186 | if not force_recache and has_proper_diff_cache: |
|
185 | 187 | diffset = cached_diff['diff'] |
|
186 | 188 | else: |
|
187 | 189 | vcs_diff = self.rhodecode_vcs_repo.get_diff( |
|
188 | 190 | commit1, commit2, |
|
189 | 191 | ignore_whitespace=hide_whitespace_changes, |
|
190 | 192 | context=diff_context) |
|
191 | 193 | |
|
192 | 194 | diff_processor = diffs.DiffProcessor( |
|
193 | 195 | vcs_diff, format='newdiff', diff_limit=diff_limit, |
|
194 | 196 | file_limit=file_limit, show_full_diff=c.fulldiff) |
|
195 | 197 | |
|
196 | 198 | _parsed = diff_processor.prepare() |
|
197 | 199 | |
|
198 | 200 | diffset = codeblocks.DiffSet( |
|
199 | 201 | repo_name=self.db_repo_name, |
|
200 | 202 | source_node_getter=codeblocks.diffset_node_getter(commit1), |
|
201 | 203 | target_node_getter=codeblocks.diffset_node_getter(commit2)) |
|
202 | 204 | |
|
203 | 205 | diffset = self.path_filter.render_patchset_filtered( |
|
204 | 206 | diffset, _parsed, commit1.raw_id, commit2.raw_id) |
|
205 | 207 | |
|
206 | 208 | # save cached diff |
|
207 | 209 | if caching_enabled: |
|
208 | 210 | cache_diff(cache_file_path, diffset, None) |
|
209 | 211 | |
|
210 | 212 | c.limited_diff = diffset.limited_diff |
|
211 | 213 | c.changes[commit.raw_id] = diffset |
|
212 | 214 | else: |
|
213 | 215 | # TODO(marcink): no cache usage here... |
|
214 | 216 | _diff = self.rhodecode_vcs_repo.get_diff( |
|
215 | 217 | commit1, commit2, |
|
216 | 218 | ignore_whitespace=hide_whitespace_changes, context=diff_context) |
|
217 | 219 | diff_processor = diffs.DiffProcessor( |
|
218 | 220 | _diff, format='newdiff', diff_limit=diff_limit, |
|
219 | 221 | file_limit=file_limit, show_full_diff=c.fulldiff) |
|
220 | 222 | # downloads/raw we only need RAW diff nothing else |
|
221 | 223 | diff = self.path_filter.get_raw_patch(diff_processor) |
|
222 | 224 | c.changes[commit.raw_id] = [None, None, None, None, diff, None, None] |
|
223 | 225 | |
|
224 | 226 | # sort comments by how they were generated |
|
225 | 227 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) |
|
226 | 228 | |
|
227 | 229 | if len(c.commit_ranges) == 1: |
|
228 | 230 | c.commit = c.commit_ranges[0] |
|
229 | 231 | c.parent_tmpl = ''.join( |
|
230 | 232 | '# Parent %s\n' % x.raw_id for x in c.commit.parents) |
|
231 | 233 | |
|
232 | 234 | if method == 'download': |
|
233 | 235 | response = Response(diff) |
|
234 | 236 | response.content_type = 'text/plain' |
|
235 | 237 | response.content_disposition = ( |
|
236 | 238 | 'attachment; filename=%s.diff' % commit_id_range[:12]) |
|
237 | 239 | return response |
|
238 | 240 | elif method == 'patch': |
|
239 | 241 | c.diff = safe_unicode(diff) |
|
240 | 242 | patch = render( |
|
241 | 243 | 'rhodecode:templates/changeset/patch_changeset.mako', |
|
242 | 244 | self._get_template_context(c), self.request) |
|
243 | 245 | response = Response(patch) |
|
244 | 246 | response.content_type = 'text/plain' |
|
245 | 247 | return response |
|
246 | 248 | elif method == 'raw': |
|
247 | 249 | response = Response(diff) |
|
248 | 250 | response.content_type = 'text/plain' |
|
249 | 251 | return response |
|
250 | 252 | elif method == 'show': |
|
251 | 253 | if len(c.commit_ranges) == 1: |
|
252 | 254 | html = render( |
|
253 | 255 | 'rhodecode:templates/changeset/changeset.mako', |
|
254 | 256 | self._get_template_context(c), self.request) |
|
255 | 257 | return Response(html) |
|
256 | 258 | else: |
|
257 | 259 | c.ancestor = None |
|
258 | 260 | c.target_repo = self.db_repo |
|
259 | 261 | html = render( |
|
260 | 262 | 'rhodecode:templates/changeset/changeset_range.mako', |
|
261 | 263 | self._get_template_context(c), self.request) |
|
262 | 264 | return Response(html) |
|
263 | 265 | |
|
264 | 266 | raise HTTPBadRequest() |
|
265 | 267 | |
|
266 | 268 | @LoginRequired() |
|
267 | 269 | @HasRepoPermissionAnyDecorator( |
|
268 | 270 | 'repository.read', 'repository.write', 'repository.admin') |
|
269 | 271 | @view_config( |
|
270 | 272 | route_name='repo_commit', request_method='GET', |
|
271 | 273 | renderer=None) |
|
272 | 274 | def repo_commit_show(self): |
|
273 | 275 | commit_id = self.request.matchdict['commit_id'] |
|
274 | 276 | return self._commit(commit_id, method='show') |
|
275 | 277 | |
|
276 | 278 | @LoginRequired() |
|
277 | 279 | @HasRepoPermissionAnyDecorator( |
|
278 | 280 | 'repository.read', 'repository.write', 'repository.admin') |
|
279 | 281 | @view_config( |
|
280 | 282 | route_name='repo_commit_raw', request_method='GET', |
|
281 | 283 | renderer=None) |
|
282 | 284 | @view_config( |
|
283 | 285 | route_name='repo_commit_raw_deprecated', request_method='GET', |
|
284 | 286 | renderer=None) |
|
285 | 287 | def repo_commit_raw(self): |
|
286 | 288 | commit_id = self.request.matchdict['commit_id'] |
|
287 | 289 | return self._commit(commit_id, method='raw') |
|
288 | 290 | |
|
289 | 291 | @LoginRequired() |
|
290 | 292 | @HasRepoPermissionAnyDecorator( |
|
291 | 293 | 'repository.read', 'repository.write', 'repository.admin') |
|
292 | 294 | @view_config( |
|
293 | 295 | route_name='repo_commit_patch', request_method='GET', |
|
294 | 296 | renderer=None) |
|
295 | 297 | def repo_commit_patch(self): |
|
296 | 298 | commit_id = self.request.matchdict['commit_id'] |
|
297 | 299 | return self._commit(commit_id, method='patch') |
|
298 | 300 | |
|
299 | 301 | @LoginRequired() |
|
300 | 302 | @HasRepoPermissionAnyDecorator( |
|
301 | 303 | 'repository.read', 'repository.write', 'repository.admin') |
|
302 | 304 | @view_config( |
|
303 | 305 | route_name='repo_commit_download', request_method='GET', |
|
304 | 306 | renderer=None) |
|
305 | 307 | def repo_commit_download(self): |
|
306 | 308 | commit_id = self.request.matchdict['commit_id'] |
|
307 | 309 | return self._commit(commit_id, method='download') |
|
308 | 310 | |
|
309 | 311 | @LoginRequired() |
|
310 | 312 | @NotAnonymous() |
|
311 | 313 | @HasRepoPermissionAnyDecorator( |
|
312 | 314 | 'repository.read', 'repository.write', 'repository.admin') |
|
313 | 315 | @CSRFRequired() |
|
314 | 316 | @view_config( |
|
315 | 317 | route_name='repo_commit_comment_create', request_method='POST', |
|
316 | 318 | renderer='json_ext') |
|
317 | 319 | def repo_commit_comment_create(self): |
|
318 | 320 | _ = self.request.translate |
|
319 | 321 | commit_id = self.request.matchdict['commit_id'] |
|
320 | 322 | |
|
321 | 323 | c = self.load_default_context() |
|
322 | 324 | status = self.request.POST.get('changeset_status', None) |
|
323 | 325 | text = self.request.POST.get('text') |
|
324 | 326 | comment_type = self.request.POST.get('comment_type') |
|
325 | 327 | resolves_comment_id = self.request.POST.get('resolves_comment_id', None) |
|
326 | 328 | |
|
327 | 329 | if status: |
|
328 | 330 | text = text or (_('Status change %(transition_icon)s %(status)s') |
|
329 | 331 | % {'transition_icon': '>', |
|
330 | 332 | 'status': ChangesetStatus.get_status_lbl(status)}) |
|
331 | 333 | |
|
332 | 334 | multi_commit_ids = [] |
|
333 | 335 | for _commit_id in self.request.POST.get('commit_ids', '').split(','): |
|
334 | 336 | if _commit_id not in ['', None, EmptyCommit.raw_id]: |
|
335 | 337 | if _commit_id not in multi_commit_ids: |
|
336 | 338 | multi_commit_ids.append(_commit_id) |
|
337 | 339 | |
|
338 | 340 | commit_ids = multi_commit_ids or [commit_id] |
|
339 | 341 | |
|
340 | 342 | comment = None |
|
341 | 343 | for current_id in filter(None, commit_ids): |
|
342 | 344 | comment = CommentsModel().create( |
|
343 | 345 | text=text, |
|
344 | 346 | repo=self.db_repo.repo_id, |
|
345 | 347 | user=self._rhodecode_db_user.user_id, |
|
346 | 348 | commit_id=current_id, |
|
347 | 349 | f_path=self.request.POST.get('f_path'), |
|
348 | 350 | line_no=self.request.POST.get('line'), |
|
349 | 351 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
350 | 352 | if status else None), |
|
351 | 353 | status_change_type=status, |
|
352 | 354 | comment_type=comment_type, |
|
353 | 355 | resolves_comment_id=resolves_comment_id, |
|
354 | 356 | auth_user=self._rhodecode_user |
|
355 | 357 | ) |
|
356 | 358 | |
|
357 | 359 | # get status if set ! |
|
358 | 360 | if status: |
|
359 | 361 | # if latest status was from pull request and it's closed |
|
360 | 362 | # disallow changing status ! |
|
361 | 363 | # dont_allow_on_closed_pull_request = True ! |
|
362 | 364 | |
|
363 | 365 | try: |
|
364 | 366 | ChangesetStatusModel().set_status( |
|
365 | 367 | self.db_repo.repo_id, |
|
366 | 368 | status, |
|
367 | 369 | self._rhodecode_db_user.user_id, |
|
368 | 370 | comment, |
|
369 | 371 | revision=current_id, |
|
370 | 372 | dont_allow_on_closed_pull_request=True |
|
371 | 373 | ) |
|
372 | 374 | except StatusChangeOnClosedPullRequestError: |
|
373 | 375 | msg = _('Changing the status of a commit associated with ' |
|
374 | 376 | 'a closed pull request is not allowed') |
|
375 | 377 | log.exception(msg) |
|
376 | 378 | h.flash(msg, category='warning') |
|
377 | 379 | raise HTTPFound(h.route_path( |
|
378 | 380 | 'repo_commit', repo_name=self.db_repo_name, |
|
379 | 381 | commit_id=current_id)) |
|
380 | 382 | |
|
381 | 383 | # finalize, commit and redirect |
|
382 | 384 | Session().commit() |
|
383 | 385 | |
|
384 | 386 | data = { |
|
385 | 387 | 'target_id': h.safeid(h.safe_unicode( |
|
386 | 388 | self.request.POST.get('f_path'))), |
|
387 | 389 | } |
|
388 | 390 | if comment: |
|
389 | 391 | c.co = comment |
|
390 | 392 | rendered_comment = render( |
|
391 | 393 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
392 | 394 | self._get_template_context(c), self.request) |
|
393 | 395 | |
|
394 | 396 | data.update(comment.get_dict()) |
|
395 | 397 | data.update({'rendered_text': rendered_comment}) |
|
396 | 398 | |
|
397 | 399 | return data |
|
398 | 400 | |
|
399 | 401 | @LoginRequired() |
|
400 | 402 | @NotAnonymous() |
|
401 | 403 | @HasRepoPermissionAnyDecorator( |
|
402 | 404 | 'repository.read', 'repository.write', 'repository.admin') |
|
403 | 405 | @CSRFRequired() |
|
404 | 406 | @view_config( |
|
405 | 407 | route_name='repo_commit_comment_preview', request_method='POST', |
|
406 | 408 | renderer='string', xhr=True) |
|
407 | 409 | def repo_commit_comment_preview(self): |
|
408 | 410 | # Technically a CSRF token is not needed as no state changes with this |
|
409 | 411 | # call. However, as this is a POST is better to have it, so automated |
|
410 | 412 | # tools don't flag it as potential CSRF. |
|
411 | 413 | # Post is required because the payload could be bigger than the maximum |
|
412 | 414 | # allowed by GET. |
|
413 | 415 | |
|
414 | 416 | text = self.request.POST.get('text') |
|
415 | 417 | renderer = self.request.POST.get('renderer') or 'rst' |
|
416 | 418 | if text: |
|
417 | 419 | return h.render(text, renderer=renderer, mentions=True) |
|
418 | 420 | return '' |
|
419 | 421 | |
|
420 | 422 | @LoginRequired() |
|
421 | 423 | @NotAnonymous() |
|
422 | 424 | @HasRepoPermissionAnyDecorator( |
|
423 | 425 | 'repository.read', 'repository.write', 'repository.admin') |
|
424 | 426 | @CSRFRequired() |
|
425 | 427 | @view_config( |
|
428 | route_name='repo_commit_comment_attachment_upload', request_method='POST', | |
|
429 | renderer='json_ext', xhr=True) | |
|
430 | def repo_commit_comment_attachment_upload(self): | |
|
431 | c = self.load_default_context() | |
|
432 | upload_key = 'attachment' | |
|
433 | ||
|
434 | file_obj = self.request.POST.get(upload_key) | |
|
435 | ||
|
436 | if file_obj is None: | |
|
437 | self.request.response.status = 400 | |
|
438 | return {'store_fid': None, | |
|
439 | 'access_path': None, | |
|
440 | 'error': '{} data field is missing'.format(upload_key)} | |
|
441 | ||
|
442 | if not hasattr(file_obj, 'filename'): | |
|
443 | self.request.response.status = 400 | |
|
444 | return {'store_fid': None, | |
|
445 | 'access_path': None, | |
|
446 | 'error': 'filename cannot be read from the data field'} | |
|
447 | ||
|
448 | filename = file_obj.filename | |
|
449 | file_display_name = filename | |
|
450 | ||
|
451 | metadata = { | |
|
452 | 'user_uploaded': {'username': self._rhodecode_user.username, | |
|
453 | 'user_id': self._rhodecode_user.user_id, | |
|
454 | 'ip': self._rhodecode_user.ip_addr}} | |
|
455 | ||
|
456 | # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size | |
|
457 | allowed_extensions = [ | |
|
458 | 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf', | |
|
459 | '.pptx', '.txt', '.xlsx', '.zip'] | |
|
460 | max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js | |
|
461 | ||
|
462 | try: | |
|
463 | storage = store_utils.get_file_storage(self.request.registry.settings) | |
|
464 | store_uid, metadata = storage.save_file( | |
|
465 | file_obj.file, filename, extra_metadata=metadata, | |
|
466 | extensions=allowed_extensions, max_filesize=max_file_size) | |
|
467 | except FileNotAllowedException: | |
|
468 | self.request.response.status = 400 | |
|
469 | permitted_extensions = ', '.join(allowed_extensions) | |
|
470 | error_msg = 'File `{}` is not allowed. ' \ | |
|
471 | 'Only following extensions are permitted: {}'.format( | |
|
472 | filename, permitted_extensions) | |
|
473 | return {'store_fid': None, | |
|
474 | 'access_path': None, | |
|
475 | 'error': error_msg} | |
|
476 | except FileOverSizeException: | |
|
477 | self.request.response.status = 400 | |
|
478 | limit_mb = h.format_byte_size_binary(max_file_size) | |
|
479 | return {'store_fid': None, | |
|
480 | 'access_path': None, | |
|
481 | 'error': 'File {} is exceeding allowed limit of {}.'.format( | |
|
482 | filename, limit_mb)} | |
|
483 | ||
|
484 | try: | |
|
485 | entry = FileStore.create( | |
|
486 | file_uid=store_uid, filename=metadata["filename"], | |
|
487 | file_hash=metadata["sha256"], file_size=metadata["size"], | |
|
488 | file_display_name=file_display_name, | |
|
489 | file_description=u'comment attachment `{}`'.format(safe_unicode(filename)), | |
|
490 | hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id, | |
|
491 | scope_repo_id=self.db_repo.repo_id | |
|
492 | ) | |
|
493 | Session().add(entry) | |
|
494 | Session().commit() | |
|
495 | log.debug('Stored upload in DB as %s', entry) | |
|
496 | except Exception: | |
|
497 | log.exception('Failed to store file %s', filename) | |
|
498 | self.request.response.status = 400 | |
|
499 | return {'store_fid': None, | |
|
500 | 'access_path': None, | |
|
501 | 'error': 'File {} failed to store in DB.'.format(filename)} | |
|
502 | ||
|
503 | Session().commit() | |
|
504 | ||
|
505 | return { | |
|
506 | 'store_fid': store_uid, | |
|
507 | 'access_path': h.route_path( | |
|
508 | 'download_file', fid=store_uid), | |
|
509 | 'fqn_access_path': h.route_url( | |
|
510 | 'download_file', fid=store_uid), | |
|
511 | 'repo_access_path': h.route_path( | |
|
512 | 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid), | |
|
513 | 'repo_fqn_access_path': h.route_url( | |
|
514 | 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid), | |
|
515 | } | |
|
516 | ||
|
517 | @LoginRequired() | |
|
518 | @NotAnonymous() | |
|
519 | @HasRepoPermissionAnyDecorator( | |
|
520 | 'repository.read', 'repository.write', 'repository.admin') | |
|
521 | @CSRFRequired() | |
|
522 | @view_config( | |
|
426 | 523 | route_name='repo_commit_comment_delete', request_method='POST', |
|
427 | 524 | renderer='json_ext') |
|
428 | 525 | def repo_commit_comment_delete(self): |
|
429 | 526 | commit_id = self.request.matchdict['commit_id'] |
|
430 | 527 | comment_id = self.request.matchdict['comment_id'] |
|
431 | 528 | |
|
432 | 529 | comment = ChangesetComment.get_or_404(comment_id) |
|
433 | 530 | if not comment: |
|
434 | 531 | log.debug('Comment with id:%s not found, skipping', comment_id) |
|
435 | 532 | # comment already deleted in another call probably |
|
436 | 533 | return True |
|
437 | 534 | |
|
438 | 535 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name) |
|
439 | 536 | super_admin = h.HasPermissionAny('hg.admin')() |
|
440 | 537 | comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id) |
|
441 | 538 | is_repo_comment = comment.repo.repo_name == self.db_repo_name |
|
442 | 539 | comment_repo_admin = is_repo_admin and is_repo_comment |
|
443 | 540 | |
|
444 | 541 | if super_admin or comment_owner or comment_repo_admin: |
|
445 | 542 | CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user) |
|
446 | 543 | Session().commit() |
|
447 | 544 | return True |
|
448 | 545 | else: |
|
449 | 546 | log.warning('No permissions for user %s to delete comment_id: %s', |
|
450 | 547 | self._rhodecode_db_user, comment_id) |
|
451 | 548 | raise HTTPNotFound() |
|
452 | 549 | |
|
453 | 550 | @LoginRequired() |
|
454 | 551 | @HasRepoPermissionAnyDecorator( |
|
455 | 552 | 'repository.read', 'repository.write', 'repository.admin') |
|
456 | 553 | @view_config( |
|
457 | 554 | route_name='repo_commit_data', request_method='GET', |
|
458 | 555 | renderer='json_ext', xhr=True) |
|
459 | 556 | def repo_commit_data(self): |
|
460 | 557 | commit_id = self.request.matchdict['commit_id'] |
|
461 | 558 | self.load_default_context() |
|
462 | 559 | |
|
463 | 560 | try: |
|
464 | 561 | return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) |
|
465 | 562 | except CommitDoesNotExistError as e: |
|
466 | 563 | return EmptyCommit(message=str(e)) |
|
467 | 564 | |
|
468 | 565 | @LoginRequired() |
|
469 | 566 | @HasRepoPermissionAnyDecorator( |
|
470 | 567 | 'repository.read', 'repository.write', 'repository.admin') |
|
471 | 568 | @view_config( |
|
472 | 569 | route_name='repo_commit_children', request_method='GET', |
|
473 | 570 | renderer='json_ext', xhr=True) |
|
474 | 571 | def repo_commit_children(self): |
|
475 | 572 | commit_id = self.request.matchdict['commit_id'] |
|
476 | 573 | self.load_default_context() |
|
477 | 574 | |
|
478 | 575 | try: |
|
479 | 576 | commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) |
|
480 | 577 | children = commit.children |
|
481 | 578 | except CommitDoesNotExistError: |
|
482 | 579 | children = [] |
|
483 | 580 | |
|
484 | 581 | result = {"results": children} |
|
485 | 582 | return result |
|
486 | 583 | |
|
487 | 584 | @LoginRequired() |
|
488 | 585 | @HasRepoPermissionAnyDecorator( |
|
489 | 586 | 'repository.read', 'repository.write', 'repository.admin') |
|
490 | 587 | @view_config( |
|
491 | 588 | route_name='repo_commit_parents', request_method='GET', |
|
492 | 589 | renderer='json_ext') |
|
493 | 590 | def repo_commit_parents(self): |
|
494 | 591 | commit_id = self.request.matchdict['commit_id'] |
|
495 | 592 | self.load_default_context() |
|
496 | 593 | |
|
497 | 594 | try: |
|
498 | 595 | commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) |
|
499 | 596 | parents = commit.parents |
|
500 | 597 | except CommitDoesNotExistError: |
|
501 | 598 | parents = [] |
|
502 | 599 | result = {"results": parents} |
|
503 | 600 | return result |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,585 +1,611 b'' | |||
|
1 | 1 | // comments.less |
|
2 | 2 | // For use in RhodeCode applications; |
|
3 | 3 | // see style guide documentation for guidelines. |
|
4 | 4 | |
|
5 | 5 | |
|
6 | 6 | // Comments |
|
7 | 7 | @comment-outdated-opacity: 0.6; |
|
8 | 8 | |
|
9 | 9 | .comments { |
|
10 | 10 | width: 100%; |
|
11 | 11 | } |
|
12 | 12 | |
|
13 | 13 | .comments-heading { |
|
14 | 14 | margin-bottom: -1px; |
|
15 | 15 | background: @grey6; |
|
16 | 16 | display: block; |
|
17 | 17 | padding: 10px 0px; |
|
18 | 18 | font-size: 18px |
|
19 | 19 | } |
|
20 | 20 | |
|
21 | 21 | #comment-tr-show { |
|
22 | 22 | padding: 5px 0; |
|
23 | 23 | } |
|
24 | 24 | |
|
25 | 25 | tr.inline-comments div { |
|
26 | 26 | max-width: 100%; |
|
27 | 27 | |
|
28 | 28 | p { |
|
29 | 29 | white-space: normal; |
|
30 | 30 | } |
|
31 | 31 | |
|
32 | 32 | code, pre, .code, dd { |
|
33 | 33 | overflow-x: auto; |
|
34 | 34 | width: 1062px; |
|
35 | 35 | } |
|
36 | 36 | |
|
37 | 37 | dd { |
|
38 | 38 | width: auto; |
|
39 | 39 | } |
|
40 | 40 | } |
|
41 | 41 | |
|
42 | 42 | #injected_page_comments { |
|
43 | 43 | .comment-previous-link, |
|
44 | 44 | .comment-next-link, |
|
45 | 45 | .comment-links-divider { |
|
46 | 46 | display: none; |
|
47 | 47 | } |
|
48 | 48 | } |
|
49 | 49 | |
|
50 | 50 | .add-comment { |
|
51 | 51 | margin-bottom: 10px; |
|
52 | 52 | } |
|
53 | 53 | .hide-comment-button .add-comment { |
|
54 | 54 | display: none; |
|
55 | 55 | } |
|
56 | 56 | |
|
57 | 57 | .comment-bubble { |
|
58 | 58 | color: @grey4; |
|
59 | 59 | margin-top: 4px; |
|
60 | 60 | margin-right: 30px; |
|
61 | 61 | visibility: hidden; |
|
62 | 62 | } |
|
63 | 63 | |
|
64 | 64 | .comment-label { |
|
65 | 65 | float: left; |
|
66 | 66 | |
|
67 | 67 | padding: 0.4em 0.4em; |
|
68 | 68 | margin: 3px 5px 0px -10px; |
|
69 | 69 | display: inline-block; |
|
70 | 70 | min-height: 0; |
|
71 | 71 | |
|
72 | 72 | text-align: center; |
|
73 | 73 | font-size: 10px; |
|
74 | 74 | line-height: .8em; |
|
75 | 75 | |
|
76 | 76 | font-family: @text-italic; |
|
77 | 77 | font-style: italic; |
|
78 | 78 | background: #fff none; |
|
79 | 79 | color: @grey4; |
|
80 | 80 | border: 1px solid @grey4; |
|
81 | 81 | white-space: nowrap; |
|
82 | 82 | |
|
83 | 83 | text-transform: uppercase; |
|
84 | 84 | min-width: 40px; |
|
85 | 85 | |
|
86 | 86 | &.todo { |
|
87 | 87 | color: @color5; |
|
88 | 88 | font-style: italic; |
|
89 | 89 | font-weight: @text-bold-italic-weight; |
|
90 | 90 | font-family: @text-bold-italic; |
|
91 | 91 | } |
|
92 | 92 | |
|
93 | 93 | .resolve { |
|
94 | 94 | cursor: pointer; |
|
95 | 95 | text-decoration: underline; |
|
96 | 96 | } |
|
97 | 97 | |
|
98 | 98 | .resolved { |
|
99 | 99 | text-decoration: line-through; |
|
100 | 100 | color: @color1; |
|
101 | 101 | } |
|
102 | 102 | .resolved a { |
|
103 | 103 | text-decoration: line-through; |
|
104 | 104 | color: @color1; |
|
105 | 105 | } |
|
106 | 106 | .resolve-text { |
|
107 | 107 | color: @color1; |
|
108 | 108 | margin: 2px 8px; |
|
109 | 109 | font-family: @text-italic; |
|
110 | 110 | font-style: italic; |
|
111 | 111 | } |
|
112 | 112 | } |
|
113 | 113 | |
|
114 | 114 | .has-spacer-after { |
|
115 | 115 | &:after { |
|
116 | 116 | content: ' | '; |
|
117 | 117 | color: @grey5; |
|
118 | 118 | } |
|
119 | 119 | } |
|
120 | 120 | |
|
121 | 121 | .has-spacer-before { |
|
122 | 122 | &:before { |
|
123 | 123 | content: ' | '; |
|
124 | 124 | color: @grey5; |
|
125 | 125 | } |
|
126 | 126 | } |
|
127 | 127 | |
|
128 | 128 | .comment { |
|
129 | 129 | |
|
130 | 130 | &.comment-general { |
|
131 | 131 | border: 1px solid @grey5; |
|
132 | 132 | padding: 5px 5px 5px 5px; |
|
133 | 133 | } |
|
134 | 134 | |
|
135 | 135 | margin: @padding 0; |
|
136 | 136 | padding: 4px 0 0 0; |
|
137 | 137 | line-height: 1em; |
|
138 | 138 | |
|
139 | 139 | .rc-user { |
|
140 | 140 | min-width: 0; |
|
141 | 141 | margin: 0px .5em 0 0; |
|
142 | 142 | |
|
143 | 143 | .user { |
|
144 | 144 | display: inline; |
|
145 | 145 | } |
|
146 | 146 | } |
|
147 | 147 | |
|
148 | 148 | .meta { |
|
149 | 149 | position: relative; |
|
150 | 150 | width: 100%; |
|
151 | 151 | border-bottom: 1px solid @grey5; |
|
152 | 152 | margin: -5px 0px; |
|
153 | 153 | line-height: 24px; |
|
154 | 154 | |
|
155 | 155 | &:hover .permalink { |
|
156 | 156 | visibility: visible; |
|
157 | 157 | color: @rcblue; |
|
158 | 158 | } |
|
159 | 159 | } |
|
160 | 160 | |
|
161 | 161 | .author, |
|
162 | 162 | .date { |
|
163 | 163 | display: inline; |
|
164 | 164 | |
|
165 | 165 | &:after { |
|
166 | 166 | content: ' | '; |
|
167 | 167 | color: @grey5; |
|
168 | 168 | } |
|
169 | 169 | } |
|
170 | 170 | |
|
171 | 171 | .author-general img { |
|
172 | 172 | top: 3px; |
|
173 | 173 | } |
|
174 | 174 | .author-inline img { |
|
175 | 175 | top: 3px; |
|
176 | 176 | } |
|
177 | 177 | |
|
178 | 178 | .status-change, |
|
179 | 179 | .permalink, |
|
180 | 180 | .changeset-status-lbl { |
|
181 | 181 | display: inline; |
|
182 | 182 | } |
|
183 | 183 | |
|
184 | 184 | .permalink { |
|
185 | 185 | visibility: hidden; |
|
186 | 186 | } |
|
187 | 187 | |
|
188 | 188 | .comment-links-divider { |
|
189 | 189 | display: inline; |
|
190 | 190 | } |
|
191 | 191 | |
|
192 | 192 | .comment-links-block { |
|
193 | 193 | float:right; |
|
194 | 194 | text-align: right; |
|
195 | 195 | min-width: 85px; |
|
196 | 196 | |
|
197 | 197 | [class^="icon-"]:before, |
|
198 | 198 | [class*=" icon-"]:before { |
|
199 | 199 | margin-left: 0; |
|
200 | 200 | margin-right: 0; |
|
201 | 201 | } |
|
202 | 202 | } |
|
203 | 203 | |
|
204 | 204 | .comment-previous-link { |
|
205 | 205 | display: inline-block; |
|
206 | 206 | |
|
207 | 207 | .arrow_comment_link{ |
|
208 | 208 | cursor: pointer; |
|
209 | 209 | i { |
|
210 | 210 | font-size:10px; |
|
211 | 211 | } |
|
212 | 212 | } |
|
213 | 213 | .arrow_comment_link.disabled { |
|
214 | 214 | cursor: default; |
|
215 | 215 | color: @grey5; |
|
216 | 216 | } |
|
217 | 217 | } |
|
218 | 218 | |
|
219 | 219 | .comment-next-link { |
|
220 | 220 | display: inline-block; |
|
221 | 221 | |
|
222 | 222 | .arrow_comment_link{ |
|
223 | 223 | cursor: pointer; |
|
224 | 224 | i { |
|
225 | 225 | font-size:10px; |
|
226 | 226 | } |
|
227 | 227 | } |
|
228 | 228 | .arrow_comment_link.disabled { |
|
229 | 229 | cursor: default; |
|
230 | 230 | color: @grey5; |
|
231 | 231 | } |
|
232 | 232 | } |
|
233 | 233 | |
|
234 | 234 | .delete-comment { |
|
235 | 235 | display: inline-block; |
|
236 | 236 | color: @rcblue; |
|
237 | 237 | |
|
238 | 238 | &:hover { |
|
239 | 239 | cursor: pointer; |
|
240 | 240 | } |
|
241 | 241 | } |
|
242 | 242 | |
|
243 | 243 | .text { |
|
244 | 244 | clear: both; |
|
245 | 245 | .border-radius(@border-radius); |
|
246 | 246 | .box-sizing(border-box); |
|
247 | 247 | |
|
248 | 248 | .markdown-block p, |
|
249 | 249 | .rst-block p { |
|
250 | 250 | margin: .5em 0 !important; |
|
251 | 251 | // TODO: lisa: This is needed because of other rst !important rules :[ |
|
252 | 252 | } |
|
253 | 253 | } |
|
254 | 254 | |
|
255 | 255 | .pr-version { |
|
256 | 256 | float: left; |
|
257 | 257 | margin: 0px 4px; |
|
258 | 258 | } |
|
259 | 259 | .pr-version-inline { |
|
260 | 260 | float: left; |
|
261 | 261 | margin: 0px 4px; |
|
262 | 262 | } |
|
263 | 263 | .pr-version-num { |
|
264 | 264 | font-size: 10px; |
|
265 | 265 | } |
|
266 | 266 | } |
|
267 | 267 | |
|
268 | 268 | @comment-padding: 5px; |
|
269 | 269 | |
|
270 | 270 | .general-comments { |
|
271 | 271 | .comment-outdated { |
|
272 | 272 | opacity: @comment-outdated-opacity; |
|
273 | 273 | } |
|
274 | 274 | } |
|
275 | 275 | |
|
276 | 276 | .inline-comments { |
|
277 | 277 | border-radius: @border-radius; |
|
278 | 278 | .comment { |
|
279 | 279 | margin: 0; |
|
280 | 280 | border-radius: @border-radius; |
|
281 | 281 | } |
|
282 | 282 | .comment-outdated { |
|
283 | 283 | opacity: @comment-outdated-opacity; |
|
284 | 284 | } |
|
285 | 285 | |
|
286 | 286 | .comment-inline { |
|
287 | 287 | background: white; |
|
288 | 288 | padding: @comment-padding @comment-padding; |
|
289 | 289 | border: @comment-padding solid @grey6; |
|
290 | 290 | |
|
291 | 291 | .text { |
|
292 | 292 | border: none; |
|
293 | 293 | } |
|
294 | 294 | .meta { |
|
295 | 295 | border-bottom: 1px solid @grey6; |
|
296 | 296 | margin: -5px 0px; |
|
297 | 297 | line-height: 24px; |
|
298 | 298 | } |
|
299 | 299 | } |
|
300 | 300 | .comment-selected { |
|
301 | 301 | border-left: 6px solid @comment-highlight-color; |
|
302 | 302 | } |
|
303 | 303 | .comment-inline-form { |
|
304 | 304 | padding: @comment-padding; |
|
305 | 305 | display: none; |
|
306 | 306 | } |
|
307 | 307 | .cb-comment-add-button { |
|
308 | 308 | margin: @comment-padding; |
|
309 | 309 | } |
|
310 | 310 | /* hide add comment button when form is open */ |
|
311 | 311 | .comment-inline-form-open ~ .cb-comment-add-button { |
|
312 | 312 | display: none; |
|
313 | 313 | } |
|
314 | 314 | .comment-inline-form-open { |
|
315 | 315 | display: block; |
|
316 | 316 | } |
|
317 | 317 | /* hide add comment button when form but no comments */ |
|
318 | 318 | .comment-inline-form:first-child + .cb-comment-add-button { |
|
319 | 319 | display: none; |
|
320 | 320 | } |
|
321 | 321 | /* hide add comment button when no comments or form */ |
|
322 | 322 | .cb-comment-add-button:first-child { |
|
323 | 323 | display: none; |
|
324 | 324 | } |
|
325 | 325 | /* hide add comment button when only comment is being deleted */ |
|
326 | 326 | .comment-deleting:first-child + .cb-comment-add-button { |
|
327 | 327 | display: none; |
|
328 | 328 | } |
|
329 | 329 | } |
|
330 | 330 | |
|
331 | 331 | |
|
332 | 332 | .show-outdated-comments { |
|
333 | 333 | display: inline; |
|
334 | 334 | color: @rcblue; |
|
335 | 335 | } |
|
336 | 336 | |
|
337 | 337 | // Comment Form |
|
338 | 338 | div.comment-form { |
|
339 | 339 | margin-top: 20px; |
|
340 | 340 | } |
|
341 | 341 | |
|
342 | 342 | .comment-form strong { |
|
343 | 343 | display: block; |
|
344 | 344 | margin-bottom: 15px; |
|
345 | 345 | } |
|
346 | 346 | |
|
347 | 347 | .comment-form textarea { |
|
348 | 348 | width: 100%; |
|
349 | 349 | height: 100px; |
|
350 | 350 | font-family: @text-monospace; |
|
351 | 351 | } |
|
352 | 352 | |
|
353 | 353 | form.comment-form { |
|
354 | 354 | margin-top: 10px; |
|
355 | 355 | margin-left: 10px; |
|
356 | 356 | } |
|
357 | 357 | |
|
358 | 358 | .comment-inline-form .comment-block-ta, |
|
359 | 359 | .comment-form .comment-block-ta, |
|
360 | 360 | .comment-form .preview-box { |
|
361 | 361 | .border-radius(@border-radius); |
|
362 | 362 | .box-sizing(border-box); |
|
363 | 363 | background-color: white; |
|
364 | 364 | } |
|
365 | 365 | |
|
366 | 366 | .comment-form-submit { |
|
367 | 367 | margin-top: 5px; |
|
368 | 368 | margin-left: 525px; |
|
369 | 369 | } |
|
370 | 370 | |
|
371 | 371 | .file-comments { |
|
372 | 372 | display: none; |
|
373 | 373 | } |
|
374 | 374 | |
|
375 | 375 | .comment-form .preview-box.unloaded, |
|
376 | 376 | .comment-inline-form .preview-box.unloaded { |
|
377 | 377 | height: 50px; |
|
378 | 378 | text-align: center; |
|
379 | 379 | padding: 20px; |
|
380 | 380 | background-color: white; |
|
381 | 381 | } |
|
382 | 382 | |
|
383 | 383 | .comment-footer { |
|
384 | 384 | position: relative; |
|
385 | 385 | width: 100%; |
|
386 | 386 | min-height: 42px; |
|
387 | 387 | |
|
388 | 388 | .status_box, |
|
389 | 389 | .cancel-button { |
|
390 | 390 | float: left; |
|
391 | 391 | display: inline-block; |
|
392 | 392 | } |
|
393 | 393 | |
|
394 | 394 | .action-buttons { |
|
395 | 395 | float: right; |
|
396 | 396 | display: inline-block; |
|
397 | 397 | } |
|
398 | 398 | |
|
399 | 399 | .action-buttons-extra { |
|
400 | 400 | display: inline-block; |
|
401 | 401 | } |
|
402 | 402 | } |
|
403 | 403 | |
|
404 | 404 | .comment-form { |
|
405 | 405 | |
|
406 | 406 | .comment { |
|
407 | 407 | margin-left: 10px; |
|
408 | 408 | } |
|
409 | 409 | |
|
410 | 410 | .comment-help { |
|
411 | 411 | color: @grey4; |
|
412 | 412 | padding: 5px 0 5px 0; |
|
413 | 413 | } |
|
414 | 414 | |
|
415 | 415 | .comment-title { |
|
416 | 416 | padding: 5px 0 5px 0; |
|
417 | 417 | } |
|
418 | 418 | |
|
419 | 419 | .comment-button { |
|
420 | 420 | display: inline-block; |
|
421 | 421 | } |
|
422 | 422 | |
|
423 | 423 | .comment-button-input { |
|
424 | 424 | margin-right: 0; |
|
425 | 425 | } |
|
426 | 426 | |
|
427 | 427 | .comment-footer { |
|
428 | 428 | margin-bottom: 110px; |
|
429 | 429 | margin-top: 10px; |
|
430 | 430 | } |
|
431 | 431 | } |
|
432 | 432 | |
|
433 | 433 | |
|
434 | 434 | .comment-form-login { |
|
435 | 435 | .comment-help { |
|
436 | 436 | padding: 0.7em; //same as the button |
|
437 | 437 | } |
|
438 | 438 | |
|
439 | 439 | div.clearfix { |
|
440 | 440 | clear: both; |
|
441 | 441 | width: 100%; |
|
442 | 442 | display: block; |
|
443 | 443 | } |
|
444 | 444 | } |
|
445 | 445 | |
|
446 | 446 | .comment-type { |
|
447 | 447 | margin: 0px; |
|
448 | 448 | border-radius: inherit; |
|
449 | 449 | border-color: @grey6; |
|
450 | 450 | } |
|
451 | 451 | |
|
452 | 452 | .preview-box { |
|
453 | 453 | min-height: 105px; |
|
454 | 454 | margin-bottom: 15px; |
|
455 | 455 | background-color: white; |
|
456 | 456 | .border-radius(@border-radius); |
|
457 | 457 | .box-sizing(border-box); |
|
458 | 458 | } |
|
459 | 459 | |
|
460 | 460 | .add-another-button { |
|
461 | 461 | margin-left: 10px; |
|
462 | 462 | margin-top: 10px; |
|
463 | 463 | margin-bottom: 10px; |
|
464 | 464 | } |
|
465 | 465 | |
|
466 | 466 | .comment .buttons { |
|
467 | 467 | float: right; |
|
468 | 468 | margin: -1px 0px 0px 0px; |
|
469 | 469 | } |
|
470 | 470 | |
|
471 | 471 | // Inline Comment Form |
|
472 | 472 | .injected_diff .comment-inline-form, |
|
473 | 473 | .comment-inline-form { |
|
474 | 474 | background-color: white; |
|
475 | 475 | margin-top: 10px; |
|
476 | 476 | margin-bottom: 20px; |
|
477 | 477 | } |
|
478 | 478 | |
|
479 | 479 | .inline-form { |
|
480 | 480 | padding: 10px 7px; |
|
481 | 481 | } |
|
482 | 482 | |
|
483 | 483 | .inline-form div { |
|
484 | 484 | max-width: 100%; |
|
485 | 485 | } |
|
486 | 486 | |
|
487 | 487 | .overlay { |
|
488 | 488 | display: none; |
|
489 | 489 | position: absolute; |
|
490 | 490 | width: 100%; |
|
491 | 491 | text-align: center; |
|
492 | 492 | vertical-align: middle; |
|
493 | 493 | font-size: 16px; |
|
494 | 494 | background: none repeat scroll 0 0 white; |
|
495 | 495 | |
|
496 | 496 | &.submitting { |
|
497 | 497 | display: block; |
|
498 | 498 | opacity: 0.5; |
|
499 | 499 | z-index: 100; |
|
500 | 500 | } |
|
501 | 501 | } |
|
502 | 502 | .comment-inline-form .overlay.submitting .overlay-text { |
|
503 | 503 | margin-top: 5%; |
|
504 | 504 | } |
|
505 | 505 | |
|
506 | 506 | .comment-inline-form .clearfix, |
|
507 | 507 | .comment-form .clearfix { |
|
508 | 508 | .border-radius(@border-radius); |
|
509 | 509 | margin: 0px; |
|
510 | 510 | } |
|
511 | 511 | |
|
512 | 512 | .comment-inline-form .comment-footer { |
|
513 | 513 | margin: 10px 0px 0px 0px; |
|
514 | 514 | } |
|
515 | 515 | |
|
516 | 516 | .hide-inline-form-button { |
|
517 | 517 | margin-left: 5px; |
|
518 | 518 | } |
|
519 | 519 | .comment-button .hide-inline-form { |
|
520 | 520 | background: white; |
|
521 | 521 | } |
|
522 | 522 | |
|
523 | 523 | .comment-area { |
|
524 | 524 | padding: 8px 12px; |
|
525 | 525 | border: 1px solid @grey5; |
|
526 | 526 | .border-radius(@border-radius); |
|
527 | 527 | |
|
528 | 528 | .resolve-action { |
|
529 | 529 | padding: 1px 0px 0px 6px; |
|
530 | 530 | } |
|
531 | 531 | |
|
532 | 532 | } |
|
533 | 533 | |
|
534 | 534 | .comment-area-header .nav-links { |
|
535 | 535 | display: flex; |
|
536 | 536 | flex-flow: row wrap; |
|
537 | 537 | -webkit-flex-flow: row wrap; |
|
538 | 538 | width: 100%; |
|
539 | 539 | } |
|
540 | 540 | |
|
541 | 541 | .comment-area-footer { |
|
542 | display: flex; | |
|
542 | min-height: 30px; | |
|
543 | 543 | } |
|
544 | 544 | |
|
545 | 545 | .comment-footer .toolbar { |
|
546 | 546 | |
|
547 | 547 | } |
|
548 | 548 | |
|
549 | .comment-attachment-uploader { | |
|
550 | border: 1px dashed white; | |
|
551 | border-radius: @border-radius; | |
|
552 | margin-top: -10px; | |
|
553 | ||
|
554 | &.dz-drag-hover { | |
|
555 | border-color: @grey3; | |
|
556 | } | |
|
557 | ||
|
558 | .dz-error-message { | |
|
559 | padding-top: 0; | |
|
560 | } | |
|
561 | } | |
|
562 | ||
|
563 | .comment-attachment-text { | |
|
564 | clear: both; | |
|
565 | font-size: 11px; | |
|
566 | color: #8F8F8F; | |
|
567 | width: 100%; | |
|
568 | .pick-attachment { | |
|
569 | color: #8F8F8F; | |
|
570 | } | |
|
571 | .pick-attachment:hover { | |
|
572 | color: @rcblue; | |
|
573 | } | |
|
574 | } | |
|
575 | ||
|
549 | 576 | .nav-links { |
|
550 | 577 | padding: 0; |
|
551 | 578 | margin: 0; |
|
552 | 579 | list-style: none; |
|
553 | 580 | height: auto; |
|
554 | 581 | border-bottom: 1px solid @grey5; |
|
555 | 582 | } |
|
556 | 583 | .nav-links li { |
|
557 | 584 | display: inline-block; |
|
558 | 585 | list-style-type: none; |
|
559 | 586 | } |
|
560 | 587 | |
|
561 | 588 | .nav-links li a.disabled { |
|
562 | 589 | cursor: not-allowed; |
|
563 | 590 | } |
|
564 | 591 | |
|
565 | 592 | .nav-links li.active a { |
|
566 | 593 | border-bottom: 2px solid @rcblue; |
|
567 | 594 | color: #000; |
|
568 | 595 | font-weight: 600; |
|
569 | 596 | } |
|
570 | 597 | .nav-links li a { |
|
571 | 598 | display: inline-block; |
|
572 | 599 | padding: 0px 10px 5px 10px; |
|
573 | 600 | margin-bottom: -1px; |
|
574 | 601 | font-size: 14px; |
|
575 | 602 | line-height: 28px; |
|
576 | 603 | color: #8f8f8f; |
|
577 | 604 | border-bottom: 2px solid transparent; |
|
578 | 605 | } |
|
579 | 606 | |
|
580 | 607 | .toolbar-text { |
|
581 | 608 | float: left; |
|
582 | margin: -5px 0px 0px 0px; | |
|
583 | 609 | font-size: 12px; |
|
584 | 610 | } |
|
585 | 611 |
@@ -1,397 +1,398 b'' | |||
|
1 | 1 | |
|
2 | 2 | /** MODAL **/ |
|
3 | 3 | .modal-open { |
|
4 | 4 | overflow:hidden; |
|
5 | 5 | } |
|
6 | 6 | body.modal-open, .modal-open .navbar-fixed-top, .modal-open .navbar-fixed-bottom { |
|
7 | 7 | margin-right:15px; |
|
8 | 8 | } |
|
9 | 9 | .modal { |
|
10 | 10 | position:fixed; |
|
11 | 11 | top:0; |
|
12 | 12 | right:0; |
|
13 | 13 | bottom:0; |
|
14 | 14 | left:0; |
|
15 | 15 | z-index:1040; |
|
16 | 16 | display:none; |
|
17 | 17 | overflow-y:scroll; |
|
18 | 18 | &.fade .modal-dialog { |
|
19 | 19 | -webkit-transform:translate(0,-25%); |
|
20 | 20 | -ms-transform:translate(0,-25%); |
|
21 | 21 | transform:translate(0,-25%); |
|
22 | 22 | -webkit-transition:-webkit-transform 0.3s ease-out; |
|
23 | 23 | -moz-transition:-moz-transform 0.3s ease-out; |
|
24 | 24 | -o-transition:-o-transform 0.3s ease-out; |
|
25 | 25 | transition:transform 0.3s ease-out; |
|
26 | 26 | } |
|
27 | 27 | &.in .modal-dialog { |
|
28 | 28 | -webkit-transform:translate(0,0); |
|
29 | 29 | -ms-transform:translate(0,0); |
|
30 | 30 | transform:translate(0,0); |
|
31 | 31 | } |
|
32 | 32 | } |
|
33 | 33 | .modal-dialog { |
|
34 | 34 | z-index:1050; |
|
35 | 35 | width:auto; |
|
36 | 36 | padding:10px; |
|
37 | 37 | margin-right:auto; |
|
38 | 38 | margin-left:auto; |
|
39 | 39 | } |
|
40 | 40 | .modal-content { |
|
41 | 41 | position:relative; |
|
42 | 42 | background-color:#ffffff; |
|
43 | 43 | border: @border-thickness solid rgba(0,0,0,0.2); |
|
44 | 44 | .border-radius(@border-radius); |
|
45 | 45 | outline:none; |
|
46 | 46 | -webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5); |
|
47 | 47 | box-shadow:0 3px 9px rgba(0,0,0,0.5); |
|
48 | 48 | background-clip:padding-box; |
|
49 | 49 | } |
|
50 | 50 | .modal-backdrop { |
|
51 | 51 | position:fixed; |
|
52 | 52 | top:0; |
|
53 | 53 | right:0; |
|
54 | 54 | bottom:0; |
|
55 | 55 | left:0; |
|
56 | 56 | z-index:1030; |
|
57 | 57 | background-color:#000000; |
|
58 | 58 | |
|
59 | 59 | &.modal-backdrop.fade { |
|
60 | 60 | opacity:0; |
|
61 | 61 | filter:alpha(opacity=0); |
|
62 | 62 | } |
|
63 | 63 | &.in { |
|
64 | 64 | opacity:0.5; |
|
65 | 65 | filter:alpha(opacity=50); |
|
66 | 66 | } |
|
67 | 67 | } |
|
68 | 68 | .modal-header { |
|
69 | 69 | min-height:16.428571429px; |
|
70 | 70 | padding:15px; |
|
71 | 71 | border-bottom: @border-thickness solid @grey6; |
|
72 | 72 | .close { |
|
73 | 73 | margin-top:-2px; |
|
74 | 74 | } |
|
75 | 75 | } |
|
76 | 76 | .modal-title { |
|
77 | 77 | margin:0; |
|
78 | 78 | line-height:1.428571429; |
|
79 | 79 | } |
|
80 | 80 | .modal-body { |
|
81 | 81 | position:relative; |
|
82 | 82 | padding:20px; |
|
83 | 83 | } |
|
84 | 84 | .modal-footer { |
|
85 | 85 | padding:19px 20px 20px; |
|
86 | 86 | margin-top:15px; |
|
87 | 87 | text-align:right; |
|
88 | 88 | border-top:1px solid #e5e5e5; |
|
89 | 89 | .btn + .btn { |
|
90 | 90 | margin-bottom:0; |
|
91 | 91 | margin-left:5px; |
|
92 | 92 | } |
|
93 | 93 | .btn-group .btn + .btn { |
|
94 | 94 | margin-left:-1px; |
|
95 | 95 | } |
|
96 | 96 | .btn-block + .btn-block { |
|
97 | 97 | margin-left:0; |
|
98 | 98 | } |
|
99 | 99 | &:before { |
|
100 | 100 | display:table; |
|
101 | 101 | content:" "; |
|
102 | 102 | } |
|
103 | 103 | &:after { |
|
104 | 104 | display:table; |
|
105 | 105 | content:" "; |
|
106 | 106 | clear:both; |
|
107 | 107 | } |
|
108 | 108 | } |
|
109 | 109 | |
|
110 | 110 | /** MARKDOWN styling **/ |
|
111 | 111 | div.markdown-block { |
|
112 | 112 | clear: both; |
|
113 | 113 | overflow: hidden; |
|
114 | 114 | margin: 0; |
|
115 | 115 | padding: 3px 15px 3px; |
|
116 | 116 | } |
|
117 | 117 | |
|
118 | 118 | div.markdown-block h1, |
|
119 | 119 | div.markdown-block h2, |
|
120 | 120 | div.markdown-block h3, |
|
121 | 121 | div.markdown-block h4, |
|
122 | 122 | div.markdown-block h5, |
|
123 | 123 | div.markdown-block h6 { |
|
124 | 124 | border-bottom: none !important; |
|
125 | 125 | padding: 0 !important; |
|
126 | 126 | overflow: visible !important; |
|
127 | 127 | } |
|
128 | 128 | |
|
129 | 129 | div.markdown-block h1, |
|
130 | 130 | div.markdown-block h2 { |
|
131 | 131 | border-bottom: 1px #e6e5e5 solid !important; |
|
132 | 132 | } |
|
133 | 133 | |
|
134 | 134 | div.markdown-block h1 { |
|
135 | 135 | font-size: 32px; |
|
136 | 136 | margin: 15px 0 15px 0 !important; |
|
137 | 137 | padding-bottom: 5px !important; |
|
138 | 138 | } |
|
139 | 139 | |
|
140 | 140 | div.markdown-block h2 { |
|
141 | 141 | font-size: 24px !important; |
|
142 | 142 | margin: 34px 0 10px 0 !important; |
|
143 | 143 | padding-top: 15px !important; |
|
144 | 144 | padding-bottom: 8px !important; |
|
145 | 145 | } |
|
146 | 146 | |
|
147 | 147 | div.markdown-block h3 { |
|
148 | 148 | font-size: 18px !important; |
|
149 | 149 | margin: 30px 0 8px 0 !important; |
|
150 | 150 | padding-bottom: 2px !important; |
|
151 | 151 | } |
|
152 | 152 | |
|
153 | 153 | div.markdown-block h4 { |
|
154 | 154 | font-size: 13px !important; |
|
155 | 155 | margin: 18px 0 3px 0 !important; |
|
156 | 156 | } |
|
157 | 157 | |
|
158 | 158 | div.markdown-block h5 { |
|
159 | 159 | font-size: 12px !important; |
|
160 | 160 | margin: 15px 0 3px 0 !important; |
|
161 | 161 | } |
|
162 | 162 | |
|
163 | 163 | div.markdown-block h6 { |
|
164 | 164 | font-size: 12px; |
|
165 | 165 | color: #777777; |
|
166 | 166 | margin: 15px 0 3px 0 !important; |
|
167 | 167 | } |
|
168 | 168 | |
|
169 | 169 | div.markdown-block hr { |
|
170 | 170 | border: 0; |
|
171 | 171 | color: #e6e5e5; |
|
172 | 172 | background-color: #e6e5e5; |
|
173 | 173 | height: 3px; |
|
174 | 174 | margin-bottom: 13px; |
|
175 | 175 | } |
|
176 | 176 | |
|
177 | 177 | div.markdown-block ol, |
|
178 | 178 | div.markdown-block ul, |
|
179 | 179 | div.markdown-block p, |
|
180 | 180 | div.markdown-block blockquote, |
|
181 | 181 | div.markdown-block dl, |
|
182 | 182 | div.markdown-block li, |
|
183 | 183 | div.markdown-block table { |
|
184 | 184 | margin: 3px 0px 13px 0px !important; |
|
185 | 185 | color: #424242 !important; |
|
186 | 186 | font-size: 13px !important; |
|
187 | 187 | font-family: @text-regular; |
|
188 | 188 | font-weight: normal !important; |
|
189 | 189 | overflow: visible !important; |
|
190 | 190 | line-height: 140% !important; |
|
191 | 191 | } |
|
192 | 192 | |
|
193 | 193 | div.markdown-block pre { |
|
194 | 194 | margin: 3px 0px 13px 0px !important; |
|
195 | 195 | padding: .5em; |
|
196 | 196 | color: #424242 !important; |
|
197 | 197 | font-size: 13px !important; |
|
198 | 198 | overflow: visible !important; |
|
199 | 199 | line-height: 140% !important; |
|
200 | 200 | background-color: @grey7; |
|
201 | 201 | } |
|
202 | 202 | |
|
203 | 203 | div.markdown-block img { |
|
204 | 204 | border-style: none; |
|
205 | 205 | background-color: #fff; |
|
206 | 206 | padding-right: 20px; |
|
207 | max-width: 100%; | |
|
207 | 208 | } |
|
208 | 209 | |
|
209 | 210 | |
|
210 | 211 | div.markdown-block strong { |
|
211 | 212 | font-weight: 600; |
|
212 | 213 | margin: 0; |
|
213 | 214 | } |
|
214 | 215 | |
|
215 | 216 | div.markdown-block ul.checkbox, |
|
216 | 217 | div.markdown-block ol.checkbox { |
|
217 | 218 | padding-left: 20px !important; |
|
218 | 219 | margin-top: 0px !important; |
|
219 | 220 | margin-bottom: 18px !important; |
|
220 | 221 | } |
|
221 | 222 | |
|
222 | 223 | div.markdown-block ul, |
|
223 | 224 | div.markdown-block ol { |
|
224 | 225 | padding-left: 30px !important; |
|
225 | 226 | margin-top: 0px !important; |
|
226 | 227 | margin-bottom: 18px !important; |
|
227 | 228 | } |
|
228 | 229 | |
|
229 | 230 | div.markdown-block ul.checkbox li, |
|
230 | 231 | div.markdown-block ol.checkbox li { |
|
231 | 232 | list-style: none !important; |
|
232 | 233 | margin: 6px !important; |
|
233 | 234 | padding: 0 !important; |
|
234 | 235 | } |
|
235 | 236 | |
|
236 | 237 | div.markdown-block ul li, |
|
237 | 238 | div.markdown-block ol li { |
|
238 | 239 | list-style: disc !important; |
|
239 | 240 | margin: 6px !important; |
|
240 | 241 | padding: 0 !important; |
|
241 | 242 | } |
|
242 | 243 | |
|
243 | 244 | div.markdown-block ol li { |
|
244 | 245 | list-style: decimal !important; |
|
245 | 246 | } |
|
246 | 247 | |
|
247 | 248 | /* |
|
248 | 249 | div.markdown-block a, |
|
249 | 250 | div.markdown-block a:visited { |
|
250 | 251 | color: #4183C4 !important; |
|
251 | 252 | background-color: inherit; |
|
252 | 253 | text-decoration: none; |
|
253 | 254 | } |
|
254 | 255 | */ |
|
255 | 256 | |
|
256 | 257 | div.markdown-block #message { |
|
257 | 258 | .border-radius(@border-radius); |
|
258 | 259 | border: @border-thickness solid @grey5; |
|
259 | 260 | display: block; |
|
260 | 261 | width: 100%; |
|
261 | 262 | height: 60px; |
|
262 | 263 | margin: 6px 0px; |
|
263 | 264 | } |
|
264 | 265 | |
|
265 | 266 | div.markdown-block button, |
|
266 | 267 | div.markdown-block #ws { |
|
267 | 268 | font-size: @basefontsize; |
|
268 | 269 | padding: 4px 6px; |
|
269 | 270 | .border-radius(@border-radius); |
|
270 | 271 | border: @border-thickness solid @grey5; |
|
271 | 272 | background-color: @grey6; |
|
272 | 273 | } |
|
273 | 274 | |
|
274 | 275 | div.markdown-block code, |
|
275 | 276 | div.markdown-block pre, |
|
276 | 277 | div.markdown-block #ws, |
|
277 | 278 | div.markdown-block #message { |
|
278 | 279 | font-family: @text-monospace; |
|
279 | 280 | font-size: 11px; |
|
280 | 281 | .border-radius(@border-radius); |
|
281 | 282 | background-color: white; |
|
282 | 283 | color: @grey3; |
|
283 | 284 | } |
|
284 | 285 | |
|
285 | 286 | |
|
286 | 287 | div.markdown-block code { |
|
287 | 288 | border: @border-thickness solid @grey6; |
|
288 | 289 | margin: 0 2px; |
|
289 | 290 | padding: 0 5px; |
|
290 | 291 | } |
|
291 | 292 | |
|
292 | 293 | div.markdown-block pre { |
|
293 | 294 | border: @border-thickness solid @grey5; |
|
294 | 295 | overflow: auto; |
|
295 | 296 | padding: .5em; |
|
296 | 297 | background-color: @grey7; |
|
297 | 298 | } |
|
298 | 299 | |
|
299 | 300 | div.markdown-block pre > code { |
|
300 | 301 | border: 0; |
|
301 | 302 | margin: 0; |
|
302 | 303 | padding: 0; |
|
303 | 304 | } |
|
304 | 305 | |
|
305 | 306 | /** RST STYLE **/ |
|
306 | 307 | div.rst-block { |
|
307 | 308 | clear: both; |
|
308 | 309 | overflow: hidden; |
|
309 | 310 | margin: 0; |
|
310 | 311 | padding: 3px 15px 3px; |
|
311 | 312 | } |
|
312 | 313 | |
|
313 | 314 | div.rst-block h2 { |
|
314 | 315 | font-weight: normal; |
|
315 | 316 | } |
|
316 | 317 | |
|
317 | 318 | div.rst-block h1, |
|
318 | 319 | div.rst-block h2, |
|
319 | 320 | div.rst-block h3, |
|
320 | 321 | div.rst-block h4, |
|
321 | 322 | div.rst-block h5, |
|
322 | 323 | div.rst-block h6 { |
|
323 | 324 | border-bottom: 0 !important; |
|
324 | 325 | margin: 0 !important; |
|
325 | 326 | padding: 0 !important; |
|
326 | 327 | line-height: 1.5em !important; |
|
327 | 328 | } |
|
328 | 329 | |
|
329 | 330 | |
|
330 | 331 | div.rst-block h1:first-child { |
|
331 | 332 | padding-top: .25em !important; |
|
332 | 333 | } |
|
333 | 334 | |
|
334 | 335 | div.rst-block h2, |
|
335 | 336 | div.rst-block h3 { |
|
336 | 337 | margin: 1em 0 !important; |
|
337 | 338 | } |
|
338 | 339 | |
|
339 | 340 | div.rst-block h1, |
|
340 | 341 | div.rst-block h2 { |
|
341 | 342 | border-bottom: 1px #e6e5e5 solid !important; |
|
342 | 343 | } |
|
343 | 344 | |
|
344 | 345 | div.rst-block h2 { |
|
345 | 346 | margin-top: 1.5em !important; |
|
346 | 347 | padding-top: .5em !important; |
|
347 | 348 | } |
|
348 | 349 | |
|
349 | 350 | div.rst-block p { |
|
350 | 351 | color: black !important; |
|
351 | 352 | margin: 1em 0 !important; |
|
352 | 353 | line-height: 1.5em !important; |
|
353 | 354 | } |
|
354 | 355 | |
|
355 | 356 | div.rst-block ul { |
|
356 | 357 | list-style: disc !important; |
|
357 | 358 | margin: 1em 0 1em 2em !important; |
|
358 | 359 | clear: both; |
|
359 | 360 | } |
|
360 | 361 | |
|
361 | 362 | div.rst-block ol { |
|
362 | 363 | list-style: decimal; |
|
363 | 364 | margin: 1em 0 1em 2em !important; |
|
364 | 365 | } |
|
365 | 366 | |
|
366 | 367 | div.rst-block pre, |
|
367 | 368 | div.rst-block code { |
|
368 | 369 | font: 12px "Bitstream Vera Sans Mono","Courier",monospace; |
|
369 | 370 | } |
|
370 | 371 | |
|
371 | 372 | div.rst-block code { |
|
372 | 373 | font-size: 12px !important; |
|
373 | 374 | background-color: ghostWhite !important; |
|
374 | 375 | color: #444 !important; |
|
375 | 376 | padding: 0 .2em !important; |
|
376 | 377 | border: 1px solid #dedede !important; |
|
377 | 378 | } |
|
378 | 379 | |
|
379 | 380 | div.rst-block pre code { |
|
380 | 381 | padding: 0 !important; |
|
381 | 382 | font-size: 12px !important; |
|
382 | 383 | background-color: #eee !important; |
|
383 | 384 | border: none !important; |
|
384 | 385 | } |
|
385 | 386 | |
|
386 | 387 | div.rst-block pre { |
|
387 | 388 | margin: 1em 0; |
|
388 | 389 | padding: @padding; |
|
389 | 390 | border: 1px solid @grey6; |
|
390 | 391 | .border-radius(@border-radius); |
|
391 | 392 | overflow: auto; |
|
392 | 393 | font-size: 12px; |
|
393 | 394 | color: #444; |
|
394 | 395 | background-color: @grey7; |
|
395 | 396 | } |
|
396 | 397 | |
|
397 | 398 |
@@ -1,510 +1,514 b'' | |||
|
1 | 1 | |
|
2 | 2 | // tables.less |
|
3 | 3 | // For use in RhodeCode application tables; |
|
4 | 4 | // see style guide documentation for guidelines. |
|
5 | 5 | |
|
6 | 6 | // TABLES |
|
7 | 7 | |
|
8 | 8 | .rctable, |
|
9 | 9 | table.rctable, |
|
10 | 10 | table.dataTable { |
|
11 | 11 | clear:both; |
|
12 | 12 | width: 100%; |
|
13 | 13 | margin: 0 auto @padding; |
|
14 | 14 | padding: 0; |
|
15 | 15 | vertical-align: baseline; |
|
16 | 16 | line-height:1.5em; |
|
17 | 17 | border: none; |
|
18 | 18 | outline: none; |
|
19 | 19 | border-collapse: collapse; |
|
20 | 20 | border-spacing: 0; |
|
21 | 21 | color: @grey2; |
|
22 | 22 | |
|
23 | 23 | b { |
|
24 | 24 | font-weight: normal; |
|
25 | 25 | } |
|
26 | 26 | |
|
27 | 27 | em { |
|
28 | 28 | font-weight: bold; |
|
29 | 29 | font-style: normal; |
|
30 | 30 | } |
|
31 | 31 | |
|
32 | 32 | th, |
|
33 | 33 | td { |
|
34 | 34 | height: auto; |
|
35 | 35 | max-width: 20%; |
|
36 | 36 | padding: .65em 0 .65em 1em; |
|
37 | 37 | vertical-align: middle; |
|
38 | 38 | border-bottom: @border-thickness solid @grey5; |
|
39 | 39 | white-space: normal; |
|
40 | 40 | |
|
41 | 41 | &.td-radio, |
|
42 | 42 | &.td-checkbox { |
|
43 | 43 | padding-right: 0; |
|
44 | 44 | text-align: center; |
|
45 | 45 | |
|
46 | 46 | input { |
|
47 | 47 | margin: 0 1em; |
|
48 | 48 | } |
|
49 | 49 | } |
|
50 | 50 | |
|
51 | 51 | &.truncate-wrap { |
|
52 | 52 | white-space: nowrap !important; |
|
53 | 53 | } |
|
54 | 54 | |
|
55 | 55 | pre { |
|
56 | 56 | margin: 0; |
|
57 | 57 | } |
|
58 | 58 | |
|
59 | 59 | .show_more { |
|
60 | 60 | height: inherit; |
|
61 | 61 | } |
|
62 | 62 | } |
|
63 | 63 | |
|
64 | 64 | .expired td { |
|
65 | 65 | background-color: @grey7; |
|
66 | 66 | } |
|
67 | 67 | .inactive td { |
|
68 | 68 | background-color: @grey6; |
|
69 | 69 | } |
|
70 | 70 | th { |
|
71 | 71 | text-align: left; |
|
72 | 72 | font-weight: @text-semibold-weight; |
|
73 | 73 | font-family: @text-semibold; |
|
74 | 74 | } |
|
75 | 75 | |
|
76 | 76 | .hl { |
|
77 | 77 | td { |
|
78 | 78 | background-color: lighten(@alert4,25%); |
|
79 | 79 | } |
|
80 | 80 | } |
|
81 | 81 | |
|
82 | 82 | // Special Data Cell Types |
|
83 | 83 | // See style guide for desciptions and examples. |
|
84 | 84 | |
|
85 | 85 | td { |
|
86 | 86 | |
|
87 | 87 | &.user { |
|
88 | 88 | padding-left: 1em; |
|
89 | 89 | } |
|
90 | 90 | |
|
91 | 91 | &.td-rss { |
|
92 | 92 | width: 20px; |
|
93 | 93 | min-width: 0; |
|
94 | 94 | margin: 0; |
|
95 | 95 | } |
|
96 | 96 | |
|
97 | 97 | &.quick_repo_menu { |
|
98 | 98 | width: 15px; |
|
99 | 99 | text-align: center; |
|
100 | 100 | |
|
101 | 101 | &:hover { |
|
102 | 102 | background-color: @grey5; |
|
103 | 103 | } |
|
104 | 104 | } |
|
105 | 105 | |
|
106 | 106 | &.td-icon { |
|
107 | 107 | min-width: 20px; |
|
108 | 108 | width: 20px; |
|
109 | 109 | } |
|
110 | 110 | |
|
111 | 111 | &.td-hash { |
|
112 | 112 | min-width: 80px; |
|
113 | 113 | width: 200px; |
|
114 | 114 | |
|
115 | 115 | .obsolete { |
|
116 | 116 | text-decoration: line-through; |
|
117 | 117 | color: lighten(@grey2,25%); |
|
118 | 118 | } |
|
119 | 119 | } |
|
120 | 120 | |
|
121 | &.td-sha { | |
|
122 | white-space: nowrap; | |
|
123 | } | |
|
124 | ||
|
121 | 125 | &.td-graphbox { |
|
122 | 126 | width: 100px; |
|
123 | 127 | max-width: 100px; |
|
124 | 128 | min-width: 100px; |
|
125 | 129 | } |
|
126 | 130 | |
|
127 | 131 | &.td-time { |
|
128 | 132 | width: 160px; |
|
129 | 133 | white-space: nowrap; |
|
130 | 134 | } |
|
131 | 135 | |
|
132 | 136 | &.annotate{ |
|
133 | 137 | padding-right: 0; |
|
134 | 138 | |
|
135 | 139 | div.annotatediv{ |
|
136 | 140 | margin: 0 0.7em; |
|
137 | 141 | } |
|
138 | 142 | } |
|
139 | 143 | |
|
140 | 144 | &.tags-col { |
|
141 | 145 | padding-right: 0; |
|
142 | 146 | } |
|
143 | 147 | |
|
144 | 148 | &.td-description { |
|
145 | 149 | min-width: 350px; |
|
146 | 150 | |
|
147 | 151 | &.truncate, .truncate-wrap { |
|
148 | 152 | white-space: nowrap; |
|
149 | 153 | overflow: hidden; |
|
150 | 154 | text-overflow: ellipsis; |
|
151 | 155 | max-width: 350px; |
|
152 | 156 | } |
|
153 | 157 | } |
|
154 | 158 | |
|
155 | 159 | &.td-grid-name { |
|
156 | 160 | white-space: nowrap; |
|
157 | 161 | min-width: 300px; |
|
158 | 162 | } |
|
159 | 163 | |
|
160 | 164 | &.td-componentname { |
|
161 | 165 | white-space: nowrap; |
|
162 | 166 | } |
|
163 | 167 | |
|
164 | 168 | &.td-name { |
|
165 | 169 | |
|
166 | 170 | } |
|
167 | 171 | |
|
168 | 172 | &.td-journalaction { |
|
169 | 173 | min-width: 300px; |
|
170 | 174 | |
|
171 | 175 | .journal_action_params { |
|
172 | 176 | // waiting for feedback |
|
173 | 177 | } |
|
174 | 178 | } |
|
175 | 179 | |
|
176 | 180 | &.td-active { |
|
177 | 181 | padding-left: .65em; |
|
178 | 182 | } |
|
179 | 183 | |
|
180 | 184 | &.td-url { |
|
181 | 185 | white-space: nowrap; |
|
182 | 186 | } |
|
183 | 187 | |
|
184 | 188 | &.td-comments { |
|
185 | 189 | min-width: 3em; |
|
186 | 190 | } |
|
187 | 191 | |
|
188 | 192 | &.td-buttons { |
|
189 | 193 | padding: .3em 0; |
|
190 | 194 | } |
|
191 | 195 | &.td-align-top { |
|
192 | 196 | vertical-align: text-top |
|
193 | 197 | } |
|
194 | 198 | &.td-action { |
|
195 | 199 | // this is for the remove/delete/edit buttons |
|
196 | 200 | padding-right: 0; |
|
197 | 201 | min-width: 95px; |
|
198 | 202 | text-transform: capitalize; |
|
199 | 203 | |
|
200 | 204 | i { |
|
201 | 205 | display: none; |
|
202 | 206 | } |
|
203 | 207 | } |
|
204 | 208 | |
|
205 | 209 | // TODO: lisa: this needs to be cleaned up with the buttons |
|
206 | 210 | .grid_edit, |
|
207 | 211 | .grid_delete { |
|
208 | 212 | display: inline-block; |
|
209 | 213 | margin: 0 @padding/3 0 0; |
|
210 | 214 | font-family: @text-light; |
|
211 | 215 | |
|
212 | 216 | i { |
|
213 | 217 | display: none; |
|
214 | 218 | } |
|
215 | 219 | } |
|
216 | 220 | |
|
217 | 221 | .grid_edit + .grid_delete { |
|
218 | 222 | border-left: @border-thickness solid @grey5; |
|
219 | 223 | padding-left: @padding/2; |
|
220 | 224 | } |
|
221 | 225 | |
|
222 | 226 | &.td-compare { |
|
223 | 227 | |
|
224 | 228 | input { |
|
225 | 229 | margin-right: 1em; |
|
226 | 230 | } |
|
227 | 231 | |
|
228 | 232 | .compare-radio-button { |
|
229 | 233 | margin: 0 1em 0 0; |
|
230 | 234 | } |
|
231 | 235 | |
|
232 | 236 | |
|
233 | 237 | } |
|
234 | 238 | |
|
235 | 239 | &.td-tags { |
|
236 | 240 | padding: .5em 1em .5em 0; |
|
237 | 241 | width: 140px; |
|
238 | 242 | |
|
239 | 243 | .tag { |
|
240 | 244 | margin: 1px; |
|
241 | 245 | float: left; |
|
242 | 246 | } |
|
243 | 247 | } |
|
244 | 248 | |
|
245 | 249 | .icon-svn, .icon-hg, .icon-git { |
|
246 | 250 | font-size: 1.4em; |
|
247 | 251 | } |
|
248 | 252 | |
|
249 | 253 | &.collapse_commit, |
|
250 | 254 | &.expand_commit { |
|
251 | 255 | padding-right: 0; |
|
252 | 256 | padding-left: 1em; |
|
253 | 257 | cursor: pointer; |
|
254 | 258 | width: 20px; |
|
255 | 259 | } |
|
256 | 260 | } |
|
257 | 261 | |
|
258 | 262 | .perm_admin_row { |
|
259 | 263 | color: @grey4; |
|
260 | 264 | background-color: @grey6; |
|
261 | 265 | } |
|
262 | 266 | |
|
263 | 267 | .noborder { |
|
264 | 268 | border: none; |
|
265 | 269 | |
|
266 | 270 | td { |
|
267 | 271 | border: none; |
|
268 | 272 | } |
|
269 | 273 | } |
|
270 | 274 | } |
|
271 | 275 | .rctable.audit-log { |
|
272 | 276 | td { |
|
273 | 277 | vertical-align: top; |
|
274 | 278 | } |
|
275 | 279 | } |
|
276 | 280 | |
|
277 | 281 | // TRUNCATING |
|
278 | 282 | // TODO: lisaq: should this possibly be moved out of tables.less? |
|
279 | 283 | // for truncated text |
|
280 | 284 | // used inside of table cells and in code block headers |
|
281 | 285 | .truncate-wrap { |
|
282 | 286 | white-space: nowrap !important; |
|
283 | 287 | |
|
284 | 288 | //truncated text |
|
285 | 289 | .truncate { |
|
286 | 290 | max-width: 450px; |
|
287 | 291 | width: 300px; |
|
288 | 292 | overflow: hidden; |
|
289 | 293 | text-overflow: ellipsis; |
|
290 | 294 | -o-text-overflow: ellipsis; |
|
291 | 295 | -ms-text-overflow: ellipsis; |
|
292 | 296 | |
|
293 | 297 | &.autoexpand { |
|
294 | 298 | width: 120px; |
|
295 | 299 | margin-right: 200px; |
|
296 | 300 | } |
|
297 | 301 | } |
|
298 | 302 | &:hover .truncate.autoexpand { |
|
299 | 303 | overflow: visible; |
|
300 | 304 | } |
|
301 | 305 | |
|
302 | 306 | .tags-truncate { |
|
303 | 307 | width: 150px; |
|
304 | 308 | height: 22px; |
|
305 | 309 | overflow: hidden; |
|
306 | 310 | |
|
307 | 311 | .tag { |
|
308 | 312 | display: inline-block; |
|
309 | 313 | } |
|
310 | 314 | |
|
311 | 315 | &.truncate { |
|
312 | 316 | height: 22px; |
|
313 | 317 | max-height:2em; |
|
314 | 318 | width: 140px; |
|
315 | 319 | } |
|
316 | 320 | } |
|
317 | 321 | } |
|
318 | 322 | |
|
319 | 323 | .apikeys_wrap { |
|
320 | 324 | margin-bottom: @padding; |
|
321 | 325 | |
|
322 | 326 | table.rctable td:first-child { |
|
323 | 327 | width: 340px; |
|
324 | 328 | } |
|
325 | 329 | } |
|
326 | 330 | |
|
327 | 331 | |
|
328 | 332 | |
|
329 | 333 | // SPECIAL CASES |
|
330 | 334 | |
|
331 | 335 | // Repository Followers |
|
332 | 336 | table.rctable.followers_data { |
|
333 | 337 | width: 75%; |
|
334 | 338 | margin: 0; |
|
335 | 339 | } |
|
336 | 340 | |
|
337 | 341 | // Repository List |
|
338 | 342 | // Group Members List |
|
339 | 343 | table.rctable.group_members, |
|
340 | 344 | table#repo_list_table { |
|
341 | 345 | min-width: 600px; |
|
342 | 346 | } |
|
343 | 347 | |
|
344 | 348 | // Keyboard mappings |
|
345 | 349 | table.keyboard-mappings { |
|
346 | 350 | th { |
|
347 | 351 | text-align: left; |
|
348 | 352 | font-weight: @text-semibold-weight; |
|
349 | 353 | font-family: @text-semibold; |
|
350 | 354 | } |
|
351 | 355 | } |
|
352 | 356 | |
|
353 | 357 | // Branches, Tags, and Bookmarks |
|
354 | 358 | #obj_list_table.dataTable { |
|
355 | 359 | td.td-time { |
|
356 | 360 | padding-right: 1em; |
|
357 | 361 | } |
|
358 | 362 | } |
|
359 | 363 | |
|
360 | 364 | // User Admin |
|
361 | 365 | .rctable.useremails, |
|
362 | 366 | .rctable.account_emails { |
|
363 | 367 | .tag, |
|
364 | 368 | .btn { |
|
365 | 369 | float: right; |
|
366 | 370 | } |
|
367 | 371 | .btn { //to line up with tags |
|
368 | 372 | margin-right: 1.65em; |
|
369 | 373 | } |
|
370 | 374 | } |
|
371 | 375 | |
|
372 | 376 | // User List |
|
373 | 377 | #user_list_table { |
|
374 | 378 | |
|
375 | 379 | td.td-user { |
|
376 | 380 | min-width: 100px; |
|
377 | 381 | } |
|
378 | 382 | } |
|
379 | 383 | |
|
380 | 384 | // Pull Request List Table |
|
381 | 385 | #pull_request_list_table.dataTable { |
|
382 | 386 | |
|
383 | 387 | //TODO: lisa: This needs to be removed once the description is adjusted |
|
384 | 388 | // for using an expand_commit button (see issue 765) |
|
385 | 389 | td { |
|
386 | 390 | vertical-align: middle; |
|
387 | 391 | } |
|
388 | 392 | } |
|
389 | 393 | |
|
390 | 394 | // Settings (no border) |
|
391 | 395 | table.rctable.dl-settings { |
|
392 | 396 | td { |
|
393 | 397 | border: none; |
|
394 | 398 | vertical-align: baseline; |
|
395 | 399 | } |
|
396 | 400 | } |
|
397 | 401 | |
|
398 | 402 | |
|
399 | 403 | // Statistics |
|
400 | 404 | table.trending_language_tbl { |
|
401 | 405 | width: 100%; |
|
402 | 406 | line-height: 1em; |
|
403 | 407 | |
|
404 | 408 | td div { |
|
405 | 409 | overflow: visible; |
|
406 | 410 | } |
|
407 | 411 | } |
|
408 | 412 | |
|
409 | 413 | .trending_language_tbl, .trending_language_tbl td { |
|
410 | 414 | border: 0; |
|
411 | 415 | margin: 0; |
|
412 | 416 | padding: 0; |
|
413 | 417 | background: transparent; |
|
414 | 418 | } |
|
415 | 419 | |
|
416 | 420 | .trending_language_tbl, .trending_language_tbl tr { |
|
417 | 421 | border-spacing: 0 3px; |
|
418 | 422 | } |
|
419 | 423 | |
|
420 | 424 | .trending_language { |
|
421 | 425 | position: relative; |
|
422 | 426 | overflow: hidden; |
|
423 | 427 | color: @text-color; |
|
424 | 428 | width: 400px; |
|
425 | 429 | |
|
426 | 430 | .lang-bar { |
|
427 | 431 | z-index: 1; |
|
428 | 432 | overflow: hidden; |
|
429 | 433 | background-color: @rcblue; |
|
430 | 434 | color: #FFF; |
|
431 | 435 | text-decoration: none; |
|
432 | 436 | } |
|
433 | 437 | |
|
434 | 438 | } |
|
435 | 439 | |
|
436 | 440 | // Changesets |
|
437 | 441 | #changesets.rctable { |
|
438 | 442 | th { |
|
439 | 443 | padding: 0 1em 0.65em 0; |
|
440 | 444 | } |
|
441 | 445 | |
|
442 | 446 | // td must be fixed height for graph |
|
443 | 447 | td { |
|
444 | 448 | height: 32px; |
|
445 | 449 | padding: 0 1em 0 0; |
|
446 | 450 | vertical-align: middle; |
|
447 | 451 | white-space: nowrap; |
|
448 | 452 | |
|
449 | 453 | &.td-description { |
|
450 | 454 | white-space: normal; |
|
451 | 455 | } |
|
452 | 456 | |
|
453 | 457 | &.expand_commit { |
|
454 | 458 | padding-right: 0; |
|
455 | 459 | cursor: pointer; |
|
456 | 460 | width: 20px; |
|
457 | 461 | } |
|
458 | 462 | } |
|
459 | 463 | } |
|
460 | 464 | |
|
461 | 465 | // Compare |
|
462 | 466 | table.compare_view_commits { |
|
463 | 467 | margin-top: @space; |
|
464 | 468 | |
|
465 | 469 | td.td-time { |
|
466 | 470 | padding-left: .5em; |
|
467 | 471 | } |
|
468 | 472 | |
|
469 | 473 | // special case to not show hover actions on hidden indicator |
|
470 | 474 | tr.compare_select_hidden:hover { |
|
471 | 475 | cursor: inherit; |
|
472 | 476 | |
|
473 | 477 | td { |
|
474 | 478 | background-color: inherit; |
|
475 | 479 | } |
|
476 | 480 | } |
|
477 | 481 | |
|
478 | 482 | tr:hover { |
|
479 | 483 | cursor: pointer; |
|
480 | 484 | |
|
481 | 485 | td { |
|
482 | 486 | background-color: lighten(@alert4,25%); |
|
483 | 487 | } |
|
484 | 488 | } |
|
485 | 489 | |
|
486 | 490 | |
|
487 | 491 | } |
|
488 | 492 | |
|
489 | 493 | .file_history { |
|
490 | 494 | td.td-actions { |
|
491 | 495 | text-align: right; |
|
492 | 496 | } |
|
493 | 497 | } |
|
494 | 498 | |
|
495 | 499 | |
|
496 | 500 | // Gist List |
|
497 | 501 | #gist_list_table { |
|
498 | 502 | td { |
|
499 | 503 | vertical-align: middle; |
|
500 | 504 | |
|
501 | 505 | div{ |
|
502 | 506 | display: inline-block; |
|
503 | 507 | vertical-align: middle; |
|
504 | 508 | } |
|
505 | 509 | |
|
506 | 510 | img{ |
|
507 | 511 | vertical-align: middle; |
|
508 | 512 | } |
|
509 | 513 | } |
|
510 | 514 | } |
@@ -1,380 +1,381 b'' | |||
|
1 | 1 | |
|
2 | 2 | /****************************************************************************** |
|
3 | 3 | * * |
|
4 | 4 | * DO NOT CHANGE THIS FILE MANUALLY * |
|
5 | 5 | * * |
|
6 | 6 | * * |
|
7 | 7 | * This file is automatically generated when the app starts up with * |
|
8 | 8 | * generate_js_files = true * |
|
9 | 9 | * * |
|
10 | 10 | * To add a route here pass jsroute=True to the route definition in the app * |
|
11 | 11 | * * |
|
12 | 12 | ******************************************************************************/ |
|
13 | 13 | function registerRCRoutes() { |
|
14 | 14 | // routes registration |
|
15 | 15 | pyroutes.register('favicon', '/favicon.ico', []); |
|
16 | 16 | pyroutes.register('robots', '/robots.txt', []); |
|
17 | 17 | pyroutes.register('auth_home', '/_admin/auth*traverse', []); |
|
18 | 18 | pyroutes.register('global_integrations_new', '/_admin/integrations/new', []); |
|
19 | 19 | pyroutes.register('global_integrations_home', '/_admin/integrations', []); |
|
20 | 20 | pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']); |
|
21 | 21 | pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']); |
|
22 | 22 | pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']); |
|
23 | 23 | pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']); |
|
24 | 24 | pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']); |
|
25 | 25 | pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']); |
|
26 | 26 | pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']); |
|
27 | 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 | 28 | pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']); |
|
29 | 29 | pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']); |
|
30 | 30 | pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']); |
|
31 | 31 | pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']); |
|
32 | 32 | pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); |
|
33 | 33 | pyroutes.register('ops_ping', '/_admin/ops/ping', []); |
|
34 | 34 | pyroutes.register('ops_error_test', '/_admin/ops/error', []); |
|
35 | 35 | pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []); |
|
36 | 36 | pyroutes.register('ops_ping_legacy', '/_admin/ping', []); |
|
37 | 37 | pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []); |
|
38 | 38 | pyroutes.register('admin_home', '/_admin', []); |
|
39 | 39 | pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []); |
|
40 | 40 | pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']); |
|
41 | 41 | pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']); |
|
42 | 42 | pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']); |
|
43 | 43 | pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']); |
|
44 | 44 | pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []); |
|
45 | 45 | pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []); |
|
46 | 46 | pyroutes.register('admin_settings_system', '/_admin/settings/system', []); |
|
47 | 47 | pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []); |
|
48 | 48 | pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []); |
|
49 | 49 | pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []); |
|
50 | 50 | pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']); |
|
51 | 51 | pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']); |
|
52 | 52 | pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []); |
|
53 | 53 | pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []); |
|
54 | 54 | pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []); |
|
55 | 55 | pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []); |
|
56 | 56 | pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []); |
|
57 | 57 | pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []); |
|
58 | 58 | pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []); |
|
59 | 59 | pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []); |
|
60 | 60 | pyroutes.register('admin_settings', '/_admin/settings', []); |
|
61 | 61 | pyroutes.register('admin_settings_update', '/_admin/settings/update', []); |
|
62 | 62 | pyroutes.register('admin_settings_global', '/_admin/settings/global', []); |
|
63 | 63 | pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []); |
|
64 | 64 | pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []); |
|
65 | 65 | pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []); |
|
66 | 66 | pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []); |
|
67 | 67 | pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []); |
|
68 | 68 | pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []); |
|
69 | 69 | pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []); |
|
70 | 70 | pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []); |
|
71 | 71 | pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []); |
|
72 | 72 | pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []); |
|
73 | 73 | pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []); |
|
74 | 74 | pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []); |
|
75 | 75 | pyroutes.register('admin_settings_email', '/_admin/settings/email', []); |
|
76 | 76 | pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []); |
|
77 | 77 | pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []); |
|
78 | 78 | pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []); |
|
79 | 79 | pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []); |
|
80 | 80 | pyroutes.register('admin_settings_search', '/_admin/settings/search', []); |
|
81 | 81 | pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []); |
|
82 | 82 | pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []); |
|
83 | 83 | pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []); |
|
84 | 84 | pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []); |
|
85 | 85 | pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []); |
|
86 | 86 | pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []); |
|
87 | 87 | pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []); |
|
88 | 88 | pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []); |
|
89 | 89 | pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []); |
|
90 | 90 | pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []); |
|
91 | 91 | pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []); |
|
92 | 92 | pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []); |
|
93 | 93 | pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []); |
|
94 | 94 | pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []); |
|
95 | 95 | pyroutes.register('users', '/_admin/users', []); |
|
96 | 96 | pyroutes.register('users_data', '/_admin/users_data', []); |
|
97 | 97 | pyroutes.register('users_create', '/_admin/users/create', []); |
|
98 | 98 | pyroutes.register('users_new', '/_admin/users/new', []); |
|
99 | 99 | pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']); |
|
100 | 100 | pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']); |
|
101 | 101 | pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']); |
|
102 | 102 | pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']); |
|
103 | 103 | pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']); |
|
104 | 104 | pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']); |
|
105 | 105 | pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']); |
|
106 | 106 | pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']); |
|
107 | 107 | pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']); |
|
108 | 108 | pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']); |
|
109 | 109 | pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']); |
|
110 | 110 | pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']); |
|
111 | 111 | pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']); |
|
112 | 112 | pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']); |
|
113 | 113 | pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']); |
|
114 | 114 | pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']); |
|
115 | 115 | pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']); |
|
116 | 116 | pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']); |
|
117 | 117 | pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']); |
|
118 | 118 | pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']); |
|
119 | 119 | pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']); |
|
120 | 120 | pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']); |
|
121 | 121 | pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']); |
|
122 | 122 | pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']); |
|
123 | 123 | pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']); |
|
124 | 124 | pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']); |
|
125 | 125 | pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']); |
|
126 | 126 | pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']); |
|
127 | 127 | pyroutes.register('user_groups', '/_admin/user_groups', []); |
|
128 | 128 | pyroutes.register('user_groups_data', '/_admin/user_groups_data', []); |
|
129 | 129 | pyroutes.register('user_groups_new', '/_admin/user_groups/new', []); |
|
130 | 130 | pyroutes.register('user_groups_create', '/_admin/user_groups/create', []); |
|
131 | 131 | pyroutes.register('repos', '/_admin/repos', []); |
|
132 | 132 | pyroutes.register('repo_new', '/_admin/repos/new', []); |
|
133 | 133 | pyroutes.register('repo_create', '/_admin/repos/create', []); |
|
134 | 134 | pyroutes.register('repo_groups', '/_admin/repo_groups', []); |
|
135 | 135 | pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []); |
|
136 | 136 | pyroutes.register('repo_group_new', '/_admin/repo_group/new', []); |
|
137 | 137 | pyroutes.register('repo_group_create', '/_admin/repo_group/create', []); |
|
138 | 138 | pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []); |
|
139 | 139 | pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []); |
|
140 | 140 | pyroutes.register('channelstream_proxy', '/_channelstream', []); |
|
141 | 141 | pyroutes.register('upload_file', '/_file_store/upload', []); |
|
142 | 142 | pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']); |
|
143 | 143 | pyroutes.register('logout', '/_admin/logout', []); |
|
144 | 144 | pyroutes.register('reset_password', '/_admin/password_reset', []); |
|
145 | 145 | pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []); |
|
146 | 146 | pyroutes.register('home', '/', []); |
|
147 | 147 | pyroutes.register('user_autocomplete_data', '/_users', []); |
|
148 | 148 | pyroutes.register('user_group_autocomplete_data', '/_user_groups', []); |
|
149 | 149 | pyroutes.register('repo_list_data', '/_repos', []); |
|
150 | 150 | pyroutes.register('repo_group_list_data', '/_repo_groups', []); |
|
151 | 151 | pyroutes.register('goto_switcher_data', '/_goto_data', []); |
|
152 | 152 | pyroutes.register('markup_preview', '/_markup_preview', []); |
|
153 | 153 | pyroutes.register('file_preview', '/_file_preview', []); |
|
154 | 154 | pyroutes.register('store_user_session_value', '/_store_session_attr', []); |
|
155 | 155 | pyroutes.register('journal', '/_admin/journal', []); |
|
156 | 156 | pyroutes.register('journal_rss', '/_admin/journal/rss', []); |
|
157 | 157 | pyroutes.register('journal_atom', '/_admin/journal/atom', []); |
|
158 | 158 | pyroutes.register('journal_public', '/_admin/public_journal', []); |
|
159 | 159 | pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []); |
|
160 | 160 | pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []); |
|
161 | 161 | pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []); |
|
162 | 162 | pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []); |
|
163 | 163 | pyroutes.register('toggle_following', '/_admin/toggle_following', []); |
|
164 | 164 | pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']); |
|
165 | 165 | pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']); |
|
166 | 166 | pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); |
|
167 | 167 | pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); |
|
168 | 168 | pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); |
|
169 | 169 | pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']); |
|
170 | 170 | pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']); |
|
171 | 171 | pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']); |
|
172 | 172 | pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']); |
|
173 | 173 | pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']); |
|
174 | 174 | pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']); |
|
175 | 175 | pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']); |
|
176 | 176 | pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']); |
|
177 | pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']); | |
|
177 | 178 | pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']); |
|
178 | 179 | pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']); |
|
179 | 180 | pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
|
180 | 181 | pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']); |
|
181 | 182 | pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']); |
|
182 | 183 | pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
183 | 184 | pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']); |
|
184 | 185 | pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']); |
|
185 | 186 | pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
186 | 187 | pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
187 | 188 | pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
188 | 189 | pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
189 | 190 | pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']); |
|
190 | 191 | pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
191 | 192 | pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
192 | 193 | pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
193 | 194 | pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
194 | 195 | pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
195 | 196 | pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
196 | 197 | pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
197 | 198 | pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
198 | 199 | pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
199 | 200 | pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
200 | 201 | pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
201 | 202 | pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
202 | 203 | pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
203 | 204 | pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']); |
|
204 | 205 | pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); |
|
205 | 206 | pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']); |
|
206 | 207 | pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']); |
|
207 | 208 | pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
208 | 209 | pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']); |
|
209 | 210 | pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
210 | 211 | pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']); |
|
211 | 212 | pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
212 | 213 | pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']); |
|
213 | 214 | 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']); |
|
214 | 215 | pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']); |
|
215 | 216 | pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']); |
|
216 | 217 | pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']); |
|
217 | 218 | pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']); |
|
218 | 219 | pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']); |
|
219 | 220 | pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']); |
|
220 | 221 | pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']); |
|
221 | 222 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
222 | 223 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); |
|
223 | 224 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); |
|
224 | 225 | pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']); |
|
225 | 226 | pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']); |
|
226 | 227 | pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']); |
|
227 | 228 | pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']); |
|
228 | 229 | pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']); |
|
229 | 230 | pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']); |
|
230 | 231 | pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']); |
|
231 | 232 | pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']); |
|
232 | 233 | 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']); |
|
233 | 234 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
234 | 235 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
235 | 236 | pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']); |
|
236 | 237 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); |
|
237 | 238 | pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']); |
|
238 | 239 | pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']); |
|
239 | 240 | pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']); |
|
240 | 241 | pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']); |
|
241 | 242 | pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']); |
|
242 | 243 | pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']); |
|
243 | 244 | pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']); |
|
244 | 245 | pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']); |
|
245 | 246 | pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']); |
|
246 | 247 | pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']); |
|
247 | 248 | pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']); |
|
248 | 249 | pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']); |
|
249 | 250 | pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']); |
|
250 | 251 | pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']); |
|
251 | 252 | pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']); |
|
252 | 253 | pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']); |
|
253 | 254 | pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']); |
|
254 | 255 | pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']); |
|
255 | 256 | pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']); |
|
256 | 257 | pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']); |
|
257 | 258 | pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']); |
|
258 | 259 | pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']); |
|
259 | 260 | pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']); |
|
260 | 261 | pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']); |
|
261 | 262 | pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']); |
|
262 | 263 | pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']); |
|
263 | 264 | pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']); |
|
264 | 265 | pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); |
|
265 | 266 | pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); |
|
266 | 267 | pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']); |
|
267 | 268 | pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']); |
|
268 | 269 | pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']); |
|
269 | 270 | pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']); |
|
270 | 271 | pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']); |
|
271 | 272 | pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); |
|
272 | 273 | pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); |
|
273 | 274 | pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']); |
|
274 | 275 | pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']); |
|
275 | 276 | pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']); |
|
276 | 277 | pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']); |
|
277 | 278 | pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']); |
|
278 | 279 | pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); |
|
279 | 280 | pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']); |
|
280 | 281 | pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']); |
|
281 | 282 | pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']); |
|
282 | 283 | pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']); |
|
283 | 284 | pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']); |
|
284 | 285 | pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']); |
|
285 | 286 | pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']); |
|
286 | 287 | pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']); |
|
287 | 288 | pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']); |
|
288 | 289 | pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']); |
|
289 | 290 | pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']); |
|
290 | 291 | pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']); |
|
291 | 292 | pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']); |
|
292 | 293 | pyroutes.register('search', '/_admin/search', []); |
|
293 | 294 | pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']); |
|
294 | 295 | pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']); |
|
295 | 296 | pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']); |
|
296 | 297 | pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']); |
|
297 | 298 | pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']); |
|
298 | 299 | pyroutes.register('my_account_profile', '/_admin/my_account/profile', []); |
|
299 | 300 | pyroutes.register('my_account_edit', '/_admin/my_account/edit', []); |
|
300 | 301 | pyroutes.register('my_account_update', '/_admin/my_account/update', []); |
|
301 | 302 | pyroutes.register('my_account_password', '/_admin/my_account/password', []); |
|
302 | 303 | pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []); |
|
303 | 304 | pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []); |
|
304 | 305 | pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []); |
|
305 | 306 | pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []); |
|
306 | 307 | pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []); |
|
307 | 308 | pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []); |
|
308 | 309 | pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []); |
|
309 | 310 | pyroutes.register('my_account_emails', '/_admin/my_account/emails', []); |
|
310 | 311 | pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []); |
|
311 | 312 | pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []); |
|
312 | 313 | pyroutes.register('my_account_repos', '/_admin/my_account/repos', []); |
|
313 | 314 | pyroutes.register('my_account_watched', '/_admin/my_account/watched', []); |
|
314 | 315 | pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []); |
|
315 | 316 | pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []); |
|
316 | 317 | pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']); |
|
317 | 318 | pyroutes.register('my_account_perms', '/_admin/my_account/perms', []); |
|
318 | 319 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); |
|
319 | 320 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); |
|
320 | 321 | pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []); |
|
321 | 322 | pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []); |
|
322 | 323 | pyroutes.register('notifications_show_all', '/_admin/notifications', []); |
|
323 | 324 | pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []); |
|
324 | 325 | pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']); |
|
325 | 326 | pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']); |
|
326 | 327 | pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']); |
|
327 | 328 | pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []); |
|
328 | 329 | pyroutes.register('gists_show', '/_admin/gists', []); |
|
329 | 330 | pyroutes.register('gists_new', '/_admin/gists/new', []); |
|
330 | 331 | pyroutes.register('gists_create', '/_admin/gists/create', []); |
|
331 | 332 | pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']); |
|
332 | 333 | pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']); |
|
333 | 334 | pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']); |
|
334 | 335 | pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']); |
|
335 | 336 | pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']); |
|
336 | 337 | pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']); |
|
337 | 338 | pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']); |
|
338 | 339 | pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']); |
|
339 | 340 | pyroutes.register('debug_style_home', '/_admin/debug_style', []); |
|
340 | 341 | pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']); |
|
341 | 342 | pyroutes.register('apiv2', '/_admin/api', []); |
|
342 | 343 | pyroutes.register('admin_settings_license', '/_admin/settings/license', []); |
|
343 | 344 | pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []); |
|
344 | 345 | pyroutes.register('login', '/_admin/login', []); |
|
345 | 346 | pyroutes.register('register', '/_admin/register', []); |
|
346 | 347 | pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']); |
|
347 | 348 | pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']); |
|
348 | 349 | pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']); |
|
349 | 350 | pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']); |
|
350 | 351 | pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']); |
|
351 | 352 | pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']); |
|
352 | 353 | pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []); |
|
353 | 354 | pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []); |
|
354 | 355 | pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []); |
|
355 | 356 | pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []); |
|
356 | 357 | pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']); |
|
357 | 358 | pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']); |
|
358 | 359 | pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']); |
|
359 | 360 | pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']); |
|
360 | 361 | pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []); |
|
361 | 362 | pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']); |
|
362 | 363 | pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []); |
|
363 | 364 | pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []); |
|
364 | 365 | pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []); |
|
365 | 366 | pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []); |
|
366 | 367 | pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []); |
|
367 | 368 | pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []); |
|
368 | 369 | pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']); |
|
369 | 370 | pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']); |
|
370 | 371 | pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']); |
|
371 | 372 | pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']); |
|
372 | 373 | pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']); |
|
373 | 374 | pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']); |
|
374 | 375 | pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']); |
|
375 | 376 | pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']); |
|
376 | 377 | pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']); |
|
377 | 378 | pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']); |
|
378 | 379 | pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']); |
|
379 | 380 | pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']); |
|
380 | 381 | } |
@@ -1,838 +1,927 b'' | |||
|
1 | 1 | // # Copyright (C) 2010-2019 RhodeCode GmbH |
|
2 | 2 | // # |
|
3 | 3 | // # This program is free software: you can redistribute it and/or modify |
|
4 | 4 | // # it under the terms of the GNU Affero General Public License, version 3 |
|
5 | 5 | // # (only), as published by the Free Software Foundation. |
|
6 | 6 | // # |
|
7 | 7 | // # This program is distributed in the hope that it will be useful, |
|
8 | 8 | // # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
9 | 9 | // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
10 | 10 | // # GNU General Public License for more details. |
|
11 | 11 | // # |
|
12 | 12 | // # You should have received a copy of the GNU Affero General Public License |
|
13 | 13 | // # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
14 | 14 | // # |
|
15 | 15 | // # This program is dual-licensed. If you wish to learn more about the |
|
16 | 16 | // # RhodeCode Enterprise Edition, including its added features, Support services, |
|
17 | 17 | // # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
18 | 18 | |
|
19 | 19 | var firefoxAnchorFix = function() { |
|
20 | 20 | // hack to make anchor links behave properly on firefox, in our inline |
|
21 | 21 | // comments generation when comments are injected firefox is misbehaving |
|
22 | 22 | // when jumping to anchor links |
|
23 | 23 | if (location.href.indexOf('#') > -1) { |
|
24 | 24 | location.href += ''; |
|
25 | 25 | } |
|
26 | 26 | }; |
|
27 | 27 | |
|
28 | 28 | var linkifyComments = function(comments) { |
|
29 | 29 | var firstCommentId = null; |
|
30 | 30 | if (comments) { |
|
31 | 31 | firstCommentId = $(comments[0]).data('comment-id'); |
|
32 | 32 | } |
|
33 | 33 | |
|
34 | 34 | if (firstCommentId){ |
|
35 | 35 | $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId); |
|
36 | 36 | } |
|
37 | 37 | }; |
|
38 | 38 | |
|
39 | 39 | var bindToggleButtons = function() { |
|
40 | 40 | $('.comment-toggle').on('click', function() { |
|
41 | 41 | $(this).parent().nextUntil('tr.line').toggle('inline-comments'); |
|
42 | 42 | }); |
|
43 | 43 | }; |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | |
|
47 | 47 | var _submitAjaxPOST = function(url, postData, successHandler, failHandler) { |
|
48 | 48 | failHandler = failHandler || function() {}; |
|
49 | 49 | postData = toQueryString(postData); |
|
50 | 50 | var request = $.ajax({ |
|
51 | 51 | url: url, |
|
52 | 52 | type: 'POST', |
|
53 | 53 | data: postData, |
|
54 | 54 | headers: {'X-PARTIAL-XHR': true} |
|
55 | 55 | }) |
|
56 | 56 | .done(function (data) { |
|
57 | 57 | successHandler(data); |
|
58 | 58 | }) |
|
59 | 59 | .fail(function (data, textStatus, errorThrown) { |
|
60 | 60 | failHandler(data, textStatus, errorThrown) |
|
61 | 61 | }); |
|
62 | 62 | return request; |
|
63 | 63 | }; |
|
64 | 64 | |
|
65 | 65 | |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | /* Comment form for main and inline comments */ |
|
69 | 69 | (function(mod) { |
|
70 | 70 | |
|
71 | 71 | if (typeof exports == "object" && typeof module == "object") { |
|
72 | 72 | // CommonJS |
|
73 | 73 | module.exports = mod(); |
|
74 | 74 | } |
|
75 | 75 | else { |
|
76 | 76 | // Plain browser env |
|
77 | 77 | (this || window).CommentForm = mod(); |
|
78 | 78 | } |
|
79 | 79 | |
|
80 | 80 | })(function() { |
|
81 | 81 | "use strict"; |
|
82 | 82 | |
|
83 | 83 | function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId) { |
|
84 | 84 | if (!(this instanceof CommentForm)) { |
|
85 | 85 | return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId); |
|
86 | 86 | } |
|
87 | 87 | |
|
88 | 88 | // bind the element instance to our Form |
|
89 | 89 | $(formElement).get(0).CommentForm = this; |
|
90 | 90 | |
|
91 | 91 | this.withLineNo = function(selector) { |
|
92 | 92 | var lineNo = this.lineNo; |
|
93 | 93 | if (lineNo === undefined) { |
|
94 | 94 | return selector |
|
95 | 95 | } else { |
|
96 | 96 | return selector + '_' + lineNo; |
|
97 | 97 | } |
|
98 | 98 | }; |
|
99 | 99 | |
|
100 | 100 | this.commitId = commitId; |
|
101 | 101 | this.pullRequestId = pullRequestId; |
|
102 | 102 | this.lineNo = lineNo; |
|
103 | 103 | this.initAutocompleteActions = initAutocompleteActions; |
|
104 | 104 | |
|
105 | 105 | this.previewButton = this.withLineNo('#preview-btn'); |
|
106 | 106 | this.previewContainer = this.withLineNo('#preview-container'); |
|
107 | 107 | |
|
108 | 108 | this.previewBoxSelector = this.withLineNo('#preview-box'); |
|
109 | 109 | |
|
110 | 110 | this.editButton = this.withLineNo('#edit-btn'); |
|
111 | 111 | this.editContainer = this.withLineNo('#edit-container'); |
|
112 | 112 | this.cancelButton = this.withLineNo('#cancel-btn'); |
|
113 | 113 | this.commentType = this.withLineNo('#comment_type'); |
|
114 | 114 | |
|
115 | 115 | this.resolvesId = null; |
|
116 | 116 | this.resolvesActionId = null; |
|
117 | 117 | |
|
118 | 118 | this.closesPr = '#close_pull_request'; |
|
119 | 119 | |
|
120 | 120 | this.cmBox = this.withLineNo('#text'); |
|
121 | 121 | this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions); |
|
122 | 122 | |
|
123 | 123 | this.statusChange = this.withLineNo('#change_status'); |
|
124 | 124 | |
|
125 | 125 | this.submitForm = formElement; |
|
126 | 126 | this.submitButton = $(this.submitForm).find('input[type="submit"]'); |
|
127 | 127 | this.submitButtonText = this.submitButton.val(); |
|
128 | 128 | |
|
129 | 129 | this.previewUrl = pyroutes.url('repo_commit_comment_preview', |
|
130 | 130 | {'repo_name': templateContext.repo_name, |
|
131 | 131 | 'commit_id': templateContext.commit_data.commit_id}); |
|
132 | 132 | |
|
133 | 133 | if (resolvesCommentId){ |
|
134 | 134 | this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId); |
|
135 | 135 | this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId); |
|
136 | 136 | $(this.commentType).prop('disabled', true); |
|
137 | 137 | $(this.commentType).addClass('disabled'); |
|
138 | 138 | |
|
139 | 139 | // disable select |
|
140 | 140 | setTimeout(function() { |
|
141 | 141 | $(self.statusChange).select2('readonly', true); |
|
142 | 142 | }, 10); |
|
143 | 143 | |
|
144 | 144 | var resolvedInfo = ( |
|
145 | 145 | '<li class="resolve-action">' + |
|
146 | 146 | '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' + |
|
147 | 147 | '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' + |
|
148 | 148 | '</li>' |
|
149 | 149 | ).format(resolvesCommentId, _gettext('resolve comment')); |
|
150 | 150 | $(resolvedInfo).insertAfter($(this.commentType).parent()); |
|
151 | 151 | } |
|
152 | 152 | |
|
153 | 153 | // based on commitId, or pullRequestId decide where do we submit |
|
154 | 154 | // out data |
|
155 | 155 | if (this.commitId){ |
|
156 | 156 | this.submitUrl = pyroutes.url('repo_commit_comment_create', |
|
157 | 157 | {'repo_name': templateContext.repo_name, |
|
158 | 158 | 'commit_id': this.commitId}); |
|
159 | 159 | this.selfUrl = pyroutes.url('repo_commit', |
|
160 | 160 | {'repo_name': templateContext.repo_name, |
|
161 | 161 | 'commit_id': this.commitId}); |
|
162 | 162 | |
|
163 | 163 | } else if (this.pullRequestId) { |
|
164 | 164 | this.submitUrl = pyroutes.url('pullrequest_comment_create', |
|
165 | 165 | {'repo_name': templateContext.repo_name, |
|
166 | 166 | 'pull_request_id': this.pullRequestId}); |
|
167 | 167 | this.selfUrl = pyroutes.url('pullrequest_show', |
|
168 | 168 | {'repo_name': templateContext.repo_name, |
|
169 | 169 | 'pull_request_id': this.pullRequestId}); |
|
170 | 170 | |
|
171 | 171 | } else { |
|
172 | 172 | throw new Error( |
|
173 | 173 | 'CommentForm requires pullRequestId, or commitId to be specified.') |
|
174 | 174 | } |
|
175 | 175 | |
|
176 | 176 | // FUNCTIONS and helpers |
|
177 | 177 | var self = this; |
|
178 | 178 | |
|
179 | 179 | this.isInline = function(){ |
|
180 | 180 | return this.lineNo && this.lineNo != 'general'; |
|
181 | 181 | }; |
|
182 | 182 | |
|
183 | 183 | this.getCmInstance = function(){ |
|
184 | 184 | return this.cm |
|
185 | 185 | }; |
|
186 | 186 | |
|
187 | 187 | this.setPlaceholder = function(placeholder) { |
|
188 | 188 | var cm = this.getCmInstance(); |
|
189 | 189 | if (cm){ |
|
190 | 190 | cm.setOption('placeholder', placeholder); |
|
191 | 191 | } |
|
192 | 192 | }; |
|
193 | 193 | |
|
194 | 194 | this.getCommentStatus = function() { |
|
195 | 195 | return $(this.submitForm).find(this.statusChange).val(); |
|
196 | 196 | }; |
|
197 | 197 | this.getCommentType = function() { |
|
198 | 198 | return $(this.submitForm).find(this.commentType).val(); |
|
199 | 199 | }; |
|
200 | 200 | |
|
201 | 201 | this.getResolvesId = function() { |
|
202 | 202 | return $(this.submitForm).find(this.resolvesId).val() || null; |
|
203 | 203 | }; |
|
204 | 204 | |
|
205 | 205 | this.getClosePr = function() { |
|
206 | 206 | return $(this.submitForm).find(this.closesPr).val() || null; |
|
207 | 207 | }; |
|
208 | 208 | |
|
209 | 209 | this.markCommentResolved = function(resolvedCommentId){ |
|
210 | 210 | $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show(); |
|
211 | 211 | $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide(); |
|
212 | 212 | }; |
|
213 | 213 | |
|
214 | 214 | this.isAllowedToSubmit = function() { |
|
215 | 215 | return !$(this.submitButton).prop('disabled'); |
|
216 | 216 | }; |
|
217 | 217 | |
|
218 | 218 | this.initStatusChangeSelector = function(){ |
|
219 | 219 | var formatChangeStatus = function(state, escapeMarkup) { |
|
220 | 220 | var originalOption = state.element; |
|
221 | 221 | var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text)); |
|
222 | 222 | return tmpl |
|
223 | 223 | }; |
|
224 | 224 | var formatResult = function(result, container, query, escapeMarkup) { |
|
225 | 225 | return formatChangeStatus(result, escapeMarkup); |
|
226 | 226 | }; |
|
227 | 227 | |
|
228 | 228 | var formatSelection = function(data, container, escapeMarkup) { |
|
229 | 229 | return formatChangeStatus(data, escapeMarkup); |
|
230 | 230 | }; |
|
231 | 231 | |
|
232 | 232 | $(this.submitForm).find(this.statusChange).select2({ |
|
233 | 233 | placeholder: _gettext('Status Review'), |
|
234 | 234 | formatResult: formatResult, |
|
235 | 235 | formatSelection: formatSelection, |
|
236 | 236 | containerCssClass: "drop-menu status_box_menu", |
|
237 | 237 | dropdownCssClass: "drop-menu-dropdown", |
|
238 | 238 | dropdownAutoWidth: true, |
|
239 | 239 | minimumResultsForSearch: -1 |
|
240 | 240 | }); |
|
241 | 241 | $(this.submitForm).find(this.statusChange).on('change', function() { |
|
242 | 242 | var status = self.getCommentStatus(); |
|
243 | 243 | |
|
244 | 244 | if (status && !self.isInline()) { |
|
245 | 245 | $(self.submitButton).prop('disabled', false); |
|
246 | 246 | } |
|
247 | 247 | |
|
248 | 248 | var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status); |
|
249 | 249 | self.setPlaceholder(placeholderText) |
|
250 | 250 | }) |
|
251 | 251 | }; |
|
252 | 252 | |
|
253 | 253 | // reset the comment form into it's original state |
|
254 | 254 | this.resetCommentFormState = function(content) { |
|
255 | 255 | content = content || ''; |
|
256 | 256 | |
|
257 | 257 | $(this.editContainer).show(); |
|
258 | 258 | $(this.editButton).parent().addClass('active'); |
|
259 | 259 | |
|
260 | 260 | $(this.previewContainer).hide(); |
|
261 | 261 | $(this.previewButton).parent().removeClass('active'); |
|
262 | 262 | |
|
263 | 263 | this.setActionButtonsDisabled(true); |
|
264 | 264 | self.cm.setValue(content); |
|
265 | 265 | self.cm.setOption("readOnly", false); |
|
266 | 266 | |
|
267 | 267 | if (this.resolvesId) { |
|
268 | 268 | // destroy the resolve action |
|
269 | 269 | $(this.resolvesId).parent().remove(); |
|
270 | 270 | } |
|
271 | 271 | // reset closingPR flag |
|
272 | 272 | $('.close-pr-input').remove(); |
|
273 | 273 | |
|
274 | 274 | $(this.statusChange).select2('readonly', false); |
|
275 | 275 | }; |
|
276 | 276 | |
|
277 | 277 | this.globalSubmitSuccessCallback = function(){ |
|
278 | 278 | // default behaviour is to call GLOBAL hook, if it's registered. |
|
279 | 279 | if (window.commentFormGlobalSubmitSuccessCallback !== undefined){ |
|
280 | 280 | commentFormGlobalSubmitSuccessCallback() |
|
281 | 281 | } |
|
282 | 282 | }; |
|
283 | 283 | |
|
284 | 284 | this.submitAjaxPOST = function(url, postData, successHandler, failHandler) { |
|
285 | 285 | return _submitAjaxPOST(url, postData, successHandler, failHandler); |
|
286 | 286 | }; |
|
287 | 287 | |
|
288 | 288 | // overwrite a submitHandler, we need to do it for inline comments |
|
289 | 289 | this.setHandleFormSubmit = function(callback) { |
|
290 | 290 | this.handleFormSubmit = callback; |
|
291 | 291 | }; |
|
292 | 292 | |
|
293 | 293 | // overwrite a submitSuccessHandler |
|
294 | 294 | this.setGlobalSubmitSuccessCallback = function(callback) { |
|
295 | 295 | this.globalSubmitSuccessCallback = callback; |
|
296 | 296 | }; |
|
297 | 297 | |
|
298 | 298 | // default handler for for submit for main comments |
|
299 | 299 | this.handleFormSubmit = function() { |
|
300 | 300 | var text = self.cm.getValue(); |
|
301 | 301 | var status = self.getCommentStatus(); |
|
302 | 302 | var commentType = self.getCommentType(); |
|
303 | 303 | var resolvesCommentId = self.getResolvesId(); |
|
304 | 304 | var closePullRequest = self.getClosePr(); |
|
305 | 305 | |
|
306 | 306 | if (text === "" && !status) { |
|
307 | 307 | return; |
|
308 | 308 | } |
|
309 | 309 | |
|
310 | 310 | var excludeCancelBtn = false; |
|
311 | 311 | var submitEvent = true; |
|
312 | 312 | self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent); |
|
313 | 313 | self.cm.setOption("readOnly", true); |
|
314 | 314 | |
|
315 | 315 | var postData = { |
|
316 | 316 | 'text': text, |
|
317 | 317 | 'changeset_status': status, |
|
318 | 318 | 'comment_type': commentType, |
|
319 | 319 | 'csrf_token': CSRF_TOKEN |
|
320 | 320 | }; |
|
321 | 321 | |
|
322 | 322 | if (resolvesCommentId) { |
|
323 | 323 | postData['resolves_comment_id'] = resolvesCommentId; |
|
324 | 324 | } |
|
325 | 325 | |
|
326 | 326 | if (closePullRequest) { |
|
327 | 327 | postData['close_pull_request'] = true; |
|
328 | 328 | } |
|
329 | 329 | |
|
330 | 330 | var submitSuccessCallback = function(o) { |
|
331 | 331 | // reload page if we change status for single commit. |
|
332 | 332 | if (status && self.commitId) { |
|
333 | 333 | location.reload(true); |
|
334 | 334 | } else { |
|
335 | 335 | $('#injected_page_comments').append(o.rendered_text); |
|
336 | 336 | self.resetCommentFormState(); |
|
337 | 337 | timeagoActivate(); |
|
338 | 338 | |
|
339 | 339 | // mark visually which comment was resolved |
|
340 | 340 | if (resolvesCommentId) { |
|
341 | 341 | self.markCommentResolved(resolvesCommentId); |
|
342 | 342 | } |
|
343 | 343 | } |
|
344 | 344 | |
|
345 | 345 | // run global callback on submit |
|
346 | 346 | self.globalSubmitSuccessCallback(); |
|
347 | 347 | |
|
348 | 348 | }; |
|
349 | 349 | var submitFailCallback = function(data) { |
|
350 | 350 | alert( |
|
351 | 351 | "Error while submitting comment.\n" + |
|
352 | 352 | "Error code {0} ({1}).".format(data.status, data.statusText) |
|
353 | 353 | ); |
|
354 | 354 | self.resetCommentFormState(text); |
|
355 | 355 | }; |
|
356 | 356 | self.submitAjaxPOST( |
|
357 | 357 | self.submitUrl, postData, submitSuccessCallback, submitFailCallback); |
|
358 | 358 | }; |
|
359 | 359 | |
|
360 | 360 | this.previewSuccessCallback = function(o) { |
|
361 | 361 | $(self.previewBoxSelector).html(o); |
|
362 | 362 | $(self.previewBoxSelector).removeClass('unloaded'); |
|
363 | 363 | |
|
364 | 364 | // swap buttons, making preview active |
|
365 | 365 | $(self.previewButton).parent().addClass('active'); |
|
366 | 366 | $(self.editButton).parent().removeClass('active'); |
|
367 | 367 | |
|
368 | 368 | // unlock buttons |
|
369 | 369 | self.setActionButtonsDisabled(false); |
|
370 | 370 | }; |
|
371 | 371 | |
|
372 | 372 | this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) { |
|
373 | 373 | excludeCancelBtn = excludeCancelBtn || false; |
|
374 | 374 | submitEvent = submitEvent || false; |
|
375 | 375 | |
|
376 | 376 | $(this.editButton).prop('disabled', state); |
|
377 | 377 | $(this.previewButton).prop('disabled', state); |
|
378 | 378 | |
|
379 | 379 | if (!excludeCancelBtn) { |
|
380 | 380 | $(this.cancelButton).prop('disabled', state); |
|
381 | 381 | } |
|
382 | 382 | |
|
383 | 383 | var submitState = state; |
|
384 | 384 | if (!submitEvent && this.getCommentStatus() && !self.isInline()) { |
|
385 | 385 | // if the value of commit review status is set, we allow |
|
386 | 386 | // submit button, but only on Main form, isInline means inline |
|
387 | 387 | submitState = false |
|
388 | 388 | } |
|
389 | 389 | |
|
390 | 390 | $(this.submitButton).prop('disabled', submitState); |
|
391 | 391 | if (submitEvent) { |
|
392 | 392 | $(this.submitButton).val(_gettext('Submitting...')); |
|
393 | 393 | } else { |
|
394 | 394 | $(this.submitButton).val(this.submitButtonText); |
|
395 | 395 | } |
|
396 | 396 | |
|
397 | 397 | }; |
|
398 | 398 | |
|
399 | 399 | // lock preview/edit/submit buttons on load, but exclude cancel button |
|
400 | 400 | var excludeCancelBtn = true; |
|
401 | 401 | this.setActionButtonsDisabled(true, excludeCancelBtn); |
|
402 | 402 | |
|
403 | 403 | // anonymous users don't have access to initialized CM instance |
|
404 | 404 | if (this.cm !== undefined){ |
|
405 | 405 | this.cm.on('change', function(cMirror) { |
|
406 | 406 | if (cMirror.getValue() === "") { |
|
407 | 407 | self.setActionButtonsDisabled(true, excludeCancelBtn) |
|
408 | 408 | } else { |
|
409 | 409 | self.setActionButtonsDisabled(false, excludeCancelBtn) |
|
410 | 410 | } |
|
411 | 411 | }); |
|
412 | 412 | } |
|
413 | 413 | |
|
414 | 414 | $(this.editButton).on('click', function(e) { |
|
415 | 415 | e.preventDefault(); |
|
416 | 416 | |
|
417 | 417 | $(self.previewButton).parent().removeClass('active'); |
|
418 | 418 | $(self.previewContainer).hide(); |
|
419 | 419 | |
|
420 | 420 | $(self.editButton).parent().addClass('active'); |
|
421 | 421 | $(self.editContainer).show(); |
|
422 | 422 | |
|
423 | 423 | }); |
|
424 | 424 | |
|
425 | 425 | $(this.previewButton).on('click', function(e) { |
|
426 | 426 | e.preventDefault(); |
|
427 | 427 | var text = self.cm.getValue(); |
|
428 | 428 | |
|
429 | 429 | if (text === "") { |
|
430 | 430 | return; |
|
431 | 431 | } |
|
432 | 432 | |
|
433 | 433 | var postData = { |
|
434 | 434 | 'text': text, |
|
435 | 435 | 'renderer': templateContext.visual.default_renderer, |
|
436 | 436 | 'csrf_token': CSRF_TOKEN |
|
437 | 437 | }; |
|
438 | 438 | |
|
439 | 439 | // lock ALL buttons on preview |
|
440 | 440 | self.setActionButtonsDisabled(true); |
|
441 | 441 | |
|
442 | 442 | $(self.previewBoxSelector).addClass('unloaded'); |
|
443 | 443 | $(self.previewBoxSelector).html(_gettext('Loading ...')); |
|
444 | 444 | |
|
445 | 445 | $(self.editContainer).hide(); |
|
446 | 446 | $(self.previewContainer).show(); |
|
447 | 447 | |
|
448 | 448 | // by default we reset state of comment preserving the text |
|
449 | 449 | var previewFailCallback = function(data){ |
|
450 | 450 | alert( |
|
451 | 451 | "Error while preview of comment.\n" + |
|
452 | 452 | "Error code {0} ({1}).".format(data.status, data.statusText) |
|
453 | 453 | ); |
|
454 | 454 | self.resetCommentFormState(text) |
|
455 | 455 | }; |
|
456 | 456 | self.submitAjaxPOST( |
|
457 | 457 | self.previewUrl, postData, self.previewSuccessCallback, |
|
458 | 458 | previewFailCallback); |
|
459 | 459 | |
|
460 | 460 | $(self.previewButton).parent().addClass('active'); |
|
461 | 461 | $(self.editButton).parent().removeClass('active'); |
|
462 | 462 | }); |
|
463 | 463 | |
|
464 | 464 | $(this.submitForm).submit(function(e) { |
|
465 | 465 | e.preventDefault(); |
|
466 | 466 | var allowedToSubmit = self.isAllowedToSubmit(); |
|
467 | 467 | if (!allowedToSubmit){ |
|
468 | 468 | return false; |
|
469 | 469 | } |
|
470 | 470 | self.handleFormSubmit(); |
|
471 | 471 | }); |
|
472 | 472 | |
|
473 | 473 | } |
|
474 | 474 | |
|
475 | 475 | return CommentForm; |
|
476 | 476 | }); |
|
477 | 477 | |
|
478 | 478 | /* comments controller */ |
|
479 | 479 | var CommentsController = function() { |
|
480 | 480 | var mainComment = '#text'; |
|
481 | 481 | var self = this; |
|
482 | 482 | |
|
483 | 483 | this.cancelComment = function(node) { |
|
484 | 484 | var $node = $(node); |
|
485 | 485 | var $td = $node.closest('td'); |
|
486 | 486 | $node.closest('.comment-inline-form').remove(); |
|
487 | 487 | return false; |
|
488 | 488 | }; |
|
489 | 489 | |
|
490 | 490 | this.getLineNumber = function(node) { |
|
491 | 491 | var $node = $(node); |
|
492 | 492 | var lineNo = $node.closest('td').attr('data-line-no'); |
|
493 | 493 | if (lineNo === undefined && $node.data('commentInline')){ |
|
494 | 494 | lineNo = $node.data('commentLineNo') |
|
495 | 495 | } |
|
496 | 496 | |
|
497 | 497 | return lineNo |
|
498 | 498 | }; |
|
499 | 499 | |
|
500 | 500 | this.scrollToComment = function(node, offset, outdated) { |
|
501 | 501 | if (offset === undefined) { |
|
502 | 502 | offset = 0; |
|
503 | 503 | } |
|
504 | 504 | var outdated = outdated || false; |
|
505 | 505 | var klass = outdated ? 'div.comment-outdated' : 'div.comment-current'; |
|
506 | 506 | |
|
507 | 507 | if (!node) { |
|
508 | 508 | node = $('.comment-selected'); |
|
509 | 509 | if (!node.length) { |
|
510 | 510 | node = $('comment-current') |
|
511 | 511 | } |
|
512 | 512 | } |
|
513 | 513 | $wrapper = $(node).closest('div.comment'); |
|
514 | 514 | $comment = $(node).closest(klass); |
|
515 | 515 | $comments = $(klass); |
|
516 | 516 | |
|
517 | 517 | // show hidden comment when referenced. |
|
518 | 518 | if (!$wrapper.is(':visible')){ |
|
519 | 519 | $wrapper.show(); |
|
520 | 520 | } |
|
521 | 521 | |
|
522 | 522 | $('.comment-selected').removeClass('comment-selected'); |
|
523 | 523 | |
|
524 | 524 | var nextIdx = $(klass).index($comment) + offset; |
|
525 | 525 | if (nextIdx >= $comments.length) { |
|
526 | 526 | nextIdx = 0; |
|
527 | 527 | } |
|
528 | 528 | var $next = $(klass).eq(nextIdx); |
|
529 | 529 | |
|
530 | 530 | var $cb = $next.closest('.cb'); |
|
531 | 531 | $cb.removeClass('cb-collapsed'); |
|
532 | 532 | |
|
533 | 533 | var $filediffCollapseState = $cb.closest('.filediff').prev(); |
|
534 | 534 | $filediffCollapseState.prop('checked', false); |
|
535 | 535 | $next.addClass('comment-selected'); |
|
536 | 536 | scrollToElement($next); |
|
537 | 537 | return false; |
|
538 | 538 | }; |
|
539 | 539 | |
|
540 | 540 | this.nextComment = function(node) { |
|
541 | 541 | return self.scrollToComment(node, 1); |
|
542 | 542 | }; |
|
543 | 543 | |
|
544 | 544 | this.prevComment = function(node) { |
|
545 | 545 | return self.scrollToComment(node, -1); |
|
546 | 546 | }; |
|
547 | 547 | |
|
548 | 548 | this.nextOutdatedComment = function(node) { |
|
549 | 549 | return self.scrollToComment(node, 1, true); |
|
550 | 550 | }; |
|
551 | 551 | |
|
552 | 552 | this.prevOutdatedComment = function(node) { |
|
553 | 553 | return self.scrollToComment(node, -1, true); |
|
554 | 554 | }; |
|
555 | 555 | |
|
556 | 556 | this.deleteComment = function(node) { |
|
557 | 557 | if (!confirm(_gettext('Delete this comment?'))) { |
|
558 | 558 | return false; |
|
559 | 559 | } |
|
560 | 560 | var $node = $(node); |
|
561 | 561 | var $td = $node.closest('td'); |
|
562 | 562 | var $comment = $node.closest('.comment'); |
|
563 | 563 | var comment_id = $comment.attr('data-comment-id'); |
|
564 | 564 | var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id); |
|
565 | 565 | var postData = { |
|
566 | 566 | 'csrf_token': CSRF_TOKEN |
|
567 | 567 | }; |
|
568 | 568 | |
|
569 | 569 | $comment.addClass('comment-deleting'); |
|
570 | 570 | $comment.hide('fast'); |
|
571 | 571 | |
|
572 | 572 | var success = function(response) { |
|
573 | 573 | $comment.remove(); |
|
574 | 574 | return false; |
|
575 | 575 | }; |
|
576 | 576 | var failure = function(data, textStatus, xhr) { |
|
577 | 577 | alert("error processing request: " + textStatus); |
|
578 | 578 | $comment.show('fast'); |
|
579 | 579 | $comment.removeClass('comment-deleting'); |
|
580 | 580 | return false; |
|
581 | 581 | }; |
|
582 | 582 | ajaxPOST(url, postData, success, failure); |
|
583 | 583 | }; |
|
584 | 584 | |
|
585 | 585 | this.toggleWideMode = function (node) { |
|
586 | 586 | if ($('#content').hasClass('wrapper')) { |
|
587 | 587 | $('#content').removeClass("wrapper"); |
|
588 | 588 | $('#content').addClass("wide-mode-wrapper"); |
|
589 | 589 | $(node).addClass('btn-success'); |
|
590 | 590 | return true |
|
591 | 591 | } else { |
|
592 | 592 | $('#content').removeClass("wide-mode-wrapper"); |
|
593 | 593 | $('#content').addClass("wrapper"); |
|
594 | 594 | $(node).removeClass('btn-success'); |
|
595 | 595 | return false |
|
596 | 596 | } |
|
597 | 597 | |
|
598 | 598 | }; |
|
599 | 599 | |
|
600 | 600 | this.toggleComments = function(node, show) { |
|
601 | 601 | var $filediff = $(node).closest('.filediff'); |
|
602 | 602 | if (show === true) { |
|
603 | 603 | $filediff.removeClass('hide-comments'); |
|
604 | 604 | } else if (show === false) { |
|
605 | 605 | $filediff.find('.hide-line-comments').removeClass('hide-line-comments'); |
|
606 | 606 | $filediff.addClass('hide-comments'); |
|
607 | 607 | } else { |
|
608 | 608 | $filediff.find('.hide-line-comments').removeClass('hide-line-comments'); |
|
609 | 609 | $filediff.toggleClass('hide-comments'); |
|
610 | 610 | } |
|
611 | 611 | return false; |
|
612 | 612 | }; |
|
613 | 613 | |
|
614 | 614 | this.toggleLineComments = function(node) { |
|
615 | 615 | self.toggleComments(node, true); |
|
616 | 616 | var $node = $(node); |
|
617 | 617 | // mark outdated comments as visible before the toggle; |
|
618 | 618 | $(node.closest('tr')).find('.comment-outdated').show(); |
|
619 | 619 | $node.closest('tr').toggleClass('hide-line-comments'); |
|
620 | 620 | }; |
|
621 | 621 | |
|
622 | 622 | this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){ |
|
623 | 623 | var pullRequestId = templateContext.pull_request_data.pull_request_id; |
|
624 | 624 | var commitId = templateContext.commit_data.commit_id; |
|
625 | 625 | |
|
626 | 626 | var commentForm = new CommentForm( |
|
627 | 627 | formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId); |
|
628 | 628 | var cm = commentForm.getCmInstance(); |
|
629 | 629 | |
|
630 | 630 | if (resolvesCommentId){ |
|
631 | 631 | var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId); |
|
632 | 632 | } |
|
633 | 633 | |
|
634 | 634 | setTimeout(function() { |
|
635 | 635 | // callbacks |
|
636 | 636 | if (cm !== undefined) { |
|
637 | 637 | commentForm.setPlaceholder(placeholderText); |
|
638 | 638 | if (commentForm.isInline()) { |
|
639 | 639 | cm.focus(); |
|
640 | 640 | cm.refresh(); |
|
641 | 641 | } |
|
642 | 642 | } |
|
643 | 643 | }, 10); |
|
644 | 644 | |
|
645 | 645 | // trigger scrolldown to the resolve comment, since it might be away |
|
646 | 646 | // from the clicked |
|
647 | 647 | if (resolvesCommentId){ |
|
648 | 648 | var actionNode = $(commentForm.resolvesActionId).offset(); |
|
649 | 649 | |
|
650 | 650 | setTimeout(function() { |
|
651 | 651 | if (actionNode) { |
|
652 | 652 | $('body, html').animate({scrollTop: actionNode.top}, 10); |
|
653 | 653 | } |
|
654 | 654 | }, 100); |
|
655 | 655 | } |
|
656 | 656 | |
|
657 | // add dropzone support | |
|
658 | var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) { | |
|
659 | var renderer = templateContext.visual.default_renderer; | |
|
660 | if (renderer == 'rst') { | |
|
661 | var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl); | |
|
662 | if (isRendered){ | |
|
663 | attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl); | |
|
664 | } | |
|
665 | } else if (renderer == 'markdown') { | |
|
666 | var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl); | |
|
667 | if (isRendered){ | |
|
668 | attachmentUrl = '!' + attachmentUrl; | |
|
669 | } | |
|
670 | } else { | |
|
671 | var attachmentUrl = '{}'.format(attachmentStoreUrl); | |
|
672 | } | |
|
673 | cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine())); | |
|
674 | ||
|
675 | return false; | |
|
676 | }; | |
|
677 | ||
|
678 | //see: https://www.dropzonejs.com/#configuration | |
|
679 | var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload', | |
|
680 | {'repo_name': templateContext.repo_name, | |
|
681 | 'commit_id': templateContext.commit_data.commit_id}) | |
|
682 | ||
|
683 | var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0).innerHTML; | |
|
684 | var selectLink = $(formElement).find('.pick-attachment').get(0); | |
|
685 | $(formElement).find('.comment-attachment-uploader').dropzone({ | |
|
686 | url: storeUrl, | |
|
687 | headers: {"X-CSRF-Token": CSRF_TOKEN}, | |
|
688 | paramName: function () { | |
|
689 | return "attachment" | |
|
690 | }, // The name that will be used to transfer the file | |
|
691 | clickable: selectLink, | |
|
692 | parallelUploads: 1, | |
|
693 | maxFiles: 10, | |
|
694 | maxFilesize: templateContext.attachment_store.max_file_size_mb, | |
|
695 | uploadMultiple: false, | |
|
696 | autoProcessQueue: true, // if false queue will not be processed automatically. | |
|
697 | createImageThumbnails: false, | |
|
698 | previewTemplate: previewTmpl, | |
|
699 | ||
|
700 | accept: function (file, done) { | |
|
701 | done(); | |
|
702 | }, | |
|
703 | init: function () { | |
|
704 | ||
|
705 | this.on("sending", function (file, xhr, formData) { | |
|
706 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide(); | |
|
707 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show(); | |
|
708 | }); | |
|
709 | ||
|
710 | this.on("success", function (file, response) { | |
|
711 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show(); | |
|
712 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide(); | |
|
713 | ||
|
714 | var isRendered = false; | |
|
715 | var ext = file.name.split('.').pop(); | |
|
716 | var imageExts = templateContext.attachment_store.image_ext; | |
|
717 | if (imageExts.indexOf(ext) !== -1){ | |
|
718 | isRendered = true; | |
|
719 | } | |
|
720 | ||
|
721 | insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered) | |
|
722 | }); | |
|
723 | ||
|
724 | this.on("error", function (file, errorMessage, xhr) { | |
|
725 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide(); | |
|
726 | ||
|
727 | var error = null; | |
|
728 | ||
|
729 | if (xhr !== undefined){ | |
|
730 | var httpStatus = xhr.status + " " + xhr.statusText; | |
|
731 | if (xhr.status >= 500) { | |
|
732 | error = httpStatus; | |
|
733 | } | |
|
734 | } | |
|
735 | ||
|
736 | if (error === null) { | |
|
737 | error = errorMessage.error || errorMessage || httpStatus; | |
|
738 | } | |
|
739 | $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error)); | |
|
740 | ||
|
741 | }); | |
|
742 | } | |
|
743 | }); | |
|
744 | ||
|
745 | ||
|
657 | 746 | return commentForm; |
|
658 | 747 | }; |
|
659 | 748 | |
|
660 | 749 | this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) { |
|
661 | 750 | |
|
662 | 751 | var tmpl = $('#cb-comment-general-form-template').html(); |
|
663 | 752 | tmpl = tmpl.format(null, 'general'); |
|
664 | 753 | var $form = $(tmpl); |
|
665 | 754 | |
|
666 | 755 | var $formPlaceholder = $('#cb-comment-general-form-placeholder'); |
|
667 | 756 | var curForm = $formPlaceholder.find('form'); |
|
668 | 757 | if (curForm){ |
|
669 | 758 | curForm.remove(); |
|
670 | 759 | } |
|
671 | 760 | $formPlaceholder.append($form); |
|
672 | 761 | |
|
673 | 762 | var _form = $($form[0]); |
|
674 | 763 | var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo']; |
|
675 | 764 | var commentForm = this.createCommentForm( |
|
676 | 765 | _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId); |
|
677 | 766 | commentForm.initStatusChangeSelector(); |
|
678 | 767 | |
|
679 | 768 | return commentForm; |
|
680 | 769 | }; |
|
681 | 770 | |
|
682 | 771 | this.createComment = function(node, resolutionComment) { |
|
683 | 772 | var resolvesCommentId = resolutionComment || null; |
|
684 | 773 | var $node = $(node); |
|
685 | 774 | var $td = $node.closest('td'); |
|
686 | 775 | var $form = $td.find('.comment-inline-form'); |
|
687 | 776 | |
|
688 | 777 | if (!$form.length) { |
|
689 | 778 | |
|
690 | 779 | var $filediff = $node.closest('.filediff'); |
|
691 | 780 | $filediff.removeClass('hide-comments'); |
|
692 | 781 | var f_path = $filediff.attr('data-f-path'); |
|
693 | 782 | var lineno = self.getLineNumber(node); |
|
694 | 783 | // create a new HTML from template |
|
695 | 784 | var tmpl = $('#cb-comment-inline-form-template').html(); |
|
696 | 785 | tmpl = tmpl.format(escapeHtml(f_path), lineno); |
|
697 | 786 | $form = $(tmpl); |
|
698 | 787 | |
|
699 | 788 | var $comments = $td.find('.inline-comments'); |
|
700 | 789 | if (!$comments.length) { |
|
701 | 790 | $comments = $( |
|
702 | 791 | $('#cb-comments-inline-container-template').html()); |
|
703 | 792 | $td.append($comments); |
|
704 | 793 | } |
|
705 | 794 | |
|
706 | 795 | $td.find('.cb-comment-add-button').before($form); |
|
707 | 796 | |
|
708 | 797 | var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno); |
|
709 | 798 | var _form = $($form[0]).find('form'); |
|
710 | 799 | var autocompleteActions = ['as_note', 'as_todo']; |
|
711 | 800 | var commentForm = this.createCommentForm( |
|
712 | 801 | _form, lineno, placeholderText, autocompleteActions, resolvesCommentId); |
|
713 | 802 | |
|
714 | 803 | $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({ |
|
715 | 804 | form: _form, |
|
716 | 805 | parent: $td[0], |
|
717 | 806 | lineno: lineno, |
|
718 | 807 | f_path: f_path} |
|
719 | 808 | ); |
|
720 | 809 | |
|
721 | 810 | // set a CUSTOM submit handler for inline comments. |
|
722 | 811 | commentForm.setHandleFormSubmit(function(o) { |
|
723 | 812 | var text = commentForm.cm.getValue(); |
|
724 | 813 | var commentType = commentForm.getCommentType(); |
|
725 | 814 | var resolvesCommentId = commentForm.getResolvesId(); |
|
726 | 815 | |
|
727 | 816 | if (text === "") { |
|
728 | 817 | return; |
|
729 | 818 | } |
|
730 | 819 | |
|
731 | 820 | if (lineno === undefined) { |
|
732 | 821 | alert('missing line !'); |
|
733 | 822 | return; |
|
734 | 823 | } |
|
735 | 824 | if (f_path === undefined) { |
|
736 | 825 | alert('missing file path !'); |
|
737 | 826 | return; |
|
738 | 827 | } |
|
739 | 828 | |
|
740 | 829 | var excludeCancelBtn = false; |
|
741 | 830 | var submitEvent = true; |
|
742 | 831 | commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent); |
|
743 | 832 | commentForm.cm.setOption("readOnly", true); |
|
744 | 833 | var postData = { |
|
745 | 834 | 'text': text, |
|
746 | 835 | 'f_path': f_path, |
|
747 | 836 | 'line': lineno, |
|
748 | 837 | 'comment_type': commentType, |
|
749 | 838 | 'csrf_token': CSRF_TOKEN |
|
750 | 839 | }; |
|
751 | 840 | if (resolvesCommentId){ |
|
752 | 841 | postData['resolves_comment_id'] = resolvesCommentId; |
|
753 | 842 | } |
|
754 | 843 | |
|
755 | 844 | var submitSuccessCallback = function(json_data) { |
|
756 | 845 | $form.remove(); |
|
757 | 846 | try { |
|
758 | 847 | var html = json_data.rendered_text; |
|
759 | 848 | var lineno = json_data.line_no; |
|
760 | 849 | var target_id = json_data.target_id; |
|
761 | 850 | |
|
762 | 851 | $comments.find('.cb-comment-add-button').before(html); |
|
763 | 852 | |
|
764 | 853 | //mark visually which comment was resolved |
|
765 | 854 | if (resolvesCommentId) { |
|
766 | 855 | commentForm.markCommentResolved(resolvesCommentId); |
|
767 | 856 | } |
|
768 | 857 | |
|
769 | 858 | // run global callback on submit |
|
770 | 859 | commentForm.globalSubmitSuccessCallback(); |
|
771 | 860 | |
|
772 | 861 | } catch (e) { |
|
773 | 862 | console.error(e); |
|
774 | 863 | } |
|
775 | 864 | |
|
776 | 865 | // re trigger the linkification of next/prev navigation |
|
777 | 866 | linkifyComments($('.inline-comment-injected')); |
|
778 | 867 | timeagoActivate(); |
|
779 | 868 | |
|
780 | 869 | if (window.updateSticky !== undefined) { |
|
781 | 870 | // potentially our comments change the active window size, so we |
|
782 | 871 | // notify sticky elements |
|
783 | 872 | updateSticky() |
|
784 | 873 | } |
|
785 | 874 | |
|
786 | 875 | commentForm.setActionButtonsDisabled(false); |
|
787 | 876 | |
|
788 | 877 | }; |
|
789 | 878 | var submitFailCallback = function(data){ |
|
790 | 879 | alert( |
|
791 | 880 | "Error while submitting comment.\n" + |
|
792 | 881 | "Error code {0} ({1}).".format(data.status, data.statusText) |
|
793 | 882 | ); |
|
794 | 883 | commentForm.resetCommentFormState(text) |
|
795 | 884 | }; |
|
796 | 885 | commentForm.submitAjaxPOST( |
|
797 | 886 | commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback); |
|
798 | 887 | }); |
|
799 | 888 | } |
|
800 | 889 | |
|
801 | 890 | $form.addClass('comment-inline-form-open'); |
|
802 | 891 | }; |
|
803 | 892 | |
|
804 | 893 | this.createResolutionComment = function(commentId){ |
|
805 | 894 | // hide the trigger text |
|
806 | 895 | $('#resolve-comment-{0}'.format(commentId)).hide(); |
|
807 | 896 | |
|
808 | 897 | var comment = $('#comment-'+commentId); |
|
809 | 898 | var commentData = comment.data(); |
|
810 | 899 | if (commentData.commentInline) { |
|
811 | 900 | this.createComment(comment, commentId) |
|
812 | 901 | } else { |
|
813 | 902 | Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId) |
|
814 | 903 | } |
|
815 | 904 | |
|
816 | 905 | return false; |
|
817 | 906 | }; |
|
818 | 907 | |
|
819 | 908 | this.submitResolution = function(commentId){ |
|
820 | 909 | var form = $('#resolve_comment_{0}'.format(commentId)).closest('form'); |
|
821 | 910 | var commentForm = form.get(0).CommentForm; |
|
822 | 911 | |
|
823 | 912 | var cm = commentForm.getCmInstance(); |
|
824 | 913 | var renderer = templateContext.visual.default_renderer; |
|
825 | 914 | if (renderer == 'rst'){ |
|
826 | 915 | var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl); |
|
827 | 916 | } else if (renderer == 'markdown') { |
|
828 | 917 | var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl); |
|
829 | 918 | } else { |
|
830 | 919 | var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl); |
|
831 | 920 | } |
|
832 | 921 | |
|
833 | 922 | cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl)); |
|
834 | 923 | form.submit(); |
|
835 | 924 | return false; |
|
836 | 925 | }; |
|
837 | 926 | |
|
838 | 927 | }; |
@@ -1,159 +1,164 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <!DOCTYPE html> |
|
3 | 3 | |
|
4 | 4 | <% |
|
5 | 5 | c.template_context['repo_name'] = getattr(c, 'repo_name', '') |
|
6 | 6 | go_import_header = '' |
|
7 | 7 | if hasattr(c, 'rhodecode_db_repo'): |
|
8 | 8 | c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type |
|
9 | 9 | c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1] |
|
10 | 10 | c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id |
|
11 | 11 | c.template_context['repo_view_type'] = h.get_repo_view_type(request) |
|
12 | 12 | |
|
13 | 13 | if getattr(c, 'repo_group', None): |
|
14 | 14 | c.template_context['repo_group_id'] = c.repo_group.group_id |
|
15 | 15 | c.template_context['repo_group_name'] = c.repo_group.group_name |
|
16 | 16 | |
|
17 | 17 | if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id: |
|
18 | 18 | c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username |
|
19 | 19 | c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email |
|
20 | 20 | c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True) |
|
21 | 21 | c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name |
|
22 | 22 | c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name |
|
23 | 23 | |
|
24 | 24 | c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer') |
|
25 | 25 | c.template_context['default_user'] = { |
|
26 | 26 | 'username': h.DEFAULT_USER, |
|
27 | 27 | 'user_id': 1 |
|
28 | 28 | } |
|
29 | 29 | c.template_context['search_context'] = { |
|
30 | 30 | 'repo_group_id': c.template_context.get('repo_group_id'), |
|
31 | 31 | 'repo_group_name': c.template_context.get('repo_group_name'), |
|
32 | 32 | 'repo_id': c.template_context.get('repo_id'), |
|
33 | 33 | 'repo_name': c.template_context.get('repo_name'), |
|
34 | 34 | 'repo_view_type': c.template_context.get('repo_view_type'), |
|
35 | 35 | } |
|
36 | 36 | |
|
37 | c.template_context['attachment_store'] = { | |
|
38 | 'max_file_size_mb': 10, | |
|
39 | 'image_ext': ["png", "jpg", "gif", "jpeg"] | |
|
40 | } | |
|
41 | ||
|
37 | 42 | %> |
|
38 | 43 | <html xmlns="http://www.w3.org/1999/xhtml"> |
|
39 | 44 | <head> |
|
40 | 45 | <title>${self.title()}</title> |
|
41 | 46 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
|
42 | 47 | |
|
43 | 48 | ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))} |
|
44 | 49 | |
|
45 | 50 | % if 'safari' in (request.user_agent or '').lower(): |
|
46 | 51 | <meta name="referrer" content="origin"> |
|
47 | 52 | % else: |
|
48 | 53 | <meta name="referrer" content="origin-when-cross-origin"> |
|
49 | 54 | % endif |
|
50 | 55 | |
|
51 | 56 | <%def name="robots()"> |
|
52 | 57 | <meta name="robots" content="index, nofollow"/> |
|
53 | 58 | </%def> |
|
54 | 59 | ${self.robots()} |
|
55 | 60 | <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" /> |
|
56 | 61 | <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script> |
|
57 | 62 | <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script> |
|
58 | 63 | |
|
59 | 64 | ## CSS definitions |
|
60 | 65 | <%def name="css()"> |
|
61 | 66 | <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/> |
|
62 | 67 | ## EXTRA FOR CSS |
|
63 | 68 | ${self.css_extra()} |
|
64 | 69 | </%def> |
|
65 | 70 | ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites |
|
66 | 71 | <%def name="css_extra()"> |
|
67 | 72 | </%def> |
|
68 | 73 | |
|
69 | 74 | ${self.css()} |
|
70 | 75 | |
|
71 | 76 | ## JAVASCRIPT |
|
72 | 77 | <%def name="js()"> |
|
73 | 78 | |
|
74 | 79 | <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script> |
|
75 | 80 | <script type="text/javascript"> |
|
76 | 81 | // register templateContext to pass template variables to JS |
|
77 | 82 | var templateContext = ${h.json.dumps(c.template_context)|n}; |
|
78 | 83 | |
|
79 | 84 | var APPLICATION_URL = "${h.route_path('home').rstrip('/')}"; |
|
80 | 85 | var APPLICATION_PLUGINS = []; |
|
81 | 86 | var ASSET_URL = "${h.asset('')}"; |
|
82 | 87 | var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}"; |
|
83 | 88 | var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}"; |
|
84 | 89 | |
|
85 | 90 | var APPENLIGHT = { |
|
86 | 91 | enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'}, |
|
87 | 92 | key: '${getattr(c, "appenlight_api_public_key", "")}', |
|
88 | 93 | % if getattr(c, 'appenlight_server_url', None): |
|
89 | 94 | serverUrl: '${getattr(c, "appenlight_server_url", "")}', |
|
90 | 95 | % endif |
|
91 | 96 | requestInfo: { |
|
92 | 97 | % if getattr(c, 'rhodecode_user', None): |
|
93 | 98 | ip: '${c.rhodecode_user.ip_addr}', |
|
94 | 99 | username: '${c.rhodecode_user.username}' |
|
95 | 100 | % endif |
|
96 | 101 | }, |
|
97 | 102 | tags: { |
|
98 | 103 | rhodecode_version: '${c.rhodecode_version}', |
|
99 | 104 | rhodecode_edition: '${c.rhodecode_edition}' |
|
100 | 105 | } |
|
101 | 106 | }; |
|
102 | 107 | |
|
103 | 108 | </script> |
|
104 | 109 | <%include file="/base/plugins_base.mako"/> |
|
105 | 110 | <!--[if lt IE 9]> |
|
106 | 111 | <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script> |
|
107 | 112 | <![endif]--> |
|
108 | 113 | <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script> |
|
109 | 114 | <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script> |
|
110 | 115 | ## avoide escaping the %N |
|
111 | 116 | <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script> |
|
112 | 117 | <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script> |
|
113 | 118 | |
|
114 | 119 | |
|
115 | 120 | ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates |
|
116 | 121 | ${self.js_extra()} |
|
117 | 122 | |
|
118 | 123 | <script type="text/javascript"> |
|
119 | 124 | Rhodecode = (function() { |
|
120 | 125 | function _Rhodecode() { |
|
121 | 126 | this.comments = new CommentsController(); |
|
122 | 127 | } |
|
123 | 128 | return new _Rhodecode(); |
|
124 | 129 | })(); |
|
125 | 130 | |
|
126 | 131 | $(document).ready(function(){ |
|
127 | 132 | show_more_event(); |
|
128 | 133 | timeagoActivate(); |
|
129 | 134 | clipboardActivate(); |
|
130 | 135 | }) |
|
131 | 136 | </script> |
|
132 | 137 | |
|
133 | 138 | </%def> |
|
134 | 139 | |
|
135 | 140 | ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates |
|
136 | 141 | <%def name="js_extra()"></%def> |
|
137 | 142 | ${self.js()} |
|
138 | 143 | |
|
139 | 144 | <%def name="head_extra()"></%def> |
|
140 | 145 | ${self.head_extra()} |
|
141 | 146 | ## extra stuff |
|
142 | 147 | %if c.pre_code: |
|
143 | 148 | ${c.pre_code|n} |
|
144 | 149 | %endif |
|
145 | 150 | </head> |
|
146 | 151 | <body id="body"> |
|
147 | 152 | <noscript> |
|
148 | 153 | <div class="noscript-error"> |
|
149 | 154 | ${_('Please enable JavaScript to use RhodeCode Enterprise')} |
|
150 | 155 | </div> |
|
151 | 156 | </noscript> |
|
152 | 157 | |
|
153 | 158 | ${next.body()} |
|
154 | 159 | %if c.post_code: |
|
155 | 160 | ${c.post_code|n} |
|
156 | 161 | %endif |
|
157 | 162 | <rhodecode-app></rhodecode-app> |
|
158 | 163 | </body> |
|
159 | 164 | </html> |
@@ -1,404 +1,420 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | ## usage: |
|
3 | 3 | ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> |
|
4 | 4 | ## ${comment.comment_block(comment)} |
|
5 | 5 | ## |
|
6 | 6 | <%namespace name="base" file="/base/base.mako"/> |
|
7 | 7 | |
|
8 | 8 | <%def name="comment_block(comment, inline=False)"> |
|
9 | 9 | <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %> |
|
10 | 10 | <% latest_ver = len(getattr(c, 'versions', [])) %> |
|
11 | 11 | % if inline: |
|
12 | 12 | <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %> |
|
13 | 13 | % else: |
|
14 | 14 | <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %> |
|
15 | 15 | % endif |
|
16 | 16 | |
|
17 | 17 | |
|
18 | 18 | <div class="comment |
|
19 | 19 | ${'comment-inline' if inline else 'comment-general'} |
|
20 | 20 | ${'comment-outdated' if outdated_at_ver else 'comment-current'}" |
|
21 | 21 | id="comment-${comment.comment_id}" |
|
22 | 22 | line="${comment.line_no}" |
|
23 | 23 | data-comment-id="${comment.comment_id}" |
|
24 | 24 | data-comment-type="${comment.comment_type}" |
|
25 | 25 | data-comment-line-no="${comment.line_no}" |
|
26 | 26 | data-comment-inline=${h.json.dumps(inline)} |
|
27 | 27 | style="${'display: none;' if outdated_at_ver else ''}"> |
|
28 | 28 | |
|
29 | 29 | <div class="meta"> |
|
30 | 30 | <div class="comment-type-label"> |
|
31 | 31 | <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}"> |
|
32 | 32 | % if comment.comment_type == 'todo': |
|
33 | 33 | % if comment.resolved: |
|
34 | 34 | <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}"> |
|
35 | 35 | <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a> |
|
36 | 36 | </div> |
|
37 | 37 | % else: |
|
38 | 38 | <div class="resolved tooltip" style="display: none"> |
|
39 | 39 | <span>${comment.comment_type}</span> |
|
40 | 40 | </div> |
|
41 | 41 | <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}"> |
|
42 | 42 | ${comment.comment_type} |
|
43 | 43 | </div> |
|
44 | 44 | % endif |
|
45 | 45 | % else: |
|
46 | 46 | % if comment.resolved_comment: |
|
47 | 47 | fix |
|
48 | 48 | <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})"> |
|
49 | 49 | <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span> |
|
50 | 50 | </a> |
|
51 | 51 | % else: |
|
52 | 52 | ${comment.comment_type or 'note'} |
|
53 | 53 | % endif |
|
54 | 54 | % endif |
|
55 | 55 | </div> |
|
56 | 56 | </div> |
|
57 | 57 | |
|
58 | 58 | <div class="author ${'author-inline' if inline else 'author-general'}"> |
|
59 | 59 | ${base.gravatar_with_user(comment.author.email, 16)} |
|
60 | 60 | </div> |
|
61 | 61 | <div class="date"> |
|
62 | 62 | ${h.age_component(comment.modified_at, time_is_local=True)} |
|
63 | 63 | </div> |
|
64 | 64 | % if inline: |
|
65 | 65 | <span></span> |
|
66 | 66 | % else: |
|
67 | 67 | <div class="status-change"> |
|
68 | 68 | % if comment.pull_request: |
|
69 | 69 | <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}"> |
|
70 | 70 | % if comment.status_change: |
|
71 | 71 | ${_('pull request #%s') % comment.pull_request.pull_request_id}: |
|
72 | 72 | % else: |
|
73 | 73 | ${_('pull request #%s') % comment.pull_request.pull_request_id} |
|
74 | 74 | % endif |
|
75 | 75 | </a> |
|
76 | 76 | % else: |
|
77 | 77 | % if comment.status_change: |
|
78 | 78 | ${_('Status change on commit')}: |
|
79 | 79 | % endif |
|
80 | 80 | % endif |
|
81 | 81 | </div> |
|
82 | 82 | % endif |
|
83 | 83 | |
|
84 | 84 | % if comment.status_change: |
|
85 | 85 | <i class="icon-circle review-status-${comment.status_change[0].status}"></i> |
|
86 | 86 | <div title="${_('Commit status')}" class="changeset-status-lbl"> |
|
87 | 87 | ${comment.status_change[0].status_lbl} |
|
88 | 88 | </div> |
|
89 | 89 | % endif |
|
90 | 90 | |
|
91 | 91 | <a class="permalink" href="#comment-${comment.comment_id}"> ¶</a> |
|
92 | 92 | |
|
93 | 93 | <div class="comment-links-block"> |
|
94 | 94 | % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id: |
|
95 | 95 | <span class="tag authortag tooltip" title="${_('Pull request author')}"> |
|
96 | 96 | ${_('author')} |
|
97 | 97 | </span> |
|
98 | 98 | | |
|
99 | 99 | % endif |
|
100 | 100 | % if inline: |
|
101 | 101 | <div class="pr-version-inline"> |
|
102 | 102 | <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}"> |
|
103 | 103 | % if outdated_at_ver: |
|
104 | 104 | <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"> |
|
105 | 105 | outdated ${'v{}'.format(pr_index_ver)} | |
|
106 | 106 | </code> |
|
107 | 107 | % elif pr_index_ver: |
|
108 | 108 | <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"> |
|
109 | 109 | ${'v{}'.format(pr_index_ver)} | |
|
110 | 110 | </code> |
|
111 | 111 | % endif |
|
112 | 112 | </a> |
|
113 | 113 | </div> |
|
114 | 114 | % else: |
|
115 | 115 | % if comment.pull_request_version_id and pr_index_ver: |
|
116 | 116 | | |
|
117 | 117 | <div class="pr-version"> |
|
118 | 118 | % if comment.outdated: |
|
119 | 119 | <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"> |
|
120 | 120 | ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)} |
|
121 | 121 | </a> |
|
122 | 122 | % else: |
|
123 | 123 | <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"> |
|
124 | 124 | <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"> |
|
125 | 125 | <code class="pr-version-num"> |
|
126 | 126 | ${'v{}'.format(pr_index_ver)} |
|
127 | 127 | </code> |
|
128 | 128 | </a> |
|
129 | 129 | </div> |
|
130 | 130 | % endif |
|
131 | 131 | </div> |
|
132 | 132 | % endif |
|
133 | 133 | % endif |
|
134 | 134 | |
|
135 | 135 | ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed |
|
136 | 136 | ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated |
|
137 | 137 | %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())): |
|
138 | 138 | ## permissions to delete |
|
139 | 139 | %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id: |
|
140 | 140 | ## TODO: dan: add edit comment here |
|
141 | 141 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a> |
|
142 | 142 | %else: |
|
143 | 143 | <button class="btn-link" disabled="disabled"> ${_('Delete')}</button> |
|
144 | 144 | %endif |
|
145 | 145 | %else: |
|
146 | 146 | <button class="btn-link" disabled="disabled"> ${_('Delete')}</button> |
|
147 | 147 | %endif |
|
148 | 148 | |
|
149 | 149 | % if outdated_at_ver: |
|
150 | 150 | | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a> |
|
151 | 151 | | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a> |
|
152 | 152 | % else: |
|
153 | 153 | | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a> |
|
154 | 154 | | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a> |
|
155 | 155 | % endif |
|
156 | 156 | |
|
157 | 157 | </div> |
|
158 | 158 | </div> |
|
159 | 159 | <div class="text"> |
|
160 | 160 | ${h.render(comment.text, renderer=comment.renderer, mentions=True)} |
|
161 | 161 | </div> |
|
162 | 162 | |
|
163 | 163 | </div> |
|
164 | 164 | </%def> |
|
165 | 165 | |
|
166 | 166 | ## generate main comments |
|
167 | 167 | <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)"> |
|
168 | 168 | <div class="general-comments" id="comments"> |
|
169 | 169 | %for comment in comments: |
|
170 | 170 | <div id="comment-tr-${comment.comment_id}"> |
|
171 | 171 | ## only render comments that are not from pull request, or from |
|
172 | 172 | ## pull request and a status change |
|
173 | 173 | %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request: |
|
174 | 174 | ${comment_block(comment)} |
|
175 | 175 | %endif |
|
176 | 176 | </div> |
|
177 | 177 | %endfor |
|
178 | 178 | ## to anchor ajax comments |
|
179 | 179 | <div id="injected_page_comments"></div> |
|
180 | 180 | </div> |
|
181 | 181 | </%def> |
|
182 | 182 | |
|
183 | 183 | |
|
184 | 184 | <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)"> |
|
185 | 185 | |
|
186 | 186 | <div class="comments"> |
|
187 | 187 | <% |
|
188 | 188 | if is_pull_request: |
|
189 | 189 | placeholder = _('Leave a comment on this Pull Request.') |
|
190 | 190 | elif is_compare: |
|
191 | 191 | placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras)) |
|
192 | 192 | else: |
|
193 | 193 | placeholder = _('Leave a comment on this Commit.') |
|
194 | 194 | %> |
|
195 | 195 | |
|
196 | 196 | % if c.rhodecode_user.username != h.DEFAULT_USER: |
|
197 | 197 | <div class="js-template" id="cb-comment-general-form-template"> |
|
198 | 198 | ## template generated for injection |
|
199 | 199 | ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)} |
|
200 | 200 | </div> |
|
201 | 201 | |
|
202 | 202 | <div id="cb-comment-general-form-placeholder" class="comment-form ac"> |
|
203 | 203 | ## inject form here |
|
204 | 204 | </div> |
|
205 | 205 | <script type="text/javascript"> |
|
206 | 206 | var lineNo = 'general'; |
|
207 | 207 | var resolvesCommentId = null; |
|
208 | 208 | var generalCommentForm = Rhodecode.comments.createGeneralComment( |
|
209 | 209 | lineNo, "${placeholder}", resolvesCommentId); |
|
210 | 210 | |
|
211 | 211 | // set custom success callback on rangeCommit |
|
212 | 212 | % if is_compare: |
|
213 | 213 | generalCommentForm.setHandleFormSubmit(function(o) { |
|
214 | 214 | var self = generalCommentForm; |
|
215 | 215 | |
|
216 | 216 | var text = self.cm.getValue(); |
|
217 | 217 | var status = self.getCommentStatus(); |
|
218 | 218 | var commentType = self.getCommentType(); |
|
219 | 219 | |
|
220 | 220 | if (text === "" && !status) { |
|
221 | 221 | return; |
|
222 | 222 | } |
|
223 | 223 | |
|
224 | 224 | // we can pick which commits we want to make the comment by |
|
225 | 225 | // selecting them via click on preview pane, this will alter the hidden inputs |
|
226 | 226 | var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0; |
|
227 | 227 | |
|
228 | 228 | var commitIds = []; |
|
229 | 229 | $('#changeset_compare_view_content .compare_select').each(function(el) { |
|
230 | 230 | var commitId = this.id.replace('row-', ''); |
|
231 | 231 | if ($(this).hasClass('hl') || !cherryPicked) { |
|
232 | 232 | $("input[data-commit-id='{0}']".format(commitId)).val(commitId); |
|
233 | 233 | commitIds.push(commitId); |
|
234 | 234 | } else { |
|
235 | 235 | $("input[data-commit-id='{0}']".format(commitId)).val('') |
|
236 | 236 | } |
|
237 | 237 | }); |
|
238 | 238 | |
|
239 | 239 | self.setActionButtonsDisabled(true); |
|
240 | 240 | self.cm.setOption("readOnly", true); |
|
241 | 241 | var postData = { |
|
242 | 242 | 'text': text, |
|
243 | 243 | 'changeset_status': status, |
|
244 | 244 | 'comment_type': commentType, |
|
245 | 245 | 'commit_ids': commitIds, |
|
246 | 246 | 'csrf_token': CSRF_TOKEN |
|
247 | 247 | }; |
|
248 | 248 | |
|
249 | 249 | var submitSuccessCallback = function(o) { |
|
250 | 250 | location.reload(true); |
|
251 | 251 | }; |
|
252 | 252 | var submitFailCallback = function(){ |
|
253 | 253 | self.resetCommentFormState(text) |
|
254 | 254 | }; |
|
255 | 255 | self.submitAjaxPOST( |
|
256 | 256 | self.submitUrl, postData, submitSuccessCallback, submitFailCallback); |
|
257 | 257 | }); |
|
258 | 258 | % endif |
|
259 | 259 | |
|
260 | ||
|
261 | 260 | </script> |
|
262 | 261 | % else: |
|
263 | 262 | ## form state when not logged in |
|
264 | 263 | <div class="comment-form ac"> |
|
265 | 264 | |
|
266 | 265 | <div class="comment-area"> |
|
267 | 266 | <div class="comment-area-header"> |
|
268 | 267 | <ul class="nav-links clearfix"> |
|
269 | 268 | <li class="active"> |
|
270 | 269 | <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a> |
|
271 | 270 | </li> |
|
272 | 271 | <li class=""> |
|
273 | 272 | <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a> |
|
274 | 273 | </li> |
|
275 | 274 | </ul> |
|
276 | 275 | </div> |
|
277 | 276 | |
|
278 | 277 | <div class="comment-area-write" style="display: block;"> |
|
279 | 278 | <div id="edit-container"> |
|
280 | 279 | <div style="padding: 40px 0"> |
|
281 | 280 | ${_('You need to be logged in to leave comments.')} |
|
282 | 281 | <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a> |
|
283 | 282 | </div> |
|
284 | 283 | </div> |
|
285 | 284 | <div id="preview-container" class="clearfix" style="display: none;"> |
|
286 | 285 | <div id="preview-box" class="preview-box"></div> |
|
287 | 286 | </div> |
|
288 | 287 | </div> |
|
289 | 288 | |
|
290 | 289 | <div class="comment-area-footer"> |
|
291 | 290 | <div class="toolbar"> |
|
292 | 291 | <div class="toolbar-text"> |
|
293 | 292 | </div> |
|
294 | 293 | </div> |
|
295 | 294 | </div> |
|
296 | 295 | </div> |
|
297 | 296 | |
|
298 | 297 | <div class="comment-footer"> |
|
299 | 298 | </div> |
|
300 | 299 | |
|
301 | 300 | </div> |
|
302 | 301 | % endif |
|
303 | 302 | |
|
304 | 303 | <script type="text/javascript"> |
|
305 | 304 | bindToggleButtons(); |
|
306 | 305 | </script> |
|
307 | 306 | </div> |
|
308 | 307 | </%def> |
|
309 | 308 | |
|
310 | 309 | |
|
311 | 310 | <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)"> |
|
311 | ||
|
312 | 312 | ## comment injected based on assumption that user is logged in |
|
313 | ||
|
314 | 313 | <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET"> |
|
315 | 314 | |
|
316 | 315 | <div class="comment-area"> |
|
317 | 316 | <div class="comment-area-header"> |
|
318 | 317 | <ul class="nav-links clearfix"> |
|
319 | 318 | <li class="active"> |
|
320 | 319 | <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a> |
|
321 | 320 | </li> |
|
322 | 321 | <li class=""> |
|
323 | 322 | <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a> |
|
324 | 323 | </li> |
|
325 | 324 | <li class="pull-right"> |
|
326 | 325 | <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type"> |
|
327 | 326 | % for val in c.visual.comment_types: |
|
328 | 327 | <option value="${val}">${val.upper()}</option> |
|
329 | 328 | % endfor |
|
330 | 329 | </select> |
|
331 | 330 | </li> |
|
332 | 331 | </ul> |
|
333 | 332 | </div> |
|
334 | 333 | |
|
335 | 334 | <div class="comment-area-write" style="display: block;"> |
|
336 | 335 | <div id="edit-container_${lineno_id}"> |
|
337 | 336 | <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea> |
|
338 | 337 | </div> |
|
339 | 338 | <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;"> |
|
340 | 339 | <div id="preview-box_${lineno_id}" class="preview-box"></div> |
|
341 | 340 | </div> |
|
342 | 341 | </div> |
|
343 | 342 | |
|
344 | <div class="comment-area-footer"> | |
|
343 | <div class="comment-area-footer comment-attachment-uploader"> | |
|
345 | 344 | <div class="toolbar"> |
|
346 | 345 | <div class="toolbar-text"> |
|
347 | 346 | ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % ( |
|
348 | 347 | ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())), |
|
349 | 348 | ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')), |
|
350 | 349 | ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.')) |
|
351 | 350 | ) |
|
352 | 351 | )|n} |
|
353 | 352 | </div> |
|
353 | ||
|
354 | <div class="comment-attachment-text"> | |
|
355 | <div class="dropzone-text"> | |
|
356 | ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br> | |
|
357 | </div> | |
|
358 | <div class="dropzone-upload" style="display:none"> | |
|
359 | <i class="icon-spin animate-spin"></i> ${_('uploading...')} | |
|
360 | </div> | |
|
361 | </div> | |
|
362 | ||
|
363 | ## comments dropzone template, empty on purpose | |
|
364 | <div style="display: none" class="comment-attachment-uploader-template"> | |
|
365 | <div class="dz-file-preview" style="margin: 0"> | |
|
366 | <div class="dz-error-message"></div> | |
|
367 | </div> | |
|
368 | </div> | |
|
369 | ||
|
354 | 370 | </div> |
|
355 | 371 | </div> |
|
356 | 372 | </div> |
|
357 | 373 | |
|
358 | 374 | <div class="comment-footer"> |
|
359 | 375 | |
|
360 | 376 | % if review_statuses: |
|
361 | 377 | <div class="status_box"> |
|
362 | 378 | <select id="change_status_${lineno_id}" name="changeset_status"> |
|
363 | 379 | <option></option> ## Placeholder |
|
364 | 380 | % for status, lbl in review_statuses: |
|
365 | 381 | <option value="${status}" data-status="${status}">${lbl}</option> |
|
366 | 382 | %if is_pull_request and change_status and status in ('approved', 'rejected'): |
|
367 | 383 | <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option> |
|
368 | 384 | %endif |
|
369 | 385 | % endfor |
|
370 | 386 | </select> |
|
371 | 387 | </div> |
|
372 | 388 | % endif |
|
373 | 389 | |
|
374 | 390 | ## inject extra inputs into the form |
|
375 | 391 | % if form_extras and isinstance(form_extras, (list, tuple)): |
|
376 | 392 | <div id="comment_form_extras"> |
|
377 | 393 | % for form_ex_el in form_extras: |
|
378 | 394 | ${form_ex_el|n} |
|
379 | 395 | % endfor |
|
380 | 396 | </div> |
|
381 | 397 | % endif |
|
382 | 398 | |
|
383 | 399 | <div class="action-buttons"> |
|
384 | 400 | ## inline for has a file, and line-number together with cancel hide button. |
|
385 | 401 | % if form_type == 'inline': |
|
386 | 402 | <input type="hidden" name="f_path" value="{0}"> |
|
387 | 403 | <input type="hidden" name="line" value="${lineno_id}"> |
|
388 | 404 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> |
|
389 | 405 | ${_('Cancel')} |
|
390 | 406 | </button> |
|
391 | 407 | % endif |
|
392 | 408 | |
|
393 | 409 | % if form_type != 'inline': |
|
394 | 410 | <div class="action-buttons-extra"></div> |
|
395 | 411 | % endif |
|
396 | 412 | |
|
397 | 413 | ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')} |
|
398 | 414 | |
|
399 | 415 | </div> |
|
400 | 416 | </div> |
|
401 | 417 | |
|
402 | 418 | </form> |
|
403 | 419 | |
|
404 | 420 | </%def> No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now