Show More
@@ -0,0 +1,89 b'' | |||||
|
1 | |RCE| 4.23.0 |RNS| | |||
|
2 | ------------------ | |||
|
3 | ||||
|
4 | Release Date | |||
|
5 | ^^^^^^^^^^^^ | |||
|
6 | ||||
|
7 | - 2020-11-20 | |||
|
8 | ||||
|
9 | ||||
|
10 | New Features | |||
|
11 | ^^^^^^^^^^^^ | |||
|
12 | ||||
|
13 | - Comments: introduced new draft comments. | |||
|
14 | ||||
|
15 | * drafts are private to author | |||
|
16 | * not triggering any notifications | |||
|
17 | * sidebar doesn't display draft comments | |||
|
18 | * They are just placeholders for longer review. | |||
|
19 | ||||
|
20 | - Comments: when channelstream is enabled, comments are pushed live, so there's no | |||
|
21 | need to refresh page to see other participant comments. | |||
|
22 | New comments are marker in the sidebar. | |||
|
23 | ||||
|
24 | - Comments: multiple changes on comments navigation/display logic. | |||
|
25 | ||||
|
26 | * toggle icon is smarter, open/hide windows according to actions. E.g commenting opens threads | |||
|
27 | * toggle are mor explicit | |||
|
28 | * possible to hide/show only single threads using the toggle icon. | |||
|
29 | * new UI for showing thread comments | |||
|
30 | ||||
|
31 | - Reviewers: new logic for author/commit-author rules. | |||
|
32 | It's not possible to define if author or commit author should be excluded, or always included in a review. | |||
|
33 | - Reviewers: no reviewers would now allow a PR to be merged, unless review rules require some. | |||
|
34 | Use case is that pr can be created without review needed, maybe just for sharing, or CI checks | |||
|
35 | - Pull requests: save permanently the state if sorting columns for pull-request grids. | |||
|
36 | - Commit ranges: enable combined diff compare directly from range selector. | |||
|
37 | ||||
|
38 | ||||
|
39 | General | |||
|
40 | ^^^^^^^ | |||
|
41 | ||||
|
42 | - Authentication: enable custom names for auth plugins. It's possible to name the authentication | |||
|
43 | buttons now for SAML plugins. | |||
|
44 | - Login: optimized UI for login/register/password reset windows. | |||
|
45 | - Repo mapper: make it more resilient to errors, it's better it executes and skip certain | |||
|
46 | repositories, rather then crash whole mapper. | |||
|
47 | - Markdown: improved styling, and fixed nl2br extensions to only do br on new elements not inline. | |||
|
48 | - Pull requests: show pr version in the my-account and repo pr listing grids. | |||
|
49 | - Archives: allowing to obtain archives without the commit short id in the name for | |||
|
50 | better automation of obtained artifacts. | |||
|
51 | New url flag called `?=with_hash=1` controls this | |||
|
52 | - Error document: update info about stored exception retrieval. | |||
|
53 | - Range diff: enable hovercards for commits in range-diff. | |||
|
54 | ||||
|
55 | ||||
|
56 | Security | |||
|
57 | ^^^^^^^^ | |||
|
58 | ||||
|
59 | ||||
|
60 | ||||
|
61 | Performance | |||
|
62 | ^^^^^^^^^^^ | |||
|
63 | ||||
|
64 | - Improved logic of repo archive, now it's much faster to run archiver as VCSServer | |||
|
65 | communication was removed, and job is delegated to VCSServer itself. | |||
|
66 | - Improved VCSServer startup times. | |||
|
67 | - Notifications: skip double rendering just to generate email title/desc. | |||
|
68 | We'll re-use those now for better performance of creating notifications. | |||
|
69 | - App: improve logging, and remove DB calls on app startup. | |||
|
70 | ||||
|
71 | ||||
|
72 | Fixes | |||
|
73 | ^^^^^ | |||
|
74 | ||||
|
75 | - Login/register: fixed header width problem on mobile devices | |||
|
76 | - Exception tracker: don't fail on empty request in context of celery app for example. | |||
|
77 | - Exceptions: improved reporting of unhandled vcsserver exceptions. | |||
|
78 | - Sidebar: fixed refresh of TODOs url. | |||
|
79 | - Remap-rescan: fixes #5636 initial rescan problem. | |||
|
80 | - API: fixed SVN raw diff export. The API method was inconsistent, and used different logic. | |||
|
81 | Now it shares the same code as raw-diff from web-ui. | |||
|
82 | ||||
|
83 | ||||
|
84 | Upgrade notes | |||
|
85 | ^^^^^^^^^^^^^ | |||
|
86 | ||||
|
87 | - Scheduled feature release. | |||
|
88 | Please note that now the reviewers logic changed a bit, it's possible to create a pull request | |||
|
89 | Without any reviewers initially, and such pull request doesn't need to have an approval for merging. |
@@ -0,0 +1,53 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | import logging | |||
|
4 | from sqlalchemy import * | |||
|
5 | ||||
|
6 | from alembic.migration import MigrationContext | |||
|
7 | from alembic.operations import Operations | |||
|
8 | ||||
|
9 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
|
10 | from rhodecode.model import meta, init_model_encryption | |||
|
11 | ||||
|
12 | ||||
|
13 | log = logging.getLogger(__name__) | |||
|
14 | ||||
|
15 | ||||
|
16 | def upgrade(migrate_engine): | |||
|
17 | """ | |||
|
18 | Upgrade operations go here. | |||
|
19 | Don't create your own engine; bind migrate_engine to your metadata | |||
|
20 | """ | |||
|
21 | _reset_base(migrate_engine) | |||
|
22 | from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db | |||
|
23 | ||||
|
24 | init_model_encryption(db) | |||
|
25 | ||||
|
26 | context = MigrationContext.configure(migrate_engine.connect()) | |||
|
27 | op = Operations(context) | |||
|
28 | ||||
|
29 | table = db.ChangesetComment.__table__ | |||
|
30 | with op.batch_alter_table(table.name) as batch_op: | |||
|
31 | new_column = Column('draft', Boolean(), nullable=True) | |||
|
32 | batch_op.add_column(new_column) | |||
|
33 | ||||
|
34 | _set_default_as_non_draft(op, meta.Session) | |||
|
35 | ||||
|
36 | ||||
|
37 | def downgrade(migrate_engine): | |||
|
38 | meta = MetaData() | |||
|
39 | meta.bind = migrate_engine | |||
|
40 | ||||
|
41 | ||||
|
42 | def fixups(models, _SESSION): | |||
|
43 | pass | |||
|
44 | ||||
|
45 | ||||
|
46 | def _set_default_as_non_draft(op, session): | |||
|
47 | params = {'draft': False} | |||
|
48 | query = text( | |||
|
49 | 'UPDATE changeset_comments SET draft = :draft' | |||
|
50 | ).bindparams(**params) | |||
|
51 | op.execute(query) | |||
|
52 | session().commit() | |||
|
53 |
@@ -0,0 +1,78 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | import logging | |||
|
4 | from sqlalchemy import * | |||
|
5 | ||||
|
6 | from alembic.migration import MigrationContext | |||
|
7 | from alembic.operations import Operations | |||
|
8 | ||||
|
9 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
|
10 | from rhodecode.model import meta, init_model_encryption | |||
|
11 | ||||
|
12 | ||||
|
13 | log = logging.getLogger(__name__) | |||
|
14 | ||||
|
15 | ||||
|
16 | def upgrade(migrate_engine): | |||
|
17 | """ | |||
|
18 | Upgrade operations go here. | |||
|
19 | Don't create your own engine; bind migrate_engine to your metadata | |||
|
20 | """ | |||
|
21 | _reset_base(migrate_engine) | |||
|
22 | from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db | |||
|
23 | ||||
|
24 | init_model_encryption(db) | |||
|
25 | ||||
|
26 | context = MigrationContext.configure(migrate_engine.connect()) | |||
|
27 | op = Operations(context) | |||
|
28 | ||||
|
29 | table = db.RepoReviewRule.__table__ | |||
|
30 | with op.batch_alter_table(table.name) as batch_op: | |||
|
31 | ||||
|
32 | new_column = Column('pr_author', UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |||
|
33 | batch_op.add_column(new_column) | |||
|
34 | ||||
|
35 | new_column = Column('commit_author', UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |||
|
36 | batch_op.add_column(new_column) | |||
|
37 | ||||
|
38 | _migrate_review_flags_to_new_cols(op, meta.Session) | |||
|
39 | ||||
|
40 | ||||
|
41 | def downgrade(migrate_engine): | |||
|
42 | meta = MetaData() | |||
|
43 | meta.bind = migrate_engine | |||
|
44 | ||||
|
45 | ||||
|
46 | def fixups(models, _SESSION): | |||
|
47 | pass | |||
|
48 | ||||
|
49 | ||||
|
50 | def _migrate_review_flags_to_new_cols(op, session): | |||
|
51 | ||||
|
52 | # set defaults for pr_author | |||
|
53 | query = text( | |||
|
54 | 'UPDATE repo_review_rules SET pr_author = :val' | |||
|
55 | ).bindparams(val='no_rule') | |||
|
56 | op.execute(query) | |||
|
57 | ||||
|
58 | # set defaults for commit_author | |||
|
59 | query = text( | |||
|
60 | 'UPDATE repo_review_rules SET commit_author = :val' | |||
|
61 | ).bindparams(val='no_rule') | |||
|
62 | op.execute(query) | |||
|
63 | ||||
|
64 | session().commit() | |||
|
65 | ||||
|
66 | # now change the flags to forbid based on | |||
|
67 | # forbid_author_to_review, forbid_commit_author_to_review | |||
|
68 | query = text( | |||
|
69 | 'UPDATE repo_review_rules SET pr_author = :val WHERE forbid_author_to_review = TRUE' | |||
|
70 | ).bindparams(val='forbid_pr_author') | |||
|
71 | op.execute(query) | |||
|
72 | ||||
|
73 | query = text( | |||
|
74 | 'UPDATE repo_review_rules SET commit_author = :val WHERE forbid_commit_author_to_review = TRUE' | |||
|
75 | ).bindparams(val='forbid_commit_author') | |||
|
76 | op.execute(query) | |||
|
77 | ||||
|
78 | session().commit() |
@@ -1,5 +1,5 b'' | |||||
1 | [bumpversion] |
|
1 | [bumpversion] | |
2 |
current_version = 4.2 |
|
2 | current_version = 4.23.0 | |
3 | message = release: Bump version {current_version} to {new_version} |
|
3 | message = release: Bump version {current_version} to {new_version} | |
4 |
|
4 | |||
5 | [bumpversion:file:rhodecode/VERSION] |
|
5 | [bumpversion:file:rhodecode/VERSION] |
@@ -5,25 +5,20 b' done = false' | |||||
5 | done = true |
|
5 | done = true | |
6 |
|
6 | |||
7 | [task:rc_tools_pinned] |
|
7 | [task:rc_tools_pinned] | |
8 | done = true |
|
|||
9 |
|
8 | |||
10 | [task:fixes_on_stable] |
|
9 | [task:fixes_on_stable] | |
11 | done = true |
|
|||
12 |
|
10 | |||
13 | [task:pip2nix_generated] |
|
11 | [task:pip2nix_generated] | |
14 | done = true |
|
|||
15 |
|
12 | |||
16 | [task:changelog_updated] |
|
13 | [task:changelog_updated] | |
17 | done = true |
|
|||
18 |
|
14 | |||
19 | [task:generate_api_docs] |
|
15 | [task:generate_api_docs] | |
20 | done = true |
|
16 | ||
|
17 | [task:updated_translation] | |||
21 |
|
18 | |||
22 | [release] |
|
19 | [release] | |
23 |
state = |
|
20 | state = in_progress | |
24 |
version = 4.2 |
|
21 | version = 4.23.0 | |
25 |
|
||||
26 | [task:updated_translation] |
|
|||
27 |
|
22 | |||
28 | [task:generate_js_routes] |
|
23 | [task:generate_js_routes] | |
29 |
|
24 |
@@ -9,6 +9,7 b' Release Notes' | |||||
9 | .. toctree:: |
|
9 | .. toctree:: | |
10 | :maxdepth: 1 |
|
10 | :maxdepth: 1 | |
11 |
|
11 | |||
|
12 | release-notes-4.23.0.rst | |||
12 | release-notes-4.22.0.rst |
|
13 | release-notes-4.22.0.rst | |
13 | release-notes-4.21.0.rst |
|
14 | release-notes-4.21.0.rst | |
14 | release-notes-4.20.1.rst |
|
15 | release-notes-4.20.1.rst |
@@ -1883,7 +1883,7 b' self: super: {' | |||||
1883 | }; |
|
1883 | }; | |
1884 | }; |
|
1884 | }; | |
1885 | "rhodecode-enterprise-ce" = super.buildPythonPackage { |
|
1885 | "rhodecode-enterprise-ce" = super.buildPythonPackage { | |
1886 |
name = "rhodecode-enterprise-ce-4.2 |
|
1886 | name = "rhodecode-enterprise-ce-4.23.0"; | |
1887 | buildInputs = [ |
|
1887 | buildInputs = [ | |
1888 | self."pytest" |
|
1888 | self."pytest" | |
1889 | self."py" |
|
1889 | self."py" |
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}' | |||||
48 | EXTENSIONS = {} |
|
48 | EXTENSIONS = {} | |
49 |
|
49 | |||
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) | |
51 |
__dbversion__ = 11 |
|
51 | __dbversion__ = 112 # defines current db version for migrations | |
52 | __platform__ = platform.system() |
|
52 | __platform__ = platform.system() | |
53 | __license__ = 'AGPLv3, and Commercial License' |
|
53 | __license__ = 'AGPLv3, and Commercial License' | |
54 | __author__ = 'RhodeCode GmbH' |
|
54 | __author__ = 'RhodeCode GmbH' |
@@ -351,7 +351,10 b' def get_pull_request_or_error(pullreques' | |||||
351 | return pull_request |
|
351 | return pull_request | |
352 |
|
352 | |||
353 |
|
353 | |||
354 | def build_commit_data(commit, detail_level): |
|
354 | def build_commit_data(rhodecode_vcs_repo, commit, detail_level): | |
|
355 | commit2 = commit | |||
|
356 | commit1 = commit.first_parent | |||
|
357 | ||||
355 | parsed_diff = [] |
|
358 | parsed_diff = [] | |
356 | if detail_level == 'extended': |
|
359 | if detail_level == 'extended': | |
357 | for f_path in commit.added_paths: |
|
360 | for f_path in commit.added_paths: | |
@@ -362,8 +365,11 b' def build_commit_data(commit, detail_lev' | |||||
362 | parsed_diff.append(_get_commit_dict(filename=f_path, op='D')) |
|
365 | parsed_diff.append(_get_commit_dict(filename=f_path, op='D')) | |
363 |
|
366 | |||
364 | elif detail_level == 'full': |
|
367 | elif detail_level == 'full': | |
365 |
from rhodecode.lib |
|
368 | from rhodecode.lib import diffs | |
366 | diff_processor = DiffProcessor(commit.diff()) |
|
369 | ||
|
370 | _diff = rhodecode_vcs_repo.get_diff(commit1, commit2,) | |||
|
371 | diff_processor = diffs.DiffProcessor(_diff, format='newdiff', show_full_diff=True) | |||
|
372 | ||||
367 | for dp in diff_processor.prepare(): |
|
373 | for dp in diff_processor.prepare(): | |
368 | del dp['stats']['ops'] |
|
374 | del dp['stats']['ops'] | |
369 | _stats = dp['stats'] |
|
375 | _stats = dp['stats'] |
@@ -317,17 +317,18 b' def get_repo_changeset(request, apiuser,' | |||||
317 | 'ret_type must be one of %s' % ( |
|
317 | 'ret_type must be one of %s' % ( | |
318 | ','.join(_changes_details_types))) |
|
318 | ','.join(_changes_details_types))) | |
319 |
|
319 | |||
|
320 | vcs_repo = repo.scm_instance() | |||
320 | pre_load = ['author', 'branch', 'date', 'message', 'parents', |
|
321 | pre_load = ['author', 'branch', 'date', 'message', 'parents', | |
321 | 'status', '_commit', '_file_paths'] |
|
322 | 'status', '_commit', '_file_paths'] | |
322 |
|
323 | |||
323 | try: |
|
324 | try: | |
324 |
c |
|
325 | commit = repo.get_commit(commit_id=revision, pre_load=pre_load) | |
325 | except TypeError as e: |
|
326 | except TypeError as e: | |
326 | raise JSONRPCError(safe_str(e)) |
|
327 | raise JSONRPCError(safe_str(e)) | |
327 |
_cs_json = c |
|
328 | _cs_json = commit.__json__() | |
328 | _cs_json['diff'] = build_commit_data(cs, changes_details) |
|
329 | _cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details) | |
329 | if changes_details == 'full': |
|
330 | if changes_details == 'full': | |
330 |
_cs_json['refs'] = c |
|
331 | _cs_json['refs'] = commit._get_refs() | |
331 | return _cs_json |
|
332 | return _cs_json | |
332 |
|
333 | |||
333 |
|
334 | |||
@@ -398,7 +399,7 b' def get_repo_changesets(request, apiuser' | |||||
398 | if cnt >= limit != -1: |
|
399 | if cnt >= limit != -1: | |
399 | break |
|
400 | break | |
400 | _cs_json = commit.__json__() |
|
401 | _cs_json = commit.__json__() | |
401 | _cs_json['diff'] = build_commit_data(commit, changes_details) |
|
402 | _cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details) | |
402 | if changes_details == 'full': |
|
403 | if changes_details == 'full': | |
403 | _cs_json['refs'] = { |
|
404 | _cs_json['refs'] = { | |
404 | 'branches': [commit.branch], |
|
405 | 'branches': [commit.branch], |
@@ -36,7 +36,7 b' from rhodecode.authentication.plugins im' | |||||
36 | from rhodecode.events import trigger |
|
36 | from rhodecode.events import trigger | |
37 | from rhodecode.model.db import true, UserNotice |
|
37 | from rhodecode.model.db import true, UserNotice | |
38 |
|
38 | |||
39 | from rhodecode.lib import audit_logger, rc_cache |
|
39 | from rhodecode.lib import audit_logger, rc_cache, auth | |
40 | from rhodecode.lib.exceptions import ( |
|
40 | from rhodecode.lib.exceptions import ( | |
41 | UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException, |
|
41 | UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException, | |
42 | UserOwnsUserGroupsException, UserOwnsPullRequestsException, |
|
42 | UserOwnsUserGroupsException, UserOwnsPullRequestsException, | |
@@ -295,6 +295,10 b' class UsersView(UserAppView):' | |||||
295 | c.allowed_extern_types = [ |
|
295 | c.allowed_extern_types = [ | |
296 | (x.uid, x.get_display_name()) for x in self.get_auth_plugins() |
|
296 | (x.uid, x.get_display_name()) for x in self.get_auth_plugins() | |
297 | ] |
|
297 | ] | |
|
298 | perms = req.registry.settings.get('available_permissions') | |||
|
299 | if not perms: | |||
|
300 | # inject info about available permissions | |||
|
301 | auth.set_available_permissions(req.registry.settings) | |||
298 |
|
302 | |||
299 | c.available_permissions = req.registry.settings['available_permissions'] |
|
303 | c.available_permissions = req.registry.settings['available_permissions'] | |
300 | PermissionModel().set_global_permission_choices( |
|
304 | PermissionModel().set_global_permission_choices( |
@@ -252,7 +252,7 b' But please check this code' | |||||
252 | var comment = $('#comment-'+commentId); |
|
252 | var comment = $('#comment-'+commentId); | |
253 | var commentData = comment.data(); |
|
253 | var commentData = comment.data(); | |
254 | if (commentData.commentInline) { |
|
254 | if (commentData.commentInline) { | |
255 | this.createComment(comment, commentId) |
|
255 | this.createComment(comment, f_path, line_no, commentId) | |
256 | } else { |
|
256 | } else { | |
257 | Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId) |
|
257 | Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId) | |
258 | } |
|
258 | } |
@@ -702,7 +702,9 b' class MyAccountView(BaseAppView, DataGri' | |||||
702 | **valid_data) |
|
702 | **valid_data) | |
703 | if old_email != valid_data['email']: |
|
703 | if old_email != valid_data['email']: | |
704 | old = UserEmailMap.query() \ |
|
704 | old = UserEmailMap.query() \ | |
705 |
.filter(UserEmailMap.user == c.user) |
|
705 | .filter(UserEmailMap.user == c.user)\ | |
|
706 | .filter(UserEmailMap.email == valid_data['email'])\ | |||
|
707 | .first() | |||
706 | old.email = old_email |
|
708 | old.email = old_email | |
707 | h.flash(_('Your account was updated successfully'), category='success') |
|
709 | h.flash(_('Your account was updated successfully'), category='success') | |
708 | Session().commit() |
|
710 | Session().commit() | |
@@ -718,6 +720,7 b' class MyAccountView(BaseAppView, DataGri' | |||||
718 | def _get_pull_requests_list(self, statuses): |
|
720 | def _get_pull_requests_list(self, statuses): | |
719 | draw, start, limit = self._extract_chunk(self.request) |
|
721 | draw, start, limit = self._extract_chunk(self.request) | |
720 | search_q, order_by, order_dir = self._extract_ordering(self.request) |
|
722 | search_q, order_by, order_dir = self._extract_ordering(self.request) | |
|
723 | ||||
721 | _render = self.request.get_partial_renderer( |
|
724 | _render = self.request.get_partial_renderer( | |
722 | 'rhodecode:templates/data_table/_dt_elements.mako') |
|
725 | 'rhodecode:templates/data_table/_dt_elements.mako') | |
723 |
|
726 | |||
@@ -735,7 +738,7 b' class MyAccountView(BaseAppView, DataGri' | |||||
735 | for pr in pull_requests: |
|
738 | for pr in pull_requests: | |
736 | repo_id = pr.target_repo_id |
|
739 | repo_id = pr.target_repo_id | |
737 | comments_count = comments_model.get_all_comments( |
|
740 | comments_count = comments_model.get_all_comments( | |
738 | repo_id, pull_request=pr, count_only=True) |
|
741 | repo_id, pull_request=pr, include_drafts=False, count_only=True) | |
739 | owned = pr.user_id == self._rhodecode_user.user_id |
|
742 | owned = pr.user_id == self._rhodecode_user.user_id | |
740 |
|
743 | |||
741 | data.append({ |
|
744 | data.append({ | |
@@ -751,7 +754,8 b' class MyAccountView(BaseAppView, DataGri' | |||||
751 | 'title': _render('pullrequest_title', pr.title, pr.description), |
|
754 | 'title': _render('pullrequest_title', pr.title, pr.description), | |
752 | 'description': h.escape(pr.description), |
|
755 | 'description': h.escape(pr.description), | |
753 | 'updated_on': _render('pullrequest_updated_on', |
|
756 | 'updated_on': _render('pullrequest_updated_on', | |
754 |
h.datetime_to_time(pr.updated_on) |
|
757 | h.datetime_to_time(pr.updated_on), | |
|
758 | pr.versions_count), | |||
755 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), |
|
759 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), | |
756 | 'created_on': _render('pullrequest_updated_on', |
|
760 | 'created_on': _render('pullrequest_updated_on', | |
757 | h.datetime_to_time(pr.created_on)), |
|
761 | h.datetime_to_time(pr.created_on)), |
@@ -355,6 +355,11 b' def includeme(config):' | |||||
355 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos', |
|
355 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos', | |
356 | repo_route=True) |
|
356 | repo_route=True) | |
357 |
|
357 | |||
|
358 | config.add_route( | |||
|
359 | name='pullrequest_drafts', | |||
|
360 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/drafts', | |||
|
361 | repo_route=True) | |||
|
362 | ||||
358 | # Artifacts, (EE feature) |
|
363 | # Artifacts, (EE feature) | |
359 | config.add_route( |
|
364 | config.add_route( | |
360 | name='repo_artifacts_list', |
|
365 | name='repo_artifacts_list', |
@@ -608,23 +608,23 b' class TestPullrequestsView(object):' | |||||
608 | pull_request.source_repo, pull_request=pull_request) |
|
608 | pull_request.source_repo, pull_request=pull_request) | |
609 | assert status == ChangesetStatus.STATUS_REJECTED |
|
609 | assert status == ChangesetStatus.STATUS_REJECTED | |
610 |
|
610 | |||
611 |
comment_id |
|
611 | for comment_id in response.json.keys(): | |
612 | test_text = 'test' |
|
612 | test_text = 'test' | |
613 | response = self.app.post( |
|
613 | response = self.app.post( | |
614 | route_path( |
|
614 | route_path( | |
615 | 'pullrequest_comment_edit', |
|
615 | 'pullrequest_comment_edit', | |
616 | repo_name=target_scm_name, |
|
616 | repo_name=target_scm_name, | |
617 | pull_request_id=pull_request_id, |
|
617 | pull_request_id=pull_request_id, | |
618 | comment_id=comment_id, |
|
618 | comment_id=comment_id, | |
619 | ), |
|
619 | ), | |
620 | extra_environ=xhr_header, |
|
620 | extra_environ=xhr_header, | |
621 | params={ |
|
621 | params={ | |
622 | 'csrf_token': csrf_token, |
|
622 | 'csrf_token': csrf_token, | |
623 | 'text': test_text, |
|
623 | 'text': test_text, | |
624 | }, |
|
624 | }, | |
625 | status=403, |
|
625 | status=403, | |
626 | ) |
|
626 | ) | |
627 | assert response.status_int == 403 |
|
627 | assert response.status_int == 403 | |
628 |
|
628 | |||
629 | def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header): |
|
629 | def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header): | |
630 | pull_request = pr_util.create_pull_request() |
|
630 | pull_request = pr_util.create_pull_request() | |
@@ -644,27 +644,27 b' class TestPullrequestsView(object):' | |||||
644 | ) |
|
644 | ) | |
645 | assert response.json |
|
645 | assert response.json | |
646 |
|
646 | |||
647 |
comment_id |
|
647 | for comment_id in response.json.keys(): | |
648 | assert comment_id |
|
648 | assert comment_id | |
649 | test_text = 'test' |
|
649 | test_text = 'test' | |
650 | self.app.post( |
|
650 | self.app.post( | |
651 | route_path( |
|
651 | route_path( | |
652 | 'pullrequest_comment_edit', |
|
652 | 'pullrequest_comment_edit', | |
653 | repo_name=target_scm_name, |
|
653 | repo_name=target_scm_name, | |
654 | pull_request_id=pull_request.pull_request_id, |
|
654 | pull_request_id=pull_request.pull_request_id, | |
655 | comment_id=comment_id, |
|
655 | comment_id=comment_id, | |
656 | ), |
|
656 | ), | |
657 | extra_environ=xhr_header, |
|
657 | extra_environ=xhr_header, | |
658 | params={ |
|
658 | params={ | |
659 | 'csrf_token': csrf_token, |
|
659 | 'csrf_token': csrf_token, | |
660 | 'text': test_text, |
|
660 | 'text': test_text, | |
661 | 'version': '0', |
|
661 | 'version': '0', | |
662 | }, |
|
662 | }, | |
663 |
|
663 | |||
664 | ) |
|
664 | ) | |
665 | text_form_db = ChangesetComment.query().filter( |
|
665 | text_form_db = ChangesetComment.query().filter( | |
666 | ChangesetComment.comment_id == comment_id).first().text |
|
666 | ChangesetComment.comment_id == comment_id).first().text | |
667 | assert test_text == text_form_db |
|
667 | assert test_text == text_form_db | |
668 |
|
668 | |||
669 | def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header): |
|
669 | def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header): | |
670 | pull_request = pr_util.create_pull_request() |
|
670 | pull_request = pr_util.create_pull_request() | |
@@ -684,26 +684,25 b' class TestPullrequestsView(object):' | |||||
684 | ) |
|
684 | ) | |
685 | assert response.json |
|
685 | assert response.json | |
686 |
|
686 | |||
687 |
comment_id |
|
687 | for comment_id in response.json.keys(): | |
688 | assert comment_id |
|
688 | test_text = 'init' | |
689 | test_text = 'init' |
|
689 | response = self.app.post( | |
690 | response = self.app.post( |
|
690 | route_path( | |
691 | route_path( |
|
691 | 'pullrequest_comment_edit', | |
692 | 'pullrequest_comment_edit', |
|
692 | repo_name=target_scm_name, | |
693 | repo_name=target_scm_name, |
|
693 | pull_request_id=pull_request.pull_request_id, | |
694 | pull_request_id=pull_request.pull_request_id, |
|
694 | comment_id=comment_id, | |
695 |
|
|
695 | ), | |
696 | ), |
|
696 | extra_environ=xhr_header, | |
697 | extra_environ=xhr_header, |
|
697 | params={ | |
698 | params={ |
|
698 | 'csrf_token': csrf_token, | |
699 |
' |
|
699 | 'text': test_text, | |
700 |
' |
|
700 | 'version': '0', | |
701 |
|
|
701 | }, | |
702 |
|
|
702 | status=404, | |
703 | status=404, |
|
|||
704 |
|
703 | |||
705 | ) |
|
704 | ) | |
706 | assert response.status_int == 404 |
|
705 | assert response.status_int == 404 | |
707 |
|
706 | |||
708 | def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header): |
|
707 | def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header): | |
709 | pull_request = pr_util.create_pull_request() |
|
708 | pull_request = pr_util.create_pull_request() | |
@@ -722,48 +721,46 b' class TestPullrequestsView(object):' | |||||
722 | extra_environ=xhr_header, |
|
721 | extra_environ=xhr_header, | |
723 | ) |
|
722 | ) | |
724 | assert response.json |
|
723 | assert response.json | |
725 |
comment_id |
|
724 | for comment_id in response.json.keys(): | |
726 | assert comment_id |
|
725 | test_text = 'test' | |
727 |
|
726 | self.app.post( | ||
728 | test_text = 'test' |
|
727 | route_path( | |
729 | self.app.post( |
|
728 | 'pullrequest_comment_edit', | |
730 | route_path( |
|
729 | repo_name=target_scm_name, | |
731 |
|
|
730 | pull_request_id=pull_request.pull_request_id, | |
732 |
|
|
731 | comment_id=comment_id, | |
733 | pull_request_id=pull_request.pull_request_id, |
|
732 | ), | |
734 | comment_id=comment_id, |
|
733 | extra_environ=xhr_header, | |
735 |
|
|
734 | params={ | |
736 | extra_environ=xhr_header, |
|
735 | 'csrf_token': csrf_token, | |
737 | params={ |
|
736 | 'text': test_text, | |
738 |
' |
|
737 | 'version': '0', | |
739 |
|
|
738 | }, | |
740 | 'version': '0', |
|
|||
741 | }, |
|
|||
742 |
|
739 | |||
743 | ) |
|
740 | ) | |
744 | test_text_v2 = 'test_v2' |
|
741 | test_text_v2 = 'test_v2' | |
745 | response = self.app.post( |
|
742 | response = self.app.post( | |
746 | route_path( |
|
743 | route_path( | |
747 | 'pullrequest_comment_edit', |
|
744 | 'pullrequest_comment_edit', | |
748 | repo_name=target_scm_name, |
|
745 | repo_name=target_scm_name, | |
749 | pull_request_id=pull_request.pull_request_id, |
|
746 | pull_request_id=pull_request.pull_request_id, | |
750 | comment_id=comment_id, |
|
747 | comment_id=comment_id, | |
751 | ), |
|
748 | ), | |
752 | extra_environ=xhr_header, |
|
749 | extra_environ=xhr_header, | |
753 | params={ |
|
750 | params={ | |
754 | 'csrf_token': csrf_token, |
|
751 | 'csrf_token': csrf_token, | |
755 | 'text': test_text_v2, |
|
752 | 'text': test_text_v2, | |
756 | 'version': '0', |
|
753 | 'version': '0', | |
757 | }, |
|
754 | }, | |
758 | status=409, |
|
755 | status=409, | |
759 | ) |
|
756 | ) | |
760 | assert response.status_int == 409 |
|
757 | assert response.status_int == 409 | |
761 |
|
758 | |||
762 | text_form_db = ChangesetComment.query().filter( |
|
759 | text_form_db = ChangesetComment.query().filter( | |
763 | ChangesetComment.comment_id == comment_id).first().text |
|
760 | ChangesetComment.comment_id == comment_id).first().text | |
764 |
|
761 | |||
765 | assert test_text == text_form_db |
|
762 | assert test_text == text_form_db | |
766 | assert test_text_v2 != text_form_db |
|
763 | assert test_text_v2 != text_form_db | |
767 |
|
764 | |||
768 | def test_comment_and_comment_edit_permissions_forbidden( |
|
765 | def test_comment_and_comment_edit_permissions_forbidden( | |
769 | self, autologin_regular_user, user_regular, user_admin, pr_util, |
|
766 | self, autologin_regular_user, user_regular, user_admin, pr_util, |
@@ -24,7 +24,8 b' from rhodecode.model.pull_request import' | |||||
24 | from rhodecode.model.db import PullRequestReviewers |
|
24 | from rhodecode.model.db import PullRequestReviewers | |
25 | # V3 - Reviewers, with default rules data |
|
25 | # V3 - Reviewers, with default rules data | |
26 | # v4 - Added observers metadata |
|
26 | # v4 - Added observers metadata | |
27 | REVIEWER_API_VERSION = 'V4' |
|
27 | # v5 - pr_author/commit_author include/exclude logic | |
|
28 | REVIEWER_API_VERSION = 'V5' | |||
28 |
|
29 | |||
29 |
|
30 | |||
30 | def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None): |
|
31 | def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None): | |
@@ -88,6 +89,7 b' def get_default_reviewers_data(current_u' | |||||
88 | 'reviewers': json_reviewers, |
|
89 | 'reviewers': json_reviewers, | |
89 | 'rules': {}, |
|
90 | 'rules': {}, | |
90 | 'rules_data': {}, |
|
91 | 'rules_data': {}, | |
|
92 | 'rules_humanized': [], | |||
91 | } |
|
93 | } | |
92 |
|
94 | |||
93 |
|
95 |
@@ -77,6 +77,7 b' class RepoCommitsView(RepoAppView):' | |||||
77 | _ = self.request.translate |
|
77 | _ = self.request.translate | |
78 | c = self.load_default_context() |
|
78 | c = self.load_default_context() | |
79 | c.fulldiff = self.request.GET.get('fulldiff') |
|
79 | c.fulldiff = self.request.GET.get('fulldiff') | |
|
80 | redirect_to_combined = str2bool(self.request.GET.get('redirect_combined')) | |||
80 |
|
81 | |||
81 | # fetch global flags of ignore ws or context lines |
|
82 | # fetch global flags of ignore ws or context lines | |
82 | diff_context = get_diff_context(self.request) |
|
83 | diff_context = get_diff_context(self.request) | |
@@ -117,6 +118,19 b' class RepoCommitsView(RepoAppView):' | |||||
117 | raise HTTPNotFound() |
|
118 | raise HTTPNotFound() | |
118 | single_commit = len(c.commit_ranges) == 1 |
|
119 | single_commit = len(c.commit_ranges) == 1 | |
119 |
|
120 | |||
|
121 | if redirect_to_combined and not single_commit: | |||
|
122 | source_ref = getattr(c.commit_ranges[0].parents[0] | |||
|
123 | if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id') | |||
|
124 | target_ref = c.commit_ranges[-1].raw_id | |||
|
125 | next_url = h.route_path( | |||
|
126 | 'repo_compare', | |||
|
127 | repo_name=c.repo_name, | |||
|
128 | source_ref_type='rev', | |||
|
129 | source_ref=source_ref, | |||
|
130 | target_ref_type='rev', | |||
|
131 | target_ref=target_ref) | |||
|
132 | raise HTTPFound(next_url) | |||
|
133 | ||||
120 | c.changes = OrderedDict() |
|
134 | c.changes = OrderedDict() | |
121 | c.lines_added = 0 |
|
135 | c.lines_added = 0 | |
122 | c.lines_deleted = 0 |
|
136 | c.lines_deleted = 0 | |
@@ -366,6 +380,121 b' class RepoCommitsView(RepoAppView):' | |||||
366 | commit_id = self.request.matchdict['commit_id'] |
|
380 | commit_id = self.request.matchdict['commit_id'] | |
367 | return self._commit(commit_id, method='download') |
|
381 | return self._commit(commit_id, method='download') | |
368 |
|
382 | |||
|
383 | def _commit_comments_create(self, commit_id, comments): | |||
|
384 | _ = self.request.translate | |||
|
385 | data = {} | |||
|
386 | if not comments: | |||
|
387 | return | |||
|
388 | ||||
|
389 | commit = self.db_repo.get_commit(commit_id) | |||
|
390 | ||||
|
391 | all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments) | |||
|
392 | for entry in comments: | |||
|
393 | c = self.load_default_context() | |||
|
394 | comment_type = entry['comment_type'] | |||
|
395 | text = entry['text'] | |||
|
396 | status = entry['status'] | |||
|
397 | is_draft = str2bool(entry['is_draft']) | |||
|
398 | resolves_comment_id = entry['resolves_comment_id'] | |||
|
399 | f_path = entry['f_path'] | |||
|
400 | line_no = entry['line'] | |||
|
401 | target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path))) | |||
|
402 | ||||
|
403 | if status: | |||
|
404 | text = text or (_('Status change %(transition_icon)s %(status)s') | |||
|
405 | % {'transition_icon': '>', | |||
|
406 | 'status': ChangesetStatus.get_status_lbl(status)}) | |||
|
407 | ||||
|
408 | comment = CommentsModel().create( | |||
|
409 | text=text, | |||
|
410 | repo=self.db_repo.repo_id, | |||
|
411 | user=self._rhodecode_db_user.user_id, | |||
|
412 | commit_id=commit_id, | |||
|
413 | f_path=f_path, | |||
|
414 | line_no=line_no, | |||
|
415 | status_change=(ChangesetStatus.get_status_lbl(status) | |||
|
416 | if status else None), | |||
|
417 | status_change_type=status, | |||
|
418 | comment_type=comment_type, | |||
|
419 | is_draft=is_draft, | |||
|
420 | resolves_comment_id=resolves_comment_id, | |||
|
421 | auth_user=self._rhodecode_user, | |||
|
422 | send_email=not is_draft, # skip notification for draft comments | |||
|
423 | ) | |||
|
424 | is_inline = comment.is_inline | |||
|
425 | ||||
|
426 | # get status if set ! | |||
|
427 | if status: | |||
|
428 | # `dont_allow_on_closed_pull_request = True` means | |||
|
429 | # if latest status was from pull request and it's closed | |||
|
430 | # disallow changing status ! | |||
|
431 | ||||
|
432 | try: | |||
|
433 | ChangesetStatusModel().set_status( | |||
|
434 | self.db_repo.repo_id, | |||
|
435 | status, | |||
|
436 | self._rhodecode_db_user.user_id, | |||
|
437 | comment, | |||
|
438 | revision=commit_id, | |||
|
439 | dont_allow_on_closed_pull_request=True | |||
|
440 | ) | |||
|
441 | except StatusChangeOnClosedPullRequestError: | |||
|
442 | msg = _('Changing the status of a commit associated with ' | |||
|
443 | 'a closed pull request is not allowed') | |||
|
444 | log.exception(msg) | |||
|
445 | h.flash(msg, category='warning') | |||
|
446 | raise HTTPFound(h.route_path( | |||
|
447 | 'repo_commit', repo_name=self.db_repo_name, | |||
|
448 | commit_id=commit_id)) | |||
|
449 | ||||
|
450 | Session().flush() | |||
|
451 | # this is somehow required to get access to some relationship | |||
|
452 | # loaded on comment | |||
|
453 | Session().refresh(comment) | |||
|
454 | ||||
|
455 | # skip notifications for drafts | |||
|
456 | if not is_draft: | |||
|
457 | CommentsModel().trigger_commit_comment_hook( | |||
|
458 | self.db_repo, self._rhodecode_user, 'create', | |||
|
459 | data={'comment': comment, 'commit': commit}) | |||
|
460 | ||||
|
461 | comment_id = comment.comment_id | |||
|
462 | data[comment_id] = { | |||
|
463 | 'target_id': target_elem_id | |||
|
464 | } | |||
|
465 | Session().flush() | |||
|
466 | ||||
|
467 | c.co = comment | |||
|
468 | c.at_version_num = 0 | |||
|
469 | c.is_new = True | |||
|
470 | rendered_comment = render( | |||
|
471 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |||
|
472 | self._get_template_context(c), self.request) | |||
|
473 | ||||
|
474 | data[comment_id].update(comment.get_dict()) | |||
|
475 | data[comment_id].update({'rendered_text': rendered_comment}) | |||
|
476 | ||||
|
477 | # finalize, commit and redirect | |||
|
478 | Session().commit() | |||
|
479 | ||||
|
480 | # skip channelstream for draft comments | |||
|
481 | if not all_drafts: | |||
|
482 | comment_broadcast_channel = channelstream.comment_channel( | |||
|
483 | self.db_repo_name, commit_obj=commit) | |||
|
484 | ||||
|
485 | comment_data = data | |||
|
486 | posted_comment_type = 'inline' if is_inline else 'general' | |||
|
487 | if len(data) == 1: | |||
|
488 | msg = _('posted {} new {} comment').format(len(data), posted_comment_type) | |||
|
489 | else: | |||
|
490 | msg = _('posted {} new {} comments').format(len(data), posted_comment_type) | |||
|
491 | ||||
|
492 | channelstream.comment_channelstream_push( | |||
|
493 | self.request, comment_broadcast_channel, self._rhodecode_user, msg, | |||
|
494 | comment_data=comment_data) | |||
|
495 | ||||
|
496 | return data | |||
|
497 | ||||
369 | @LoginRequired() |
|
498 | @LoginRequired() | |
370 | @NotAnonymous() |
|
499 | @NotAnonymous() | |
371 | @HasRepoPermissionAnyDecorator( |
|
500 | @HasRepoPermissionAnyDecorator( | |
@@ -378,17 +507,6 b' class RepoCommitsView(RepoAppView):' | |||||
378 | _ = self.request.translate |
|
507 | _ = self.request.translate | |
379 | commit_id = self.request.matchdict['commit_id'] |
|
508 | commit_id = self.request.matchdict['commit_id'] | |
380 |
|
509 | |||
381 | c = self.load_default_context() |
|
|||
382 | status = self.request.POST.get('changeset_status', None) |
|
|||
383 | text = self.request.POST.get('text') |
|
|||
384 | comment_type = self.request.POST.get('comment_type') |
|
|||
385 | resolves_comment_id = self.request.POST.get('resolves_comment_id', None) |
|
|||
386 |
|
||||
387 | if status: |
|
|||
388 | text = text or (_('Status change %(transition_icon)s %(status)s') |
|
|||
389 | % {'transition_icon': '>', |
|
|||
390 | 'status': ChangesetStatus.get_status_lbl(status)}) |
|
|||
391 |
|
||||
392 | multi_commit_ids = [] |
|
510 | multi_commit_ids = [] | |
393 | for _commit_id in self.request.POST.get('commit_ids', '').split(','): |
|
511 | for _commit_id in self.request.POST.get('commit_ids', '').split(','): | |
394 | if _commit_id not in ['', None, EmptyCommit.raw_id]: |
|
512 | if _commit_id not in ['', None, EmptyCommit.raw_id]: | |
@@ -397,81 +515,23 b' class RepoCommitsView(RepoAppView):' | |||||
397 |
|
515 | |||
398 | commit_ids = multi_commit_ids or [commit_id] |
|
516 | commit_ids = multi_commit_ids or [commit_id] | |
399 |
|
517 | |||
400 |
|
|
518 | data = [] | |
|
519 | # Multiple comments for each passed commit id | |||
401 | for current_id in filter(None, commit_ids): |
|
520 | for current_id in filter(None, commit_ids): | |
402 |
comment = |
|
521 | comment_data = { | |
403 | text=text, |
|
522 | 'comment_type': self.request.POST.get('comment_type'), | |
404 | repo=self.db_repo.repo_id, |
|
523 | 'text': self.request.POST.get('text'), | |
405 | user=self._rhodecode_db_user.user_id, |
|
524 | 'status': self.request.POST.get('changeset_status', None), | |
406 | commit_id=current_id, |
|
525 | 'is_draft': self.request.POST.get('draft'), | |
407 |
|
|
526 | 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None), | |
408 |
|
|
527 | 'close_pull_request': self.request.POST.get('close_pull_request'), | |
409 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
528 | 'f_path': self.request.POST.get('f_path'), | |
410 | if status else None), |
|
529 | 'line': self.request.POST.get('line'), | |
411 | status_change_type=status, |
|
530 | } | |
412 | comment_type=comment_type, |
|
531 | comment = self._commit_comments_create(commit_id=current_id, comments=[comment_data]) | |
413 | resolves_comment_id=resolves_comment_id, |
|
532 | data.append(comment) | |
414 | auth_user=self._rhodecode_user |
|
|||
415 | ) |
|
|||
416 | is_inline = comment.is_inline |
|
|||
417 |
|
||||
418 | # get status if set ! |
|
|||
419 | if status: |
|
|||
420 | # if latest status was from pull request and it's closed |
|
|||
421 | # disallow changing status ! |
|
|||
422 | # dont_allow_on_closed_pull_request = True ! |
|
|||
423 |
|
533 | |||
424 | try: |
|
534 | return data if len(data) > 1 else data[0] | |
425 | ChangesetStatusModel().set_status( |
|
|||
426 | self.db_repo.repo_id, |
|
|||
427 | status, |
|
|||
428 | self._rhodecode_db_user.user_id, |
|
|||
429 | comment, |
|
|||
430 | revision=current_id, |
|
|||
431 | dont_allow_on_closed_pull_request=True |
|
|||
432 | ) |
|
|||
433 | except StatusChangeOnClosedPullRequestError: |
|
|||
434 | msg = _('Changing the status of a commit associated with ' |
|
|||
435 | 'a closed pull request is not allowed') |
|
|||
436 | log.exception(msg) |
|
|||
437 | h.flash(msg, category='warning') |
|
|||
438 | raise HTTPFound(h.route_path( |
|
|||
439 | 'repo_commit', repo_name=self.db_repo_name, |
|
|||
440 | commit_id=current_id)) |
|
|||
441 |
|
||||
442 | commit = self.db_repo.get_commit(current_id) |
|
|||
443 | CommentsModel().trigger_commit_comment_hook( |
|
|||
444 | self.db_repo, self._rhodecode_user, 'create', |
|
|||
445 | data={'comment': comment, 'commit': commit}) |
|
|||
446 |
|
||||
447 | # finalize, commit and redirect |
|
|||
448 | Session().commit() |
|
|||
449 |
|
||||
450 | data = { |
|
|||
451 | 'target_id': h.safeid(h.safe_unicode( |
|
|||
452 | self.request.POST.get('f_path'))), |
|
|||
453 | } |
|
|||
454 | if comment: |
|
|||
455 | c.co = comment |
|
|||
456 | c.at_version_num = 0 |
|
|||
457 | rendered_comment = render( |
|
|||
458 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
|||
459 | self._get_template_context(c), self.request) |
|
|||
460 |
|
||||
461 | data.update(comment.get_dict()) |
|
|||
462 | data.update({'rendered_text': rendered_comment}) |
|
|||
463 |
|
||||
464 | comment_broadcast_channel = channelstream.comment_channel( |
|
|||
465 | self.db_repo_name, commit_obj=commit) |
|
|||
466 |
|
||||
467 | comment_data = data |
|
|||
468 | comment_type = 'inline' if is_inline else 'general' |
|
|||
469 | channelstream.comment_channelstream_push( |
|
|||
470 | self.request, comment_broadcast_channel, self._rhodecode_user, |
|
|||
471 | _('posted a new {} comment').format(comment_type), |
|
|||
472 | comment_data=comment_data) |
|
|||
473 |
|
||||
474 | return data |
|
|||
475 |
|
535 | |||
476 | @LoginRequired() |
|
536 | @LoginRequired() | |
477 | @NotAnonymous() |
|
537 | @NotAnonymous() | |
@@ -665,6 +725,7 b' class RepoCommitsView(RepoAppView):' | |||||
665 | def repo_commit_comment_edit(self): |
|
725 | def repo_commit_comment_edit(self): | |
666 | self.load_default_context() |
|
726 | self.load_default_context() | |
667 |
|
727 | |||
|
728 | commit_id = self.request.matchdict['commit_id'] | |||
668 | comment_id = self.request.matchdict['comment_id'] |
|
729 | comment_id = self.request.matchdict['comment_id'] | |
669 | comment = ChangesetComment.get_or_404(comment_id) |
|
730 | comment = ChangesetComment.get_or_404(comment_id) | |
670 |
|
731 | |||
@@ -717,11 +778,11 b' class RepoCommitsView(RepoAppView):' | |||||
717 | if not comment_history: |
|
778 | if not comment_history: | |
718 | raise HTTPNotFound() |
|
779 | raise HTTPNotFound() | |
719 |
|
780 | |||
720 | commit_id = self.request.matchdict['commit_id'] |
|
781 | if not comment.draft: | |
721 | commit = self.db_repo.get_commit(commit_id) |
|
782 | commit = self.db_repo.get_commit(commit_id) | |
722 | CommentsModel().trigger_commit_comment_hook( |
|
783 | CommentsModel().trigger_commit_comment_hook( | |
723 | self.db_repo, self._rhodecode_user, 'edit', |
|
784 | self.db_repo, self._rhodecode_user, 'edit', | |
724 | data={'comment': comment, 'commit': commit}) |
|
785 | data={'comment': comment, 'commit': commit}) | |
725 |
|
786 | |||
726 | Session().commit() |
|
787 | Session().commit() | |
727 | return { |
|
788 | return { |
@@ -325,6 +325,21 b' class RepoFilesView(RepoAppView):' | |||||
325 |
|
325 | |||
326 | return lf_enabled |
|
326 | return lf_enabled | |
327 |
|
327 | |||
|
328 | def _get_archive_name(self, db_repo_name, commit_sha, ext, subrepos=False, path_sha=''): | |||
|
329 | # original backward compat name of archive | |||
|
330 | clean_name = safe_str(db_repo_name.replace('/', '_')) | |||
|
331 | ||||
|
332 | # e.g vcsserver.zip | |||
|
333 | # e.g vcsserver-abcdefgh.zip | |||
|
334 | # e.g vcsserver-abcdefgh-defghijk.zip | |||
|
335 | archive_name = '{}{}{}{}{}'.format( | |||
|
336 | clean_name, | |||
|
337 | '-sub' if subrepos else '', | |||
|
338 | commit_sha, | |||
|
339 | '-{}'.format(path_sha) if path_sha else '', | |||
|
340 | ext) | |||
|
341 | return archive_name | |||
|
342 | ||||
328 | @LoginRequired() |
|
343 | @LoginRequired() | |
329 | @HasRepoPermissionAnyDecorator( |
|
344 | @HasRepoPermissionAnyDecorator( | |
330 | 'repository.read', 'repository.write', 'repository.admin') |
|
345 | 'repository.read', 'repository.write', 'repository.admin') | |
@@ -339,6 +354,7 b' class RepoFilesView(RepoAppView):' | |||||
339 | default_at_path = '/' |
|
354 | default_at_path = '/' | |
340 | fname = self.request.matchdict['fname'] |
|
355 | fname = self.request.matchdict['fname'] | |
341 | subrepos = self.request.GET.get('subrepos') == 'true' |
|
356 | subrepos = self.request.GET.get('subrepos') == 'true' | |
|
357 | with_hash = str2bool(self.request.GET.get('with_hash', '1')) | |||
342 | at_path = self.request.GET.get('at_path') or default_at_path |
|
358 | at_path = self.request.GET.get('at_path') or default_at_path | |
343 |
|
359 | |||
344 | if not self.db_repo.enable_downloads: |
|
360 | if not self.db_repo.enable_downloads: | |
@@ -364,30 +380,30 b' class RepoFilesView(RepoAppView):' | |||||
364 | except Exception: |
|
380 | except Exception: | |
365 | return Response(_('No node at path {} for this repository').format(at_path)) |
|
381 | return Response(_('No node at path {} for this repository').format(at_path)) | |
366 |
|
382 | |||
367 | path_sha = sha1(at_path)[:8] |
|
383 | # path sha is part of subdir | |
368 |
|
384 | path_sha = '' | ||
369 | # original backward compat name of archive |
|
385 | if at_path != default_at_path: | |
370 | clean_name = safe_str(self.db_repo_name.replace('/', '_')) |
|
386 | path_sha = sha1(at_path)[:8] | |
371 | short_sha = safe_str(commit.short_id) |
|
387 | short_sha = '-{}'.format(safe_str(commit.short_id)) | |
|
388 | # used for cache etc | |||
|
389 | archive_name = self._get_archive_name( | |||
|
390 | self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, | |||
|
391 | path_sha=path_sha) | |||
372 |
|
392 | |||
373 | if at_path == default_at_path: |
|
393 | if not with_hash: | |
374 | archive_name = '{}-{}{}{}'.format( |
|
394 | short_sha = '' | |
375 | clean_name, |
|
395 | path_sha = '' | |
376 | '-sub' if subrepos else '', |
|
396 | ||
377 | short_sha, |
|
397 | # what end client gets served | |
378 | ext) |
|
398 | response_archive_name = self._get_archive_name( | |
379 | # custom path and new name |
|
399 | self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, | |
380 | else: |
|
400 | path_sha=path_sha) | |
381 | archive_name = '{}-{}{}-{}{}'.format( |
|
401 | # remove extension from our archive directory name | |
382 | clean_name, |
|
402 | archive_dir_name = response_archive_name[:-len(ext)] | |
383 | '-sub' if subrepos else '', |
|
|||
384 | short_sha, |
|
|||
385 | path_sha, |
|
|||
386 | ext) |
|
|||
387 |
|
403 | |||
388 | use_cached_archive = False |
|
404 | use_cached_archive = False | |
389 |
archive_cache_ |
|
405 | archive_cache_dir = CONFIG.get('archive_cache_dir') | |
390 |
|
|
406 | archive_cache_enabled = archive_cache_dir and not self.request.GET.get('no_cache') | |
391 | cached_archive_path = None |
|
407 | cached_archive_path = None | |
392 |
|
408 | |||
393 | if archive_cache_enabled: |
|
409 | if archive_cache_enabled: | |
@@ -403,12 +419,14 b' class RepoFilesView(RepoAppView):' | |||||
403 | else: |
|
419 | else: | |
404 | log.debug('Archive %s is not yet cached', archive_name) |
|
420 | log.debug('Archive %s is not yet cached', archive_name) | |
405 |
|
421 | |||
|
422 | # generate new archive, as previous was not found in the cache | |||
406 | if not use_cached_archive: |
|
423 | if not use_cached_archive: | |
407 | # generate new archive |
|
424 | _dir = os.path.abspath(archive_cache_dir) if archive_cache_dir else None | |
408 | fd, archive = tempfile.mkstemp() |
|
425 | fd, archive = tempfile.mkstemp(dir=_dir) | |
409 | log.debug('Creating new temp archive in %s', archive) |
|
426 | log.debug('Creating new temp archive in %s', archive) | |
410 | try: |
|
427 | try: | |
411 |
commit.archive_repo(archive, |
|
428 | commit.archive_repo(archive, archive_dir_name=archive_dir_name, | |
|
429 | kind=fileformat, subrepos=subrepos, | |||
412 | archive_at_path=at_path) |
|
430 | archive_at_path=at_path) | |
413 | except ImproperArchiveTypeError: |
|
431 | except ImproperArchiveTypeError: | |
414 | return _('Unknown archive type') |
|
432 | return _('Unknown archive type') | |
@@ -445,8 +463,7 b' class RepoFilesView(RepoAppView):' | |||||
445 | yield data |
|
463 | yield data | |
446 |
|
464 | |||
447 | response = Response(app_iter=get_chunked_archive(archive)) |
|
465 | response = Response(app_iter=get_chunked_archive(archive)) | |
448 | response.content_disposition = str( |
|
466 | response.content_disposition = str('attachment; filename=%s' % response_archive_name) | |
449 | 'attachment; filename=%s' % archive_name) |
|
|||
450 | response.content_type = str(content_type) |
|
467 | response.content_type = str(content_type) | |
451 |
|
468 | |||
452 | return response |
|
469 | return response |
@@ -47,7 +47,7 b' from rhodecode.lib.vcs.exceptions import' | |||||
47 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
47 | from rhodecode.model.changeset_status import ChangesetStatusModel | |
48 | from rhodecode.model.comment import CommentsModel |
|
48 | from rhodecode.model.comment import CommentsModel | |
49 | from rhodecode.model.db import ( |
|
49 | from rhodecode.model.db import ( | |
50 | func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository, |
|
50 | func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository, | |
51 | PullRequestReviewers) |
|
51 | PullRequestReviewers) | |
52 | from rhodecode.model.forms import PullRequestForm |
|
52 | from rhodecode.model.forms import PullRequestForm | |
53 | from rhodecode.model.meta import Session |
|
53 | from rhodecode.model.meta import Session | |
@@ -107,7 +107,8 b' class RepoPullRequestsView(RepoAppView, ' | |||||
107 | comments_model = CommentsModel() |
|
107 | comments_model = CommentsModel() | |
108 | for pr in pull_requests: |
|
108 | for pr in pull_requests: | |
109 | comments_count = comments_model.get_all_comments( |
|
109 | comments_count = comments_model.get_all_comments( | |
110 |
self.db_repo.repo_id, pull_request=pr, |
|
110 | self.db_repo.repo_id, pull_request=pr, | |
|
111 | include_drafts=False, count_only=True) | |||
111 |
|
112 | |||
112 | data.append({ |
|
113 | data.append({ | |
113 | 'name': _render('pullrequest_name', |
|
114 | 'name': _render('pullrequest_name', | |
@@ -120,7 +121,8 b' class RepoPullRequestsView(RepoAppView, ' | |||||
120 | 'title': _render('pullrequest_title', pr.title, pr.description), |
|
121 | 'title': _render('pullrequest_title', pr.title, pr.description), | |
121 | 'description': h.escape(pr.description), |
|
122 | 'description': h.escape(pr.description), | |
122 | 'updated_on': _render('pullrequest_updated_on', |
|
123 | 'updated_on': _render('pullrequest_updated_on', | |
123 |
h.datetime_to_time(pr.updated_on) |
|
124 | h.datetime_to_time(pr.updated_on), | |
|
125 | pr.versions_count), | |||
124 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), |
|
126 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), | |
125 | 'created_on': _render('pullrequest_updated_on', |
|
127 | 'created_on': _render('pullrequest_updated_on', | |
126 | h.datetime_to_time(pr.created_on)), |
|
128 | h.datetime_to_time(pr.created_on)), | |
@@ -268,12 +270,14 b' class RepoPullRequestsView(RepoAppView, ' | |||||
268 |
|
270 | |||
269 | return diffset |
|
271 | return diffset | |
270 |
|
272 | |||
271 | def register_comments_vars(self, c, pull_request, versions): |
|
273 | def register_comments_vars(self, c, pull_request, versions, include_drafts=True): | |
272 | comments_model = CommentsModel() |
|
274 | comments_model = CommentsModel() | |
273 |
|
275 | |||
274 | # GENERAL COMMENTS with versions # |
|
276 | # GENERAL COMMENTS with versions # | |
275 | q = comments_model._all_general_comments_of_pull_request(pull_request) |
|
277 | q = comments_model._all_general_comments_of_pull_request(pull_request) | |
276 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
278 | q = q.order_by(ChangesetComment.comment_id.asc()) | |
|
279 | if not include_drafts: | |||
|
280 | q = q.filter(ChangesetComment.draft == false()) | |||
277 | general_comments = q |
|
281 | general_comments = q | |
278 |
|
282 | |||
279 | # pick comments we want to render at current version |
|
283 | # pick comments we want to render at current version | |
@@ -283,6 +287,8 b' class RepoPullRequestsView(RepoAppView, ' | |||||
283 | # INLINE COMMENTS with versions # |
|
287 | # INLINE COMMENTS with versions # | |
284 | q = comments_model._all_inline_comments_of_pull_request(pull_request) |
|
288 | q = comments_model._all_inline_comments_of_pull_request(pull_request) | |
285 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
289 | q = q.order_by(ChangesetComment.comment_id.asc()) | |
|
290 | if not include_drafts: | |||
|
291 | q = q.filter(ChangesetComment.draft == false()) | |||
286 | inline_comments = q |
|
292 | inline_comments = q | |
287 |
|
293 | |||
288 | c.inline_versions = comments_model.aggregate_comments( |
|
294 | c.inline_versions = comments_model.aggregate_comments( | |
@@ -422,16 +428,12 b' class RepoPullRequestsView(RepoAppView, ' | |||||
422 | c.allowed_to_close = c.allowed_to_merge and not pr_closed |
|
428 | c.allowed_to_close = c.allowed_to_merge and not pr_closed | |
423 |
|
429 | |||
424 | c.forbid_adding_reviewers = False |
|
430 | c.forbid_adding_reviewers = False | |
425 | c.forbid_author_to_review = False |
|
|||
426 | c.forbid_commit_author_to_review = False |
|
|||
427 |
|
431 | |||
428 | if pull_request_latest.reviewer_data and \ |
|
432 | if pull_request_latest.reviewer_data and \ | |
429 | 'rules' in pull_request_latest.reviewer_data: |
|
433 | 'rules' in pull_request_latest.reviewer_data: | |
430 | rules = pull_request_latest.reviewer_data['rules'] or {} |
|
434 | rules = pull_request_latest.reviewer_data['rules'] or {} | |
431 | try: |
|
435 | try: | |
432 | c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers') |
|
436 | c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers') | |
433 | c.forbid_author_to_review = rules.get('forbid_author_to_review') |
|
|||
434 | c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review') |
|
|||
435 | except Exception: |
|
437 | except Exception: | |
436 | pass |
|
438 | pass | |
437 |
|
439 | |||
@@ -499,6 +501,11 b' class RepoPullRequestsView(RepoAppView, ' | |||||
499 | c.resolved_comments = CommentsModel() \ |
|
501 | c.resolved_comments = CommentsModel() \ | |
500 | .get_pull_request_resolved_todos(pull_request_latest) |
|
502 | .get_pull_request_resolved_todos(pull_request_latest) | |
501 |
|
503 | |||
|
504 | # Drafts | |||
|
505 | c.draft_comments = CommentsModel().get_pull_request_drafts( | |||
|
506 | self._rhodecode_db_user.user_id, | |||
|
507 | pull_request_latest) | |||
|
508 | ||||
502 | # if we use version, then do not show later comments |
|
509 | # if we use version, then do not show later comments | |
503 | # than current version |
|
510 | # than current version | |
504 | display_inline_comments = collections.defaultdict( |
|
511 | display_inline_comments = collections.defaultdict( | |
@@ -979,8 +986,9 b' class RepoPullRequestsView(RepoAppView, ' | |||||
979 | } |
|
986 | } | |
980 | return data |
|
987 | return data | |
981 |
|
988 | |||
982 | def _get_existing_ids(self, post_data): |
|
989 | @classmethod | |
983 | return filter(lambda e: e, map(safe_int, aslist(post_data.get('comments'), ','))) |
|
990 | def get_comment_ids(cls, post_data): | |
|
991 | return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ','))) | |||
984 |
|
992 | |||
985 | @LoginRequired() |
|
993 | @LoginRequired() | |
986 | @NotAnonymous() |
|
994 | @NotAnonymous() | |
@@ -1015,10 +1023,10 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1015 | if at_version and at_version != PullRequest.LATEST_VER |
|
1023 | if at_version and at_version != PullRequest.LATEST_VER | |
1016 | else None) |
|
1024 | else None) | |
1017 |
|
1025 | |||
1018 | self.register_comments_vars(c, pull_request_latest, versions) |
|
1026 | self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False) | |
1019 | all_comments = c.inline_comments_flat + c.comments |
|
1027 | all_comments = c.inline_comments_flat + c.comments | |
1020 |
|
1028 | |||
1021 |
existing_ids = self. |
|
1029 | existing_ids = self.get_comment_ids(self.request.POST) | |
1022 | return _render('comments_table', all_comments, len(all_comments), |
|
1030 | return _render('comments_table', all_comments, len(all_comments), | |
1023 | existing_ids=existing_ids) |
|
1031 | existing_ids=existing_ids) | |
1024 |
|
1032 | |||
@@ -1055,12 +1063,12 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1055 | else None) |
|
1063 | else None) | |
1056 |
|
1064 | |||
1057 | c.unresolved_comments = CommentsModel() \ |
|
1065 | c.unresolved_comments = CommentsModel() \ | |
1058 | .get_pull_request_unresolved_todos(pull_request) |
|
1066 | .get_pull_request_unresolved_todos(pull_request, include_drafts=False) | |
1059 | c.resolved_comments = CommentsModel() \ |
|
1067 | c.resolved_comments = CommentsModel() \ | |
1060 | .get_pull_request_resolved_todos(pull_request) |
|
1068 | .get_pull_request_resolved_todos(pull_request, include_drafts=False) | |
1061 |
|
1069 | |||
1062 | all_comments = c.unresolved_comments + c.resolved_comments |
|
1070 | all_comments = c.unresolved_comments + c.resolved_comments | |
1063 |
existing_ids = self. |
|
1071 | existing_ids = self.get_comment_ids(self.request.POST) | |
1064 | return _render('comments_table', all_comments, len(c.unresolved_comments), |
|
1072 | return _render('comments_table', all_comments, len(c.unresolved_comments), | |
1065 | todo_comments=True, existing_ids=existing_ids) |
|
1073 | todo_comments=True, existing_ids=existing_ids) | |
1066 |
|
1074 | |||
@@ -1068,6 +1076,48 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1068 | @NotAnonymous() |
|
1076 | @NotAnonymous() | |
1069 | @HasRepoPermissionAnyDecorator( |
|
1077 | @HasRepoPermissionAnyDecorator( | |
1070 | 'repository.read', 'repository.write', 'repository.admin') |
|
1078 | 'repository.read', 'repository.write', 'repository.admin') | |
|
1079 | @view_config( | |||
|
1080 | route_name='pullrequest_drafts', request_method='POST', | |||
|
1081 | renderer='string_html', xhr=True) | |||
|
1082 | def pullrequest_drafts(self): | |||
|
1083 | self.load_default_context() | |||
|
1084 | ||||
|
1085 | pull_request = PullRequest.get_or_404( | |||
|
1086 | self.request.matchdict['pull_request_id']) | |||
|
1087 | pull_request_id = pull_request.pull_request_id | |||
|
1088 | version = self.request.GET.get('version') | |||
|
1089 | ||||
|
1090 | _render = self.request.get_partial_renderer( | |||
|
1091 | 'rhodecode:templates/base/sidebar.mako') | |||
|
1092 | c = _render.get_call_context() | |||
|
1093 | ||||
|
1094 | (pull_request_latest, | |||
|
1095 | pull_request_at_ver, | |||
|
1096 | pull_request_display_obj, | |||
|
1097 | at_version) = PullRequestModel().get_pr_version( | |||
|
1098 | pull_request_id, version=version) | |||
|
1099 | versions = pull_request_display_obj.versions() | |||
|
1100 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |||
|
1101 | c.versions = versions + [latest_ver] | |||
|
1102 | ||||
|
1103 | c.at_version = at_version | |||
|
1104 | c.at_version_num = (at_version | |||
|
1105 | if at_version and at_version != PullRequest.LATEST_VER | |||
|
1106 | else None) | |||
|
1107 | ||||
|
1108 | c.draft_comments = CommentsModel() \ | |||
|
1109 | .get_pull_request_drafts(self._rhodecode_db_user.user_id, pull_request) | |||
|
1110 | ||||
|
1111 | all_comments = c.draft_comments | |||
|
1112 | ||||
|
1113 | existing_ids = self.get_comment_ids(self.request.POST) | |||
|
1114 | return _render('comments_table', all_comments, len(all_comments), | |||
|
1115 | existing_ids=existing_ids, draft_comments=True) | |||
|
1116 | ||||
|
1117 | @LoginRequired() | |||
|
1118 | @NotAnonymous() | |||
|
1119 | @HasRepoPermissionAnyDecorator( | |||
|
1120 | 'repository.read', 'repository.write', 'repository.admin') | |||
1071 | @CSRFRequired() |
|
1121 | @CSRFRequired() | |
1072 | @view_config( |
|
1122 | @view_config( | |
1073 | route_name='pullrequest_create', request_method='POST', |
|
1123 | route_name='pullrequest_create', request_method='POST', | |
@@ -1514,6 +1564,152 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1514 | self._rhodecode_user) |
|
1564 | self._rhodecode_user) | |
1515 | raise HTTPNotFound() |
|
1565 | raise HTTPNotFound() | |
1516 |
|
1566 | |||
|
1567 | def _pull_request_comments_create(self, pull_request, comments): | |||
|
1568 | _ = self.request.translate | |||
|
1569 | data = {} | |||
|
1570 | if not comments: | |||
|
1571 | return | |||
|
1572 | pull_request_id = pull_request.pull_request_id | |||
|
1573 | ||||
|
1574 | all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments) | |||
|
1575 | ||||
|
1576 | for entry in comments: | |||
|
1577 | c = self.load_default_context() | |||
|
1578 | comment_type = entry['comment_type'] | |||
|
1579 | text = entry['text'] | |||
|
1580 | status = entry['status'] | |||
|
1581 | is_draft = str2bool(entry['is_draft']) | |||
|
1582 | resolves_comment_id = entry['resolves_comment_id'] | |||
|
1583 | close_pull_request = entry['close_pull_request'] | |||
|
1584 | f_path = entry['f_path'] | |||
|
1585 | line_no = entry['line'] | |||
|
1586 | target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path))) | |||
|
1587 | ||||
|
1588 | # the logic here should work like following, if we submit close | |||
|
1589 | # pr comment, use `close_pull_request_with_comment` function | |||
|
1590 | # else handle regular comment logic | |||
|
1591 | ||||
|
1592 | if close_pull_request: | |||
|
1593 | # only owner or admin or person with write permissions | |||
|
1594 | allowed_to_close = PullRequestModel().check_user_update( | |||
|
1595 | pull_request, self._rhodecode_user) | |||
|
1596 | if not allowed_to_close: | |||
|
1597 | log.debug('comment: forbidden because not allowed to close ' | |||
|
1598 | 'pull request %s', pull_request_id) | |||
|
1599 | raise HTTPForbidden() | |||
|
1600 | ||||
|
1601 | # This also triggers `review_status_change` | |||
|
1602 | comment, status = PullRequestModel().close_pull_request_with_comment( | |||
|
1603 | pull_request, self._rhodecode_user, self.db_repo, message=text, | |||
|
1604 | auth_user=self._rhodecode_user) | |||
|
1605 | Session().flush() | |||
|
1606 | is_inline = comment.is_inline | |||
|
1607 | ||||
|
1608 | PullRequestModel().trigger_pull_request_hook( | |||
|
1609 | pull_request, self._rhodecode_user, 'comment', | |||
|
1610 | data={'comment': comment}) | |||
|
1611 | ||||
|
1612 | else: | |||
|
1613 | # regular comment case, could be inline, or one with status. | |||
|
1614 | # for that one we check also permissions | |||
|
1615 | # Additionally ENSURE if somehow draft is sent we're then unable to change status | |||
|
1616 | allowed_to_change_status = PullRequestModel().check_user_change_status( | |||
|
1617 | pull_request, self._rhodecode_user) and not is_draft | |||
|
1618 | ||||
|
1619 | if status and allowed_to_change_status: | |||
|
1620 | message = (_('Status change %(transition_icon)s %(status)s') | |||
|
1621 | % {'transition_icon': '>', | |||
|
1622 | 'status': ChangesetStatus.get_status_lbl(status)}) | |||
|
1623 | text = text or message | |||
|
1624 | ||||
|
1625 | comment = CommentsModel().create( | |||
|
1626 | text=text, | |||
|
1627 | repo=self.db_repo.repo_id, | |||
|
1628 | user=self._rhodecode_user.user_id, | |||
|
1629 | pull_request=pull_request, | |||
|
1630 | f_path=f_path, | |||
|
1631 | line_no=line_no, | |||
|
1632 | status_change=(ChangesetStatus.get_status_lbl(status) | |||
|
1633 | if status and allowed_to_change_status else None), | |||
|
1634 | status_change_type=(status | |||
|
1635 | if status and allowed_to_change_status else None), | |||
|
1636 | comment_type=comment_type, | |||
|
1637 | is_draft=is_draft, | |||
|
1638 | resolves_comment_id=resolves_comment_id, | |||
|
1639 | auth_user=self._rhodecode_user, | |||
|
1640 | send_email=not is_draft, # skip notification for draft comments | |||
|
1641 | ) | |||
|
1642 | is_inline = comment.is_inline | |||
|
1643 | ||||
|
1644 | if allowed_to_change_status: | |||
|
1645 | # calculate old status before we change it | |||
|
1646 | old_calculated_status = pull_request.calculated_review_status() | |||
|
1647 | ||||
|
1648 | # get status if set ! | |||
|
1649 | if status: | |||
|
1650 | ChangesetStatusModel().set_status( | |||
|
1651 | self.db_repo.repo_id, | |||
|
1652 | status, | |||
|
1653 | self._rhodecode_user.user_id, | |||
|
1654 | comment, | |||
|
1655 | pull_request=pull_request | |||
|
1656 | ) | |||
|
1657 | ||||
|
1658 | Session().flush() | |||
|
1659 | # this is somehow required to get access to some relationship | |||
|
1660 | # loaded on comment | |||
|
1661 | Session().refresh(comment) | |||
|
1662 | ||||
|
1663 | # skip notifications for drafts | |||
|
1664 | if not is_draft: | |||
|
1665 | PullRequestModel().trigger_pull_request_hook( | |||
|
1666 | pull_request, self._rhodecode_user, 'comment', | |||
|
1667 | data={'comment': comment}) | |||
|
1668 | ||||
|
1669 | # we now calculate the status of pull request, and based on that | |||
|
1670 | # calculation we set the commits status | |||
|
1671 | calculated_status = pull_request.calculated_review_status() | |||
|
1672 | if old_calculated_status != calculated_status: | |||
|
1673 | PullRequestModel().trigger_pull_request_hook( | |||
|
1674 | pull_request, self._rhodecode_user, 'review_status_change', | |||
|
1675 | data={'status': calculated_status}) | |||
|
1676 | ||||
|
1677 | comment_id = comment.comment_id | |||
|
1678 | data[comment_id] = { | |||
|
1679 | 'target_id': target_elem_id | |||
|
1680 | } | |||
|
1681 | Session().flush() | |||
|
1682 | ||||
|
1683 | c.co = comment | |||
|
1684 | c.at_version_num = None | |||
|
1685 | c.is_new = True | |||
|
1686 | rendered_comment = render( | |||
|
1687 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |||
|
1688 | self._get_template_context(c), self.request) | |||
|
1689 | ||||
|
1690 | data[comment_id].update(comment.get_dict()) | |||
|
1691 | data[comment_id].update({'rendered_text': rendered_comment}) | |||
|
1692 | ||||
|
1693 | Session().commit() | |||
|
1694 | ||||
|
1695 | # skip channelstream for draft comments | |||
|
1696 | if not all_drafts: | |||
|
1697 | comment_broadcast_channel = channelstream.comment_channel( | |||
|
1698 | self.db_repo_name, pull_request_obj=pull_request) | |||
|
1699 | ||||
|
1700 | comment_data = data | |||
|
1701 | posted_comment_type = 'inline' if is_inline else 'general' | |||
|
1702 | if len(data) == 1: | |||
|
1703 | msg = _('posted {} new {} comment').format(len(data), posted_comment_type) | |||
|
1704 | else: | |||
|
1705 | msg = _('posted {} new {} comments').format(len(data), posted_comment_type) | |||
|
1706 | ||||
|
1707 | channelstream.comment_channelstream_push( | |||
|
1708 | self.request, comment_broadcast_channel, self._rhodecode_user, msg, | |||
|
1709 | comment_data=comment_data) | |||
|
1710 | ||||
|
1711 | return data | |||
|
1712 | ||||
1517 | @LoginRequired() |
|
1713 | @LoginRequired() | |
1518 | @NotAnonymous() |
|
1714 | @NotAnonymous() | |
1519 | @HasRepoPermissionAnyDecorator( |
|
1715 | @HasRepoPermissionAnyDecorator( | |
@@ -1525,9 +1721,7 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1525 | def pull_request_comment_create(self): |
|
1721 | def pull_request_comment_create(self): | |
1526 | _ = self.request.translate |
|
1722 | _ = self.request.translate | |
1527 |
|
1723 | |||
1528 | pull_request = PullRequest.get_or_404( |
|
1724 | pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id']) | |
1529 | self.request.matchdict['pull_request_id']) |
|
|||
1530 | pull_request_id = pull_request.pull_request_id |
|
|||
1531 |
|
1725 | |||
1532 | if pull_request.is_closed(): |
|
1726 | if pull_request.is_closed(): | |
1533 | log.debug('comment: forbidden because pull request is closed') |
|
1727 | log.debug('comment: forbidden because pull request is closed') | |
@@ -1539,124 +1733,17 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1539 | log.debug('comment: forbidden because pull request is from forbidden repo') |
|
1733 | log.debug('comment: forbidden because pull request is from forbidden repo') | |
1540 | raise HTTPForbidden() |
|
1734 | raise HTTPForbidden() | |
1541 |
|
1735 | |||
1542 | c = self.load_default_context() |
|
1736 | comment_data = { | |
1543 |
|
1737 | 'comment_type': self.request.POST.get('comment_type'), | ||
1544 |
|
|
1738 | 'text': self.request.POST.get('text'), | |
1545 |
|
|
1739 | 'status': self.request.POST.get('changeset_status', None), | |
1546 |
|
|
1740 | 'is_draft': self.request.POST.get('draft'), | |
1547 |
resolves_comment_id |
|
1741 | 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None), | |
1548 |
close_pull_request |
|
1742 | 'close_pull_request': self.request.POST.get('close_pull_request'), | |
1549 |
|
1743 | 'f_path': self.request.POST.get('f_path'), | ||
1550 | # the logic here should work like following, if we submit close |
|
1744 | 'line': self.request.POST.get('line'), | |
1551 | # pr comment, use `close_pull_request_with_comment` function |
|
|||
1552 | # else handle regular comment logic |
|
|||
1553 |
|
||||
1554 | if close_pull_request: |
|
|||
1555 | # only owner or admin or person with write permissions |
|
|||
1556 | allowed_to_close = PullRequestModel().check_user_update( |
|
|||
1557 | pull_request, self._rhodecode_user) |
|
|||
1558 | if not allowed_to_close: |
|
|||
1559 | log.debug('comment: forbidden because not allowed to close ' |
|
|||
1560 | 'pull request %s', pull_request_id) |
|
|||
1561 | raise HTTPForbidden() |
|
|||
1562 |
|
||||
1563 | # This also triggers `review_status_change` |
|
|||
1564 | comment, status = PullRequestModel().close_pull_request_with_comment( |
|
|||
1565 | pull_request, self._rhodecode_user, self.db_repo, message=text, |
|
|||
1566 | auth_user=self._rhodecode_user) |
|
|||
1567 | Session().flush() |
|
|||
1568 | is_inline = comment.is_inline |
|
|||
1569 |
|
||||
1570 | PullRequestModel().trigger_pull_request_hook( |
|
|||
1571 | pull_request, self._rhodecode_user, 'comment', |
|
|||
1572 | data={'comment': comment}) |
|
|||
1573 |
|
||||
1574 | else: |
|
|||
1575 | # regular comment case, could be inline, or one with status. |
|
|||
1576 | # for that one we check also permissions |
|
|||
1577 |
|
||||
1578 | allowed_to_change_status = PullRequestModel().check_user_change_status( |
|
|||
1579 | pull_request, self._rhodecode_user) |
|
|||
1580 |
|
||||
1581 | if status and allowed_to_change_status: |
|
|||
1582 | message = (_('Status change %(transition_icon)s %(status)s') |
|
|||
1583 | % {'transition_icon': '>', |
|
|||
1584 | 'status': ChangesetStatus.get_status_lbl(status)}) |
|
|||
1585 | text = text or message |
|
|||
1586 |
|
||||
1587 | comment = CommentsModel().create( |
|
|||
1588 | text=text, |
|
|||
1589 | repo=self.db_repo.repo_id, |
|
|||
1590 | user=self._rhodecode_user.user_id, |
|
|||
1591 | pull_request=pull_request, |
|
|||
1592 | f_path=self.request.POST.get('f_path'), |
|
|||
1593 | line_no=self.request.POST.get('line'), |
|
|||
1594 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
|||
1595 | if status and allowed_to_change_status else None), |
|
|||
1596 | status_change_type=(status |
|
|||
1597 | if status and allowed_to_change_status else None), |
|
|||
1598 | comment_type=comment_type, |
|
|||
1599 | resolves_comment_id=resolves_comment_id, |
|
|||
1600 | auth_user=self._rhodecode_user |
|
|||
1601 | ) |
|
|||
1602 | is_inline = comment.is_inline |
|
|||
1603 |
|
||||
1604 | if allowed_to_change_status: |
|
|||
1605 | # calculate old status before we change it |
|
|||
1606 | old_calculated_status = pull_request.calculated_review_status() |
|
|||
1607 |
|
||||
1608 | # get status if set ! |
|
|||
1609 | if status: |
|
|||
1610 | ChangesetStatusModel().set_status( |
|
|||
1611 | self.db_repo.repo_id, |
|
|||
1612 | status, |
|
|||
1613 | self._rhodecode_user.user_id, |
|
|||
1614 | comment, |
|
|||
1615 | pull_request=pull_request |
|
|||
1616 | ) |
|
|||
1617 |
|
||||
1618 | Session().flush() |
|
|||
1619 | # this is somehow required to get access to some relationship |
|
|||
1620 | # loaded on comment |
|
|||
1621 | Session().refresh(comment) |
|
|||
1622 |
|
||||
1623 | PullRequestModel().trigger_pull_request_hook( |
|
|||
1624 | pull_request, self._rhodecode_user, 'comment', |
|
|||
1625 | data={'comment': comment}) |
|
|||
1626 |
|
||||
1627 | # we now calculate the status of pull request, and based on that |
|
|||
1628 | # calculation we set the commits status |
|
|||
1629 | calculated_status = pull_request.calculated_review_status() |
|
|||
1630 | if old_calculated_status != calculated_status: |
|
|||
1631 | PullRequestModel().trigger_pull_request_hook( |
|
|||
1632 | pull_request, self._rhodecode_user, 'review_status_change', |
|
|||
1633 | data={'status': calculated_status}) |
|
|||
1634 |
|
||||
1635 | Session().commit() |
|
|||
1636 |
|
||||
1637 | data = { |
|
|||
1638 | 'target_id': h.safeid(h.safe_unicode( |
|
|||
1639 | self.request.POST.get('f_path'))), |
|
|||
1640 | } |
|
1745 | } | |
1641 | if comment: |
|
1746 | data = self._pull_request_comments_create(pull_request, [comment_data]) | |
1642 | c.co = comment |
|
|||
1643 | c.at_version_num = None |
|
|||
1644 | rendered_comment = render( |
|
|||
1645 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
|||
1646 | self._get_template_context(c), self.request) |
|
|||
1647 |
|
||||
1648 | data.update(comment.get_dict()) |
|
|||
1649 | data.update({'rendered_text': rendered_comment}) |
|
|||
1650 |
|
||||
1651 | comment_broadcast_channel = channelstream.comment_channel( |
|
|||
1652 | self.db_repo_name, pull_request_obj=pull_request) |
|
|||
1653 |
|
||||
1654 | comment_data = data |
|
|||
1655 | comment_type = 'inline' if is_inline else 'general' |
|
|||
1656 | channelstream.comment_channelstream_push( |
|
|||
1657 | self.request, comment_broadcast_channel, self._rhodecode_user, |
|
|||
1658 | _('posted a new {} comment').format(comment_type), |
|
|||
1659 | comment_data=comment_data) |
|
|||
1660 |
|
1747 | |||
1661 | return data |
|
1748 | return data | |
1662 |
|
1749 | |||
@@ -1741,11 +1828,6 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1741 | log.debug('comment: forbidden because pull request is closed') |
|
1828 | log.debug('comment: forbidden because pull request is closed') | |
1742 | raise HTTPForbidden() |
|
1829 | raise HTTPForbidden() | |
1743 |
|
1830 | |||
1744 | if not comment: |
|
|||
1745 | log.debug('Comment with id:%s not found, skipping', comment_id) |
|
|||
1746 | # comment already deleted in another call probably |
|
|||
1747 | return True |
|
|||
1748 |
|
||||
1749 | if comment.pull_request.is_closed(): |
|
1831 | if comment.pull_request.is_closed(): | |
1750 | # don't allow deleting comments on closed pull request |
|
1832 | # don't allow deleting comments on closed pull request | |
1751 | raise HTTPForbidden() |
|
1833 | raise HTTPForbidden() | |
@@ -1796,10 +1878,10 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1796 | raise HTTPNotFound() |
|
1878 | raise HTTPNotFound() | |
1797 |
|
1879 | |||
1798 | Session().commit() |
|
1880 | Session().commit() | |
1799 |
|
1881 | if not comment.draft: | ||
1800 | PullRequestModel().trigger_pull_request_hook( |
|
1882 | PullRequestModel().trigger_pull_request_hook( | |
1801 | pull_request, self._rhodecode_user, 'comment_edit', |
|
1883 | pull_request, self._rhodecode_user, 'comment_edit', | |
1802 | data={'comment': comment}) |
|
1884 | data={'comment': comment}) | |
1803 |
|
1885 | |||
1804 | return { |
|
1886 | return { | |
1805 | 'comment_history_id': comment_history.comment_history_id, |
|
1887 | 'comment_history_id': comment_history.comment_history_id, |
@@ -215,9 +215,10 b' class RhodeCodeAuthPluginBase(object):' | |||||
215 | """ |
|
215 | """ | |
216 | return self._plugin_id |
|
216 | return self._plugin_id | |
217 |
|
217 | |||
218 | def get_display_name(self): |
|
218 | def get_display_name(self, load_from_settings=False): | |
219 | """ |
|
219 | """ | |
220 | Returns a translation string for displaying purposes. |
|
220 | Returns a translation string for displaying purposes. | |
|
221 | if load_from_settings is set, plugin settings can override the display name | |||
221 | """ |
|
222 | """ | |
222 | raise NotImplementedError('Not implemented in base class') |
|
223 | raise NotImplementedError('Not implemented in base class') | |
223 |
|
224 |
@@ -213,7 +213,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||||
213 | def get_settings_schema(self): |
|
213 | def get_settings_schema(self): | |
214 | return CrowdSettingsSchema() |
|
214 | return CrowdSettingsSchema() | |
215 |
|
215 | |||
216 | def get_display_name(self): |
|
216 | def get_display_name(self, load_from_settings=False): | |
217 | return _('CROWD') |
|
217 | return _('CROWD') | |
218 |
|
218 | |||
219 | @classmethod |
|
219 | @classmethod |
@@ -95,7 +95,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||||
95 | route_name='auth_home', |
|
95 | route_name='auth_home', | |
96 | context=HeadersAuthnResource) |
|
96 | context=HeadersAuthnResource) | |
97 |
|
97 | |||
98 | def get_display_name(self): |
|
98 | def get_display_name(self, load_from_settings=False): | |
99 | return _('Headers') |
|
99 | return _('Headers') | |
100 |
|
100 | |||
101 | def get_settings_schema(self): |
|
101 | def get_settings_schema(self): |
@@ -89,7 +89,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||||
89 | def get_settings_schema(self): |
|
89 | def get_settings_schema(self): | |
90 | return JasigCasSettingsSchema() |
|
90 | return JasigCasSettingsSchema() | |
91 |
|
91 | |||
92 | def get_display_name(self): |
|
92 | def get_display_name(self, load_from_settings=False): | |
93 | return _('Jasig-CAS') |
|
93 | return _('Jasig-CAS') | |
94 |
|
94 | |||
95 | @hybrid_property |
|
95 | @hybrid_property |
@@ -421,7 +421,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||||
421 | def get_settings_schema(self): |
|
421 | def get_settings_schema(self): | |
422 | return LdapSettingsSchema() |
|
422 | return LdapSettingsSchema() | |
423 |
|
423 | |||
424 | def get_display_name(self): |
|
424 | def get_display_name(self, load_from_settings=False): | |
425 | return _('LDAP') |
|
425 | return _('LDAP') | |
426 |
|
426 | |||
427 | @classmethod |
|
427 | @classmethod |
@@ -95,7 +95,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||||
95 | route_name='auth_home', |
|
95 | route_name='auth_home', | |
96 | context=PamAuthnResource) |
|
96 | context=PamAuthnResource) | |
97 |
|
97 | |||
98 | def get_display_name(self): |
|
98 | def get_display_name(self, load_from_settings=False): | |
99 | return _('PAM') |
|
99 | return _('PAM') | |
100 |
|
100 | |||
101 | @classmethod |
|
101 | @classmethod |
@@ -75,7 +75,7 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP' | |||||
75 | def get_settings_schema(self): |
|
75 | def get_settings_schema(self): | |
76 | return RhodeCodeSettingsSchema() |
|
76 | return RhodeCodeSettingsSchema() | |
77 |
|
77 | |||
78 | def get_display_name(self): |
|
78 | def get_display_name(self, load_from_settings=False): | |
79 | return _('RhodeCode Internal') |
|
79 | return _('RhodeCode Internal') | |
80 |
|
80 | |||
81 | @classmethod |
|
81 | @classmethod |
@@ -73,7 +73,7 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP' | |||||
73 | def get_settings_schema(self): |
|
73 | def get_settings_schema(self): | |
74 | return RhodeCodeSettingsSchema() |
|
74 | return RhodeCodeSettingsSchema() | |
75 |
|
75 | |||
76 | def get_display_name(self): |
|
76 | def get_display_name(self, load_from_settings=False): | |
77 | return _('Rhodecode Token') |
|
77 | return _('Rhodecode Token') | |
78 |
|
78 | |||
79 | @classmethod |
|
79 | @classmethod |
@@ -53,7 +53,7 b' from rhodecode.lib.utils2 import aslist ' | |||||
53 | from rhodecode.lib.exc_tracking import store_exception |
|
53 | from rhodecode.lib.exc_tracking import store_exception | |
54 | from rhodecode.subscribers import ( |
|
54 | from rhodecode.subscribers import ( | |
55 | scan_repositories_if_enabled, write_js_routes_if_enabled, |
|
55 | scan_repositories_if_enabled, write_js_routes_if_enabled, | |
56 |
write_metadata_if_needed, write_usage_data |
|
56 | write_metadata_if_needed, write_usage_data) | |
57 |
|
57 | |||
58 |
|
58 | |||
59 | log = logging.getLogger(__name__) |
|
59 | log = logging.getLogger(__name__) | |
@@ -310,8 +310,6 b' def includeme(config):' | |||||
310 |
|
310 | |||
311 | # Add subscribers. |
|
311 | # Add subscribers. | |
312 | if load_all: |
|
312 | if load_all: | |
313 | config.add_subscriber(inject_app_settings, |
|
|||
314 | pyramid.events.ApplicationCreated) |
|
|||
315 | config.add_subscriber(scan_repositories_if_enabled, |
|
313 | config.add_subscriber(scan_repositories_if_enabled, | |
316 | pyramid.events.ApplicationCreated) |
|
314 | pyramid.events.ApplicationCreated) | |
317 | config.add_subscriber(write_metadata_if_needed, |
|
315 | config.add_subscriber(write_metadata_if_needed, |
@@ -67,7 +67,7 b' markdown_tags = [' | |||||
67 |
|
67 | |||
68 | markdown_attrs = { |
|
68 | markdown_attrs = { | |
69 | "*": ["class", "style", "align"], |
|
69 | "*": ["class", "style", "align"], | |
70 | "img": ["src", "alt", "title"], |
|
70 | "img": ["src", "alt", "title", "width", "height", "hspace", "align"], | |
71 | "a": ["href", "alt", "title", "name", "data-hovercard-alt", "data-hovercard-url"], |
|
71 | "a": ["href", "alt", "title", "name", "data-hovercard-alt", "data-hovercard-url"], | |
72 | "abbr": ["title"], |
|
72 | "abbr": ["title"], | |
73 | "acronym": ["title"], |
|
73 | "acronym": ["title"], |
@@ -339,13 +339,12 b' def comment_channelstream_push(request, ' | |||||
339 |
|
339 | |||
340 | comment_data = kwargs.pop('comment_data', {}) |
|
340 | comment_data = kwargs.pop('comment_data', {}) | |
341 | user_data = kwargs.pop('user_data', {}) |
|
341 | user_data = kwargs.pop('user_data', {}) | |
342 |
comment_id = comment_data. |
|
342 | comment_id = comment_data.keys()[0] if comment_data else '' | |
343 |
|
343 | |||
344 |
message = '<strong>{}</strong> {} #{} |
|
344 | message = '<strong>{}</strong> {} #{}'.format( | |
345 | user.username, |
|
345 | user.username, | |
346 | msg, |
|
346 | msg, | |
347 | comment_id, |
|
347 | comment_id, | |
348 | _reload_link(_('Reload page to see new comments')), |
|
|||
349 | ) |
|
348 | ) | |
350 |
|
349 | |||
351 | message_obj = { |
|
350 | message_obj = { |
@@ -1148,7 +1148,7 b' class DiffLimitExceeded(Exception):' | |||||
1148 |
|
1148 | |||
1149 | # NOTE(marcink): if diffs.mako change, probably this |
|
1149 | # NOTE(marcink): if diffs.mako change, probably this | |
1150 | # needs a bump to next version |
|
1150 | # needs a bump to next version | |
1151 |
CURRENT_DIFF_VERSION = 'v |
|
1151 | CURRENT_DIFF_VERSION = 'v5' | |
1152 |
|
1152 | |||
1153 |
|
1153 | |||
1154 | def _cleanup_cache_file(cached_diff_file): |
|
1154 | def _cleanup_cache_file(cached_diff_file): |
@@ -110,7 +110,7 b' def _store_exception(exc_id, exc_type_na' | |||||
110 |
|
110 | |||
111 | mail_server = app.CONFIG.get('smtp_server') or None |
|
111 | mail_server = app.CONFIG.get('smtp_server') or None | |
112 | send_email = send_email and mail_server |
|
112 | send_email = send_email and mail_server | |
113 | if send_email: |
|
113 | if send_email and request: | |
114 | try: |
|
114 | try: | |
115 | send_exc_email(request, exc_id, exc_type_name) |
|
115 | send_exc_email(request, exc_id, exc_type_name) | |
116 | except Exception: |
|
116 | except Exception: |
@@ -18,17 +18,85 b'' | |||||
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
|
21 | import re | |||
21 | import markdown |
|
22 | import markdown | |
|
23 | import xml.etree.ElementTree as etree | |||
22 |
|
24 | |||
23 | from markdown.extensions import Extension |
|
25 | from markdown.extensions import Extension | |
24 | from markdown.extensions.fenced_code import FencedCodeExtension |
|
26 | from markdown.extensions.fenced_code import FencedCodeExtension | |
25 | from markdown.extensions.smart_strong import SmartEmphasisExtension |
|
27 | from markdown.extensions.smart_strong import SmartEmphasisExtension | |
26 | from markdown.extensions.tables import TableExtension |
|
28 | from markdown.extensions.tables import TableExtension | |
27 |
from markdown. |
|
29 | from markdown.inlinepatterns import Pattern | |
28 |
|
30 | |||
29 | import gfm |
|
31 | import gfm | |
30 |
|
32 | |||
31 |
|
33 | |||
|
34 | class InlineProcessor(Pattern): | |||
|
35 | """ | |||
|
36 | Base class that inline patterns subclass. | |||
|
37 | This is the newer style inline processor that uses a more | |||
|
38 | efficient and flexible search approach. | |||
|
39 | """ | |||
|
40 | ||||
|
41 | def __init__(self, pattern, md=None): | |||
|
42 | """ | |||
|
43 | Create an instant of an inline pattern. | |||
|
44 | Keyword arguments: | |||
|
45 | * pattern: A regular expression that matches a pattern | |||
|
46 | """ | |||
|
47 | self.pattern = pattern | |||
|
48 | self.compiled_re = re.compile(pattern, re.DOTALL | re.UNICODE) | |||
|
49 | ||||
|
50 | # Api for Markdown to pass safe_mode into instance | |||
|
51 | self.safe_mode = False | |||
|
52 | self.md = md | |||
|
53 | ||||
|
54 | def handleMatch(self, m, data): | |||
|
55 | """Return a ElementTree element from the given match and the | |||
|
56 | start and end index of the matched text. | |||
|
57 | If `start` and/or `end` are returned as `None`, it will be | |||
|
58 | assumed that the processor did not find a valid region of text. | |||
|
59 | Subclasses should override this method. | |||
|
60 | Keyword arguments: | |||
|
61 | * m: A re match object containing a match of the pattern. | |||
|
62 | * data: The buffer current under analysis | |||
|
63 | Returns: | |||
|
64 | * el: The ElementTree element, text or None. | |||
|
65 | * start: The start of the region that has been matched or None. | |||
|
66 | * end: The end of the region that has been matched or None. | |||
|
67 | """ | |||
|
68 | pass # pragma: no cover | |||
|
69 | ||||
|
70 | ||||
|
71 | class SimpleTagInlineProcessor(InlineProcessor): | |||
|
72 | """ | |||
|
73 | Return element of type `tag` with a text attribute of group(2) | |||
|
74 | of a Pattern. | |||
|
75 | """ | |||
|
76 | def __init__(self, pattern, tag): | |||
|
77 | InlineProcessor.__init__(self, pattern) | |||
|
78 | self.tag = tag | |||
|
79 | ||||
|
80 | def handleMatch(self, m, data): # pragma: no cover | |||
|
81 | el = etree.Element(self.tag) | |||
|
82 | el.text = m.group(2) | |||
|
83 | return el, m.start(0), m.end(0) | |||
|
84 | ||||
|
85 | ||||
|
86 | class SubstituteTagInlineProcessor(SimpleTagInlineProcessor): | |||
|
87 | """ Return an element of type `tag` with no children. """ | |||
|
88 | def handleMatch(self, m, data): | |||
|
89 | return etree.Element(self.tag), m.start(0), m.end(0) | |||
|
90 | ||||
|
91 | ||||
|
92 | class Nl2BrExtension(Extension): | |||
|
93 | BR_RE = r'\n' | |||
|
94 | ||||
|
95 | def extendMarkdown(self, md, md_globals): | |||
|
96 | br_tag = SubstituteTagInlineProcessor(self.BR_RE, 'br') | |||
|
97 | md.inlinePatterns.add('nl', br_tag, '_end') | |||
|
98 | ||||
|
99 | ||||
32 | class GithubFlavoredMarkdownExtension(Extension): |
|
100 | class GithubFlavoredMarkdownExtension(Extension): | |
33 | """ |
|
101 | """ | |
34 | An extension that is as compatible as possible with GitHub-flavored |
|
102 | An extension that is as compatible as possible with GitHub-flavored | |
@@ -51,6 +119,7 b' class GithubFlavoredMarkdownExtension(Ex' | |||||
51 |
|
119 | |||
52 | def extendMarkdown(self, md, md_globals): |
|
120 | def extendMarkdown(self, md, md_globals): | |
53 | # Built-in extensions |
|
121 | # Built-in extensions | |
|
122 | Nl2BrExtension().extendMarkdown(md, md_globals) | |||
54 | FencedCodeExtension().extendMarkdown(md, md_globals) |
|
123 | FencedCodeExtension().extendMarkdown(md, md_globals) | |
55 | SmartEmphasisExtension().extendMarkdown(md, md_globals) |
|
124 | SmartEmphasisExtension().extendMarkdown(md, md_globals) | |
56 | TableExtension().extendMarkdown(md, md_globals) |
|
125 | TableExtension().extendMarkdown(md, md_globals) | |
@@ -68,7 +137,6 b' class GithubFlavoredMarkdownExtension(Ex' | |||||
68 | gfm.TaskListExtension([ |
|
137 | gfm.TaskListExtension([ | |
69 | ('list_attrs', {'class': 'checkbox'}) |
|
138 | ('list_attrs', {'class': 'checkbox'}) | |
70 | ]).extendMarkdown(md, md_globals) |
|
139 | ]).extendMarkdown(md, md_globals) | |
71 | Nl2BrExtension().extendMarkdown(md, md_globals) |
|
|||
72 |
|
140 | |||
73 |
|
141 | |||
74 | # Global Vars |
|
142 | # Global Vars |
@@ -74,7 +74,11 b' def configure_dogpile_cache(settings):' | |||||
74 |
|
74 | |||
75 | new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name)) |
|
75 | new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name)) | |
76 | new_region.function_key_generator = backend_key_generator(new_region.actual_backend) |
|
76 | new_region.function_key_generator = backend_key_generator(new_region.actual_backend) | |
77 | log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__) |
|
77 | if log.isEnabledFor(logging.DEBUG): | |
|
78 | region_args = dict(backend=new_region.actual_backend.__class__, | |||
|
79 | region_invalidator=new_region.region_invalidator.__class__) | |||
|
80 | log.debug('dogpile: registering a new region `%s` %s', region_name, region_args) | |||
|
81 | ||||
78 | region_meta.dogpile_cache_regions[region_name] = new_region |
|
82 | region_meta.dogpile_cache_regions[region_name] = new_region | |
79 |
|
83 | |||
80 |
|
84 |
@@ -915,8 +915,9 b' class BaseCommit(object):' | |||||
915 | list of parent commits |
|
915 | list of parent commits | |
916 |
|
916 | |||
917 | """ |
|
917 | """ | |
|
918 | repository = None | |||
|
919 | branch = None | |||
918 |
|
920 | |||
919 | branch = None |
|
|||
920 | """ |
|
921 | """ | |
921 | Depending on the backend this should be set to the branch name of the |
|
922 | Depending on the backend this should be set to the branch name of the | |
922 | commit. Backends not supporting branches on commits should leave this |
|
923 | commit. Backends not supporting branches on commits should leave this | |
@@ -1192,13 +1193,14 b' class BaseCommit(object):' | |||||
1192 | return None |
|
1193 | return None | |
1193 |
|
1194 | |||
1194 | def archive_repo(self, archive_dest_path, kind='tgz', subrepos=None, |
|
1195 | def archive_repo(self, archive_dest_path, kind='tgz', subrepos=None, | |
1195 |
|
|
1196 | archive_dir_name=None, write_metadata=False, mtime=None, | |
|
1197 | archive_at_path='/'): | |||
1196 | """ |
|
1198 | """ | |
1197 | Creates an archive containing the contents of the repository. |
|
1199 | Creates an archive containing the contents of the repository. | |
1198 |
|
1200 | |||
1199 | :param archive_dest_path: path to the file which to create the archive. |
|
1201 | :param archive_dest_path: path to the file which to create the archive. | |
1200 | :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``. |
|
1202 | :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``. | |
1201 |
:param |
|
1203 | :param archive_dir_name: name of root directory in archive. | |
1202 | Default is repository name and commit's short_id joined with dash: |
|
1204 | Default is repository name and commit's short_id joined with dash: | |
1203 | ``"{repo_name}-{short_id}"``. |
|
1205 | ``"{repo_name}-{short_id}"``. | |
1204 | :param write_metadata: write a metadata file into archive. |
|
1206 | :param write_metadata: write a metadata file into archive. | |
@@ -1214,43 +1216,26 b' class BaseCommit(object):' | |||||
1214 | 'Archive kind (%s) not supported use one of %s' % |
|
1216 | 'Archive kind (%s) not supported use one of %s' % | |
1215 | (kind, allowed_kinds)) |
|
1217 | (kind, allowed_kinds)) | |
1216 |
|
1218 | |||
1217 |
|
|
1219 | archive_dir_name = self._validate_archive_prefix(archive_dir_name) | |
1218 |
|
||||
1219 | mtime = mtime is not None or time.mktime(self.date.timetuple()) |
|
1220 | mtime = mtime is not None or time.mktime(self.date.timetuple()) | |
1220 |
|
1221 | commit_id = self.raw_id | ||
1221 | file_info = [] |
|
|||
1222 | cur_rev = self.repository.get_commit(commit_id=self.raw_id) |
|
|||
1223 | for _r, _d, files in cur_rev.walk(archive_at_path): |
|
|||
1224 | for f in files: |
|
|||
1225 | f_path = os.path.join(prefix, f.path) |
|
|||
1226 | file_info.append( |
|
|||
1227 | (f_path, f.mode, f.is_link(), f.raw_bytes)) |
|
|||
1228 |
|
1222 | |||
1229 | if write_metadata: |
|
1223 | return self.repository._remote.archive_repo( | |
1230 | metadata = [ |
|
1224 | archive_dest_path, kind, mtime, archive_at_path, | |
1231 | ('repo_name', self.repository.name), |
|
1225 | archive_dir_name, commit_id) | |
1232 | ('commit_id', self.raw_id), |
|
|||
1233 | ('mtime', mtime), |
|
|||
1234 | ('branch', self.branch), |
|
|||
1235 | ('tags', ','.join(self.tags)), |
|
|||
1236 | ] |
|
|||
1237 | meta = ["%s:%s" % (f_name, value) for f_name, value in metadata] |
|
|||
1238 | file_info.append(('.archival.txt', 0o644, False, '\n'.join(meta))) |
|
|||
1239 |
|
1226 | |||
1240 | connection.Hg.archive_repo(archive_dest_path, mtime, file_info, kind) |
|
1227 | def _validate_archive_prefix(self, archive_dir_name): | |
1241 |
|
1228 | if archive_dir_name is None: | ||
1242 | def _validate_archive_prefix(self, prefix): |
|
1229 | archive_dir_name = self._ARCHIVE_PREFIX_TEMPLATE.format( | |
1243 | if prefix is None: |
|
|||
1244 | prefix = self._ARCHIVE_PREFIX_TEMPLATE.format( |
|
|||
1245 | repo_name=safe_str(self.repository.name), |
|
1230 | repo_name=safe_str(self.repository.name), | |
1246 | short_id=self.short_id) |
|
1231 | short_id=self.short_id) | |
1247 |
elif not isinstance( |
|
1232 | elif not isinstance(archive_dir_name, str): | |
1248 |
raise ValueError("prefix not a bytes object: %s" % repr( |
|
1233 | raise ValueError("prefix not a bytes object: %s" % repr(archive_dir_name)) | |
1249 |
elif |
|
1234 | elif archive_dir_name.startswith('/'): | |
1250 | raise VCSError("Prefix cannot start with leading slash") |
|
1235 | raise VCSError("Prefix cannot start with leading slash") | |
1251 |
elif |
|
1236 | elif archive_dir_name.strip() == '': | |
1252 | raise VCSError("Prefix cannot be empty") |
|
1237 | raise VCSError("Prefix cannot be empty") | |
1253 |
return |
|
1238 | return archive_dir_name | |
1254 |
|
1239 | |||
1255 | @LazyProperty |
|
1240 | @LazyProperty | |
1256 | def root(self): |
|
1241 | def root(self): |
@@ -214,16 +214,19 b' def map_vcs_exceptions(func):' | |||||
214 | # to translate them to the proper exception class in the vcs |
|
214 | # to translate them to the proper exception class in the vcs | |
215 | # client layer. |
|
215 | # client layer. | |
216 | kind = getattr(e, '_vcs_kind', None) |
|
216 | kind = getattr(e, '_vcs_kind', None) | |
|
217 | exc_name = getattr(e, '_vcs_server_org_exc_name', None) | |||
217 |
|
218 | |||
218 | if kind: |
|
219 | if kind: | |
219 | if any(e.args): |
|
220 | if any(e.args): | |
220 | args = e.args |
|
221 | args = [a for a in e.args] | |
|
222 | args[0] = '{}:'.format(exc_name) # prefix first arg with org exc name | |||
221 | else: |
|
223 | else: | |
222 |
args = [__traceback_info__ or ' |
|
224 | args = [__traceback_info__ or '{}: UnhandledException'.format(exc_name)] | |
223 | if debug or __traceback_info__ and kind not in ['unhandled', 'lookup']: |
|
225 | if debug or __traceback_info__ and kind not in ['unhandled', 'lookup']: | |
224 | # for other than unhandled errors also log the traceback |
|
226 | # for other than unhandled errors also log the traceback | |
225 | # can be useful for debugging |
|
227 | # can be useful for debugging | |
226 | log.error(__traceback_info__) |
|
228 | log.error(__traceback_info__) | |
|
229 | ||||
227 | raise _EXCEPTION_MAP[kind](*args) |
|
230 | raise _EXCEPTION_MAP[kind](*args) | |
228 | else: |
|
231 | else: | |
229 | raise |
|
232 | raise |
@@ -37,6 +37,7 b' from rhodecode.lib.exceptions import Com' | |||||
37 | from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int |
|
37 | from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int | |
38 | from rhodecode.model import BaseModel |
|
38 | from rhodecode.model import BaseModel | |
39 | from rhodecode.model.db import ( |
|
39 | from rhodecode.model.db import ( | |
|
40 | false, true, | |||
40 | ChangesetComment, |
|
41 | ChangesetComment, | |
41 | User, |
|
42 | User, | |
42 | Notification, |
|
43 | Notification, | |
@@ -160,7 +161,7 b' class CommentsModel(BaseModel):' | |||||
160 |
|
161 | |||
161 | return todos |
|
162 | return todos | |
162 |
|
163 | |||
163 | def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True): |
|
164 | def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True, include_drafts=True): | |
164 |
|
165 | |||
165 | todos = Session().query(ChangesetComment) \ |
|
166 | todos = Session().query(ChangesetComment) \ | |
166 | .filter(ChangesetComment.pull_request == pull_request) \ |
|
167 | .filter(ChangesetComment.pull_request == pull_request) \ | |
@@ -168,6 +169,9 b' class CommentsModel(BaseModel):' | |||||
168 | .filter(ChangesetComment.comment_type |
|
169 | .filter(ChangesetComment.comment_type | |
169 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
170 | == ChangesetComment.COMMENT_TYPE_TODO) | |
170 |
|
171 | |||
|
172 | if not include_drafts: | |||
|
173 | todos = todos.filter(ChangesetComment.draft == false()) | |||
|
174 | ||||
171 | if not show_outdated: |
|
175 | if not show_outdated: | |
172 | todos = todos.filter( |
|
176 | todos = todos.filter( | |
173 | coalesce(ChangesetComment.display_state, '') != |
|
177 | coalesce(ChangesetComment.display_state, '') != | |
@@ -177,7 +181,7 b' class CommentsModel(BaseModel):' | |||||
177 |
|
181 | |||
178 | return todos |
|
182 | return todos | |
179 |
|
183 | |||
180 | def get_pull_request_resolved_todos(self, pull_request, show_outdated=True): |
|
184 | def get_pull_request_resolved_todos(self, pull_request, show_outdated=True, include_drafts=True): | |
181 |
|
185 | |||
182 | todos = Session().query(ChangesetComment) \ |
|
186 | todos = Session().query(ChangesetComment) \ | |
183 | .filter(ChangesetComment.pull_request == pull_request) \ |
|
187 | .filter(ChangesetComment.pull_request == pull_request) \ | |
@@ -185,6 +189,9 b' class CommentsModel(BaseModel):' | |||||
185 | .filter(ChangesetComment.comment_type |
|
189 | .filter(ChangesetComment.comment_type | |
186 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
190 | == ChangesetComment.COMMENT_TYPE_TODO) | |
187 |
|
191 | |||
|
192 | if not include_drafts: | |||
|
193 | todos = todos.filter(ChangesetComment.draft == false()) | |||
|
194 | ||||
188 | if not show_outdated: |
|
195 | if not show_outdated: | |
189 | todos = todos.filter( |
|
196 | todos = todos.filter( | |
190 | coalesce(ChangesetComment.display_state, '') != |
|
197 | coalesce(ChangesetComment.display_state, '') != | |
@@ -194,7 +201,14 b' class CommentsModel(BaseModel):' | |||||
194 |
|
201 | |||
195 | return todos |
|
202 | return todos | |
196 |
|
203 | |||
197 | def get_commit_unresolved_todos(self, commit_id, show_outdated=True): |
|
204 | def get_pull_request_drafts(self, user_id, pull_request): | |
|
205 | drafts = Session().query(ChangesetComment) \ | |||
|
206 | .filter(ChangesetComment.pull_request == pull_request) \ | |||
|
207 | .filter(ChangesetComment.user_id == user_id) \ | |||
|
208 | .filter(ChangesetComment.draft == true()) | |||
|
209 | return drafts.all() | |||
|
210 | ||||
|
211 | def get_commit_unresolved_todos(self, commit_id, show_outdated=True, include_drafts=True): | |||
198 |
|
212 | |||
199 | todos = Session().query(ChangesetComment) \ |
|
213 | todos = Session().query(ChangesetComment) \ | |
200 | .filter(ChangesetComment.revision == commit_id) \ |
|
214 | .filter(ChangesetComment.revision == commit_id) \ | |
@@ -202,6 +216,9 b' class CommentsModel(BaseModel):' | |||||
202 | .filter(ChangesetComment.comment_type |
|
216 | .filter(ChangesetComment.comment_type | |
203 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
217 | == ChangesetComment.COMMENT_TYPE_TODO) | |
204 |
|
218 | |||
|
219 | if not include_drafts: | |||
|
220 | todos = todos.filter(ChangesetComment.draft == false()) | |||
|
221 | ||||
205 | if not show_outdated: |
|
222 | if not show_outdated: | |
206 | todos = todos.filter( |
|
223 | todos = todos.filter( | |
207 | coalesce(ChangesetComment.display_state, '') != |
|
224 | coalesce(ChangesetComment.display_state, '') != | |
@@ -211,7 +228,7 b' class CommentsModel(BaseModel):' | |||||
211 |
|
228 | |||
212 | return todos |
|
229 | return todos | |
213 |
|
230 | |||
214 | def get_commit_resolved_todos(self, commit_id, show_outdated=True): |
|
231 | def get_commit_resolved_todos(self, commit_id, show_outdated=True, include_drafts=True): | |
215 |
|
232 | |||
216 | todos = Session().query(ChangesetComment) \ |
|
233 | todos = Session().query(ChangesetComment) \ | |
217 | .filter(ChangesetComment.revision == commit_id) \ |
|
234 | .filter(ChangesetComment.revision == commit_id) \ | |
@@ -219,6 +236,9 b' class CommentsModel(BaseModel):' | |||||
219 | .filter(ChangesetComment.comment_type |
|
236 | .filter(ChangesetComment.comment_type | |
220 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
237 | == ChangesetComment.COMMENT_TYPE_TODO) | |
221 |
|
238 | |||
|
239 | if not include_drafts: | |||
|
240 | todos = todos.filter(ChangesetComment.draft == false()) | |||
|
241 | ||||
222 | if not show_outdated: |
|
242 | if not show_outdated: | |
223 | todos = todos.filter( |
|
243 | todos = todos.filter( | |
224 | coalesce(ChangesetComment.display_state, '') != |
|
244 | coalesce(ChangesetComment.display_state, '') != | |
@@ -228,11 +248,15 b' class CommentsModel(BaseModel):' | |||||
228 |
|
248 | |||
229 | return todos |
|
249 | return todos | |
230 |
|
250 | |||
231 | def get_commit_inline_comments(self, commit_id): |
|
251 | def get_commit_inline_comments(self, commit_id, include_drafts=True): | |
232 | inline_comments = Session().query(ChangesetComment) \ |
|
252 | inline_comments = Session().query(ChangesetComment) \ | |
233 | .filter(ChangesetComment.line_no != None) \ |
|
253 | .filter(ChangesetComment.line_no != None) \ | |
234 | .filter(ChangesetComment.f_path != None) \ |
|
254 | .filter(ChangesetComment.f_path != None) \ | |
235 | .filter(ChangesetComment.revision == commit_id) |
|
255 | .filter(ChangesetComment.revision == commit_id) | |
|
256 | ||||
|
257 | if not include_drafts: | |||
|
258 | inline_comments = inline_comments.filter(ChangesetComment.draft == false()) | |||
|
259 | ||||
236 | inline_comments = inline_comments.all() |
|
260 | inline_comments = inline_comments.all() | |
237 | return inline_comments |
|
261 | return inline_comments | |
238 |
|
262 | |||
@@ -245,7 +269,7 b' class CommentsModel(BaseModel):' | |||||
245 |
|
269 | |||
246 | def create(self, text, repo, user, commit_id=None, pull_request=None, |
|
270 | def create(self, text, repo, user, commit_id=None, pull_request=None, | |
247 | f_path=None, line_no=None, status_change=None, |
|
271 | f_path=None, line_no=None, status_change=None, | |
248 | status_change_type=None, comment_type=None, |
|
272 | status_change_type=None, comment_type=None, is_draft=False, | |
249 | resolves_comment_id=None, closing_pr=False, send_email=True, |
|
273 | resolves_comment_id=None, closing_pr=False, send_email=True, | |
250 | renderer=None, auth_user=None, extra_recipients=None): |
|
274 | renderer=None, auth_user=None, extra_recipients=None): | |
251 | """ |
|
275 | """ | |
@@ -262,6 +286,7 b' class CommentsModel(BaseModel):' | |||||
262 | :param line_no: |
|
286 | :param line_no: | |
263 | :param status_change: Label for status change |
|
287 | :param status_change: Label for status change | |
264 | :param comment_type: Type of comment |
|
288 | :param comment_type: Type of comment | |
|
289 | :param is_draft: is comment a draft only | |||
265 | :param resolves_comment_id: id of comment which this one will resolve |
|
290 | :param resolves_comment_id: id of comment which this one will resolve | |
266 | :param status_change_type: type of status change |
|
291 | :param status_change_type: type of status change | |
267 | :param closing_pr: |
|
292 | :param closing_pr: | |
@@ -288,6 +313,7 b' class CommentsModel(BaseModel):' | |||||
288 | validated_kwargs = schema.deserialize(dict( |
|
313 | validated_kwargs = schema.deserialize(dict( | |
289 | comment_body=text, |
|
314 | comment_body=text, | |
290 | comment_type=comment_type, |
|
315 | comment_type=comment_type, | |
|
316 | is_draft=is_draft, | |||
291 | comment_file=f_path, |
|
317 | comment_file=f_path, | |
292 | comment_line=line_no, |
|
318 | comment_line=line_no, | |
293 | renderer_type=renderer, |
|
319 | renderer_type=renderer, | |
@@ -296,6 +322,7 b' class CommentsModel(BaseModel):' | |||||
296 | repo=repo.repo_id, |
|
322 | repo=repo.repo_id, | |
297 | user=user.user_id, |
|
323 | user=user.user_id, | |
298 | )) |
|
324 | )) | |
|
325 | is_draft = validated_kwargs['is_draft'] | |||
299 |
|
326 | |||
300 | comment = ChangesetComment() |
|
327 | comment = ChangesetComment() | |
301 | comment.renderer = validated_kwargs['renderer_type'] |
|
328 | comment.renderer = validated_kwargs['renderer_type'] | |
@@ -303,6 +330,7 b' class CommentsModel(BaseModel):' | |||||
303 | comment.f_path = validated_kwargs['comment_file'] |
|
330 | comment.f_path = validated_kwargs['comment_file'] | |
304 | comment.line_no = validated_kwargs['comment_line'] |
|
331 | comment.line_no = validated_kwargs['comment_line'] | |
305 | comment.comment_type = validated_kwargs['comment_type'] |
|
332 | comment.comment_type = validated_kwargs['comment_type'] | |
|
333 | comment.draft = is_draft | |||
306 |
|
334 | |||
307 | comment.repo = repo |
|
335 | comment.repo = repo | |
308 | comment.author = user |
|
336 | comment.author = user | |
@@ -438,9 +466,6 b' class CommentsModel(BaseModel):' | |||||
438 |
|
466 | |||
439 | if send_email: |
|
467 | if send_email: | |
440 | recipients += [self._get_user(u) for u in (extra_recipients or [])] |
|
468 | recipients += [self._get_user(u) for u in (extra_recipients or [])] | |
441 | # pre-generate the subject for notification itself |
|
|||
442 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( |
|
|||
443 | notification_type, **kwargs) |
|
|||
444 |
|
469 | |||
445 | mention_recipients = set( |
|
470 | mention_recipients = set( | |
446 | self._extract_mentions(text)).difference(recipients) |
|
471 | self._extract_mentions(text)).difference(recipients) | |
@@ -448,8 +473,8 b' class CommentsModel(BaseModel):' | |||||
448 | # create notification objects, and emails |
|
473 | # create notification objects, and emails | |
449 | NotificationModel().create( |
|
474 | NotificationModel().create( | |
450 | created_by=user, |
|
475 | created_by=user, | |
451 |
notification_subject= |
|
476 | notification_subject='', # Filled in based on the notification_type | |
452 |
notification_body= |
|
477 | notification_body='', # Filled in based on the notification_type | |
453 | notification_type=notification_type, |
|
478 | notification_type=notification_type, | |
454 | recipients=recipients, |
|
479 | recipients=recipients, | |
455 | mention_recipients=mention_recipients, |
|
480 | mention_recipients=mention_recipients, | |
@@ -462,10 +487,11 b' class CommentsModel(BaseModel):' | |||||
462 | else: |
|
487 | else: | |
463 | action = 'repo.commit.comment.create' |
|
488 | action = 'repo.commit.comment.create' | |
464 |
|
489 | |||
465 | comment_data = comment.get_api_data() |
|
490 | if not is_draft: | |
|
491 | comment_data = comment.get_api_data() | |||
466 |
|
492 | |||
467 | self._log_audit_action( |
|
493 | self._log_audit_action( | |
468 | action, {'data': comment_data}, auth_user, comment) |
|
494 | action, {'data': comment_data}, auth_user, comment) | |
469 |
|
495 | |||
470 | return comment |
|
496 | return comment | |
471 |
|
497 | |||
@@ -541,7 +567,8 b' class CommentsModel(BaseModel):' | |||||
541 |
|
567 | |||
542 | return comment |
|
568 | return comment | |
543 |
|
569 | |||
544 |
def get_all_comments(self, repo_id, revision=None, pull_request=None, |
|
570 | def get_all_comments(self, repo_id, revision=None, pull_request=None, | |
|
571 | include_drafts=True, count_only=False): | |||
545 | q = ChangesetComment.query()\ |
|
572 | q = ChangesetComment.query()\ | |
546 | .filter(ChangesetComment.repo_id == repo_id) |
|
573 | .filter(ChangesetComment.repo_id == repo_id) | |
547 | if revision: |
|
574 | if revision: | |
@@ -551,6 +578,8 b' class CommentsModel(BaseModel):' | |||||
551 | q = q.filter(ChangesetComment.pull_request_id == pull_request.pull_request_id) |
|
578 | q = q.filter(ChangesetComment.pull_request_id == pull_request.pull_request_id) | |
552 | else: |
|
579 | else: | |
553 | raise Exception('Please specify commit or pull_request') |
|
580 | raise Exception('Please specify commit or pull_request') | |
|
581 | if not include_drafts: | |||
|
582 | q = q.filter(ChangesetComment.draft == false()) | |||
554 | q = q.order_by(ChangesetComment.created_on) |
|
583 | q = q.order_by(ChangesetComment.created_on) | |
555 | if count_only: |
|
584 | if count_only: | |
556 | return q.count() |
|
585 | return q.count() | |
@@ -697,7 +726,8 b' class CommentsModel(BaseModel):' | |||||
697 | path=comment.f_path, diff_line=diff_line) |
|
726 | path=comment.f_path, diff_line=diff_line) | |
698 | except (diffs.LineNotInDiffException, |
|
727 | except (diffs.LineNotInDiffException, | |
699 | diffs.FileNotInDiffException): |
|
728 | diffs.FileNotInDiffException): | |
700 | comment.display_state = ChangesetComment.COMMENT_OUTDATED |
|
729 | if not comment.draft: | |
|
730 | comment.display_state = ChangesetComment.COMMENT_OUTDATED | |||
701 | return |
|
731 | return | |
702 |
|
732 | |||
703 | if old_context == new_context: |
|
733 | if old_context == new_context: | |
@@ -707,14 +737,15 b' class CommentsModel(BaseModel):' | |||||
707 | new_diff_lines = new_diff_proc.find_context( |
|
737 | new_diff_lines = new_diff_proc.find_context( | |
708 | path=comment.f_path, context=old_context, |
|
738 | path=comment.f_path, context=old_context, | |
709 | offset=self.DIFF_CONTEXT_BEFORE) |
|
739 | offset=self.DIFF_CONTEXT_BEFORE) | |
710 | if not new_diff_lines: |
|
740 | if not new_diff_lines and not comment.draft: | |
711 | comment.display_state = ChangesetComment.COMMENT_OUTDATED |
|
741 | comment.display_state = ChangesetComment.COMMENT_OUTDATED | |
712 | else: |
|
742 | else: | |
713 | new_diff_line = self._choose_closest_diff_line( |
|
743 | new_diff_line = self._choose_closest_diff_line( | |
714 | diff_line, new_diff_lines) |
|
744 | diff_line, new_diff_lines) | |
715 | comment.line_no = _diff_to_comment_line_number(new_diff_line) |
|
745 | comment.line_no = _diff_to_comment_line_number(new_diff_line) | |
716 | else: |
|
746 | else: | |
717 | comment.display_state = ChangesetComment.COMMENT_OUTDATED |
|
747 | if not comment.draft: | |
|
748 | comment.display_state = ChangesetComment.COMMENT_OUTDATED | |||
718 |
|
749 | |||
719 | def _should_relocate_diff_line(self, diff_line): |
|
750 | def _should_relocate_diff_line(self, diff_line): | |
720 | """ |
|
751 | """ |
@@ -3767,6 +3767,7 b' class ChangesetComment(Base, BaseModel):' | |||||
3767 | renderer = Column('renderer', Unicode(64), nullable=True) |
|
3767 | renderer = Column('renderer', Unicode(64), nullable=True) | |
3768 | display_state = Column('display_state', Unicode(128), nullable=True) |
|
3768 | display_state = Column('display_state', Unicode(128), nullable=True) | |
3769 | immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE) |
|
3769 | immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE) | |
|
3770 | draft = Column('draft', Boolean(), nullable=True, default=False) | |||
3770 |
|
3771 | |||
3771 | comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE) |
|
3772 | comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE) | |
3772 | resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True) |
|
3773 | resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True) | |
@@ -5057,8 +5058,14 b' class RepoReviewRule(Base, BaseModel):' | |||||
5057 | _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob |
|
5058 | _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob | |
5058 |
|
5059 | |||
5059 | use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False) |
|
5060 | use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False) | |
5060 | forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False) |
|
5061 | ||
5061 | forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False) |
|
5062 | # Legacy fields, just for backward compat | |
|
5063 | _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False) | |||
|
5064 | _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False) | |||
|
5065 | ||||
|
5066 | pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |||
|
5067 | commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |||
|
5068 | ||||
5062 | forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False) |
|
5069 | forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False) | |
5063 |
|
5070 | |||
5064 | rule_users = relationship('RepoReviewRuleUser') |
|
5071 | rule_users = relationship('RepoReviewRuleUser') | |
@@ -5094,6 +5101,22 b' class RepoReviewRule(Base, BaseModel):' | |||||
5094 | self._validate_pattern(value) |
|
5101 | self._validate_pattern(value) | |
5095 | self._file_pattern = value or '*' |
|
5102 | self._file_pattern = value or '*' | |
5096 |
|
5103 | |||
|
5104 | @hybrid_property | |||
|
5105 | def forbid_pr_author_to_review(self): | |||
|
5106 | return self.pr_author == 'forbid_pr_author' | |||
|
5107 | ||||
|
5108 | @hybrid_property | |||
|
5109 | def include_pr_author_to_review(self): | |||
|
5110 | return self.pr_author == 'include_pr_author' | |||
|
5111 | ||||
|
5112 | @hybrid_property | |||
|
5113 | def forbid_commit_author_to_review(self): | |||
|
5114 | return self.commit_author == 'forbid_commit_author' | |||
|
5115 | ||||
|
5116 | @hybrid_property | |||
|
5117 | def include_commit_author_to_review(self): | |||
|
5118 | return self.commit_author == 'include_commit_author' | |||
|
5119 | ||||
5097 | def matches(self, source_branch, target_branch, files_changed): |
|
5120 | def matches(self, source_branch, target_branch, files_changed): | |
5098 | """ |
|
5121 | """ | |
5099 | Check if this review rule matches a branch/files in a pull request |
|
5122 | Check if this review rule matches a branch/files in a pull request |
@@ -55,7 +55,7 b' class NotificationModel(BaseModel):' | |||||
55 | ' of Notification got %s' % type(notification)) |
|
55 | ' of Notification got %s' % type(notification)) | |
56 |
|
56 | |||
57 | def create( |
|
57 | def create( | |
58 | self, created_by, notification_subject, notification_body, |
|
58 | self, created_by, notification_subject='', notification_body='', | |
59 | notification_type=Notification.TYPE_MESSAGE, recipients=None, |
|
59 | notification_type=Notification.TYPE_MESSAGE, recipients=None, | |
60 | mention_recipients=None, with_email=True, email_kwargs=None): |
|
60 | mention_recipients=None, with_email=True, email_kwargs=None): | |
61 | """ |
|
61 | """ | |
@@ -64,11 +64,12 b' class NotificationModel(BaseModel):' | |||||
64 |
|
64 | |||
65 | :param created_by: int, str or User instance. User who created this |
|
65 | :param created_by: int, str or User instance. User who created this | |
66 | notification |
|
66 | notification | |
67 | :param notification_subject: subject of notification itself |
|
67 | :param notification_subject: subject of notification itself, | |
|
68 | it will be generated automatically from notification_type if not specified | |||
68 | :param notification_body: body of notification text |
|
69 | :param notification_body: body of notification text | |
|
70 | it will be generated automatically from notification_type if not specified | |||
69 | :param notification_type: type of notification, based on that we |
|
71 | :param notification_type: type of notification, based on that we | |
70 | pick templates |
|
72 | pick templates | |
71 |
|
||||
72 | :param recipients: list of int, str or User objects, when None |
|
73 | :param recipients: list of int, str or User objects, when None | |
73 | is given send to all admins |
|
74 | is given send to all admins | |
74 | :param mention_recipients: list of int, str or User objects, |
|
75 | :param mention_recipients: list of int, str or User objects, | |
@@ -82,14 +83,19 b' class NotificationModel(BaseModel):' | |||||
82 | if recipients and not getattr(recipients, '__iter__', False): |
|
83 | if recipients and not getattr(recipients, '__iter__', False): | |
83 | raise Exception('recipients must be an iterable object') |
|
84 | raise Exception('recipients must be an iterable object') | |
84 |
|
85 | |||
|
86 | if not (notification_subject and notification_body) and not notification_type: | |||
|
87 | raise ValueError('notification_subject, and notification_body ' | |||
|
88 | 'cannot be empty when notification_type is not specified') | |||
|
89 | ||||
85 | created_by_obj = self._get_user(created_by) |
|
90 | created_by_obj = self._get_user(created_by) | |
|
91 | ||||
|
92 | if not created_by_obj: | |||
|
93 | raise Exception('unknown user %s' % created_by) | |||
|
94 | ||||
86 | # default MAIN body if not given |
|
95 | # default MAIN body if not given | |
87 | email_kwargs = email_kwargs or {'body': notification_body} |
|
96 | email_kwargs = email_kwargs or {'body': notification_body} | |
88 | mention_recipients = mention_recipients or set() |
|
97 | mention_recipients = mention_recipients or set() | |
89 |
|
98 | |||
90 | if not created_by_obj: |
|
|||
91 | raise Exception('unknown user %s' % created_by) |
|
|||
92 |
|
||||
93 | if recipients is None: |
|
99 | if recipients is None: | |
94 | # recipients is None means to all admins |
|
100 | # recipients is None means to all admins | |
95 | recipients_objs = User.query().filter(User.admin == true()).all() |
|
101 | recipients_objs = User.query().filter(User.admin == true()).all() | |
@@ -113,6 +119,15 b' class NotificationModel(BaseModel):' | |||||
113 | # add mentioned users into recipients |
|
119 | # add mentioned users into recipients | |
114 | final_recipients = set(recipients_objs).union(mention_recipients) |
|
120 | final_recipients = set(recipients_objs).union(mention_recipients) | |
115 |
|
121 | |||
|
122 | (subject, email_body, email_body_plaintext) = \ | |||
|
123 | EmailNotificationModel().render_email(notification_type, **email_kwargs) | |||
|
124 | ||||
|
125 | if not notification_subject: | |||
|
126 | notification_subject = subject | |||
|
127 | ||||
|
128 | if not notification_body: | |||
|
129 | notification_body = email_body_plaintext | |||
|
130 | ||||
116 | notification = Notification.create( |
|
131 | notification = Notification.create( | |
117 | created_by=created_by_obj, subject=notification_subject, |
|
132 | created_by=created_by_obj, subject=notification_subject, | |
118 | body=notification_body, recipients=final_recipients, |
|
133 | body=notification_body, recipients=final_recipients, |
@@ -578,7 +578,7 b' class PermissionModel(BaseModel):' | |||||
578 | return user_group_write_permissions |
|
578 | return user_group_write_permissions | |
579 |
|
579 | |||
580 | def trigger_permission_flush(self, affected_user_ids=None): |
|
580 | def trigger_permission_flush(self, affected_user_ids=None): | |
581 | affected_user_ids or User.get_all_user_ids() |
|
581 | affected_user_ids = affected_user_ids or User.get_all_user_ids() | |
582 | events.trigger(events.UserPermissionsChange(affected_user_ids)) |
|
582 | events.trigger(events.UserPermissionsChange(affected_user_ids)) | |
583 |
|
583 | |||
584 | def flush_user_permission_caches(self, changes, affected_user_ids=None): |
|
584 | def flush_user_permission_caches(self, changes, affected_user_ids=None): |
@@ -1502,15 +1502,11 b' class PullRequestModel(BaseModel):' | |||||
1502 | 'user_role': role |
|
1502 | 'user_role': role | |
1503 | } |
|
1503 | } | |
1504 |
|
1504 | |||
1505 | # pre-generate the subject for notification itself |
|
|||
1506 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( |
|
|||
1507 | notification_type, **kwargs) |
|
|||
1508 |
|
||||
1509 | # create notification objects, and emails |
|
1505 | # create notification objects, and emails | |
1510 | NotificationModel().create( |
|
1506 | NotificationModel().create( | |
1511 | created_by=current_rhodecode_user, |
|
1507 | created_by=current_rhodecode_user, | |
1512 |
notification_subject= |
|
1508 | notification_subject='', # Filled in based on the notification_type | |
1513 | notification_body=body_plaintext, |
|
1509 | notification_body='', # Filled in based on the notification_type | |
1514 | notification_type=notification_type, |
|
1510 | notification_type=notification_type, | |
1515 | recipients=recipients, |
|
1511 | recipients=recipients, | |
1516 | email_kwargs=kwargs, |
|
1512 | email_kwargs=kwargs, | |
@@ -1579,14 +1575,11 b' class PullRequestModel(BaseModel):' | |||||
1579 | 'thread_ids': [pr_url], |
|
1575 | 'thread_ids': [pr_url], | |
1580 | } |
|
1576 | } | |
1581 |
|
1577 | |||
1582 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( |
|
|||
1583 | EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, **email_kwargs) |
|
|||
1584 |
|
||||
1585 | # create notification objects, and emails |
|
1578 | # create notification objects, and emails | |
1586 | NotificationModel().create( |
|
1579 | NotificationModel().create( | |
1587 | created_by=updating_user, |
|
1580 | created_by=updating_user, | |
1588 |
notification_subject= |
|
1581 | notification_subject='', # Filled in based on the notification_type | |
1589 | notification_body=body_plaintext, |
|
1582 | notification_body='', # Filled in based on the notification_type | |
1590 | notification_type=EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, |
|
1583 | notification_type=EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, | |
1591 | recipients=recipients, |
|
1584 | recipients=recipients, | |
1592 | email_kwargs=email_kwargs, |
|
1585 | email_kwargs=email_kwargs, | |
@@ -2067,6 +2060,8 b' class MergeCheck(object):' | |||||
2067 | self.error_details = OrderedDict() |
|
2060 | self.error_details = OrderedDict() | |
2068 | self.source_commit = AttributeDict() |
|
2061 | self.source_commit = AttributeDict() | |
2069 | self.target_commit = AttributeDict() |
|
2062 | self.target_commit = AttributeDict() | |
|
2063 | self.reviewers_count = 0 | |||
|
2064 | self.observers_count = 0 | |||
2070 |
|
2065 | |||
2071 | def __repr__(self): |
|
2066 | def __repr__(self): | |
2072 | return '<MergeCheck(possible:{}, failed:{}, errors:{})>'.format( |
|
2067 | return '<MergeCheck(possible:{}, failed:{}, errors:{})>'.format( | |
@@ -2128,11 +2123,12 b' class MergeCheck(object):' | |||||
2128 | # review status, must be always present |
|
2123 | # review status, must be always present | |
2129 | review_status = pull_request.calculated_review_status() |
|
2124 | review_status = pull_request.calculated_review_status() | |
2130 | merge_check.review_status = review_status |
|
2125 | merge_check.review_status = review_status | |
|
2126 | merge_check.reviewers_count = pull_request.reviewers_count | |||
|
2127 | merge_check.observers_count = pull_request.observers_count | |||
2131 |
|
2128 | |||
2132 | status_approved = review_status == ChangesetStatus.STATUS_APPROVED |
|
2129 | status_approved = review_status == ChangesetStatus.STATUS_APPROVED | |
2133 | if not status_approved: |
|
2130 | if not status_approved and merge_check.reviewers_count: | |
2134 | log.debug("MergeCheck: cannot merge, approval is pending.") |
|
2131 | log.debug("MergeCheck: cannot merge, approval is pending.") | |
2135 |
|
||||
2136 | msg = _('Pull request reviewer approval is pending.') |
|
2132 | msg = _('Pull request reviewer approval is pending.') | |
2137 |
|
2133 | |||
2138 | merge_check.push_error('warning', msg, cls.REVIEW_CHECK, review_status) |
|
2134 | merge_check.push_error('warning', msg, cls.REVIEW_CHECK, review_status) |
@@ -231,6 +231,10 b' class ScmModel(BaseModel):' | |||||
231 | with_wire={"cache": False}) |
|
231 | with_wire={"cache": False}) | |
232 | except OSError: |
|
232 | except OSError: | |
233 | continue |
|
233 | continue | |
|
234 | except RepositoryError: | |||
|
235 | log.exception('Failed to create a repo') | |||
|
236 | continue | |||
|
237 | ||||
234 | log.debug('found %s paths with repositories', len(repos)) |
|
238 | log.debug('found %s paths with repositories', len(repos)) | |
235 | return repos |
|
239 | return repos | |
236 |
|
240 |
@@ -425,15 +425,12 b' class UserModel(BaseModel):' | |||||
425 | 'date': datetime.datetime.now() |
|
425 | 'date': datetime.datetime.now() | |
426 | } |
|
426 | } | |
427 | notification_type = EmailNotificationModel.TYPE_REGISTRATION |
|
427 | notification_type = EmailNotificationModel.TYPE_REGISTRATION | |
428 | # pre-generate the subject for notification itself |
|
|||
429 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( |
|
|||
430 | notification_type, **kwargs) |
|
|||
431 |
|
428 | |||
432 | # create notification objects, and emails |
|
429 | # create notification objects, and emails | |
433 | NotificationModel().create( |
|
430 | NotificationModel().create( | |
434 | created_by=new_user, |
|
431 | created_by=new_user, | |
435 |
notification_subject= |
|
432 | notification_subject='', # Filled in based on the notification_type | |
436 |
notification_body= |
|
433 | notification_body='', # Filled in based on the notification_type | |
437 | notification_type=notification_type, |
|
434 | notification_type=notification_type, | |
438 | recipients=None, # all admins |
|
435 | recipients=None, # all admins | |
439 | email_kwargs=kwargs, |
|
436 | email_kwargs=kwargs, |
@@ -60,7 +60,7 b' class CommentSchema(colander.MappingSche' | |||||
60 | colander.String(), |
|
60 | colander.String(), | |
61 | validator=colander.OneOf(ChangesetComment.COMMENT_TYPES), |
|
61 | validator=colander.OneOf(ChangesetComment.COMMENT_TYPES), | |
62 | missing=ChangesetComment.COMMENT_TYPE_NOTE) |
|
62 | missing=ChangesetComment.COMMENT_TYPE_NOTE) | |
63 |
|
63 | is_draft = colander.SchemaNode(colander.Boolean(),missing=False) | ||
64 | comment_file = colander.SchemaNode(colander.String(), missing=None) |
|
64 | comment_file = colander.SchemaNode(colander.String(), missing=None) | |
65 | comment_line = colander.SchemaNode(colander.String(), missing=None) |
|
65 | comment_line = colander.SchemaNode(colander.String(), missing=None) | |
66 | status_change = colander.SchemaNode( |
|
66 | status_change = colander.SchemaNode( |
@@ -162,7 +162,6 b' input[type="button"] {' | |||||
162 | } |
|
162 | } | |
163 | } |
|
163 | } | |
164 |
|
164 | |||
165 | .btn-warning, |
|
|||
166 | .btn-danger, |
|
165 | .btn-danger, | |
167 | .revoke_perm, |
|
166 | .revoke_perm, | |
168 | .btn-x, |
|
167 | .btn-x, | |
@@ -196,6 +195,36 b' input[type="button"] {' | |||||
196 | } |
|
195 | } | |
197 | } |
|
196 | } | |
198 |
|
197 | |||
|
198 | .btn-warning { | |||
|
199 | .border ( @border-thickness, @alert3 ); | |||
|
200 | background-color: white; | |||
|
201 | color: @alert3; | |||
|
202 | ||||
|
203 | a { | |||
|
204 | color: @alert3; | |||
|
205 | } | |||
|
206 | ||||
|
207 | &:hover, | |||
|
208 | &.active { | |||
|
209 | .border ( @border-thickness, @alert3 ); | |||
|
210 | color: white; | |||
|
211 | background-color: @alert3; | |||
|
212 | ||||
|
213 | a { | |||
|
214 | color: white; | |||
|
215 | } | |||
|
216 | } | |||
|
217 | ||||
|
218 | i { | |||
|
219 | display:none; | |||
|
220 | } | |||
|
221 | ||||
|
222 | &:disabled { | |||
|
223 | background-color: white; | |||
|
224 | color: @alert3; | |||
|
225 | } | |||
|
226 | } | |||
|
227 | ||||
199 | .btn-approved-status { |
|
228 | .btn-approved-status { | |
200 | .border ( @border-thickness, @alert1 ); |
|
229 | .border ( @border-thickness, @alert1 ); | |
201 | background-color: white; |
|
230 | background-color: white; | |
@@ -264,7 +293,6 b' input[type="button"] {' | |||||
264 | margin-left: -1px; |
|
293 | margin-left: -1px; | |
265 | padding-left: 2px; |
|
294 | padding-left: 2px; | |
266 | padding-right: 2px; |
|
295 | padding-right: 2px; | |
267 | border-left: 1px solid @grey3; |
|
|||
268 | } |
|
296 | } | |
269 | } |
|
297 | } | |
270 |
|
298 | |||
@@ -342,7 +370,7 b' input[type="button"] {' | |||||
342 | color: @alert2; |
|
370 | color: @alert2; | |
343 |
|
371 | |||
344 | &:hover { |
|
372 | &:hover { | |
345 | color: darken(@alert2,30%); |
|
373 | color: darken(@alert2, 30%); | |
346 | } |
|
374 | } | |
347 |
|
375 | |||
348 | &:disabled { |
|
376 | &:disabled { | |
@@ -402,6 +430,37 b' input[type="button"] {' | |||||
402 | } |
|
430 | } | |
403 |
|
431 | |||
404 |
|
432 | |||
|
433 | input[type="submit"].btn-warning { | |||
|
434 | &:extend(.btn-warning); | |||
|
435 | ||||
|
436 | &:focus { | |||
|
437 | outline: 0; | |||
|
438 | } | |||
|
439 | ||||
|
440 | &:hover { | |||
|
441 | &:extend(.btn-warning:hover); | |||
|
442 | } | |||
|
443 | ||||
|
444 | &.btn-link { | |||
|
445 | &:extend(.btn-link); | |||
|
446 | color: @alert3; | |||
|
447 | ||||
|
448 | &:disabled { | |||
|
449 | color: @alert3; | |||
|
450 | background-color: transparent; | |||
|
451 | } | |||
|
452 | } | |||
|
453 | ||||
|
454 | &:disabled { | |||
|
455 | .border ( @border-thickness-buttons, @alert3 ); | |||
|
456 | background-color: white; | |||
|
457 | color: @alert3; | |||
|
458 | opacity: 0.5; | |||
|
459 | } | |||
|
460 | } | |||
|
461 | ||||
|
462 | ||||
|
463 | ||||
405 | // TODO: johbo: Form button tweaks, check if we can use the classes instead |
|
464 | // TODO: johbo: Form button tweaks, check if we can use the classes instead | |
406 | input[type="submit"] { |
|
465 | input[type="submit"] { | |
407 | &:extend(.btn-primary); |
|
466 | &:extend(.btn-primary); |
@@ -1002,7 +1002,7 b' input.filediff-collapse-state {' | |||||
1002 | .nav-chunk { |
|
1002 | .nav-chunk { | |
1003 | position: absolute; |
|
1003 | position: absolute; | |
1004 | right: 20px; |
|
1004 | right: 20px; | |
1005 |
margin-top: -1 |
|
1005 | margin-top: -15px; | |
1006 | } |
|
1006 | } | |
1007 |
|
1007 | |||
1008 | .nav-chunk.selected { |
|
1008 | .nav-chunk.selected { |
@@ -4,7 +4,7 b'' | |||||
4 |
|
4 | |||
5 |
|
5 | |||
6 | // Comments |
|
6 | // Comments | |
7 |
@comment-outdated-opacity: 0 |
|
7 | @comment-outdated-opacity: 1.0; | |
8 |
|
8 | |||
9 | .comments { |
|
9 | .comments { | |
10 | width: 100%; |
|
10 | width: 100%; | |
@@ -61,28 +61,37 b' tr.inline-comments div {' | |||||
61 | visibility: hidden; |
|
61 | visibility: hidden; | |
62 | } |
|
62 | } | |
63 |
|
63 | |||
|
64 | .comment-draft { | |||
|
65 | float: left; | |||
|
66 | margin-right: 10px; | |||
|
67 | font-weight: 400; | |||
|
68 | color: @color-draft; | |||
|
69 | } | |||
|
70 | ||||
|
71 | .comment-new { | |||
|
72 | float: left; | |||
|
73 | margin-right: 10px; | |||
|
74 | font-weight: 400; | |||
|
75 | color: @color-new; | |||
|
76 | } | |||
|
77 | ||||
64 | .comment-label { |
|
78 | .comment-label { | |
65 | float: left; |
|
79 | float: left; | |
66 |
|
80 | |||
67 |
padding: 0 |
|
81 | padding: 0 8px 0 0; | |
68 | margin: 2px 4px 0px 0px; |
|
|||
69 | display: inline-block; |
|
|||
70 | min-height: 0; |
|
82 | min-height: 0; | |
71 |
|
83 | |||
72 | text-align: center; |
|
84 | text-align: center; | |
73 | font-size: 10px; |
|
85 | font-size: 10px; | |
74 | line-height: .8em; |
|
|||
75 |
|
86 | |||
76 | font-family: @text-italic; |
|
87 | font-family: @text-italic; | |
77 | font-style: italic; |
|
88 | font-style: italic; | |
78 | background: #fff none; |
|
89 | background: #fff none; | |
79 | color: @grey3; |
|
90 | color: @grey3; | |
80 | border: 1px solid @grey4; |
|
|||
81 | white-space: nowrap; |
|
91 | white-space: nowrap; | |
82 |
|
92 | |||
83 | text-transform: uppercase; |
|
93 | text-transform: uppercase; | |
84 | min-width: 50px; |
|
94 | min-width: 50px; | |
85 | border-radius: 4px; |
|
|||
86 |
|
95 | |||
87 | &.todo { |
|
96 | &.todo { | |
88 | color: @color5; |
|
97 | color: @color5; | |
@@ -270,64 +279,165 b' tr.inline-comments div {' | |||||
270 | .comment-outdated { |
|
279 | .comment-outdated { | |
271 | opacity: @comment-outdated-opacity; |
|
280 | opacity: @comment-outdated-opacity; | |
272 | } |
|
281 | } | |
|
282 | ||||
|
283 | .comment-outdated-label { | |||
|
284 | color: @grey3; | |||
|
285 | padding-right: 4px; | |||
|
286 | } | |||
273 | } |
|
287 | } | |
274 |
|
288 | |||
275 | .inline-comments { |
|
289 | .inline-comments { | |
276 | border-radius: @border-radius; |
|
290 | ||
277 | .comment { |
|
291 | .comment { | |
278 | margin: 0; |
|
292 | margin: 0; | |
279 | border-radius: @border-radius; |
|
|||
280 | } |
|
293 | } | |
|
294 | ||||
281 | .comment-outdated { |
|
295 | .comment-outdated { | |
282 | opacity: @comment-outdated-opacity; |
|
296 | opacity: @comment-outdated-opacity; | |
283 | } |
|
297 | } | |
284 |
|
298 | |||
|
299 | .comment-outdated-label { | |||
|
300 | color: @grey3; | |||
|
301 | padding-right: 4px; | |||
|
302 | } | |||
|
303 | ||||
285 | .comment-inline { |
|
304 | .comment-inline { | |
|
305 | ||||
|
306 | &:first-child { | |||
|
307 | margin: 4px 4px 0 4px; | |||
|
308 | border-top: 1px solid @grey5; | |||
|
309 | border-bottom: 0 solid @grey5; | |||
|
310 | border-left: 1px solid @grey5; | |||
|
311 | border-right: 1px solid @grey5; | |||
|
312 | .border-radius-top(4px); | |||
|
313 | } | |||
|
314 | ||||
|
315 | &:only-child { | |||
|
316 | margin: 4px 4px 0 4px; | |||
|
317 | border-top: 1px solid @grey5; | |||
|
318 | border-bottom: 0 solid @grey5; | |||
|
319 | border-left: 1px solid @grey5; | |||
|
320 | border-right: 1px solid @grey5; | |||
|
321 | .border-radius-top(4px); | |||
|
322 | } | |||
|
323 | ||||
286 | background: white; |
|
324 | background: white; | |
287 | padding: @comment-padding @comment-padding; |
|
325 | padding: @comment-padding @comment-padding; | |
288 | border: @comment-padding solid @grey6; |
|
326 | margin: 0 4px 0 4px; | |
|
327 | border-top: 0 solid @grey5; | |||
|
328 | border-bottom: 0 solid @grey5; | |||
|
329 | border-left: 1px solid @grey5; | |||
|
330 | border-right: 1px solid @grey5; | |||
289 |
|
331 | |||
290 | .text { |
|
332 | .text { | |
291 | border: none; |
|
333 | border: none; | |
292 | } |
|
334 | } | |
|
335 | ||||
293 | .meta { |
|
336 | .meta { | |
294 | border-bottom: 1px solid @grey6; |
|
337 | border-bottom: 1px solid @grey6; | |
295 | margin: -5px 0px; |
|
338 | margin: -5px 0px; | |
296 | line-height: 24px; |
|
339 | line-height: 24px; | |
297 | } |
|
340 | } | |
|
341 | ||||
298 | } |
|
342 | } | |
299 | .comment-selected { |
|
343 | .comment-selected { | |
300 | border-left: 6px solid @comment-highlight-color; |
|
344 | border-left: 6px solid @comment-highlight-color; | |
301 | } |
|
345 | } | |
|
346 | ||||
|
347 | .comment-inline-form-open { | |||
|
348 | display: block !important; | |||
|
349 | } | |||
|
350 | ||||
302 | .comment-inline-form { |
|
351 | .comment-inline-form { | |
303 | padding: @comment-padding; |
|
|||
304 | display: none; |
|
352 | display: none; | |
305 | } |
|
353 | } | |
306 | .cb-comment-add-button { |
|
354 | ||
307 | margin: @comment-padding; |
|
355 | .comment-inline-form-edit { | |
|
356 | padding: 0; | |||
|
357 | margin: 0px 4px 2px 4px; | |||
|
358 | } | |||
|
359 | ||||
|
360 | .reply-thread-container { | |||
|
361 | display: table; | |||
|
362 | width: 100%; | |||
|
363 | padding: 0px 4px 4px 4px; | |||
|
364 | } | |||
|
365 | ||||
|
366 | .reply-thread-container-wrapper { | |||
|
367 | margin: 0 4px 4px 4px; | |||
|
368 | border-top: 0 solid @grey5; | |||
|
369 | border-bottom: 1px solid @grey5; | |||
|
370 | border-left: 1px solid @grey5; | |||
|
371 | border-right: 1px solid @grey5; | |||
|
372 | .border-radius-bottom(4px); | |||
|
373 | } | |||
|
374 | ||||
|
375 | .reply-thread-gravatar { | |||
|
376 | display: table-cell; | |||
|
377 | width: 24px; | |||
|
378 | height: 24px; | |||
|
379 | padding-top: 10px; | |||
|
380 | padding-left: 10px; | |||
|
381 | background-color: #eeeeee; | |||
|
382 | vertical-align: top; | |||
308 | } |
|
383 | } | |
309 | /* hide add comment button when form is open */ |
|
384 | ||
|
385 | .reply-thread-reply-button { | |||
|
386 | display: table-cell; | |||
|
387 | width: 100%; | |||
|
388 | height: 33px; | |||
|
389 | padding: 3px 8px; | |||
|
390 | margin-left: 8px; | |||
|
391 | background-color: #eeeeee; | |||
|
392 | } | |||
|
393 | ||||
|
394 | .reply-thread-reply-button .cb-comment-add-button { | |||
|
395 | border-radius: 4px; | |||
|
396 | width: 100%; | |||
|
397 | padding: 6px 2px; | |||
|
398 | text-align: left; | |||
|
399 | cursor: text; | |||
|
400 | color: @grey3; | |||
|
401 | } | |||
|
402 | .reply-thread-reply-button .cb-comment-add-button:hover { | |||
|
403 | background-color: white; | |||
|
404 | color: @grey2; | |||
|
405 | } | |||
|
406 | ||||
|
407 | .reply-thread-last { | |||
|
408 | display: table-cell; | |||
|
409 | width: 10px; | |||
|
410 | } | |||
|
411 | ||||
|
412 | /* Hide reply box when it's a first element, | |||
|
413 | can happen when drafts are saved but not shown to specific user, | |||
|
414 | or there are outdated comments hidden | |||
|
415 | */ | |||
|
416 | .reply-thread-container-wrapper:first-child:not(.comment-form-active) { | |||
|
417 | display: none; | |||
|
418 | } | |||
|
419 | ||||
|
420 | .reply-thread-container-wrapper.comment-outdated { | |||
|
421 | display: none | |||
|
422 | } | |||
|
423 | ||||
|
424 | /* hide add comment button when form is open */ | |||
310 | .comment-inline-form-open ~ .cb-comment-add-button { |
|
425 | .comment-inline-form-open ~ .cb-comment-add-button { | |
311 | display: none; |
|
426 | display: none; | |
312 | } |
|
427 | } | |
313 | .comment-inline-form-open { |
|
428 | ||
314 | display: block; |
|
|||
315 | } |
|
|||
316 | /* hide add comment button when form but no comments */ |
|
|||
317 | .comment-inline-form:first-child + .cb-comment-add-button { |
|
|||
318 | display: none; |
|
|||
319 | } |
|
|||
320 | /* hide add comment button when no comments or form */ |
|
|||
321 | .cb-comment-add-button:first-child { |
|
|||
322 | display: none; |
|
|||
323 | } |
|
|||
324 | /* hide add comment button when only comment is being deleted */ |
|
429 | /* hide add comment button when only comment is being deleted */ | |
325 | .comment-deleting:first-child + .cb-comment-add-button { |
|
430 | .comment-deleting:first-child + .cb-comment-add-button { | |
326 | display: none; |
|
431 | display: none; | |
327 | } |
|
432 | } | |
|
433 | ||||
|
434 | /* hide add comment button when form but no comments */ | |||
|
435 | .comment-inline-form:first-child + .cb-comment-add-button { | |||
|
436 | display: none; | |||
|
437 | } | |||
|
438 | ||||
328 | } |
|
439 | } | |
329 |
|
440 | |||
330 |
|
||||
331 | .show-outdated-comments { |
|
441 | .show-outdated-comments { | |
332 | display: inline; |
|
442 | display: inline; | |
333 | color: @rcblue; |
|
443 | color: @rcblue; | |
@@ -380,23 +490,36 b' form.comment-form {' | |||||
380 | } |
|
490 | } | |
381 |
|
491 | |||
382 | .comment-footer { |
|
492 | .comment-footer { | |
383 | position: relative; |
|
493 | display: table; | |
384 | width: 100%; |
|
494 | width: 100%; | |
385 |
|
|
495 | height: 42px; | |
386 |
|
496 | |||
387 |
.status |
|
497 | .comment-status-box, | |
388 | .cancel-button { |
|
498 | .cancel-button { | |
389 | float: left; |
|
|||
390 | display: inline-block; |
|
499 | display: inline-block; | |
391 | } |
|
500 | } | |
392 |
|
501 | |||
393 |
.status |
|
502 | .comment-status-box { | |
394 | margin-left: 10px; |
|
503 | margin-left: 10px; | |
395 | } |
|
504 | } | |
396 |
|
505 | |||
397 | .action-buttons { |
|
506 | .action-buttons { | |
398 |
|
|
507 | display: table-cell; | |
399 | display: inline-block; |
|
508 | padding: 5px 0 5px 2px; | |
|
509 | } | |||
|
510 | ||||
|
511 | .toolbar-text { | |||
|
512 | height: 28px; | |||
|
513 | display: table-cell; | |||
|
514 | vertical-align: baseline; | |||
|
515 | font-size: 11px; | |||
|
516 | color: @grey4; | |||
|
517 | text-align: right; | |||
|
518 | ||||
|
519 | a { | |||
|
520 | color: @grey4; | |||
|
521 | } | |||
|
522 | ||||
400 | } |
|
523 | } | |
401 |
|
524 | |||
402 | .action-buttons-extra { |
|
525 | .action-buttons-extra { | |
@@ -427,10 +550,10 b' form.comment-form {' | |||||
427 | margin-right: 0; |
|
550 | margin-right: 0; | |
428 | } |
|
551 | } | |
429 |
|
552 | |||
430 | .comment-footer { |
|
553 | #save_general { | |
431 |
margin- |
|
554 | margin-left: -6px; | |
432 | margin-top: 10px; |
|
|||
433 | } |
|
555 | } | |
|
556 | ||||
434 | } |
|
557 | } | |
435 |
|
558 | |||
436 |
|
559 | |||
@@ -482,8 +605,8 b' form.comment-form {' | |||||
482 | .injected_diff .comment-inline-form, |
|
605 | .injected_diff .comment-inline-form, | |
483 | .comment-inline-form { |
|
606 | .comment-inline-form { | |
484 | background-color: white; |
|
607 | background-color: white; | |
485 |
margin-top: |
|
608 | margin-top: 4px; | |
486 |
margin-bottom: |
|
609 | margin-bottom: 10px; | |
487 | } |
|
610 | } | |
488 |
|
611 | |||
489 | .inline-form { |
|
612 | .inline-form { | |
@@ -519,9 +642,6 b' form.comment-form {' | |||||
519 | margin: 0px; |
|
642 | margin: 0px; | |
520 | } |
|
643 | } | |
521 |
|
644 | |||
522 | .comment-inline-form .comment-footer { |
|
|||
523 | margin: 10px 0px 0px 0px; |
|
|||
524 | } |
|
|||
525 |
|
645 | |||
526 | .hide-inline-form-button { |
|
646 | .hide-inline-form-button { | |
527 | margin-left: 5px; |
|
647 | margin-left: 5px; | |
@@ -547,6 +667,7 b' comment-area-text {' | |||||
547 |
|
667 | |||
548 | .comment-area-header { |
|
668 | .comment-area-header { | |
549 | height: 35px; |
|
669 | height: 35px; | |
|
670 | border-bottom: 1px solid @grey5; | |||
550 | } |
|
671 | } | |
551 |
|
672 | |||
552 | .comment-area-header .nav-links { |
|
673 | .comment-area-header .nav-links { | |
@@ -554,6 +675,7 b' comment-area-text {' | |||||
554 | flex-flow: row wrap; |
|
675 | flex-flow: row wrap; | |
555 | -webkit-flex-flow: row wrap; |
|
676 | -webkit-flex-flow: row wrap; | |
556 | width: 100%; |
|
677 | width: 100%; | |
|
678 | border: none; | |||
557 | } |
|
679 | } | |
558 |
|
680 | |||
559 | .comment-area-footer { |
|
681 | .comment-area-footer { | |
@@ -622,14 +744,3 b' comment-area-text {' | |||||
622 | border-bottom: 2px solid transparent; |
|
744 | border-bottom: 2px solid transparent; | |
623 | } |
|
745 | } | |
624 |
|
746 | |||
625 | .toolbar-text { |
|
|||
626 | float: right; |
|
|||
627 | font-size: 11px; |
|
|||
628 | color: @grey4; |
|
|||
629 | text-align: right; |
|
|||
630 |
|
||||
631 | a { |
|
|||
632 | color: @grey4; |
|
|||
633 | } |
|
|||
634 | } |
|
|||
635 |
|
@@ -213,7 +213,6 b' div.markdown-block pre {' | |||||
213 | div.markdown-block img { |
|
213 | div.markdown-block img { | |
214 | border-style: none; |
|
214 | border-style: none; | |
215 | background-color: #fff; |
|
215 | background-color: #fff; | |
216 | padding-right: 20px; |
|
|||
217 | max-width: 100%; |
|
216 | max-width: 100%; | |
218 | } |
|
217 | } | |
219 |
|
218 | |||
@@ -274,6 +273,13 b' div.markdown-block #ws {' | |||||
274 | background-color: @grey6; |
|
273 | background-color: @grey6; | |
275 | } |
|
274 | } | |
276 |
|
275 | |||
|
276 | div.markdown-block p { | |||
|
277 | margin-top: 0; | |||
|
278 | margin-bottom: 16px; | |||
|
279 | padding: 0; | |||
|
280 | line-height: unset; | |||
|
281 | } | |||
|
282 | ||||
277 | div.markdown-block code, |
|
283 | div.markdown-block code, | |
278 | div.markdown-block pre, |
|
284 | div.markdown-block pre, | |
279 | div.markdown-block #ws, |
|
285 | div.markdown-block #ws, |
@@ -2,7 +2,7 b'' | |||||
2 |
|
2 | |||
3 |
|
3 | |||
4 | .loginbox { |
|
4 | .loginbox { | |
5 |
max-width: |
|
5 | max-width: 960px; | |
6 | margin: @pagepadding auto; |
|
6 | margin: @pagepadding auto; | |
7 | font-family: @text-light; |
|
7 | font-family: @text-light; | |
8 | border: @border-thickness solid @grey5; |
|
8 | border: @border-thickness solid @grey5; | |
@@ -22,13 +22,27 b'' | |||||
22 | float: none; |
|
22 | float: none; | |
23 | } |
|
23 | } | |
24 |
|
24 | |||
25 | .header { |
|
25 | .header-account { | |
|
26 | min-height: 49px; | |||
26 | width: 100%; |
|
27 | width: 100%; | |
27 |
padding: 0 |
|
28 | padding: 0 @header-padding; | |
28 | box-sizing: border-box; |
|
29 | box-sizing: border-box; | |
|
30 | position: relative; | |||
|
31 | vertical-align: bottom; | |||
|
32 | ||||
|
33 | background-color: @grey1; | |||
|
34 | color: @grey5; | |||
29 |
|
35 | |||
30 | .title { |
|
36 | .title { | |
31 | padding: 0; |
|
37 | padding: 0; | |
|
38 | overflow: visible; | |||
|
39 | } | |||
|
40 | ||||
|
41 | &:before, | |||
|
42 | &:after { | |||
|
43 | content: ""; | |||
|
44 | clear: both; | |||
|
45 | width: 100%; | |||
32 | } |
|
46 | } | |
33 | } |
|
47 | } | |
34 |
|
48 | |||
@@ -69,7 +83,7 b'' | |||||
69 | .sign-in-image { |
|
83 | .sign-in-image { | |
70 | display: block; |
|
84 | display: block; | |
71 | width: 65%; |
|
85 | width: 65%; | |
72 |
margin: |
|
86 | margin: 1% auto; | |
73 | } |
|
87 | } | |
74 |
|
88 | |||
75 | .sign-in-title { |
|
89 | .sign-in-title { |
@@ -263,9 +263,6 b' input.inline[type="file"] {' | |||||
263 | // HEADER |
|
263 | // HEADER | |
264 | .header { |
|
264 | .header { | |
265 |
|
265 | |||
266 | // TODO: johbo: Fix login pages, so that they work without a min-height |
|
|||
267 | // for the header and then remove the min-height. I chose a smaller value |
|
|||
268 | // intentionally here to avoid rendering issues in the main navigation. |
|
|||
269 |
|
|
266 | min-height: 49px; | |
270 | min-width: 1024px; |
|
267 | min-width: 1024px; | |
271 |
|
268 | |||
@@ -1143,9 +1140,8 b' label {' | |||||
1143 | margin-left: -15px; |
|
1140 | margin-left: -15px; | |
1144 | } |
|
1141 | } | |
1145 |
|
1142 | |||
1146 | #rev_range_container, #rev_range_clear, #rev_range_more { |
|
1143 | #rev_range_action { | |
1147 |
|
|
1144 | margin-bottom: -8px; | |
1148 | margin-bottom: -5px; |
|
|||
1149 | } |
|
1145 | } | |
1150 |
|
1146 | |||
1151 | #filter_changelog { |
|
1147 | #filter_changelog { | |
@@ -1591,9 +1587,9 b' table.integrations {' | |||||
1591 | } |
|
1587 | } | |
1592 | .pr-details-title { |
|
1588 | .pr-details-title { | |
1593 | height: 20px; |
|
1589 | height: 20px; | |
1594 |
line-height: |
|
1590 | line-height: 16px; | |
1595 |
|
1591 | |||
1596 |
padding-bottom: |
|
1592 | padding-bottom: 4px; | |
1597 | border-bottom: @border-thickness solid @grey5; |
|
1593 | border-bottom: @border-thickness solid @grey5; | |
1598 |
|
1594 | |||
1599 | .action_button.disabled { |
|
1595 | .action_button.disabled { | |
@@ -3212,7 +3208,12 b' details:not([open]) > :not(summary) {' | |||||
3212 |
|
3208 | |||
3213 | .sidebar-element { |
|
3209 | .sidebar-element { | |
3214 | margin-top: 20px; |
|
3210 | margin-top: 20px; | |
3215 | } |
|
3211 | ||
|
3212 | .icon-draft { | |||
|
3213 | color: @color-draft | |||
|
3214 | } | |||
|
3215 | } | |||
|
3216 | ||||
3216 |
|
3217 | |||
3217 | .right-sidebar-collapsed-state { |
|
3218 | .right-sidebar-collapsed-state { | |
3218 | display: flex; |
|
3219 | display: flex; | |
@@ -3235,5 +3236,4 b' details:not([open]) > :not(summary) {' | |||||
3235 |
|
3236 | |||
3236 | .old-comments-marker td { |
|
3237 | .old-comments-marker td { | |
3237 | padding-top: 15px; |
|
3238 | padding-top: 15px; | |
3238 | border-bottom: 1px solid @grey5; |
|
3239 | } | |
3239 | } |
|
@@ -115,11 +115,9 b' div.readme_box pre {' | |||||
115 | div.readme_box img { |
|
115 | div.readme_box img { | |
116 | border-style: none; |
|
116 | border-style: none; | |
117 | background-color: #fff; |
|
117 | background-color: #fff; | |
118 | padding-right: 20px; |
|
|||
119 | max-width: 100%; |
|
118 | max-width: 100%; | |
120 | } |
|
119 | } | |
121 |
|
120 | |||
122 |
|
||||
123 | div.readme_box strong { |
|
121 | div.readme_box strong { | |
124 | font-weight: 600; |
|
122 | font-weight: 600; | |
125 | margin: 0; |
|
123 | margin: 0; | |
@@ -152,6 +150,13 b' div.readme_box a:visited {' | |||||
152 | } |
|
150 | } | |
153 | */ |
|
151 | */ | |
154 |
|
152 | |||
|
153 | div.readme_box p { | |||
|
154 | margin-top: 0; | |||
|
155 | margin-bottom: 16px; | |||
|
156 | padding: 0; | |||
|
157 | line-height: unset; | |||
|
158 | } | |||
|
159 | ||||
155 |
|
160 | |||
156 | div.readme_box button { |
|
161 | div.readme_box button { | |
157 | font-size: @basefontsize; |
|
162 | font-size: @basefontsize; |
@@ -47,6 +47,8 b'' | |||||
47 |
|
47 | |||
48 | // Highlight color for lines and colors |
|
48 | // Highlight color for lines and colors | |
49 | @comment-highlight-color: #ffd887; |
|
49 | @comment-highlight-color: #ffd887; | |
|
50 | @color-draft: darken(@alert3, 30%); | |||
|
51 | @color-new: darken(@alert1, 5%); | |||
50 |
|
52 | |||
51 | // FONTS |
|
53 | // FONTS | |
52 | @basefontsize: 13px; |
|
54 | @basefontsize: 13px; |
@@ -248,6 +248,7 b' function registerRCRoutes() {' | |||||
248 | 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']); |
|
248 | 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']); | |
249 | pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']); |
|
249 | pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']); | |
250 | pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']); |
|
250 | pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']); | |
|
251 | pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']); | |||
251 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
252 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); | |
252 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
253 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); | |
253 | pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']); |
|
254 | pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']); | |
@@ -386,6 +387,8 b' function registerRCRoutes() {' | |||||
386 | pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []); |
|
387 | pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []); | |
387 | pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []); |
|
388 | pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []); | |
388 | pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []); |
|
389 | pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []); | |
|
390 | pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']); | |||
|
391 | pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']); | |||
389 | pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']); |
|
392 | pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']); | |
390 | pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']); |
|
393 | pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']); | |
391 | pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']); |
|
394 | pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']); |
@@ -71,14 +71,20 b' export class RhodecodeApp extends Polyme' | |||||
71 | if (elem) { |
|
71 | if (elem) { | |
72 | elem.handleNotification(data); |
|
72 | elem.handleNotification(data); | |
73 | } |
|
73 | } | |
74 |
|
||||
75 | } |
|
74 | } | |
76 |
|
75 | |||
77 | handleComment(data) { |
|
76 | handleComment(data) { | |
78 | if (data.message.comment_id) { |
|
77 | ||
|
78 | if (data.message.comment_data.length !== 0) { | |||
79 | if (window.refreshAllComments !== undefined) { |
|
79 | if (window.refreshAllComments !== undefined) { | |
80 | refreshAllComments() |
|
80 | refreshAllComments() | |
81 | } |
|
81 | } | |
|
82 | var json_data = data.message.comment_data; | |||
|
83 | ||||
|
84 | if (window.commentsController !== undefined) { | |||
|
85 | ||||
|
86 | window.commentsController.attachComment(json_data) | |||
|
87 | } | |||
82 | } |
|
88 | } | |
83 | } |
|
89 | } | |
84 |
|
90 |
@@ -704,3 +704,13 b' var storeUserSessionAttr = function (key' | |||||
704 | ajaxPOST(pyroutes.url('store_user_session_value'), postData, success); |
|
704 | ajaxPOST(pyroutes.url('store_user_session_value'), postData, success); | |
705 | return false; |
|
705 | return false; | |
706 | }; |
|
706 | }; | |
|
707 | ||||
|
708 | ||||
|
709 | var getUserSessionAttr = function(key) { | |||
|
710 | var storeKey = templateContext.session_attrs; | |||
|
711 | var val = storeKey[key] | |||
|
712 | if (val !== undefined) { | |||
|
713 | return JSON.parse(val) | |||
|
714 | } | |||
|
715 | return null | |||
|
716 | } |
@@ -349,7 +349,12 b' var initCommentBoxCodeMirror = function(' | |||||
349 | }; |
|
349 | }; | |
350 |
|
350 | |||
351 | var submitForm = function(cm, pred) { |
|
351 | var submitForm = function(cm, pred) { | |
352 | $(cm.display.input.textarea.form).submit(); |
|
352 | $(cm.display.input.textarea.form).find('.submit-comment-action').click(); | |
|
353 | return CodeMirror.Pass; | |||
|
354 | }; | |||
|
355 | ||||
|
356 | var submitFormAsDraft = function(cm, pred) { | |||
|
357 | $(cm.display.input.textarea.form).find('.submit-draft-action').click(); | |||
353 | return CodeMirror.Pass; |
|
358 | return CodeMirror.Pass; | |
354 | }; |
|
359 | }; | |
355 |
|
360 | |||
@@ -475,9 +480,11 b' var initCommentBoxCodeMirror = function(' | |||||
475 | // submit form on Meta-Enter |
|
480 | // submit form on Meta-Enter | |
476 | if (OSType === "mac") { |
|
481 | if (OSType === "mac") { | |
477 | extraKeys["Cmd-Enter"] = submitForm; |
|
482 | extraKeys["Cmd-Enter"] = submitForm; | |
|
483 | extraKeys["Shift-Cmd-Enter"] = submitFormAsDraft; | |||
478 | } |
|
484 | } | |
479 | else { |
|
485 | else { | |
480 | extraKeys["Ctrl-Enter"] = submitForm; |
|
486 | extraKeys["Ctrl-Enter"] = submitForm; | |
|
487 | extraKeys["Shift-Ctrl-Enter"] = submitFormAsDraft; | |||
481 | } |
|
488 | } | |
482 |
|
489 | |||
483 | if (triggerActions) { |
|
490 | if (triggerActions) { |
@@ -124,16 +124,20 b' var _submitAjaxPOST = function(url, post' | |||||
124 | this.statusChange = this.withLineNo('#change_status'); |
|
124 | this.statusChange = this.withLineNo('#change_status'); | |
125 |
|
125 | |||
126 | this.submitForm = formElement; |
|
126 | this.submitForm = formElement; | |
127 | this.submitButton = $(this.submitForm).find('input[type="submit"]'); |
|
127 | ||
|
128 | this.submitButton = $(this.submitForm).find('.submit-comment-action'); | |||
128 | this.submitButtonText = this.submitButton.val(); |
|
129 | this.submitButtonText = this.submitButton.val(); | |
129 |
|
130 | |||
|
131 | this.submitDraftButton = $(this.submitForm).find('.submit-draft-action'); | |||
|
132 | this.submitDraftButtonText = this.submitDraftButton.val(); | |||
130 |
|
133 | |||
131 | this.previewUrl = pyroutes.url('repo_commit_comment_preview', |
|
134 | this.previewUrl = pyroutes.url('repo_commit_comment_preview', | |
132 | {'repo_name': templateContext.repo_name, |
|
135 | {'repo_name': templateContext.repo_name, | |
133 | 'commit_id': templateContext.commit_data.commit_id}); |
|
136 | 'commit_id': templateContext.commit_data.commit_id}); | |
134 |
|
137 | |||
135 | if (edit){ |
|
138 | if (edit){ | |
136 |
this.submitButton |
|
139 | this.submitDraftButton.hide(); | |
|
140 | this.submitButtonText = _gettext('Update Comment'); | |||
137 | $(this.commentType).prop('disabled', true); |
|
141 | $(this.commentType).prop('disabled', true); | |
138 | $(this.commentType).addClass('disabled'); |
|
142 | $(this.commentType).addClass('disabled'); | |
139 | var editInfo = |
|
143 | var editInfo = | |
@@ -215,10 +219,17 b' var _submitAjaxPOST = function(url, post' | |||||
215 | this.getCommentStatus = function() { |
|
219 | this.getCommentStatus = function() { | |
216 | return $(this.submitForm).find(this.statusChange).val(); |
|
220 | return $(this.submitForm).find(this.statusChange).val(); | |
217 | }; |
|
221 | }; | |
|
222 | ||||
218 | this.getCommentType = function() { |
|
223 | this.getCommentType = function() { | |
219 | return $(this.submitForm).find(this.commentType).val(); |
|
224 | return $(this.submitForm).find(this.commentType).val(); | |
220 | }; |
|
225 | }; | |
221 |
|
226 | |||
|
227 | this.getDraftState = function () { | |||
|
228 | var submitterElem = $(this.submitForm).find('input[type="submit"].submitter'); | |||
|
229 | var data = $(submitterElem).data('isDraft'); | |||
|
230 | return data | |||
|
231 | } | |||
|
232 | ||||
222 | this.getResolvesId = function() { |
|
233 | this.getResolvesId = function() { | |
223 | return $(this.submitForm).find(this.resolvesId).val() || null; |
|
234 | return $(this.submitForm).find(this.resolvesId).val() || null; | |
224 | }; |
|
235 | }; | |
@@ -233,7 +244,9 b' var _submitAjaxPOST = function(url, post' | |||||
233 | }; |
|
244 | }; | |
234 |
|
245 | |||
235 | this.isAllowedToSubmit = function() { |
|
246 | this.isAllowedToSubmit = function() { | |
236 |
r |
|
247 | var commentDisabled = $(this.submitButton).prop('disabled'); | |
|
248 | var draftDisabled = $(this.submitDraftButton).prop('disabled'); | |||
|
249 | return !commentDisabled && !draftDisabled; | |||
237 | }; |
|
250 | }; | |
238 |
|
251 | |||
239 | this.initStatusChangeSelector = function(){ |
|
252 | this.initStatusChangeSelector = function(){ | |
@@ -259,11 +272,13 b' var _submitAjaxPOST = function(url, post' | |||||
259 | dropdownAutoWidth: true, |
|
272 | dropdownAutoWidth: true, | |
260 | minimumResultsForSearch: -1 |
|
273 | minimumResultsForSearch: -1 | |
261 | }); |
|
274 | }); | |
|
275 | ||||
262 | $(this.submitForm).find(this.statusChange).on('change', function() { |
|
276 | $(this.submitForm).find(this.statusChange).on('change', function() { | |
263 | var status = self.getCommentStatus(); |
|
277 | var status = self.getCommentStatus(); | |
264 |
|
278 | |||
265 | if (status && !self.isInline()) { |
|
279 | if (status && !self.isInline()) { | |
266 | $(self.submitButton).prop('disabled', false); |
|
280 | $(self.submitButton).prop('disabled', false); | |
|
281 | $(self.submitDraftButton).prop('disabled', false); | |||
267 | } |
|
282 | } | |
268 |
|
283 | |||
269 | var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status); |
|
284 | var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status); | |
@@ -295,10 +310,10 b' var _submitAjaxPOST = function(url, post' | |||||
295 | $(this.statusChange).select2('readonly', false); |
|
310 | $(this.statusChange).select2('readonly', false); | |
296 | }; |
|
311 | }; | |
297 |
|
312 | |||
298 | this.globalSubmitSuccessCallback = function(){ |
|
313 | this.globalSubmitSuccessCallback = function(comment){ | |
299 | // default behaviour is to call GLOBAL hook, if it's registered. |
|
314 | // default behaviour is to call GLOBAL hook, if it's registered. | |
300 | if (window.commentFormGlobalSubmitSuccessCallback !== undefined){ |
|
315 | if (window.commentFormGlobalSubmitSuccessCallback !== undefined){ | |
301 | commentFormGlobalSubmitSuccessCallback(); |
|
316 | commentFormGlobalSubmitSuccessCallback(comment); | |
302 | } |
|
317 | } | |
303 | }; |
|
318 | }; | |
304 |
|
319 | |||
@@ -321,6 +336,7 b' var _submitAjaxPOST = function(url, post' | |||||
321 | var text = self.cm.getValue(); |
|
336 | var text = self.cm.getValue(); | |
322 | var status = self.getCommentStatus(); |
|
337 | var status = self.getCommentStatus(); | |
323 | var commentType = self.getCommentType(); |
|
338 | var commentType = self.getCommentType(); | |
|
339 | var isDraft = self.getDraftState(); | |||
324 | var resolvesCommentId = self.getResolvesId(); |
|
340 | var resolvesCommentId = self.getResolvesId(); | |
325 | var closePullRequest = self.getClosePr(); |
|
341 | var closePullRequest = self.getClosePr(); | |
326 |
|
342 | |||
@@ -348,12 +364,15 b' var _submitAjaxPOST = function(url, post' | |||||
348 | postData['close_pull_request'] = true; |
|
364 | postData['close_pull_request'] = true; | |
349 | } |
|
365 | } | |
350 |
|
366 | |||
351 |
|
|
367 | // submitSuccess for general comments | |
|
368 | var submitSuccessCallback = function(json_data) { | |||
352 | // reload page if we change status for single commit. |
|
369 | // reload page if we change status for single commit. | |
353 | if (status && self.commitId) { |
|
370 | if (status && self.commitId) { | |
354 | location.reload(true); |
|
371 | location.reload(true); | |
355 | } else { |
|
372 | } else { | |
356 | $('#injected_page_comments').append(o.rendered_text); |
|
373 | // inject newly created comments, json_data is {<comment_id>: {}} | |
|
374 | self.attachGeneralComment(json_data) | |||
|
375 | ||||
357 | self.resetCommentFormState(); |
|
376 | self.resetCommentFormState(); | |
358 | timeagoActivate(); |
|
377 | timeagoActivate(); | |
359 | tooltipActivate(); |
|
378 | tooltipActivate(); | |
@@ -365,7 +384,7 b' var _submitAjaxPOST = function(url, post' | |||||
365 | } |
|
384 | } | |
366 |
|
385 | |||
367 | // run global callback on submit |
|
386 | // run global callback on submit | |
368 | self.globalSubmitSuccessCallback(); |
|
387 | self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id}); | |
369 |
|
388 | |||
370 | }; |
|
389 | }; | |
371 | var submitFailCallback = function(jqXHR, textStatus, errorThrown) { |
|
390 | var submitFailCallback = function(jqXHR, textStatus, errorThrown) { | |
@@ -409,10 +428,20 b' var _submitAjaxPOST = function(url, post' | |||||
409 | } |
|
428 | } | |
410 |
|
429 | |||
411 | $(this.submitButton).prop('disabled', submitState); |
|
430 | $(this.submitButton).prop('disabled', submitState); | |
|
431 | $(this.submitDraftButton).prop('disabled', submitState); | |||
|
432 | ||||
412 | if (submitEvent) { |
|
433 | if (submitEvent) { | |
413 | $(this.submitButton).val(_gettext('Submitting...')); |
|
434 | var isDraft = self.getDraftState(); | |
|
435 | ||||
|
436 | if (isDraft) { | |||
|
437 | $(this.submitDraftButton).val(_gettext('Saving Draft...')); | |||
|
438 | } else { | |||
|
439 | $(this.submitButton).val(_gettext('Submitting...')); | |||
|
440 | } | |||
|
441 | ||||
414 | } else { |
|
442 | } else { | |
415 | $(this.submitButton).val(this.submitButtonText); |
|
443 | $(this.submitButton).val(this.submitButtonText); | |
|
444 | $(this.submitDraftButton).val(this.submitDraftButtonText); | |||
416 | } |
|
445 | } | |
417 |
|
446 | |||
418 | }; |
|
447 | }; | |
@@ -488,6 +517,7 b' var _submitAjaxPOST = function(url, post' | |||||
488 | if (!allowedToSubmit){ |
|
517 | if (!allowedToSubmit){ | |
489 | return false; |
|
518 | return false; | |
490 | } |
|
519 | } | |
|
520 | ||||
491 | self.handleFormSubmit(); |
|
521 | self.handleFormSubmit(); | |
492 | }); |
|
522 | }); | |
493 |
|
523 | |||
@@ -538,26 +568,6 b' var CommentsController = function() {' | |||||
538 | var mainComment = '#text'; |
|
568 | var mainComment = '#text'; | |
539 | var self = this; |
|
569 | var self = this; | |
540 |
|
570 | |||
541 | this.cancelComment = function (node) { |
|
|||
542 | var $node = $(node); |
|
|||
543 | var edit = $(this).attr('edit'); |
|
|||
544 | if (edit) { |
|
|||
545 | var $general_comments = null; |
|
|||
546 | var $inline_comments = $node.closest('div.inline-comments'); |
|
|||
547 | if (!$inline_comments.length) { |
|
|||
548 | $general_comments = $('#comments'); |
|
|||
549 | var $comment = $general_comments.parent().find('div.comment:hidden'); |
|
|||
550 | // show hidden general comment form |
|
|||
551 | $('#cb-comment-general-form-placeholder').show(); |
|
|||
552 | } else { |
|
|||
553 | var $comment = $inline_comments.find('div.comment:hidden'); |
|
|||
554 | } |
|
|||
555 | $comment.show(); |
|
|||
556 | } |
|
|||
557 | $node.closest('.comment-inline-form').remove(); |
|
|||
558 | return false; |
|
|||
559 | }; |
|
|||
560 |
|
||||
561 | this.showVersion = function (comment_id, comment_history_id) { |
|
571 | this.showVersion = function (comment_id, comment_history_id) { | |
562 |
|
572 | |||
563 | var historyViewUrl = pyroutes.url( |
|
573 | var historyViewUrl = pyroutes.url( | |
@@ -655,12 +665,51 b' var CommentsController = function() {' | |||||
655 | return self.scrollToComment(node, -1, true); |
|
665 | return self.scrollToComment(node, -1, true); | |
656 | }; |
|
666 | }; | |
657 |
|
667 | |||
|
668 | this.cancelComment = function (node) { | |||
|
669 | var $node = $(node); | |||
|
670 | var edit = $(this).attr('edit'); | |||
|
671 | var $inlineComments = $node.closest('div.inline-comments'); | |||
|
672 | ||||
|
673 | if (edit) { | |||
|
674 | var $general_comments = null; | |||
|
675 | if (!$inlineComments.length) { | |||
|
676 | $general_comments = $('#comments'); | |||
|
677 | var $comment = $general_comments.parent().find('div.comment:hidden'); | |||
|
678 | // show hidden general comment form | |||
|
679 | $('#cb-comment-general-form-placeholder').show(); | |||
|
680 | } else { | |||
|
681 | var $comment = $inlineComments.find('div.comment:hidden'); | |||
|
682 | } | |||
|
683 | $comment.show(); | |||
|
684 | } | |||
|
685 | var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper') | |||
|
686 | $replyWrapper.removeClass('comment-form-active'); | |||
|
687 | ||||
|
688 | var lastComment = $inlineComments.find('.comment-inline').last(); | |||
|
689 | if ($(lastComment).hasClass('comment-outdated')) { | |||
|
690 | $replyWrapper.hide(); | |||
|
691 | } | |||
|
692 | ||||
|
693 | $node.closest('.comment-inline-form').remove(); | |||
|
694 | return false; | |||
|
695 | }; | |||
|
696 | ||||
658 | this._deleteComment = function(node) { |
|
697 | this._deleteComment = function(node) { | |
659 | var $node = $(node); |
|
698 | var $node = $(node); | |
660 | var $td = $node.closest('td'); |
|
699 | var $td = $node.closest('td'); | |
661 | var $comment = $node.closest('.comment'); |
|
700 | var $comment = $node.closest('.comment'); | |
662 |
var comment_id = $comment. |
|
701 | var comment_id = $($comment).data('commentId'); | |
663 | var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id); |
|
702 | var isDraft = $($comment).data('commentDraft'); | |
|
703 | ||||
|
704 | var pullRequestId = templateContext.pull_request_data.pull_request_id; | |||
|
705 | var commitId = templateContext.commit_data.commit_id; | |||
|
706 | ||||
|
707 | if (pullRequestId) { | |||
|
708 | var url = pyroutes.url('pullrequest_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "pull_request_id": pullRequestId}) | |||
|
709 | } else if (commitId) { | |||
|
710 | var url = pyroutes.url('repo_commit_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "commit_id": commitId}) | |||
|
711 | } | |||
|
712 | ||||
664 | var postData = { |
|
713 | var postData = { | |
665 | 'csrf_token': CSRF_TOKEN |
|
714 | 'csrf_token': CSRF_TOKEN | |
666 | }; |
|
715 | }; | |
@@ -677,10 +726,14 b' var CommentsController = function() {' | |||||
677 | updateSticky() |
|
726 | updateSticky() | |
678 | } |
|
727 | } | |
679 |
|
728 | |||
680 | if (window.refreshAllComments !== undefined) { |
|
729 | if (window.refreshAllComments !== undefined && !isDraft) { | |
681 | // if we have this handler, run it, and refresh all comments boxes |
|
730 | // if we have this handler, run it, and refresh all comments boxes | |
682 | refreshAllComments() |
|
731 | refreshAllComments() | |
683 | } |
|
732 | } | |
|
733 | else if (window.refreshDraftComments !== undefined && isDraft) { | |||
|
734 | // if we have this handler, run it, and refresh all comments boxes | |||
|
735 | refreshDraftComments(); | |||
|
736 | } | |||
684 | return false; |
|
737 | return false; | |
685 | }; |
|
738 | }; | |
686 |
|
739 | |||
@@ -695,8 +748,6 b' var CommentsController = function() {' | |||||
695 | }; |
|
748 | }; | |
696 | ajaxPOST(url, postData, success, failure); |
|
749 | ajaxPOST(url, postData, success, failure); | |
697 |
|
750 | |||
698 |
|
||||
699 |
|
||||
700 | } |
|
751 | } | |
701 |
|
752 | |||
702 | this.deleteComment = function(node) { |
|
753 | this.deleteComment = function(node) { | |
@@ -716,7 +767,59 b' var CommentsController = function() {' | |||||
716 | }) |
|
767 | }) | |
717 | }; |
|
768 | }; | |
718 |
|
769 | |||
|
770 | this._finalizeDrafts = function(commentIds) { | |||
|
771 | ||||
|
772 | var pullRequestId = templateContext.pull_request_data.pull_request_id; | |||
|
773 | var commitId = templateContext.commit_data.commit_id; | |||
|
774 | ||||
|
775 | if (pullRequestId) { | |||
|
776 | var url = pyroutes.url('pullrequest_draft_comments_submit', {"repo_name": templateContext.repo_name, "pull_request_id": pullRequestId}) | |||
|
777 | } else if (commitId) { | |||
|
778 | var url = pyroutes.url('commit_draft_comments_submit', {"repo_name": templateContext.repo_name, "commit_id": commitId}) | |||
|
779 | } | |||
|
780 | ||||
|
781 | // remove the drafts so we can lock them before submit. | |||
|
782 | $.each(commentIds, function(idx, val){ | |||
|
783 | $('#comment-{0}'.format(val)).remove(); | |||
|
784 | }) | |||
|
785 | ||||
|
786 | var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN}; | |||
|
787 | ||||
|
788 | var submitSuccessCallback = function(json_data) { | |||
|
789 | self.attachInlineComment(json_data); | |||
|
790 | ||||
|
791 | if (window.refreshDraftComments !== undefined) { | |||
|
792 | // if we have this handler, run it, and refresh all comments boxes | |||
|
793 | refreshDraftComments() | |||
|
794 | } | |||
|
795 | ||||
|
796 | return false; | |||
|
797 | }; | |||
|
798 | ||||
|
799 | ajaxPOST(url, postData, submitSuccessCallback) | |||
|
800 | ||||
|
801 | } | |||
|
802 | ||||
|
803 | this.finalizeDrafts = function(commentIds, callback) { | |||
|
804 | ||||
|
805 | SwalNoAnimation.fire({ | |||
|
806 | title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length), | |||
|
807 | icon: 'warning', | |||
|
808 | showCancelButton: true, | |||
|
809 | confirmButtonText: _gettext('Yes'), | |||
|
810 | ||||
|
811 | }).then(function(result) { | |||
|
812 | if (result.value) { | |||
|
813 | if (callback !== undefined) { | |||
|
814 | callback(result) | |||
|
815 | } | |||
|
816 | self._finalizeDrafts(commentIds); | |||
|
817 | } | |||
|
818 | }) | |||
|
819 | }; | |||
|
820 | ||||
719 | this.toggleWideMode = function (node) { |
|
821 | this.toggleWideMode = function (node) { | |
|
822 | ||||
720 | if ($('#content').hasClass('wrapper')) { |
|
823 | if ($('#content').hasClass('wrapper')) { | |
721 | $('#content').removeClass("wrapper"); |
|
824 | $('#content').removeClass("wrapper"); | |
722 | $('#content').addClass("wide-mode-wrapper"); |
|
825 | $('#content').addClass("wide-mode-wrapper"); | |
@@ -731,16 +834,49 b' var CommentsController = function() {' | |||||
731 |
|
834 | |||
732 | }; |
|
835 | }; | |
733 |
|
836 | |||
734 | this.toggleComments = function(node, show) { |
|
837 | /** | |
|
838 | * Turn off/on all comments in file diff | |||
|
839 | */ | |||
|
840 | this.toggleDiffComments = function(node) { | |||
|
841 | // Find closes filediff container | |||
735 | var $filediff = $(node).closest('.filediff'); |
|
842 | var $filediff = $(node).closest('.filediff'); | |
|
843 | if ($(node).hasClass('toggle-on')) { | |||
|
844 | var show = false; | |||
|
845 | } else if ($(node).hasClass('toggle-off')) { | |||
|
846 | var show = true; | |||
|
847 | } | |||
|
848 | ||||
|
849 | // Toggle each individual comment block, so we can un-toggle single ones | |||
|
850 | $.each($filediff.find('.toggle-comment-action'), function(idx, val) { | |||
|
851 | self.toggleLineComments($(val), show) | |||
|
852 | }) | |||
|
853 | ||||
|
854 | // since we change the height of the diff container that has anchor points for upper | |||
|
855 | // sticky header, we need to tell it to re-calculate those | |||
|
856 | if (window.updateSticky !== undefined) { | |||
|
857 | // potentially our comments change the active window size, so we | |||
|
858 | // notify sticky elements | |||
|
859 | updateSticky() | |||
|
860 | } | |||
|
861 | ||||
|
862 | return false; | |||
|
863 | } | |||
|
864 | ||||
|
865 | this.toggleLineComments = function(node, show) { | |||
|
866 | ||||
|
867 | var trElem = $(node).closest('tr') | |||
|
868 | ||||
736 | if (show === true) { |
|
869 | if (show === true) { | |
737 | $filediff.removeClass('hide-comments'); |
|
870 | // mark outdated comments as visible before the toggle; | |
|
871 | $(trElem).find('.comment-outdated').show(); | |||
|
872 | $(trElem).removeClass('hide-line-comments'); | |||
738 | } else if (show === false) { |
|
873 | } else if (show === false) { | |
739 | $filediff.find('.hide-line-comments').removeClass('hide-line-comments'); |
|
874 | $(trElem).find('.comment-outdated').hide(); | |
740 |
|
|
875 | $(trElem).addClass('hide-line-comments'); | |
741 | } else { |
|
876 | } else { | |
742 | $filediff.find('.hide-line-comments').removeClass('hide-line-comments'); |
|
877 | // mark outdated comments as visible before the toggle; | |
743 | $filediff.toggleClass('hide-comments'); |
|
878 | $(trElem).find('.comment-outdated').show(); | |
|
879 | $(trElem).toggleClass('hide-line-comments'); | |||
744 | } |
|
880 | } | |
745 |
|
881 | |||
746 | // since we change the height of the diff container that has anchor points for upper |
|
882 | // since we change the height of the diff container that has anchor points for upper | |
@@ -751,15 +887,6 b' var CommentsController = function() {' | |||||
751 | updateSticky() |
|
887 | updateSticky() | |
752 | } |
|
888 | } | |
753 |
|
889 | |||
754 | return false; |
|
|||
755 | }; |
|
|||
756 |
|
||||
757 | this.toggleLineComments = function(node) { |
|
|||
758 | self.toggleComments(node, true); |
|
|||
759 | var $node = $(node); |
|
|||
760 | // mark outdated comments as visible before the toggle; |
|
|||
761 | $(node.closest('tr')).find('.comment-outdated').show(); |
|
|||
762 | $node.closest('tr').toggleClass('hide-line-comments'); |
|
|||
763 | }; |
|
890 | }; | |
764 |
|
891 | |||
765 | this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){ |
|
892 | this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){ | |
@@ -913,63 +1040,58 b' var CommentsController = function() {' | |||||
913 | return commentForm; |
|
1040 | return commentForm; | |
914 | }; |
|
1041 | }; | |
915 |
|
1042 | |||
916 | this.editComment = function(node) { |
|
1043 | this.editComment = function(node, line_no, f_path) { | |
|
1044 | self.edit = true; | |||
917 | var $node = $(node); |
|
1045 | var $node = $(node); | |
|
1046 | var $td = $node.closest('td'); | |||
|
1047 | ||||
918 | var $comment = $(node).closest('.comment'); |
|
1048 | var $comment = $(node).closest('.comment'); | |
919 |
var comment_id = $comment. |
|
1049 | var comment_id = $($comment).data('commentId'); | |
920 | var $form = null |
|
1050 | var isDraft = $($comment).data('commentDraft'); | |
|
1051 | var $editForm = null | |||
921 |
|
1052 | |||
922 | var $comments = $node.closest('div.inline-comments'); |
|
1053 | var $comments = $node.closest('div.inline-comments'); | |
923 | var $general_comments = null; |
|
1054 | var $general_comments = null; | |
924 | var lineno = null; |
|
|||
925 |
|
1055 | |||
926 | if($comments.length){ |
|
1056 | if($comments.length){ | |
927 | // inline comments setup |
|
1057 | // inline comments setup | |
928 |
$ |
|
1058 | $editForm = $comments.find('.comment-inline-form'); | |
929 | lineno = self.getLineNumber(node) |
|
1059 | line_no = self.getLineNumber(node) | |
930 | } |
|
1060 | } | |
931 | else{ |
|
1061 | else{ | |
932 | // general comments setup |
|
1062 | // general comments setup | |
933 | $comments = $('#comments'); |
|
1063 | $comments = $('#comments'); | |
934 |
$ |
|
1064 | $editForm = $comments.find('.comment-inline-form'); | |
935 | lineno = $comment[0].id |
|
1065 | line_no = $comment[0].id | |
936 | $('#cb-comment-general-form-placeholder').hide(); |
|
1066 | $('#cb-comment-general-form-placeholder').hide(); | |
937 | } |
|
1067 | } | |
938 |
|
1068 | |||
939 | this.edit = true; |
|
1069 | if ($editForm.length === 0) { | |
940 |
|
1070 | |||
941 | if (!$form.length) { |
|
1071 | // unhide all comments if they are hidden for a proper REPLY mode | |
942 |
|
||||
943 | var $filediff = $node.closest('.filediff'); |
|
1072 | var $filediff = $node.closest('.filediff'); | |
944 | $filediff.removeClass('hide-comments'); |
|
1073 | $filediff.removeClass('hide-comments'); | |
945 | var f_path = $filediff.attr('data-f-path'); |
|
|||
946 |
|
||||
947 | // create a new HTML from template |
|
|||
948 |
|
1074 | |||
949 | var tmpl = $('#cb-comment-inline-form-template').html(); |
|
1075 | $editForm = self.createNewFormWrapper(f_path, line_no); | |
950 | tmpl = tmpl.format(escapeHtml(f_path), lineno); |
|
1076 | if(f_path && line_no) { | |
951 | $form = $(tmpl); |
|
1077 | $editForm.addClass('comment-inline-form-edit') | |
952 | $comment.after($form) |
|
1078 | } | |
953 |
|
1079 | |||
954 | var _form = $($form[0]).find('form'); |
|
1080 | $comment.after($editForm) | |
|
1081 | ||||
|
1082 | var _form = $($editForm[0]).find('form'); | |||
955 | var autocompleteActions = ['as_note',]; |
|
1083 | var autocompleteActions = ['as_note',]; | |
956 | var commentForm = this.createCommentForm( |
|
1084 | var commentForm = this.createCommentForm( | |
957 | _form, lineno, '', autocompleteActions, resolvesCommentId, |
|
1085 | _form, line_no, '', autocompleteActions, resolvesCommentId, | |
958 | this.edit, comment_id); |
|
1086 | this.edit, comment_id); | |
959 | var old_comment_text_binary = $comment.attr('data-comment-text'); |
|
1087 | var old_comment_text_binary = $comment.attr('data-comment-text'); | |
960 | var old_comment_text = b64DecodeUnicode(old_comment_text_binary); |
|
1088 | var old_comment_text = b64DecodeUnicode(old_comment_text_binary); | |
961 | commentForm.cm.setValue(old_comment_text); |
|
1089 | commentForm.cm.setValue(old_comment_text); | |
962 | $comment.hide(); |
|
1090 | $comment.hide(); | |
|
1091 | tooltipActivate(); | |||
963 |
|
1092 | |||
964 | $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({ |
|
1093 | // set a CUSTOM submit handler for inline comment edit action. | |
965 | form: _form, |
|
1094 | commentForm.setHandleFormSubmit(function(o) { | |
966 | parent: $comments, |
|
|||
967 | lineno: lineno, |
|
|||
968 | f_path: f_path} |
|
|||
969 | ); |
|
|||
970 |
|
||||
971 | // set a CUSTOM submit handler for inline comments. |
|
|||
972 | commentForm.setHandleFormSubmit(function(o) { |
|
|||
973 | var text = commentForm.cm.getValue(); |
|
1095 | var text = commentForm.cm.getValue(); | |
974 | var commentType = commentForm.getCommentType(); |
|
1096 | var commentType = commentForm.getCommentType(); | |
975 |
|
1097 | |||
@@ -1000,14 +1122,15 b' var CommentsController = function() {' | |||||
1000 | var postData = { |
|
1122 | var postData = { | |
1001 | 'text': text, |
|
1123 | 'text': text, | |
1002 | 'f_path': f_path, |
|
1124 | 'f_path': f_path, | |
1003 | 'line': lineno, |
|
1125 | 'line': line_no, | |
1004 | 'comment_type': commentType, |
|
1126 | 'comment_type': commentType, | |
|
1127 | 'draft': isDraft, | |||
1005 | 'version': version, |
|
1128 | 'version': version, | |
1006 | 'csrf_token': CSRF_TOKEN |
|
1129 | 'csrf_token': CSRF_TOKEN | |
1007 | }; |
|
1130 | }; | |
1008 |
|
1131 | |||
1009 | var submitSuccessCallback = function(json_data) { |
|
1132 | var submitSuccessCallback = function(json_data) { | |
1010 |
$ |
|
1133 | $editForm.remove(); | |
1011 | $comment.show(); |
|
1134 | $comment.show(); | |
1012 | var postData = { |
|
1135 | var postData = { | |
1013 | 'text': text, |
|
1136 | 'text': text, | |
@@ -1072,8 +1195,7 b' var CommentsController = function() {' | |||||
1072 | 'commit_id': templateContext.commit_data.commit_id}); |
|
1195 | 'commit_id': templateContext.commit_data.commit_id}); | |
1073 |
|
1196 | |||
1074 | _submitAjaxPOST( |
|
1197 | _submitAjaxPOST( | |
1075 | previewUrl, postData, successRenderCommit, |
|
1198 | previewUrl, postData, successRenderCommit, failRenderCommit | |
1076 | failRenderCommit |
|
|||
1077 | ); |
|
1199 | ); | |
1078 |
|
1200 | |||
1079 | try { |
|
1201 | try { | |
@@ -1084,7 +1206,7 b' var CommentsController = function() {' | |||||
1084 | $comments.find('.cb-comment-add-button').before(html); |
|
1206 | $comments.find('.cb-comment-add-button').before(html); | |
1085 |
|
1207 | |||
1086 | // run global callback on submit |
|
1208 | // run global callback on submit | |
1087 | commentForm.globalSubmitSuccessCallback(); |
|
1209 | commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id}); | |
1088 |
|
1210 | |||
1089 | } catch (e) { |
|
1211 | } catch (e) { | |
1090 | console.error(e); |
|
1212 | console.error(e); | |
@@ -1101,10 +1223,14 b' var CommentsController = function() {' | |||||
1101 | updateSticky() |
|
1223 | updateSticky() | |
1102 | } |
|
1224 | } | |
1103 |
|
1225 | |||
1104 | if (window.refreshAllComments !== undefined) { |
|
1226 | if (window.refreshAllComments !== undefined && !isDraft) { | |
1105 | // if we have this handler, run it, and refresh all comments boxes |
|
1227 | // if we have this handler, run it, and refresh all comments boxes | |
1106 | refreshAllComments() |
|
1228 | refreshAllComments() | |
1107 | } |
|
1229 | } | |
|
1230 | else if (window.refreshDraftComments !== undefined && isDraft) { | |||
|
1231 | // if we have this handler, run it, and refresh all comments boxes | |||
|
1232 | refreshDraftComments(); | |||
|
1233 | } | |||
1108 |
|
1234 | |||
1109 | commentForm.setActionButtonsDisabled(false); |
|
1235 | commentForm.setActionButtonsDisabled(false); | |
1110 |
|
1236 | |||
@@ -1129,66 +1255,122 b' var CommentsController = function() {' | |||||
1129 | }); |
|
1255 | }); | |
1130 | } |
|
1256 | } | |
1131 |
|
1257 | |||
1132 |
$ |
|
1258 | $editForm.addClass('comment-inline-form-open'); | |
1133 | }; |
|
1259 | }; | |
1134 |
|
1260 | |||
1135 |
this. |
|
1261 | this.attachComment = function(json_data) { | |
1136 | var resolvesCommentId = resolutionComment || null; |
|
1262 | var self = this; | |
|
1263 | $.each(json_data, function(idx, val) { | |||
|
1264 | var json_data_elem = [val] | |||
|
1265 | var isInline = val.comment_f_path && val.comment_lineno | |||
|
1266 | ||||
|
1267 | if (isInline) { | |||
|
1268 | self.attachInlineComment(json_data_elem) | |||
|
1269 | } else { | |||
|
1270 | self.attachGeneralComment(json_data_elem) | |||
|
1271 | } | |||
|
1272 | }) | |||
|
1273 | ||||
|
1274 | } | |||
|
1275 | ||||
|
1276 | this.attachGeneralComment = function(json_data) { | |||
|
1277 | $.each(json_data, function(idx, val) { | |||
|
1278 | $('#injected_page_comments').append(val.rendered_text); | |||
|
1279 | }) | |||
|
1280 | } | |||
|
1281 | ||||
|
1282 | this.attachInlineComment = function(json_data) { | |||
|
1283 | ||||
|
1284 | $.each(json_data, function (idx, val) { | |||
|
1285 | var line_qry = '*[data-line-no="{0}"]'.format(val.line_no); | |||
|
1286 | var html = val.rendered_text; | |||
|
1287 | var $inlineComments = $('#' + val.target_id) | |||
|
1288 | .find(line_qry) | |||
|
1289 | .find('.inline-comments'); | |||
|
1290 | ||||
|
1291 | var lastComment = $inlineComments.find('.comment-inline').last(); | |||
|
1292 | ||||
|
1293 | if (lastComment.length === 0) { | |||
|
1294 | // first comment, we append simply | |||
|
1295 | $inlineComments.find('.reply-thread-container-wrapper').before(html); | |||
|
1296 | } else { | |||
|
1297 | $(lastComment).after(html) | |||
|
1298 | } | |||
|
1299 | ||||
|
1300 | }) | |||
|
1301 | ||||
|
1302 | }; | |||
|
1303 | ||||
|
1304 | this.createNewFormWrapper = function(f_path, line_no) { | |||
|
1305 | // create a new reply HTML form from template | |||
|
1306 | var tmpl = $('#cb-comment-inline-form-template').html(); | |||
|
1307 | tmpl = tmpl.format(escapeHtml(f_path), line_no); | |||
|
1308 | return $(tmpl); | |||
|
1309 | } | |||
|
1310 | ||||
|
1311 | this.createComment = function(node, f_path, line_no, resolutionComment) { | |||
|
1312 | self.edit = false; | |||
1137 | var $node = $(node); |
|
1313 | var $node = $(node); | |
1138 | var $td = $node.closest('td'); |
|
1314 | var $td = $node.closest('td'); | |
1139 | var $form = $td.find('.comment-inline-form'); |
|
1315 | var resolvesCommentId = resolutionComment || null; | |
1140 | this.edit = false; |
|
|||
1141 |
|
1316 | |||
1142 | if (!$form.length) { |
|
1317 | var $replyForm = $td.find('.comment-inline-form'); | |
1143 |
|
1318 | |||
1144 | var $filediff = $node.closest('.filediff'); |
|
1319 | // if form isn't existing, we're generating a new one and injecting it. | |
1145 | $filediff.removeClass('hide-comments'); |
|
1320 | if ($replyForm.length === 0) { | |
1146 | var f_path = $filediff.attr('data-f-path'); |
|
1321 | ||
1147 | var lineno = self.getLineNumber(node); |
|
1322 | // unhide/expand all comments if they are hidden for a proper REPLY mode | |
1148 | // create a new HTML from template |
|
1323 | self.toggleLineComments($node, true); | |
1149 | var tmpl = $('#cb-comment-inline-form-template').html(); |
|
1324 | ||
1150 |
|
|
1325 | $replyForm = self.createNewFormWrapper(f_path, line_no); | |
1151 | $form = $(tmpl); |
|
|||
1152 |
|
1326 | |||
1153 | var $comments = $td.find('.inline-comments'); |
|
1327 | var $comments = $td.find('.inline-comments'); | |
1154 | if (!$comments.length) { |
|
1328 | ||
1155 | $comments = $( |
|
1329 | // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first | |
1156 | $('#cb-comments-inline-container-template').html()); |
|
1330 | if ($comments.length===0) { | |
1157 | $td.append($comments); |
|
1331 | var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no) | |
|
1332 | var $reply_container = $('#cb-comments-inline-container-template') | |||
|
1333 | $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn); | |||
|
1334 | $td.append($($reply_container).html()); | |||
1158 | } |
|
1335 | } | |
1159 |
|
1336 | |||
1160 | $td.find('.cb-comment-add-button').before($form); |
|
1337 | // default comment button exists, so we prepend the form for leaving initial comment | |
|
1338 | $td.find('.cb-comment-add-button').before($replyForm); | |||
|
1339 | // set marker, that we have a open form | |||
|
1340 | var $replyWrapper = $td.find('.reply-thread-container-wrapper') | |||
|
1341 | $replyWrapper.addClass('comment-form-active'); | |||
1161 |
|
1342 | |||
1162 | var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno); |
|
1343 | var lastComment = $comments.find('.comment-inline').last(); | |
1163 | var _form = $($form[0]).find('form'); |
|
1344 | if ($(lastComment).hasClass('comment-outdated')) { | |
|
1345 | $replyWrapper.show(); | |||
|
1346 | } | |||
|
1347 | ||||
|
1348 | var _form = $($replyForm[0]).find('form'); | |||
1164 | var autocompleteActions = ['as_note', 'as_todo']; |
|
1349 | var autocompleteActions = ['as_note', 'as_todo']; | |
1165 | var comment_id=null; |
|
1350 | var comment_id=null; | |
1166 | var commentForm = this.createCommentForm( |
|
1351 | var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no); | |
1167 | _form, lineno, placeholderText, autocompleteActions, resolvesCommentId, this.edit, comment_id); |
|
1352 | var commentForm = self.createCommentForm( | |
1168 |
|
1353 | _form, line_no, placeholderText, autocompleteActions, resolvesCommentId, | ||
1169 | $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({ |
|
1354 | self.edit, comment_id); | |
1170 | form: _form, |
|
|||
1171 | parent: $td[0], |
|
|||
1172 | lineno: lineno, |
|
|||
1173 | f_path: f_path} |
|
|||
1174 | ); |
|
|||
1175 |
|
1355 | |||
1176 | // set a CUSTOM submit handler for inline comments. |
|
1356 | // set a CUSTOM submit handler for inline comments. | |
1177 | commentForm.setHandleFormSubmit(function(o) { |
|
1357 | commentForm.setHandleFormSubmit(function(o) { | |
1178 | var text = commentForm.cm.getValue(); |
|
1358 | var text = commentForm.cm.getValue(); | |
1179 | var commentType = commentForm.getCommentType(); |
|
1359 | var commentType = commentForm.getCommentType(); | |
1180 | var resolvesCommentId = commentForm.getResolvesId(); |
|
1360 | var resolvesCommentId = commentForm.getResolvesId(); | |
|
1361 | var isDraft = commentForm.getDraftState(); | |||
1181 |
|
1362 | |||
1182 | if (text === "") { |
|
1363 | if (text === "") { | |
1183 | return; |
|
1364 | return; | |
1184 | } |
|
1365 | } | |
1185 |
|
1366 | |||
1186 | if (lineno === undefined) { |
|
1367 | if (line_no === undefined) { | |
1187 | alert('missing line !'); |
|
1368 | alert('Error: unable to fetch line number for this inline comment !'); | |
1188 | return; |
|
1369 | return; | |
1189 | } |
|
1370 | } | |
|
1371 | ||||
1190 | if (f_path === undefined) { |
|
1372 | if (f_path === undefined) { | |
1191 | alert('missing file path !'); |
|
1373 | alert('Error: unable to fetch file path for this inline comment !'); | |
1192 | return; |
|
1374 | return; | |
1193 | } |
|
1375 | } | |
1194 |
|
1376 | |||
@@ -1199,66 +1381,79 b' var CommentsController = function() {' | |||||
1199 | var postData = { |
|
1381 | var postData = { | |
1200 | 'text': text, |
|
1382 | 'text': text, | |
1201 | 'f_path': f_path, |
|
1383 | 'f_path': f_path, | |
1202 | 'line': lineno, |
|
1384 | 'line': line_no, | |
1203 | 'comment_type': commentType, |
|
1385 | 'comment_type': commentType, | |
|
1386 | 'draft': isDraft, | |||
1204 | 'csrf_token': CSRF_TOKEN |
|
1387 | 'csrf_token': CSRF_TOKEN | |
1205 | }; |
|
1388 | }; | |
1206 | if (resolvesCommentId){ |
|
1389 | if (resolvesCommentId){ | |
1207 | postData['resolves_comment_id'] = resolvesCommentId; |
|
1390 | postData['resolves_comment_id'] = resolvesCommentId; | |
1208 | } |
|
1391 | } | |
1209 |
|
1392 | |||
|
1393 | // submitSuccess for inline commits | |||
1210 | var submitSuccessCallback = function(json_data) { |
|
1394 | var submitSuccessCallback = function(json_data) { | |
1211 | $form.remove(); |
|
1395 | ||
1212 | try { |
|
1396 | $replyForm.remove(); | |
1213 | var html = json_data.rendered_text; |
|
1397 | $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active'); | |
1214 | var lineno = json_data.line_no; |
|
1398 | ||
1215 | var target_id = json_data.target_id; |
|
1399 | try { | |
|
1400 | ||||
|
1401 | // inject newly created comments, json_data is {<comment_id>: {}} | |||
|
1402 | self.attachInlineComment(json_data) | |||
1216 |
|
1403 | |||
1217 | $comments.find('.cb-comment-add-button').before(html); |
|
1404 | //mark visually which comment was resolved | |
|
1405 | if (resolvesCommentId) { | |||
|
1406 | commentForm.markCommentResolved(resolvesCommentId); | |||
|
1407 | } | |||
1218 |
|
1408 | |||
1219 | //mark visually which comment was resolved |
|
1409 | // run global callback on submit | |
1220 | if (resolvesCommentId) { |
|
1410 | commentForm.globalSubmitSuccessCallback({ | |
1221 | commentForm.markCommentResolved(resolvesCommentId); |
|
1411 | draft: isDraft, | |
|
1412 | comment_id: comment_id | |||
|
1413 | }); | |||
|
1414 | ||||
|
1415 | } catch (e) { | |||
|
1416 | console.error(e); | |||
1222 | } |
|
1417 | } | |
1223 |
|
1418 | |||
1224 | // run global callback on submit |
|
|||
1225 | commentForm.globalSubmitSuccessCallback(); |
|
|||
1226 |
|
||||
1227 | } catch (e) { |
|
|||
1228 | console.error(e); |
|
|||
1229 | } |
|
|||
1230 |
|
||||
1231 | // re trigger the linkification of next/prev navigation |
|
|||
1232 | linkifyComments($('.inline-comment-injected')); |
|
|||
1233 | timeagoActivate(); |
|
|||
1234 | tooltipActivate(); |
|
|||
1235 |
|
||||
1236 | if (window.updateSticky !== undefined) { |
|
1419 | if (window.updateSticky !== undefined) { | |
1237 | // potentially our comments change the active window size, so we |
|
1420 | // potentially our comments change the active window size, so we | |
1238 | // notify sticky elements |
|
1421 | // notify sticky elements | |
1239 | updateSticky() |
|
1422 | updateSticky() | |
1240 | } |
|
1423 | } | |
1241 |
|
1424 | |||
1242 | if (window.refreshAllComments !== undefined) { |
|
1425 | if (window.refreshAllComments !== undefined && !isDraft) { | |
1243 | // if we have this handler, run it, and refresh all comments boxes |
|
1426 | // if we have this handler, run it, and refresh all comments boxes | |
1244 | refreshAllComments() |
|
1427 | refreshAllComments() | |
1245 | } |
|
1428 | } | |
|
1429 | else if (window.refreshDraftComments !== undefined && isDraft) { | |||
|
1430 | // if we have this handler, run it, and refresh all comments boxes | |||
|
1431 | refreshDraftComments(); | |||
|
1432 | } | |||
1246 |
|
1433 | |||
1247 | commentForm.setActionButtonsDisabled(false); |
|
1434 | commentForm.setActionButtonsDisabled(false); | |
1248 |
|
1435 | |||
|
1436 | // re trigger the linkification of next/prev navigation | |||
|
1437 | linkifyComments($('.inline-comment-injected')); | |||
|
1438 | timeagoActivate(); | |||
|
1439 | tooltipActivate(); | |||
1249 | }; |
|
1440 | }; | |
|
1441 | ||||
1250 | var submitFailCallback = function(jqXHR, textStatus, errorThrown) { |
|
1442 | var submitFailCallback = function(jqXHR, textStatus, errorThrown) { | |
1251 | var prefix = "Error while submitting comment.\n" |
|
1443 | var prefix = "Error while submitting comment.\n" | |
1252 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); |
|
1444 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); | |
1253 | ajaxErrorSwal(message); |
|
1445 | ajaxErrorSwal(message); | |
1254 | commentForm.resetCommentFormState(text) |
|
1446 | commentForm.resetCommentFormState(text) | |
1255 | }; |
|
1447 | }; | |
|
1448 | ||||
1256 | commentForm.submitAjaxPOST( |
|
1449 | commentForm.submitAjaxPOST( | |
1257 | commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback); |
|
1450 | commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback); | |
1258 | }); |
|
1451 | }); | |
1259 | } |
|
1452 | } | |
1260 |
|
1453 | |||
1261 | $form.addClass('comment-inline-form-open'); |
|
1454 | // Finally "open" our reply form, since we know there are comments and we have the "attached" old form | |
|
1455 | $replyForm.addClass('comment-inline-form-open'); | |||
|
1456 | tooltipActivate(); | |||
1262 | }; |
|
1457 | }; | |
1263 |
|
1458 | |||
1264 | this.createResolutionComment = function(commentId){ |
|
1459 | this.createResolutionComment = function(commentId){ | |
@@ -1268,9 +1463,12 b' var CommentsController = function() {' | |||||
1268 | var comment = $('#comment-'+commentId); |
|
1463 | var comment = $('#comment-'+commentId); | |
1269 | var commentData = comment.data(); |
|
1464 | var commentData = comment.data(); | |
1270 | if (commentData.commentInline) { |
|
1465 | if (commentData.commentInline) { | |
1271 | this.createComment(comment, commentId) |
|
1466 | var f_path = commentData.fPath; | |
|
1467 | var line_no = commentData.lineNo; | |||
|
1468 | //TODO check this if we need to give f_path/line_no | |||
|
1469 | this.createComment(comment, f_path, line_no, commentId) | |||
1272 | } else { |
|
1470 | } else { | |
1273 |
|
|
1471 | this.createGeneralComment('general', "$placeholder", commentId) | |
1274 | } |
|
1472 | } | |
1275 |
|
1473 | |||
1276 | return false; |
|
1474 | return false; | |
@@ -1296,3 +1494,8 b' var CommentsController = function() {' | |||||
1296 | }; |
|
1494 | }; | |
1297 |
|
1495 | |||
1298 | }; |
|
1496 | }; | |
|
1497 | ||||
|
1498 | window.commentHelp = function(renderer) { | |||
|
1499 | var funcData = {'renderer': renderer} | |||
|
1500 | return renderTemplate('commentHelpHovercard', funcData) | |||
|
1501 | } No newline at end of file |
@@ -42,12 +42,22 b' window.toggleElement = function (elem, t' | |||||
42 | var $elem = $(elem); |
|
42 | var $elem = $(elem); | |
43 | var $target = $(target); |
|
43 | var $target = $(target); | |
44 |
|
44 | |||
45 | if ($target.is(':visible') || $target.length === 0) { |
|
45 | if (target !== undefined) { | |
|
46 | var show = $target.is(':visible') || $target.length === 0; | |||
|
47 | } else { | |||
|
48 | var show = $elem.hasClass('toggle-off') | |||
|
49 | } | |||
|
50 | ||||
|
51 | if (show) { | |||
46 | $target.hide(); |
|
52 | $target.hide(); | |
47 | $elem.html($elem.data('toggleOn')) |
|
53 | $elem.html($elem.data('toggleOn')) | |
|
54 | $elem.addClass('toggle-on') | |||
|
55 | $elem.removeClass('toggle-off') | |||
48 | } else { |
|
56 | } else { | |
49 | $target.show(); |
|
57 | $target.show(); | |
50 | $elem.html($elem.data('toggleOff')) |
|
58 | $elem.html($elem.data('toggleOff')) | |
|
59 | $elem.addClass('toggle-off') | |||
|
60 | $elem.removeClass('toggle-on') | |||
51 | } |
|
61 | } | |
52 |
|
62 | |||
53 | return false |
|
63 | return false |
@@ -182,83 +182,34 b' window.ReviewersController = function ()' | |||||
182 | if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) { |
|
182 | if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) { | |
183 | // default rule, case for older repo that don't have any rules stored |
|
183 | // default rule, case for older repo that don't have any rules stored | |
184 | self.$rulesList.append( |
|
184 | self.$rulesList.append( | |
185 | self.addRule( |
|
185 | self.addRule(_gettext('All reviewers must vote.')) | |
186 | _gettext('All reviewers must vote.')) |
|
|||
187 | ); |
|
186 | ); | |
188 | return self.forbidUsers |
|
187 | return self.forbidUsers | |
189 | } |
|
188 | } | |
190 |
|
189 | |||
191 |
if (data.rules. |
|
190 | if (data.rules.forbid_adding_reviewers) { | |
192 | if (data.rules.voting < 0) { |
|
191 | $('#add_reviewer_input').remove(); | |
193 | self.$rulesList.append( |
|
|||
194 | self.addRule( |
|
|||
195 | _gettext('All individual reviewers must vote.')) |
|
|||
196 | ) |
|
|||
197 | } else if (data.rules.voting === 1) { |
|
|||
198 | self.$rulesList.append( |
|
|||
199 | self.addRule( |
|
|||
200 | _gettext('At least {0} reviewer must vote.').format(data.rules.voting)) |
|
|||
201 | ) |
|
|||
202 |
|
||||
203 | } else { |
|
|||
204 | self.$rulesList.append( |
|
|||
205 | self.addRule( |
|
|||
206 | _gettext('At least {0} reviewers must vote.').format(data.rules.voting)) |
|
|||
207 | ) |
|
|||
208 | } |
|
|||
209 | } |
|
192 | } | |
210 |
|
193 | |||
211 |
if (data.rules. |
|
194 | if (data.rules_data !== undefined && data.rules_data.forbidden_users !== undefined) { | |
212 |
$.each(data.rules. |
|
195 | $.each(data.rules_data.forbidden_users, function(idx, val){ | |
213 | self.$rulesList.append( |
|
196 | self.forbidUsers.push(val) | |
214 | self.addRule(rule_data.text) |
|
197 | }) | |
215 | ) |
|
|||
216 | }); |
|
|||
217 | } |
|
|||
218 |
|
||||
219 | if (data.rules.use_code_authors_for_review) { |
|
|||
220 | self.$rulesList.append( |
|
|||
221 | self.addRule( |
|
|||
222 | _gettext('Reviewers picked from source code changes.')) |
|
|||
223 | ) |
|
|||
224 | } |
|
198 | } | |
225 |
|
199 | |||
226 | if (data.rules.forbid_adding_reviewers) { |
|
200 | if (data.rules_humanized !== undefined && data.rules_humanized.length > 0) { | |
227 | $('#add_reviewer_input').remove(); |
|
201 | $.each(data.rules_humanized, function(idx, val) { | |
|
202 | self.$rulesList.append( | |||
|
203 | self.addRule(val) | |||
|
204 | ) | |||
|
205 | }) | |||
|
206 | } else { | |||
|
207 | // we don't have any rules set, so we inform users about it | |||
228 | self.$rulesList.append( |
|
208 | self.$rulesList.append( | |
229 | self.addRule( |
|
209 | self.addRule(_gettext('No additional review rules set.')) | |
230 | _gettext('Adding new reviewers is forbidden.')) |
|
|||
231 | ) |
|
|||
232 | } |
|
|||
233 |
|
||||
234 | if (data.rules.forbid_author_to_review) { |
|
|||
235 | self.forbidUsers.push(data.rules_data.pr_author); |
|
|||
236 | self.$rulesList.append( |
|
|||
237 | self.addRule( |
|
|||
238 | _gettext('Author is not allowed to be a reviewer.')) |
|
|||
239 | ) |
|
210 | ) | |
240 | } |
|
211 | } | |
241 |
|
212 | |||
242 | if (data.rules.forbid_commit_author_to_review) { |
|
|||
243 |
|
||||
244 | if (data.rules_data.forbidden_users) { |
|
|||
245 | $.each(data.rules_data.forbidden_users, function (index, member_data) { |
|
|||
246 | self.forbidUsers.push(member_data) |
|
|||
247 | }); |
|
|||
248 | } |
|
|||
249 |
|
||||
250 | self.$rulesList.append( |
|
|||
251 | self.addRule( |
|
|||
252 | _gettext('Commit Authors are not allowed to be a reviewer.')) |
|
|||
253 | ) |
|
|||
254 | } |
|
|||
255 |
|
||||
256 | // we don't have any rules set, so we inform users about it |
|
|||
257 | if (self.enabledRules.length === 0) { |
|
|||
258 | self.addRule( |
|
|||
259 | _gettext('No review rules set.')) |
|
|||
260 | } |
|
|||
261 |
|
||||
262 | return self.forbidUsers |
|
213 | return self.forbidUsers | |
263 | }; |
|
214 | }; | |
264 |
|
215 | |||
@@ -1066,14 +1017,14 b' window.ReviewerPresenceController = func' | |||||
1066 | this.handlePresence = function (data) { |
|
1017 | this.handlePresence = function (data) { | |
1067 | if (data.type == 'presence' && data.channel === self.channel) { |
|
1018 | if (data.type == 'presence' && data.channel === self.channel) { | |
1068 | this.storeUsers(data.users); |
|
1019 | this.storeUsers(data.users); | |
1069 | this.render() |
|
1020 | this.render(); | |
1070 | } |
|
1021 | } | |
1071 | }; |
|
1022 | }; | |
1072 |
|
1023 | |||
1073 | this.handleChannelUpdate = function (data) { |
|
1024 | this.handleChannelUpdate = function (data) { | |
1074 | if (data.channel === this.channel) { |
|
1025 | if (data.channel === this.channel) { | |
1075 | this.storeUsers(data.state.users); |
|
1026 | this.storeUsers(data.state.users); | |
1076 | this.render() |
|
1027 | this.render(); | |
1077 | } |
|
1028 | } | |
1078 |
|
1029 | |||
1079 | }; |
|
1030 | }; | |
@@ -1085,6 +1036,30 b' window.ReviewerPresenceController = func' | |||||
1085 |
|
1036 | |||
1086 | }; |
|
1037 | }; | |
1087 |
|
1038 | |||
|
1039 | window.refreshCommentsSuccess = function(targetNode, counterNode, extraCallback) { | |||
|
1040 | var $targetElem = targetNode; | |||
|
1041 | var $counterElem = counterNode; | |||
|
1042 | ||||
|
1043 | return function (data) { | |||
|
1044 | var newCount = $(data).data('counter'); | |||
|
1045 | if (newCount !== undefined) { | |||
|
1046 | var callback = function () { | |||
|
1047 | $counterElem.animate({'opacity': 1.00}, 200) | |||
|
1048 | $counterElem.html(newCount); | |||
|
1049 | }; | |||
|
1050 | $counterElem.animate({'opacity': 0.15}, 200, callback); | |||
|
1051 | } | |||
|
1052 | ||||
|
1053 | $targetElem.css('opacity', 1); | |||
|
1054 | $targetElem.html(data); | |||
|
1055 | tooltipActivate(); | |||
|
1056 | ||||
|
1057 | if (extraCallback !== undefined) { | |||
|
1058 | extraCallback(data) | |||
|
1059 | } | |||
|
1060 | } | |||
|
1061 | } | |||
|
1062 | ||||
1088 | window.refreshComments = function (version) { |
|
1063 | window.refreshComments = function (version) { | |
1089 | version = version || templateContext.pull_request_data.pull_request_version || ''; |
|
1064 | version = version || templateContext.pull_request_data.pull_request_version || ''; | |
1090 |
|
1065 | |||
@@ -1109,23 +1084,8 b' window.refreshComments = function (versi' | |||||
1109 |
|
1084 | |||
1110 | var $targetElem = $('.comments-content-table'); |
|
1085 | var $targetElem = $('.comments-content-table'); | |
1111 | $targetElem.css('opacity', 0.3); |
|
1086 | $targetElem.css('opacity', 0.3); | |
1112 |
|
1087 | var $counterElem = $('#comments-count'); | ||
1113 | var success = function (data) { |
|
1088 | var success = refreshCommentsSuccess($targetElem, $counterElem); | |
1114 | var $counterElem = $('#comments-count'); |
|
|||
1115 | var newCount = $(data).data('counter'); |
|
|||
1116 | if (newCount !== undefined) { |
|
|||
1117 | var callback = function () { |
|
|||
1118 | $counterElem.animate({'opacity': 1.00}, 200) |
|
|||
1119 | $counterElem.html(newCount); |
|
|||
1120 | }; |
|
|||
1121 | $counterElem.animate({'opacity': 0.15}, 200, callback); |
|
|||
1122 | } |
|
|||
1123 |
|
||||
1124 | $targetElem.css('opacity', 1); |
|
|||
1125 | $targetElem.html(data); |
|
|||
1126 | tooltipActivate(); |
|
|||
1127 | } |
|
|||
1128 |
|
||||
1129 | ajaxPOST(loadUrl, data, success, null, {}) |
|
1089 | ajaxPOST(loadUrl, data, success, null, {}) | |
1130 |
|
1090 | |||
1131 | } |
|
1091 | } | |
@@ -1139,7 +1099,7 b' window.refreshTODOs = function (version)' | |||||
1139 | 'repo_name': templateContext.repo_name, |
|
1099 | 'repo_name': templateContext.repo_name, | |
1140 | 'version': version, |
|
1100 | 'version': version, | |
1141 | }; |
|
1101 | }; | |
1142 |
var loadUrl = pyroutes.url('pullrequest_ |
|
1102 | var loadUrl = pyroutes.url('pullrequest_todos', params); | |
1143 | } // commit case |
|
1103 | } // commit case | |
1144 | else { |
|
1104 | else { | |
1145 | return |
|
1105 | return | |
@@ -1153,27 +1113,46 b' window.refreshTODOs = function (version)' | |||||
1153 | var data = {"comments": currentIDs}; |
|
1113 | var data = {"comments": currentIDs}; | |
1154 | var $targetElem = $('.todos-content-table'); |
|
1114 | var $targetElem = $('.todos-content-table'); | |
1155 | $targetElem.css('opacity', 0.3); |
|
1115 | $targetElem.css('opacity', 0.3); | |
1156 |
|
1116 | var $counterElem = $('#todos-count'); | ||
1157 | var success = function (data) { |
|
1117 | var success = refreshCommentsSuccess($targetElem, $counterElem); | |
1158 | var $counterElem = $('#todos-count') |
|
|||
1159 | var newCount = $(data).data('counter'); |
|
|||
1160 | if (newCount !== undefined) { |
|
|||
1161 | var callback = function () { |
|
|||
1162 | $counterElem.animate({'opacity': 1.00}, 200) |
|
|||
1163 | $counterElem.html(newCount); |
|
|||
1164 | }; |
|
|||
1165 | $counterElem.animate({'opacity': 0.15}, 200, callback); |
|
|||
1166 | } |
|
|||
1167 |
|
||||
1168 | $targetElem.css('opacity', 1); |
|
|||
1169 | $targetElem.html(data); |
|
|||
1170 | tooltipActivate(); |
|
|||
1171 | } |
|
|||
1172 |
|
1118 | |||
1173 | ajaxPOST(loadUrl, data, success, null, {}) |
|
1119 | ajaxPOST(loadUrl, data, success, null, {}) | |
1174 |
|
1120 | |||
1175 | } |
|
1121 | } | |
1176 |
|
1122 | |||
|
1123 | window.refreshDraftComments = function () { | |||
|
1124 | ||||
|
1125 | // Pull request case | |||
|
1126 | if (templateContext.pull_request_data.pull_request_id !== null) { | |||
|
1127 | var params = { | |||
|
1128 | 'pull_request_id': templateContext.pull_request_data.pull_request_id, | |||
|
1129 | 'repo_name': templateContext.repo_name, | |||
|
1130 | }; | |||
|
1131 | var loadUrl = pyroutes.url('pullrequest_drafts', params); | |||
|
1132 | } // commit case | |||
|
1133 | else { | |||
|
1134 | return | |||
|
1135 | } | |||
|
1136 | ||||
|
1137 | var data = {}; | |||
|
1138 | ||||
|
1139 | var $targetElem = $('.drafts-content-table'); | |||
|
1140 | $targetElem.css('opacity', 0.3); | |||
|
1141 | var $counterElem = $('#drafts-count'); | |||
|
1142 | var extraCallback = function(data) { | |||
|
1143 | if ($(data).data('counter') == 0){ | |||
|
1144 | $('#draftsTable').hide(); | |||
|
1145 | } else { | |||
|
1146 | $('#draftsTable').show(); | |||
|
1147 | } | |||
|
1148 | // uncheck on load the select all checkbox | |||
|
1149 | $('[name=select_all_drafts]').prop('checked', 0); | |||
|
1150 | } | |||
|
1151 | var success = refreshCommentsSuccess($targetElem, $counterElem, extraCallback); | |||
|
1152 | ||||
|
1153 | ajaxPOST(loadUrl, data, success, null, {}) | |||
|
1154 | }; | |||
|
1155 | ||||
1177 | window.refreshAllComments = function (version) { |
|
1156 | window.refreshAllComments = function (version) { | |
1178 | version = version || templateContext.pull_request_data.pull_request_version || ''; |
|
1157 | version = version || templateContext.pull_request_data.pull_request_version || ''; | |
1179 |
|
1158 |
@@ -1,7 +1,5 b'' | |||||
1 | /__MAIN_APP__ - launched when rhodecode-app element is attached to DOM |
|
1 | /__MAIN_APP__ - launched when rhodecode-app element is attached to DOM | |
2 | /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed |
|
2 | /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed | |
3 | /ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview |
|
|||
4 | /ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created |
|
|||
5 | /notifications - shows new event notifications |
|
3 | /notifications - shows new event notifications | |
6 | /connection_controller/subscribe - subscribes user to new channels |
|
4 | /connection_controller/subscribe - subscribes user to new channels | |
7 | /connection_controller/presence - receives presence change messages |
|
5 | /connection_controller/presence - receives presence change messages |
@@ -104,12 +104,6 b' def add_request_user_context(event):' | |||||
104 | request.environ['rc_req_id'] = req_id |
|
104 | request.environ['rc_req_id'] = req_id | |
105 |
|
105 | |||
106 |
|
106 | |||
107 | def inject_app_settings(event): |
|
|||
108 | settings = event.app.registry.settings |
|
|||
109 | # inject info about available permissions |
|
|||
110 | auth.set_available_permissions(settings) |
|
|||
111 |
|
||||
112 |
|
||||
113 | def scan_repositories_if_enabled(event): |
|
107 | def scan_repositories_if_enabled(event): | |
114 | """ |
|
108 | """ | |
115 | This is subscribed to the `pyramid.events.ApplicationCreated` event. It |
|
109 | This is subscribed to the `pyramid.events.ApplicationCreated` event. It |
@@ -46,6 +46,8 b'' | |||||
46 | $pullRequestListTable.DataTable({ |
|
46 | $pullRequestListTable.DataTable({ | |
47 | processing: true, |
|
47 | processing: true, | |
48 | serverSide: true, |
|
48 | serverSide: true, | |
|
49 | stateSave: true, | |||
|
50 | stateDuration: -1, | |||
49 | ajax: { |
|
51 | ajax: { | |
50 | "url": "${h.route_path('my_account_pullrequests_data')}", |
|
52 | "url": "${h.route_path('my_account_pullrequests_data')}", | |
51 | "data": function (d) { |
|
53 | "data": function (d) { | |
@@ -119,6 +121,10 b'' | |||||
119 | if (data['owned']) { |
|
121 | if (data['owned']) { | |
120 | $(row).addClass('owned'); |
|
122 | $(row).addClass('owned'); | |
121 | } |
|
123 | } | |
|
124 | }, | |||
|
125 | "stateSaveParams": function (settings, data) { | |||
|
126 | data.search.search = ""; // Don't save search | |||
|
127 | data.start = 0; // don't save pagination | |||
122 | } |
|
128 | } | |
123 | }); |
|
129 | }); | |
124 | $pullRequestListTable.on('xhr.dt', function (e, settings, json, xhr) { |
|
130 | $pullRequestListTable.on('xhr.dt', function (e, settings, json, xhr) { | |
@@ -129,6 +135,7 b'' | |||||
129 | $pullRequestListTable.css('opacity', 0.3); |
|
135 | $pullRequestListTable.css('opacity', 0.3); | |
130 | }); |
|
136 | }); | |
131 |
|
137 | |||
|
138 | ||||
132 | // filter |
|
139 | // filter | |
133 | $('#q_filter').on('keyup', |
|
140 | $('#q_filter').on('keyup', | |
134 | $.debounce(250, function () { |
|
141 | $.debounce(250, function () { |
@@ -1225,6 +1225,14 b'' | |||||
1225 | (function () { |
|
1225 | (function () { | |
1226 | "use sctrict"; |
|
1226 | "use sctrict"; | |
1227 |
|
1227 | |||
|
1228 | // details block auto-hide menu | |||
|
1229 | $(document).mouseup(function(e) { | |||
|
1230 | var container = $('.details-inline-block'); | |||
|
1231 | if (!container.is(e.target) && container.has(e.target).length === 0) { | |||
|
1232 | $('.details-inline-block[open]').removeAttr('open') | |||
|
1233 | } | |||
|
1234 | }); | |||
|
1235 | ||||
1228 | var $sideBar = $('.right-sidebar'); |
|
1236 | var $sideBar = $('.right-sidebar'); | |
1229 | var expanded = $sideBar.hasClass('right-sidebar-expanded'); |
|
1237 | var expanded = $sideBar.hasClass('right-sidebar-expanded'); | |
1230 | var sidebarState = templateContext.session_attrs.sidebarState; |
|
1238 | var sidebarState = templateContext.session_attrs.sidebarState; |
@@ -4,7 +4,7 b'' | |||||
4 | ## ${sidebar.comments_table()} |
|
4 | ## ${sidebar.comments_table()} | |
5 | <%namespace name="base" file="/base/base.mako"/> |
|
5 | <%namespace name="base" file="/base/base.mako"/> | |
6 |
|
6 | |||
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)"> |
|
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, draft_comments=False, existing_ids=None, is_pr=True)"> | |
8 | <% |
|
8 | <% | |
9 | if todo_comments: |
|
9 | if todo_comments: | |
10 | cls_ = 'todos-content-table' |
|
10 | cls_ = 'todos-content-table' | |
@@ -15,10 +15,13 b'' | |||||
15 | # own comments first |
|
15 | # own comments first | |
16 | user_id = 0 |
|
16 | user_id = 0 | |
17 | return '{}'.format(str(entry.comment_id).zfill(10000)) |
|
17 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |
|
18 | elif draft_comments: | |||
|
19 | cls_ = 'drafts-content-table' | |||
|
20 | def sorter(entry): | |||
|
21 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |||
18 | else: |
|
22 | else: | |
19 | cls_ = 'comments-content-table' |
|
23 | cls_ = 'comments-content-table' | |
20 | def sorter(entry): |
|
24 | def sorter(entry): | |
21 | user_id = entry.author.user_id |
|
|||
22 | return '{}'.format(str(entry.comment_id).zfill(10000)) |
|
25 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |
23 |
|
26 | |||
24 | existing_ids = existing_ids or [] |
|
27 | existing_ids = existing_ids or [] | |
@@ -31,8 +34,12 b'' | |||||
31 | <% |
|
34 | <% | |
32 | display = '' |
|
35 | display = '' | |
33 | _cls = '' |
|
36 | _cls = '' | |
|
37 | ## Extra precaution to not show drafts in the sidebar for todo/comments | |||
|
38 | if comment_obj.draft and not draft_comments: | |||
|
39 | continue | |||
34 | %> |
|
40 | %> | |
35 |
|
41 | |||
|
42 | ||||
36 | <% |
|
43 | <% | |
37 | comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) |
|
44 | comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) | |
38 | prev_comment_ver_index = 0 |
|
45 | prev_comment_ver_index = 0 | |
@@ -83,6 +90,11 b'' | |||||
83 | % endif |
|
90 | % endif | |
84 |
|
91 | |||
85 | <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}"> |
|
92 | <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}"> | |
|
93 | % if draft_comments: | |||
|
94 | <td style="width: 15px;"> | |||
|
95 | ${h.checkbox('submit_draft', id=None, value=comment_obj.comment_id)} | |||
|
96 | </td> | |||
|
97 | % endif | |||
86 | <td class="td-todo-number"> |
|
98 | <td class="td-todo-number"> | |
87 | <% |
|
99 | <% | |
88 | version_info = '' |
|
100 | version_info = '' |
@@ -24,8 +24,6 b'' | |||||
24 |
|
24 | |||
25 | <%def name="main()"> |
|
25 | <%def name="main()"> | |
26 | <script type="text/javascript"> |
|
26 | <script type="text/javascript"> | |
27 | // TODO: marcink switch this to pyroutes |
|
|||
28 | AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}"; |
|
|||
29 | templateContext.commit_data.commit_id = "${c.commit.raw_id}"; |
|
27 | templateContext.commit_data.commit_id = "${c.commit.raw_id}"; | |
30 | </script> |
|
28 | </script> | |
31 |
|
29 |
@@ -1,4 +1,4 b'' | |||||
1 | ## this is a dummy html file for partial rendering on server and sending |
|
1 | ## this is a dummy html file for partial rendering on server and sending | |
2 | ## generated output via ajax after comment submit |
|
2 | ## generated output via ajax after comment submit | |
3 | <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> |
|
3 | <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> | |
4 | ${comment.comment_block(c.co, inline=c.co.is_inline)} |
|
4 | ${comment.comment_block(c.co, inline=c.co.is_inline, is_new=c.is_new)} |
@@ -3,20 +3,25 b'' | |||||
3 | ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> |
|
3 | ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> | |
4 | ## ${comment.comment_block(comment)} |
|
4 | ## ${comment.comment_block(comment)} | |
5 | ## |
|
5 | ## | |
|
6 | <%namespace name="base" file="/base/base.mako"/> | |||
6 |
|
|
7 | ||
7 | <%! |
|
8 | <%! | |
8 | from rhodecode.lib import html_filters |
|
9 | from rhodecode.lib import html_filters | |
9 | %> |
|
10 | %> | |
10 |
|
11 | |||
11 | <%namespace name="base" file="/base/base.mako"/> |
|
12 | ||
12 | <%def name="comment_block(comment, inline=False, active_pattern_entries=None)"> |
|
13 | <%def name="comment_block(comment, inline=False, active_pattern_entries=None, is_new=False)"> | |
13 |
|
14 | |||
14 | <% |
|
15 | <% | |
15 |
|
|
16 | from rhodecode.model.comment import CommentsModel | |
16 |
|
|
17 | comment_model = CommentsModel() | |
|
18 | ||||
|
19 | comment_ver = comment.get_index_version(getattr(c, 'versions', [])) | |||
|
20 | latest_ver = len(getattr(c, 'versions', [])) | |||
|
21 | visible_for_user = True | |||
|
22 | if comment.draft: | |||
|
23 | visible_for_user = comment.user_id == c.rhodecode_user.user_id | |||
17 | %> |
|
24 | %> | |
18 | <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %> |
|
|||
19 | <% latest_ver = len(getattr(c, 'versions', [])) %> |
|
|||
20 |
|
25 | |||
21 | % if inline: |
|
26 | % if inline: | |
22 | <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %> |
|
27 | <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %> | |
@@ -24,6 +29,7 b'' | |||||
24 | <% outdated_at_ver = comment.older_than_version(c.at_version_num) %> |
|
29 | <% outdated_at_ver = comment.older_than_version(c.at_version_num) %> | |
25 | % endif |
|
30 | % endif | |
26 |
|
31 | |||
|
32 | % if visible_for_user: | |||
27 | <div class="comment |
|
33 | <div class="comment | |
28 | ${'comment-inline' if inline else 'comment-general'} |
|
34 | ${'comment-inline' if inline else 'comment-general'} | |
29 | ${'comment-outdated' if outdated_at_ver else 'comment-current'}" |
|
35 | ${'comment-outdated' if outdated_at_ver else 'comment-current'}" | |
@@ -31,14 +37,26 b'' | |||||
31 | line="${comment.line_no}" |
|
37 | line="${comment.line_no}" | |
32 | data-comment-id="${comment.comment_id}" |
|
38 | data-comment-id="${comment.comment_id}" | |
33 | data-comment-type="${comment.comment_type}" |
|
39 | data-comment-type="${comment.comment_type}" | |
|
40 | data-comment-draft=${h.json.dumps(comment.draft)} | |||
34 | data-comment-renderer="${comment.renderer}" |
|
41 | data-comment-renderer="${comment.renderer}" | |
35 | data-comment-text="${comment.text | html_filters.base64,n}" |
|
42 | data-comment-text="${comment.text | html_filters.base64,n}" | |
|
43 | data-comment-f-path="${comment.f_path}" | |||
36 | data-comment-line-no="${comment.line_no}" |
|
44 | data-comment-line-no="${comment.line_no}" | |
37 | data-comment-inline=${h.json.dumps(inline)} |
|
45 | data-comment-inline=${h.json.dumps(inline)} | |
38 | style="${'display: none;' if outdated_at_ver else ''}"> |
|
46 | style="${'display: none;' if outdated_at_ver else ''}"> | |
39 |
|
47 | |||
40 | <div class="meta"> |
|
48 | <div class="meta"> | |
41 | <div class="comment-type-label"> |
|
49 | <div class="comment-type-label"> | |
|
50 | % if comment.draft: | |||
|
51 | <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}."> | |||
|
52 | DRAFT | |||
|
53 | </div> | |||
|
54 | % elif is_new: | |||
|
55 | <div class="tooltip comment-new" title="${_('This comment was added while you browsed this page')}."> | |||
|
56 | NEW | |||
|
57 | </div> | |||
|
58 | % endif | |||
|
59 | ||||
42 | <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}"> |
|
60 | <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}"> | |
43 |
|
61 | |||
44 | ## TODO COMMENT |
|
62 | ## TODO COMMENT | |
@@ -90,7 +108,7 b'' | |||||
90 |
|
108 | |||
91 | </div> |
|
109 | </div> | |
92 | </div> |
|
110 | </div> | |
93 |
|
111 | ## NOTE 0 and .. => because we disable it for now until UI ready | ||
94 | % if 0 and comment.status_change: |
|
112 | % if 0 and comment.status_change: | |
95 | <div class="pull-left"> |
|
113 | <div class="pull-left"> | |
96 | <span class="tag authortag tooltip" title="${_('Status from pull request.')}"> |
|
114 | <span class="tag authortag tooltip" title="${_('Status from pull request.')}"> | |
@@ -100,10 +118,12 b'' | |||||
100 | </span> |
|
118 | </span> | |
101 | </div> |
|
119 | </div> | |
102 | % endif |
|
120 | % endif | |
103 |
|
121 | ## Since only author can see drafts, we don't show it | ||
|
122 | % if not comment.draft: | |||
104 | <div class="author ${'author-inline' if inline else 'author-general'}"> |
|
123 | <div class="author ${'author-inline' if inline else 'author-general'}"> | |
105 | ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)} |
|
124 | ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)} | |
106 | </div> |
|
125 | </div> | |
|
126 | % endif | |||
107 |
|
127 | |||
108 | <div class="date"> |
|
128 | <div class="date"> | |
109 | ${h.age_component(comment.modified_at, time_is_local=True)} |
|
129 | ${h.age_component(comment.modified_at, time_is_local=True)} | |
@@ -164,7 +184,7 b'' | |||||
164 | % if inline: |
|
184 | % if inline: | |
165 | <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}"> |
|
185 | <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}"> | |
166 | % if outdated_at_ver: |
|
186 | % if outdated_at_ver: | |
167 |
<code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"> |
|
187 | <strong class="comment-outdated-label">outdated</strong> <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code> | |
168 | <code class="action-divider">|</code> |
|
188 | <code class="action-divider">|</code> | |
169 | % elif comment_ver: |
|
189 | % elif comment_ver: | |
170 | <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code> |
|
190 | <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code> | |
@@ -210,11 +230,17 b'' | |||||
210 | %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id): |
|
230 | %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id): | |
211 | <div class="dropdown-divider"></div> |
|
231 | <div class="dropdown-divider"></div> | |
212 | <div class="dropdown-item"> |
|
232 | <div class="dropdown-item"> | |
213 | <a onclick="return Rhodecode.comments.editComment(this);" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a> |
|
233 | <a onclick="return Rhodecode.comments.editComment(this, '${comment.line_no}', '${comment.f_path}');" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a> | |
214 | </div> |
|
234 | </div> | |
215 | <div class="dropdown-item"> |
|
235 | <div class="dropdown-item"> | |
216 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a> |
|
236 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a> | |
217 | </div> |
|
237 | </div> | |
|
238 | ## Only available in EE edition | |||
|
239 | % if comment.draft and c.rhodecode_edition_id == 'EE': | |||
|
240 | <div class="dropdown-item"> | |||
|
241 | <a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a> | |||
|
242 | </div> | |||
|
243 | % endif | |||
218 | %else: |
|
244 | %else: | |
219 | <div class="dropdown-divider"></div> |
|
245 | <div class="dropdown-divider"></div> | |
220 | <div class="dropdown-item"> |
|
246 | <div class="dropdown-item"> | |
@@ -252,6 +278,7 b'' | |||||
252 | </div> |
|
278 | </div> | |
253 |
|
279 | |||
254 | </div> |
|
280 | </div> | |
|
281 | % endif | |||
255 | </%def> |
|
282 | </%def> | |
256 |
|
283 | |||
257 | ## generate main comments |
|
284 | ## generate main comments | |
@@ -298,10 +325,9 b'' | |||||
298 | ## inject form here |
|
325 | ## inject form here | |
299 | </div> |
|
326 | </div> | |
300 | <script type="text/javascript"> |
|
327 | <script type="text/javascript"> | |
301 | var lineNo = 'general'; |
|
|||
302 | var resolvesCommentId = null; |
|
328 | var resolvesCommentId = null; | |
303 | var generalCommentForm = Rhodecode.comments.createGeneralComment( |
|
329 | var generalCommentForm = Rhodecode.comments.createGeneralComment( | |
304 |
|
|
330 | 'general', "${placeholder}", resolvesCommentId); | |
305 |
|
331 | |||
306 | // set custom success callback on rangeCommit |
|
332 | // set custom success callback on rangeCommit | |
307 | % if is_compare: |
|
333 | % if is_compare: | |
@@ -311,6 +337,7 b'' | |||||
311 | var text = self.cm.getValue(); |
|
337 | var text = self.cm.getValue(); | |
312 | var status = self.getCommentStatus(); |
|
338 | var status = self.getCommentStatus(); | |
313 | var commentType = self.getCommentType(); |
|
339 | var commentType = self.getCommentType(); | |
|
340 | var isDraft = self.getDraftState(); | |||
314 |
|
341 | |||
315 | if (text === "" && !status) { |
|
342 | if (text === "" && !status) { | |
316 | return; |
|
343 | return; | |
@@ -337,6 +364,7 b'' | |||||
337 | 'text': text, |
|
364 | 'text': text, | |
338 | 'changeset_status': status, |
|
365 | 'changeset_status': status, | |
339 | 'comment_type': commentType, |
|
366 | 'comment_type': commentType, | |
|
367 | 'draft': isDraft, | |||
340 | 'commit_ids': commitIds, |
|
368 | 'commit_ids': commitIds, | |
341 | 'csrf_token': CSRF_TOKEN |
|
369 | 'csrf_token': CSRF_TOKEN | |
342 | }; |
|
370 | }; | |
@@ -371,7 +399,7 b'' | |||||
371 |
|
399 | |||
372 | <div class="comment-area-write" style="display: block;"> |
|
400 | <div class="comment-area-write" style="display: block;"> | |
373 | <div id="edit-container"> |
|
401 | <div id="edit-container"> | |
374 |
<div style="padding: |
|
402 | <div style="padding: 20px 0px 0px 0;"> | |
375 | ${_('You need to be logged in to leave comments.')} |
|
403 | ${_('You need to be logged in to leave comments.')} | |
376 | <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a> |
|
404 | <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a> | |
377 | </div> |
|
405 | </div> | |
@@ -430,7 +458,7 b'' | |||||
430 | </div> |
|
458 | </div> | |
431 |
|
459 | |||
432 | <div class="comment-area-write" style="display: block;"> |
|
460 | <div class="comment-area-write" style="display: block;"> | |
433 | <div id="edit-container_${lineno_id}"> |
|
461 | <div id="edit-container_${lineno_id}" style="margin-top: -1px"> | |
434 | <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea> |
|
462 | <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea> | |
435 | </div> |
|
463 | </div> | |
436 | <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;"> |
|
464 | <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;"> | |
@@ -477,39 +505,50 b'' | |||||
477 | <div class="action-buttons-extra"></div> |
|
505 | <div class="action-buttons-extra"></div> | |
478 | % endif |
|
506 | % endif | |
479 |
|
507 | |||
480 |
<input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_(' |
|
508 | <input class="btn btn-success comment-button-input submit-comment-action" id="save_${lineno_id}" name="save" type="submit" value="${_('Add comment')}" data-is-draft=false onclick="$(this).addClass('submitter')"> | |
|
509 | ||||
|
510 | % if form_type == 'inline': | |||
|
511 | % if c.rhodecode_edition_id == 'EE': | |||
|
512 | ## Disable the button for CE, the "real" validation is in the backend code anyway | |||
|
513 | <input class="btn btn-warning comment-button-input submit-draft-action" id="save_draft_${lineno_id}" name="save_draft" type="submit" value="${_('Add draft')}" data-is-draft=true onclick="$(this).addClass('submitter')"> | |||
|
514 | % else: | |||
|
515 | <input class="btn btn-warning comment-button-input submit-draft-action disabled" disabled="disabled" type="submit" value="${_('Add draft')}" onclick="return false;" title="Draft comments only available in EE edition of RhodeCode"> | |||
|
516 | % endif | |||
|
517 | % endif | |||
|
518 | ||||
|
519 | % if review_statuses: | |||
|
520 | <div class="comment-status-box"> | |||
|
521 | <select id="change_status_${lineno_id}" name="changeset_status"> | |||
|
522 | <option></option> ## Placeholder | |||
|
523 | % for status, lbl in review_statuses: | |||
|
524 | <option value="${status}" data-status="${status}">${lbl}</option> | |||
|
525 | %if is_pull_request and change_status and status in ('approved', 'rejected'): | |||
|
526 | <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option> | |||
|
527 | %endif | |||
|
528 | % endfor | |||
|
529 | </select> | |||
|
530 | </div> | |||
|
531 | % endif | |||
481 |
|
532 | |||
482 | ## inline for has a file, and line-number together with cancel hide button. |
|
533 | ## inline for has a file, and line-number together with cancel hide button. | |
483 | % if form_type == 'inline': |
|
534 | % if form_type == 'inline': | |
484 | <input type="hidden" name="f_path" value="{0}"> |
|
535 | <input type="hidden" name="f_path" value="{0}"> | |
485 | <input type="hidden" name="line" value="${lineno_id}"> |
|
536 | <input type="hidden" name="line" value="${lineno_id}"> | |
486 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> |
|
537 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> | |
487 | ${_('Cancel')} |
|
538 | <i class="icon-cancel-circled2"></i> | |
488 | </button> |
|
539 | </button> | |
489 | % endif |
|
540 | % endif | |
490 | </div> |
|
541 | </div> | |
491 |
|
|
542 | ||
492 | % if review_statuses: |
|
|||
493 | <div class="status_box"> |
|
|||
494 | <select id="change_status_${lineno_id}" name="changeset_status"> |
|
|||
495 | <option></option> ## Placeholder |
|
|||
496 | % for status, lbl in review_statuses: |
|
|||
497 | <option value="${status}" data-status="${status}">${lbl}</option> |
|
|||
498 | %if is_pull_request and change_status and status in ('approved', 'rejected'): |
|
|||
499 | <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option> |
|
|||
500 | %endif |
|
|||
501 | % endfor |
|
|||
502 | </select> |
|
|||
503 | </div> |
|
|||
504 | % endif |
|
|||
505 |
|
||||
506 | <div class="toolbar-text"> |
|
543 | <div class="toolbar-text"> | |
507 | <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %> |
|
544 | <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %> | |
508 |
${_(' |
|
545 | <span>${_('{} is supported.').format(renderer_url)|n} | |
509 | <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> |
|
546 | ||
510 | ${_('and')} |
|
547 | <i class="icon-info-circled tooltip-hovercard" | |
511 | <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span> |
|
548 | data-hovercard-alt="ALT" | |
512 | ${_('actions supported.')} |
|
549 | data-hovercard-url="javascript:commentHelp('${c.visual.default_renderer.upper()}')" | |
|
550 | data-comment-json-b64='${h.b64(h.json.dumps({}))}'></i> | |||
|
551 | </span> | |||
513 | </div> |
|
552 | </div> | |
514 | </div> |
|
553 | </div> | |
515 |
|
554 |
@@ -104,7 +104,9 b'' | |||||
104 | %for commit in c.commit_ranges: |
|
104 | %for commit in c.commit_ranges: | |
105 | ## commit range header for each individual diff |
|
105 | ## commit range header for each individual diff | |
106 | <h3> |
|
106 | <h3> | |
107 | <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a> |
|
107 | <a class="tooltip-hovercard revision" data-hovercard-alt="Commit: ${commit.short_id}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=commit.raw_id)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}"> | |
|
108 | ${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))} | |||
|
109 | </a> | |||
108 | </h3> |
|
110 | </h3> | |
109 |
|
111 | |||
110 | ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])} |
|
112 | ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])} |
@@ -1,3 +1,4 b'' | |||||
|
1 | <%namespace name="base" file="/base/base.mako"/> | |||
1 | <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/> |
|
2 | <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/> | |
2 |
|
3 | |||
3 | <%def name="diff_line_anchor(commit, filename, line, type)"><% |
|
4 | <%def name="diff_line_anchor(commit, filename, line, type)"><% | |
@@ -74,24 +75,9 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||||
74 |
|
75 | |||
75 | <div class="js-template" id="cb-comment-inline-form-template"> |
|
76 | <div class="js-template" id="cb-comment-inline-form-template"> | |
76 | <div class="comment-inline-form ac"> |
|
77 | <div class="comment-inline-form ac"> | |
77 |
|
78 | %if not c.rhodecode_user.is_default: | ||
78 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
|||
79 | ## render template for inline comments |
|
79 | ## render template for inline comments | |
80 | ${commentblock.comment_form(form_type='inline')} |
|
80 | ${commentblock.comment_form(form_type='inline')} | |
81 | %else: |
|
|||
82 | ${h.form('', class_='inline-form comment-form-login', method='get')} |
|
|||
83 | <div class="pull-left"> |
|
|||
84 | <div class="comment-help pull-right"> |
|
|||
85 | ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a> |
|
|||
86 | </div> |
|
|||
87 | </div> |
|
|||
88 | <div class="comment-button pull-right"> |
|
|||
89 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> |
|
|||
90 | ${_('Cancel')} |
|
|||
91 | </button> |
|
|||
92 | </div> |
|
|||
93 | <div class="clearfix"></div> |
|
|||
94 | ${h.end_form()} |
|
|||
95 | %endif |
|
81 | %endif | |
96 | </div> |
|
82 | </div> | |
97 | </div> |
|
83 | </div> | |
@@ -287,7 +273,7 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||||
287 | <label for="filediff-collapse-${id(filediff)}" class="filediff-heading"> |
|
273 | <label for="filediff-collapse-${id(filediff)}" class="filediff-heading"> | |
288 | <% |
|
274 | <% | |
289 | file_comments = (get_inline_comments(inline_comments, filediff.patch['filename']) or {}).values() |
|
275 | file_comments = (get_inline_comments(inline_comments, filediff.patch['filename']) or {}).values() | |
290 | total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated] |
|
276 | total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not (_c.outdated or _c.draft)] | |
291 | %> |
|
277 | %> | |
292 | <div class="filediff-collapse-indicator icon-"></div> |
|
278 | <div class="filediff-collapse-indicator icon-"></div> | |
293 |
|
279 | |||
@@ -327,7 +313,7 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||||
327 | </label> |
|
313 | </label> | |
328 |
|
314 | |||
329 | ${diff_menu(filediff, use_comments=use_comments)} |
|
315 | ${diff_menu(filediff, use_comments=use_comments)} | |
330 | <table data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}"> |
|
316 | <table id="file-${h.safeid(h.safe_unicode(filediff.patch['filename']))}" data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}"> | |
331 |
|
317 | |||
332 | ## new/deleted/empty content case |
|
318 | ## new/deleted/empty content case | |
333 | % if not filediff.hunks: |
|
319 | % if not filediff.hunks: | |
@@ -626,8 +612,10 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||||
626 |
|
612 | |||
627 | % if use_comments: |
|
613 | % if use_comments: | |
628 | | |
|
614 | | | |
629 |
<a href="#" onclick=" |
|
615 | <a href="#" onclick="Rhodecode.comments.toggleDiffComments(this);return toggleElement(this)" | |
630 | <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span> |
|
616 | data-toggle-on="${_('Hide comments')}" | |
|
617 | data-toggle-off="${_('Show comments')}"> | |||
|
618 | <span class="hide-comment-button">${_('Hide comments')}</span> | |||
631 | </a> |
|
619 | </a> | |
632 | % endif |
|
620 | % endif | |
633 |
|
621 | |||
@@ -637,23 +625,37 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||||
637 | </%def> |
|
625 | </%def> | |
638 |
|
626 | |||
639 |
|
627 | |||
640 | <%def name="inline_comments_container(comments, active_pattern_entries=None)"> |
|
628 | <%def name="inline_comments_container(comments, active_pattern_entries=None, line_no='', f_path='')"> | |
641 |
|
629 | |||
642 | <div class="inline-comments"> |
|
630 | <div class="inline-comments"> | |
643 | %for comment in comments: |
|
631 | %for comment in comments: | |
644 | ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)} |
|
632 | ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)} | |
645 | %endfor |
|
633 | %endfor | |
646 | % if comments and comments[-1].outdated: |
|
634 | ||
647 | <span class="btn btn-secondary cb-comment-add-button comment-outdated}" style="display: none;}"> |
|
635 | <% | |
648 | ${_('Add another comment')} |
|
636 | extra_class = '' | |
649 | </span> |
|
637 | extra_style = '' | |
650 | % else: |
|
638 | ||
651 | <span onclick="return Rhodecode.comments.createComment(this)" class="btn btn-secondary cb-comment-add-button"> |
|
639 | if comments and comments[-1].outdated_at_version(c.at_version_num): | |
652 | ${_('Add another comment')} |
|
640 | extra_class = ' comment-outdated' | |
653 | </span> |
|
641 | extra_style = 'display: none;' | |
654 | % endif |
|
642 | ||
|
643 | %> | |||
655 |
|
644 | |||
|
645 | <div class="reply-thread-container-wrapper${extra_class}" style="${extra_style}"> | |||
|
646 | <div class="reply-thread-container${extra_class}"> | |||
|
647 | <div class="reply-thread-gravatar"> | |||
|
648 | ${base.gravatar(c.rhodecode_user.email, 20, tooltip=True, user=c.rhodecode_user)} | |||
|
649 | </div> | |||
|
650 | <div class="reply-thread-reply-button"> | |||
|
651 | ## initial reply button, some JS logic can append here a FORM to leave a first comment. | |||
|
652 | <button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">Reply...</button> | |||
|
653 | </div> | |||
|
654 | <div class="reply-thread-last"></div> | |||
|
655 | </div> | |||
|
656 | </div> | |||
656 | </div> |
|
657 | </div> | |
|
658 | ||||
657 | </%def> |
|
659 | </%def> | |
658 |
|
660 | |||
659 | <%! |
|
661 | <%! | |
@@ -711,16 +713,19 b' def get_comments_for(diff_type, comments' | |||||
711 | data-line-no="${line.original.lineno}" |
|
713 | data-line-no="${line.original.lineno}" | |
712 | > |
|
714 | > | |
713 |
|
715 | |||
714 | <% line_old_comments = None %> |
|
716 | <% line_old_comments, line_old_comments_no_drafts = None, None %> | |
715 | %if line.original.get_comment_args: |
|
717 | %if line.original.get_comment_args: | |
716 | <% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %> |
|
718 | <% | |
|
719 | line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) | |||
|
720 | line_old_comments_no_drafts = [c for c in line_old_comments if not c.draft] if line_old_comments else [] | |||
|
721 | has_outdated = any([x.outdated for x in line_old_comments_no_drafts]) | |||
|
722 | %> | |||
717 | %endif |
|
723 | %endif | |
718 | %if line_old_comments: |
|
724 | %if line_old_comments_no_drafts: | |
719 | <% has_outdated = any([x.outdated for x in line_old_comments]) %> |
|
|||
720 | % if has_outdated: |
|
725 | % if has_outdated: | |
721 |
<i class="tooltip icon-comment-toggle" title="${_(' |
|
726 | <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
722 | % else: |
|
727 | % else: | |
723 |
<i class="tooltip icon-comment" title="${_(' |
|
728 | <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
724 | % endif |
|
729 | % endif | |
725 | %endif |
|
730 | %endif | |
726 | </td> |
|
731 | </td> | |
@@ -734,16 +739,18 b' def get_comments_for(diff_type, comments' | |||||
734 | <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a> |
|
739 | <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a> | |
735 | %endif |
|
740 | %endif | |
736 | </td> |
|
741 | </td> | |
|
742 | ||||
|
743 | <% line_no = 'o{}'.format(line.original.lineno) %> | |||
737 | <td class="cb-content ${action_class(line.original.action)}" |
|
744 | <td class="cb-content ${action_class(line.original.action)}" | |
738 |
data-line-no=" |
|
745 | data-line-no="${line_no}" | |
739 | > |
|
746 | > | |
740 | %if use_comments and line.original.lineno: |
|
747 | %if use_comments and line.original.lineno: | |
741 | ${render_add_comment_button()} |
|
748 | ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])} | |
742 | %endif |
|
749 | %endif | |
743 | <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span> |
|
750 | <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span> | |
744 |
|
751 | |||
745 | %if use_comments and line.original.lineno and line_old_comments: |
|
752 | %if use_comments and line.original.lineno and line_old_comments: | |
746 | ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries)} |
|
753 | ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])} | |
747 | %endif |
|
754 | %endif | |
748 |
|
755 | |||
749 | </td> |
|
756 | </td> | |
@@ -752,18 +759,20 b' def get_comments_for(diff_type, comments' | |||||
752 | > |
|
759 | > | |
753 | <div> |
|
760 | <div> | |
754 |
|
|
761 | ||
|
762 | <% line_new_comments, line_new_comments_no_drafts = None, None %> | |||
755 | %if line.modified.get_comment_args: |
|
763 | %if line.modified.get_comment_args: | |
756 | <% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %> |
|
764 | <% | |
757 | %else: |
|
765 | line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) | |
758 | <% line_new_comments = None%> |
|
766 | line_new_comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else [] | |
|
767 | has_outdated = any([x.outdated for x in line_new_comments_no_drafts]) | |||
|
768 | %> | |||
759 | %endif |
|
769 | %endif | |
760 | %if line_new_comments: |
|
|||
761 |
|
770 | |||
762 | <% has_outdated = any([x.outdated for x in line_new_comments]) %> |
|
771 | %if line_new_comments_no_drafts: | |
763 | % if has_outdated: |
|
772 | % if has_outdated: | |
764 |
<i class="tooltip icon-comment-toggle" title="${_(' |
|
773 | <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
765 | % else: |
|
774 | % else: | |
766 |
<i class="tooltip icon-comment" title="${_(' |
|
775 | <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
767 | % endif |
|
776 | % endif | |
768 | %endif |
|
777 | %endif | |
769 | </div> |
|
778 | </div> | |
@@ -778,22 +787,25 b' def get_comments_for(diff_type, comments' | |||||
778 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a> |
|
787 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a> | |
779 | %endif |
|
788 | %endif | |
780 | </td> |
|
789 | </td> | |
|
790 | ||||
|
791 | <% line_no = 'n{}'.format(line.modified.lineno) %> | |||
781 | <td class="cb-content ${action_class(line.modified.action)}" |
|
792 | <td class="cb-content ${action_class(line.modified.action)}" | |
782 |
data-line-no=" |
|
793 | data-line-no="${line_no}" | |
783 | > |
|
794 | > | |
784 | %if use_comments and line.modified.lineno: |
|
795 | %if use_comments and line.modified.lineno: | |
785 | ${render_add_comment_button()} |
|
796 | ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])} | |
786 | %endif |
|
797 | %endif | |
787 | <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span> |
|
798 | <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span> | |
788 | %if use_comments and line.modified.lineno and line_new_comments: |
|
|||
789 | ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)} |
|
|||
790 | %endif |
|
|||
791 | % if line_action in ['+', '-'] and prev_line_action not in ['+', '-']: |
|
799 | % if line_action in ['+', '-'] and prev_line_action not in ['+', '-']: | |
792 | <div class="nav-chunk" style="visibility: hidden"> |
|
800 | <div class="nav-chunk" style="visibility: hidden"> | |
793 | <i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i> |
|
801 | <i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i> | |
794 | </div> |
|
802 | </div> | |
795 | <% chunk_count +=1 %> |
|
803 | <% chunk_count +=1 %> | |
796 | % endif |
|
804 | % endif | |
|
805 | %if use_comments and line.modified.lineno and line_new_comments: | |||
|
806 | ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])} | |||
|
807 | %endif | |||
|
808 | ||||
797 | </td> |
|
809 | </td> | |
798 | </tr> |
|
810 | </tr> | |
799 | %endfor |
|
811 | %endfor | |
@@ -814,20 +826,22 b' def get_comments_for(diff_type, comments' | |||||
814 | <td class="cb-data ${action_class(action)}"> |
|
826 | <td class="cb-data ${action_class(action)}"> | |
815 | <div> |
|
827 | <div> | |
816 |
|
|
828 | ||
817 | %if comments_args: |
|
829 | <% comments, comments_no_drafts = None, None %> | |
818 | <% comments = get_comments_for('unified', inline_comments, *comments_args) %> |
|
830 | %if comments_args: | |
819 |
% |
|
831 | <% | |
820 | <% comments = None %> |
|
832 | comments = get_comments_for('unified', inline_comments, *comments_args) | |
821 | %endif |
|
833 | comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else [] | |
|
834 | has_outdated = any([x.outdated for x in comments_no_drafts]) | |||
|
835 | %> | |||
|
836 | %endif | |||
822 |
|
837 | |||
823 | % if comments: |
|
838 | % if comments_no_drafts: | |
824 | <% has_outdated = any([x.outdated for x in comments]) %> |
|
839 | % if has_outdated: | |
825 | % if has_outdated: |
|
840 | <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
826 | <i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> |
|
841 | % else: | |
827 | % else: |
|
842 | <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
828 | <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> |
|
843 | % endif | |
829 | % endif |
|
844 | % endif | |
830 | % endif |
|
|||
831 | </div> |
|
845 | </div> | |
832 | </td> |
|
846 | </td> | |
833 | <td class="cb-lineno ${action_class(action)}" |
|
847 | <td class="cb-lineno ${action_class(action)}" | |
@@ -850,15 +864,16 b' def get_comments_for(diff_type, comments' | |||||
850 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a> |
|
864 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a> | |
851 | %endif |
|
865 | %endif | |
852 | </td> |
|
866 | </td> | |
|
867 | <% line_no = '{}{}'.format(new_line_no and 'n' or 'o', new_line_no or old_line_no) %> | |||
853 | <td class="cb-content ${action_class(action)}" |
|
868 | <td class="cb-content ${action_class(action)}" | |
854 | data-line-no="${(new_line_no and 'n' or 'o')}${(new_line_no or old_line_no)}" |
|
869 | data-line-no="${line_no}" | |
855 | > |
|
870 | > | |
856 | %if use_comments: |
|
871 | %if use_comments: | |
857 | ${render_add_comment_button()} |
|
872 | ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])} | |
858 | %endif |
|
873 | %endif | |
859 | <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span> |
|
874 | <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span> | |
860 | %if use_comments and comments: |
|
875 | %if use_comments and comments: | |
861 | ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)} |
|
876 | ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])} | |
862 | %endif |
|
877 | %endif | |
863 | </td> |
|
878 | </td> | |
864 | </tr> |
|
879 | </tr> | |
@@ -879,10 +894,12 b' def get_comments_for(diff_type, comments' | |||||
879 | </%def>file changes |
|
894 | </%def>file changes | |
880 |
|
895 | |||
881 |
|
896 | |||
882 | <%def name="render_add_comment_button()"> |
|
897 | <%def name="render_add_comment_button(line_no='', f_path='')"> | |
883 | <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)"> |
|
898 | % if not c.rhodecode_user.is_default: | |
|
899 | <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)"> | |||
884 | <span><i class="icon-comment"></i></span> |
|
900 | <span><i class="icon-comment"></i></span> | |
885 | </button> |
|
901 | </button> | |
|
902 | % endif | |||
886 | </%def> |
|
903 | </%def> | |
887 |
|
904 | |||
888 | <%def name="render_diffset_menu(diffset, range_diff_on=None, commit=None, pull_request_menu=None)"> |
|
905 | <%def name="render_diffset_menu(diffset, range_diff_on=None, commit=None, pull_request_menu=None)"> |
@@ -108,7 +108,26 b'' | |||||
108 | <i class="icon-cancel-circled2"></i> |
|
108 | <i class="icon-cancel-circled2"></i> | |
109 | </div> |
|
109 | </div> | |
110 | <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div> |
|
110 | <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div> | |
111 | <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a> |
|
111 | ||
|
112 | <div id="rev_range_action" class="btn-group btn-group-actions" style="display:none;"> | |||
|
113 | <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a> | |||
|
114 | ||||
|
115 | <a class="btn btn-success btn-sm btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button"> | |||
|
116 | <i class="icon-down"></i> | |||
|
117 | </a> | |||
|
118 | ||||
|
119 | <div class="btn-action-switcher-container right-align"> | |||
|
120 | <ul class="btn-action-switcher" role="menu" style="min-width: 220px; width: max-content"> | |||
|
121 | <li> | |||
|
122 | ## JS fills the URL | |||
|
123 | <a id="rev_range_combined_url" class="btn btn-primary btn-sm" href=""> | |||
|
124 | ${_('Show combined diff')} | |||
|
125 | </a> | |||
|
126 | </li> | |||
|
127 | </ul> | |||
|
128 | </div> | |||
|
129 | </div> | |||
|
130 | ||||
112 | </th> |
|
131 | </th> | |
113 |
|
132 | |||
114 | ## commit message expand arrow |
|
133 | ## commit message expand arrow | |
@@ -147,6 +166,9 b'' | |||||
147 | var $commitRangeMore = $('#rev_range_more'); |
|
166 | var $commitRangeMore = $('#rev_range_more'); | |
148 | var $commitRangeContainer = $('#rev_range_container'); |
|
167 | var $commitRangeContainer = $('#rev_range_container'); | |
149 | var $commitRangeClear = $('#rev_range_clear'); |
|
168 | var $commitRangeClear = $('#rev_range_clear'); | |
|
169 | var $commitRangeAction = $('#rev_range_action'); | |||
|
170 | var $commitRangeCombinedUrl = $('#rev_range_combined_url'); | |||
|
171 | var $compareFork = $('#compare_fork_button'); | |||
150 |
|
172 | |||
151 | var checkboxRangeSelector = function(e){ |
|
173 | var checkboxRangeSelector = function(e){ | |
152 | var selectedCheckboxes = []; |
|
174 | var selectedCheckboxes = []; | |
@@ -169,9 +191,8 b'' | |||||
169 | } |
|
191 | } | |
170 |
|
192 | |||
171 | if (selectedCheckboxes.length > 0) { |
|
193 | if (selectedCheckboxes.length > 0) { | |
172 |
$ |
|
194 | $compareFork.hide(); | |
173 | var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data(); |
|
195 | var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data(); | |
174 |
|
||||
175 | var revStart = commitStart.commitId; |
|
196 | var revStart = commitStart.commitId; | |
176 |
|
197 | |||
177 | var commitEnd = $(selectedCheckboxes[0]).data(); |
|
198 | var commitEnd = $(selectedCheckboxes[0]).data(); | |
@@ -181,7 +202,9 b'' | |||||
181 | var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId); |
|
202 | var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId); | |
182 |
|
203 | |||
183 | var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd}); |
|
204 | var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd}); | |
184 | var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end); |
|
205 | var urlCombined = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd, 'redirect_combined': '1'}); | |
|
206 | ||||
|
207 | var link = _gettext('Show commit range {0}<i class="icon-angle-right"></i>{1}').format(lbl_start, lbl_end); | |||
185 |
|
208 | |||
186 | if (selectedCheckboxes.length > 1) { |
|
209 | if (selectedCheckboxes.length > 1) { | |
187 | $commitRangeClear.show(); |
|
210 | $commitRangeClear.show(); | |
@@ -192,9 +215,12 b'' | |||||
192 | .html(link) |
|
215 | .html(link) | |
193 | .show(); |
|
216 | .show(); | |
194 |
|
217 | |||
|
218 | $commitRangeCombinedUrl.attr('href', urlCombined); | |||
|
219 | $commitRangeAction.show(); | |||
195 |
|
220 | |||
196 | } else { |
|
221 | } else { | |
197 | $commitRangeContainer.hide(); |
|
222 | $commitRangeContainer.hide(); | |
|
223 | $commitRangeAction.hide(); | |||
198 | $commitRangeClear.show(); |
|
224 | $commitRangeClear.show(); | |
199 | $commitRangeMore.show(); |
|
225 | $commitRangeMore.show(); | |
200 | } |
|
226 | } | |
@@ -212,6 +238,7 b'' | |||||
212 | $commitRangeContainer.hide(); |
|
238 | $commitRangeContainer.hide(); | |
213 | $commitRangeClear.hide(); |
|
239 | $commitRangeClear.hide(); | |
214 | $commitRangeMore.hide(); |
|
240 | $commitRangeMore.hide(); | |
|
241 | $commitRangeAction.hide(); | |||
215 |
|
242 | |||
216 | %if c.branch_name: |
|
243 | %if c.branch_name: | |
217 | var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'}); |
|
244 | var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'}); | |
@@ -220,7 +247,7 b'' | |||||
220 | var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'}); |
|
247 | var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'}); | |
221 | open_new_pull_request.attr('href', _url); |
|
248 | open_new_pull_request.attr('href', _url); | |
222 | %endif |
|
249 | %endif | |
223 |
$ |
|
250 | $compareFork.show(); | |
224 | } |
|
251 | } | |
225 | }; |
|
252 | }; | |
226 |
|
253 |
@@ -397,7 +397,10 b'' | |||||
397 | % endif |
|
397 | % endif | |
398 | </%def> |
|
398 | </%def> | |
399 |
|
399 | |||
400 | <%def name="pullrequest_updated_on(updated_on)"> |
|
400 | <%def name="pullrequest_updated_on(updated_on, pr_version=None)"> | |
|
401 | % if pr_version: | |||
|
402 | <code>v${pr_version}</code> | |||
|
403 | % endif | |||
401 | ${h.age_component(h.time_to_utcdatetime(updated_on))} |
|
404 | ${h.age_component(h.time_to_utcdatetime(updated_on))} | |
402 | </%def> |
|
405 | </%def> | |
403 |
|
406 | |||
@@ -456,7 +459,7 b'' | |||||
456 | </div> |
|
459 | </div> | |
457 |
|
460 | |||
458 | <div class="markup-form-area-write" style="display: block;"> |
|
461 | <div class="markup-form-area-write" style="display: block;"> | |
459 | <div id="edit-container_${form_id}"> |
|
462 | <div id="edit-container_${form_id}" style="margin-top: -1px"> | |
460 | <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea> |
|
463 | <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea> | |
461 | </div> |
|
464 | </div> | |
462 | <div id="preview-container_${form_id}" class="clearfix" style="display: none;"> |
|
465 | <div id="preview-container_${form_id}" class="clearfix" style="display: none;"> |
@@ -237,6 +237,24 b' if (show_disabled) {' | |||||
237 |
|
237 | |||
238 | </script> |
|
238 | </script> | |
239 |
|
239 | |||
|
240 | <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate"> | |||
|
241 | ||||
|
242 | <div> | |||
|
243 | Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/> | |||
|
244 | Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/> | |||
|
245 | <br/> | |||
|
246 | Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/> | |||
|
247 | <br/> | |||
|
248 | <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/> | |||
|
249 | They are permanent until deleted, or converted to regular comments.<br/> | |||
|
250 | <br/> | |||
|
251 | <br/> | |||
|
252 | </div> | |||
|
253 | ||||
|
254 | </script> | |||
|
255 | ||||
|
256 | ||||
|
257 | ||||
240 | ##// END OF EJS Templates |
|
258 | ##// END OF EJS Templates | |
241 | </div> |
|
259 | </div> | |
242 |
|
260 |
@@ -337,7 +337,6 b' text_monospace = "\'Menlo\', \'Liberation M' | |||||
337 | div.markdown-block img { |
|
337 | div.markdown-block img { | |
338 | border-style: none; |
|
338 | border-style: none; | |
339 | background-color: #fff; |
|
339 | background-color: #fff; | |
340 | padding-right: 20px; |
|
|||
341 | max-width: 100% |
|
340 | max-width: 100% | |
342 | } |
|
341 | } | |
343 |
|
342 | |||
@@ -395,6 +394,13 b' text_monospace = "\'Menlo\', \'Liberation M' | |||||
395 | background-color: #eeeeee |
|
394 | background-color: #eeeeee | |
396 | } |
|
395 | } | |
397 |
|
396 | |||
|
397 | div.markdown-block p { | |||
|
398 | margin-top: 0; | |||
|
399 | margin-bottom: 16px; | |||
|
400 | padding: 0; | |||
|
401 | line-height: unset; | |||
|
402 | } | |||
|
403 | ||||
398 | div.markdown-block code, |
|
404 | div.markdown-block code, | |
399 | div.markdown-block pre, |
|
405 | div.markdown-block pre, | |
400 | div.markdown-block #ws, |
|
406 | div.markdown-block #ws, |
@@ -82,8 +82,8 b'' | |||||
82 | <p> |
|
82 | <p> | |
83 | <strong>Exception ID: <code><a href="${c.exception_id_url}">${c.exception_id}</a></code> </strong> <br/> |
|
83 | <strong>Exception ID: <code><a href="${c.exception_id_url}">${c.exception_id}</a></code> </strong> <br/> | |
84 |
|
84 | |||
85 |
Super-admins can see detail |
|
85 | Super-admins can see details of the above error in the exception tracker found under | |
86 | Please include the above link for further details of this exception. |
|
86 | <a href="${h.route_url('admin_settings_exception_tracker')}">admin > settings > exception tracker</a>. | |
87 | </p> |
|
87 | </p> | |
88 | </div> |
|
88 | </div> | |
89 | % endif |
|
89 | % endif |
@@ -29,7 +29,7 b'' | |||||
29 | </a> |
|
29 | </a> | |
30 |
|
30 | |||
31 | <div class="btn-action-switcher-container right-align"> |
|
31 | <div class="btn-action-switcher-container right-align"> | |
32 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px"> |
|
32 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px; width: max-content"> | |
33 | <li> |
|
33 | <li> | |
34 | <a class="action_button" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}"> |
|
34 | <a class="action_button" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}"> | |
35 | <i class="icon-upload"></i> |
|
35 | <i class="icon-upload"></i> | |
@@ -44,18 +44,41 b'' | |||||
44 | % endif |
|
44 | % endif | |
45 |
|
45 | |||
46 | % if c.enable_downloads: |
|
46 | % if c.enable_downloads: | |
47 | <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %> |
|
47 | <% | |
48 | <div class="btn btn-default new-file"> |
|
48 | at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) | |
49 |
|
|
49 | if c.f_path == '/': | |
50 | <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}"> |
|
50 | label = _('Full tree as {}') | |
51 | ${_('Download full tree ZIP')} |
|
51 | _query = {'with_hash': '1'} | |
|
52 | else: | |||
|
53 | label = _('This tree as {}') | |||
|
54 | _query = {'at_path':c.f_path, 'with_hash': '1'} | |||
|
55 | %> | |||
|
56 | ||||
|
57 | <div class="btn-group btn-group-actions new-file"> | |||
|
58 | <a class="archive_link btn btn-default" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname='{}{}'.format(c.commit.raw_id, '.zip'), _query=_query)}"> | |||
|
59 | <i class="icon-download"></i> | |||
|
60 | ${label.format('.zip')} | |||
52 | </a> |
|
61 | </a> | |
53 | % else: |
|
62 | ||
54 | <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}"> |
|
63 | <a class="tooltip btn btn-default btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more download options')}"> | |
55 | ${_('Download this tree ZIP')} |
|
64 | <i class="icon-down"></i> | |
56 | </a> |
|
65 | </a> | |
57 | % endif |
|
66 | ||
58 | </div> |
|
67 | <div class="btn-action-switcher-container left-align"> | |
|
68 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px; width: max-content"> | |||
|
69 | % for a_type, content_type, extension in h.ARCHIVE_SPECS: | |||
|
70 | % if extension not in ['.zip']: | |||
|
71 | <li> | |||
|
72 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname='{}{}'.format(c.commit.raw_id, extension), _query=_query)}"> | |||
|
73 | <i class="icon-download"></i> | |||
|
74 | ${label.format(extension)} | |||
|
75 | </a> | |||
|
76 | </li> | |||
|
77 | % endif | |||
|
78 | % endfor | |||
|
79 | </ul> | |||
|
80 | </div> | |||
|
81 | </div> | |||
59 | % endif |
|
82 | % endif | |
60 |
|
83 | |||
61 | <div class="files-quick-filter"> |
|
84 | <div class="files-quick-filter"> |
@@ -7,10 +7,10 b'' | |||||
7 | · ${h.branding(c.rhodecode_name)} |
|
7 | · ${h.branding(c.rhodecode_name)} | |
8 | %endif |
|
8 | %endif | |
9 | </%def> |
|
9 | </%def> | |
|
10 | <style>body{background-color:#eeeeee;}</style> | |||
10 |
|
11 | |||
11 | <style>body{background-color:#eeeeee;}</style> |
|
|||
12 | <div class="loginbox"> |
|
12 | <div class="loginbox"> | |
13 | <div class="header"> |
|
13 | <div class="header-account"> | |
14 | <div id="header-inner" class="title"> |
|
14 | <div id="header-inner" class="title"> | |
15 | <div id="logo"> |
|
15 | <div id="logo"> | |
16 | <div class="logo-wrapper"> |
|
16 | <div class="logo-wrapper"> | |
@@ -28,12 +28,12 b'' | |||||
28 | <div class="loginwrapper"> |
|
28 | <div class="loginwrapper"> | |
29 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
29 | <rhodecode-toast id="notifications"></rhodecode-toast> | |
30 |
|
30 | |||
31 |
<div class=" |
|
31 | <div class="auth-image-wrapper"> | |
32 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> |
|
32 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> | |
33 | </div> |
|
33 | </div> | |
34 |
|
34 | |||
35 | <%block name="above_login_button" /> |
|
35 | <div id="login"> | |
36 | <div id="login" class="right-column"> |
|
36 | <%block name="above_login_button" /> | |
37 | <!-- login --> |
|
37 | <!-- login --> | |
38 | <div class="sign-in-title"> |
|
38 | <div class="sign-in-title"> | |
39 | <h1>${_('Sign In using username/password')}</h1> |
|
39 | <h1>${_('Sign In using username/password')}</h1> |
@@ -10,7 +10,7 b'' | |||||
10 | <style>body{background-color:#eeeeee;}</style> |
|
10 | <style>body{background-color:#eeeeee;}</style> | |
11 |
|
11 | |||
12 | <div class="loginbox"> |
|
12 | <div class="loginbox"> | |
13 | <div class="header"> |
|
13 | <div class="header-account"> | |
14 | <div id="header-inner" class="title"> |
|
14 | <div id="header-inner" class="title"> | |
15 | <div id="logo"> |
|
15 | <div id="logo"> | |
16 | <div class="logo-wrapper"> |
|
16 | <div class="logo-wrapper"> | |
@@ -27,7 +27,8 b'' | |||||
27 |
|
27 | |||
28 | <div class="loginwrapper"> |
|
28 | <div class="loginwrapper"> | |
29 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
29 | <rhodecode-toast id="notifications"></rhodecode-toast> | |
30 | <div class="left-column"> |
|
30 | ||
|
31 | <div class="auth-image-wrapper"> | |||
31 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> |
|
32 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> | |
32 | </div> |
|
33 | </div> | |
33 |
|
34 | |||
@@ -43,7 +44,7 b'' | |||||
43 | </p> |
|
44 | </p> | |
44 | </div> |
|
45 | </div> | |
45 | %else: |
|
46 | %else: | |
46 |
<div id="register" |
|
47 | <div id="register"> | |
47 | <!-- login --> |
|
48 | <!-- login --> | |
48 | <div class="sign-in-title"> |
|
49 | <div class="sign-in-title"> | |
49 | <h1>${_('Reset your Password')}</h1> |
|
50 | <h1>${_('Reset your Password')}</h1> |
@@ -32,8 +32,6 b'' | |||||
32 | %> |
|
32 | %> | |
33 |
|
33 | |||
34 | <script type="text/javascript"> |
|
34 | <script type="text/javascript"> | |
35 | // TODO: marcink switch this to pyroutes |
|
|||
36 | AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}"; |
|
|||
37 | templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id}; |
|
35 | templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id}; | |
38 | templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}'; |
|
36 | templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}'; | |
39 | </script> |
|
37 | </script> | |
@@ -552,6 +550,42 b'' | |||||
552 | ## CONTENT |
|
550 | ## CONTENT | |
553 | <div class="sidebar-content"> |
|
551 | <div class="sidebar-content"> | |
554 |
|
552 | |||
|
553 | ## Drafts | |||
|
554 | % if c.rhodecode_edition_id == 'EE': | |||
|
555 | <div id="draftsTable" class="sidebar-element clear-both" style="display: ${'block' if c.draft_comments else 'none'}"> | |||
|
556 | <div class="tooltip right-sidebar-collapsed-state" style="display: none;" onclick="toggleSidebar(); return false" title="${_('Drafts')}"> | |||
|
557 | <i class="icon-comment icon-draft"></i> | |||
|
558 | <span id="drafts-count">${len(c.draft_comments)}</span> | |||
|
559 | </div> | |||
|
560 | ||||
|
561 | <div class="right-sidebar-expanded-state pr-details-title"> | |||
|
562 | <span style="padding-left: 2px"> | |||
|
563 | <input name="select_all_drafts" type="checkbox" onclick="$('[name=submit_draft]').prop('checked', !$('[name=submit_draft]').prop('checked'))"> | |||
|
564 | </span> | |||
|
565 | <span class="sidebar-heading noselect" onclick="refreshDraftComments(); return false"> | |||
|
566 | <i class="icon-comment icon-draft"></i> | |||
|
567 | ${_('Drafts')} | |||
|
568 | </span> | |||
|
569 | <span class="block-right action_button last-item" onclick="submitDrafts(event)">${_('Submit')}</span> | |||
|
570 | </div> | |||
|
571 | ||||
|
572 | <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers"> | |||
|
573 | % if c.draft_comments: | |||
|
574 | ${sidebar.comments_table(c.draft_comments, len(c.draft_comments), draft_comments=True)} | |||
|
575 | % else: | |||
|
576 | <table class="drafts-content-table"> | |||
|
577 | <tr> | |||
|
578 | <td> | |||
|
579 | ${_('No TODOs yet')} | |||
|
580 | </td> | |||
|
581 | </tr> | |||
|
582 | </table> | |||
|
583 | % endif | |||
|
584 | </div> | |||
|
585 | ||||
|
586 | </div> | |||
|
587 | % endif | |||
|
588 | ||||
555 | ## RULES SUMMARY/RULES |
|
589 | ## RULES SUMMARY/RULES | |
556 | <div class="sidebar-element clear-both"> |
|
590 | <div class="sidebar-element clear-both"> | |
557 | <% vote_title = _ungettext( |
|
591 | <% vote_title = _ungettext( | |
@@ -678,7 +712,7 b'' | |||||
678 | % endif |
|
712 | % endif | |
679 |
|
713 | |||
680 | ## TODOs |
|
714 | ## TODOs | |
681 | <div class="sidebar-element clear-both"> |
|
715 | <div id="todosTable" class="sidebar-element clear-both"> | |
682 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs"> |
|
716 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs"> | |
683 | <i class="icon-flag-filled"></i> |
|
717 | <i class="icon-flag-filled"></i> | |
684 | <span id="todos-count">${len(c.unresolved_comments)}</span> |
|
718 | <span id="todos-count">${len(c.unresolved_comments)}</span> | |
@@ -712,7 +746,7 b'' | |||||
712 | % if c.unresolved_comments + c.resolved_comments: |
|
746 | % if c.unresolved_comments + c.resolved_comments: | |
713 | ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)} |
|
747 | ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)} | |
714 | % else: |
|
748 | % else: | |
715 | <table> |
|
749 | <table class="todos-content-table"> | |
716 | <tr> |
|
750 | <tr> | |
717 | <td> |
|
751 | <td> | |
718 | ${_('No TODOs yet')} |
|
752 | ${_('No TODOs yet')} | |
@@ -725,7 +759,7 b'' | |||||
725 | </div> |
|
759 | </div> | |
726 |
|
760 | |||
727 | ## COMMENTS |
|
761 | ## COMMENTS | |
728 | <div class="sidebar-element clear-both"> |
|
762 | <div id="commentsTable" class="sidebar-element clear-both"> | |
729 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}"> |
|
763 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}"> | |
730 | <i class="icon-comment" style="color: #949494"></i> |
|
764 | <i class="icon-comment" style="color: #949494"></i> | |
731 | <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span> |
|
765 | <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span> | |
@@ -763,7 +797,7 b'' | |||||
763 | % if c.inline_comments_flat + c.comments: |
|
797 | % if c.inline_comments_flat + c.comments: | |
764 | ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))} |
|
798 | ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))} | |
765 | % else: |
|
799 | % else: | |
766 | <table> |
|
800 | <table class="comments-content-table"> | |
767 | <tr> |
|
801 | <tr> | |
768 | <td> |
|
802 | <td> | |
769 | ${_('No Comments yet')} |
|
803 | ${_('No Comments yet')} | |
@@ -846,6 +880,7 b' versionController.init();' | |||||
846 |
|
880 | |||
847 | reviewersController = new ReviewersController(); |
|
881 | reviewersController = new ReviewersController(); | |
848 | commitsController = new CommitsController(); |
|
882 | commitsController = new CommitsController(); | |
|
883 | commentsController = new CommentsController(); | |||
849 |
|
884 | |||
850 | updateController = new UpdatePrController(); |
|
885 | updateController = new UpdatePrController(); | |
851 |
|
886 | |||
@@ -891,6 +926,23 b' window.setObserversData = ${c.pull_reque' | |||||
891 | ); |
|
926 | ); | |
892 | }; |
|
927 | }; | |
893 |
|
928 | |||
|
929 | window.submitDrafts = function (event) { | |||
|
930 | var target = $(event.currentTarget); | |||
|
931 | var callback = function (result) { | |||
|
932 | target.removeAttr('onclick').html('saving...'); | |||
|
933 | } | |||
|
934 | var draftIds = []; | |||
|
935 | $.each($('[name=submit_draft]:checked'), function (idx, val) { | |||
|
936 | draftIds.push(parseInt($(val).val())); | |||
|
937 | }) | |||
|
938 | if (draftIds.length > 0) { | |||
|
939 | Rhodecode.comments.finalizeDrafts(draftIds, callback); | |||
|
940 | } | |||
|
941 | else { | |||
|
942 | ||||
|
943 | } | |||
|
944 | } | |||
|
945 | ||||
894 | window.closePullRequest = function (status) { |
|
946 | window.closePullRequest = function (status) { | |
895 | if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) { |
|
947 | if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) { | |
896 | return false; |
|
948 | return false; | |
@@ -980,9 +1032,11 b' window.setObserversData = ${c.pull_reque' | |||||
980 | $(btns).each(fn_display); |
|
1032 | $(btns).each(fn_display); | |
981 | }); |
|
1033 | }); | |
982 |
|
1034 | |||
983 | // register submit callback on commentForm form to track TODOs |
|
1035 | // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions | |
984 | window.commentFormGlobalSubmitSuccessCallback = function () { |
|
1036 | window.commentFormGlobalSubmitSuccessCallback = function (comment) { | |
985 | refreshMergeChecks(); |
|
1037 | if (!comment.draft) { | |
|
1038 | refreshMergeChecks(); | |||
|
1039 | } | |||
986 | }; |
|
1040 | }; | |
987 |
|
1041 | |||
988 | ReviewerAutoComplete('#user', reviewersController); |
|
1042 | ReviewerAutoComplete('#user', reviewersController); | |
@@ -994,7 +1048,8 b' window.setObserversData = ${c.pull_reque' | |||||
994 |
|
1048 | |||
995 | var channel = '${c.pr_broadcast_channel}'; |
|
1049 | var channel = '${c.pr_broadcast_channel}'; | |
996 | new ReviewerPresenceController(channel) |
|
1050 | new ReviewerPresenceController(channel) | |
997 |
|
1051 | // register globally so inject comment logic can re-use it. | ||
|
1052 | window.commentsController = commentsController; | |||
998 | }) |
|
1053 | }) | |
999 | </script> |
|
1054 | </script> | |
1000 |
|
1055 |
@@ -74,6 +74,8 b'' | |||||
74 | $pullRequestListTable.DataTable({ |
|
74 | $pullRequestListTable.DataTable({ | |
75 | processing: true, |
|
75 | processing: true, | |
76 | serverSide: true, |
|
76 | serverSide: true, | |
|
77 | stateSave: true, | |||
|
78 | stateDuration: -1, | |||
77 | ajax: { |
|
79 | ajax: { | |
78 | "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}", |
|
80 | "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}", | |
79 | "data": function (d) { |
|
81 | "data": function (d) { | |
@@ -114,6 +116,10 b'' | |||||
114 | if (data['closed']) { |
|
116 | if (data['closed']) { | |
115 | $(row).addClass('closed'); |
|
117 | $(row).addClass('closed'); | |
116 | } |
|
118 | } | |
|
119 | }, | |||
|
120 | "stateSaveParams": function (settings, data) { | |||
|
121 | data.search.search = ""; // Don't save search | |||
|
122 | data.start = 0; // don't save pagination | |||
117 | } |
|
123 | } | |
118 | }); |
|
124 | }); | |
119 |
|
125 |
@@ -10,7 +10,7 b'' | |||||
10 | <style>body{background-color:#eeeeee;}</style> |
|
10 | <style>body{background-color:#eeeeee;}</style> | |
11 |
|
11 | |||
12 | <div class="loginbox"> |
|
12 | <div class="loginbox"> | |
13 | <div class="header"> |
|
13 | <div class="header-account"> | |
14 | <div id="header-inner" class="title"> |
|
14 | <div id="header-inner" class="title"> | |
15 | <div id="logo"> |
|
15 | <div id="logo"> | |
16 | <div class="logo-wrapper"> |
|
16 | <div class="logo-wrapper"> | |
@@ -27,11 +27,13 b'' | |||||
27 |
|
27 | |||
28 | <div class="loginwrapper"> |
|
28 | <div class="loginwrapper"> | |
29 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
29 | <rhodecode-toast id="notifications"></rhodecode-toast> | |
30 | <div class="left-column"> |
|
30 | ||
|
31 | <div class="auth-image-wrapper"> | |||
31 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> |
|
32 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> | |
32 | </div> |
|
33 | </div> | |
33 | <%block name="above_register_button" /> |
|
34 | ||
34 |
<div id="register" |
|
35 | <div id="register"> | |
|
36 | <%block name="above_register_button" /> | |||
35 | <!-- login --> |
|
37 | <!-- login --> | |
36 | <div class="sign-in-title"> |
|
38 | <div class="sign-in-title"> | |
37 | % if external_auth_provider: |
|
39 | % if external_auth_provider: |
@@ -187,7 +187,7 b'' | |||||
187 | <div class="enabled pull-left" style="margin-right: 10px"> |
|
187 | <div class="enabled pull-left" style="margin-right: 10px"> | |
188 |
|
188 | |||
189 | <div class="btn-group btn-group-actions"> |
|
189 | <div class="btn-group btn-group-actions"> | |
190 |
<a class="archive_link btn btn-small" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+'.zip' |
|
190 | <a class="archive_link btn btn-small" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+'.zip', _query={'with_hash': '1'})}"> | |
191 | <i class="icon-download"></i> |
|
191 | <i class="icon-download"></i> | |
192 | ${c.rhodecode_db_repo.landing_ref_name}.zip |
|
192 | ${c.rhodecode_db_repo.landing_ref_name}.zip | |
193 | ## replaced by some JS on select |
|
193 | ## replaced by some JS on select | |
@@ -198,12 +198,11 b'' | |||||
198 | </a> |
|
198 | </a> | |
199 |
|
199 | |||
200 | <div class="btn-action-switcher-container left-align"> |
|
200 | <div class="btn-action-switcher-container left-align"> | |
201 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px"> |
|
201 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px; width: max-content"> | |
202 | % for a_type, content_type, extension in h.ARCHIVE_SPECS: |
|
202 | % for a_type, content_type, extension in h.ARCHIVE_SPECS: | |
203 | % if extension not in ['.zip']: |
|
203 | % if extension not in ['.zip']: | |
204 | <li> |
|
204 | <li> | |
205 |
|
205 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+extension, _query={'with_hash': '1'})}"> | ||
206 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+extension)}"> |
|
|||
207 | <i class="icon-download"></i> |
|
206 | <i class="icon-download"></i> | |
208 | ${c.rhodecode_db_repo.landing_ref_name+extension} |
|
207 | ${c.rhodecode_db_repo.landing_ref_name+extension} | |
209 | </a> |
|
208 | </a> |
@@ -49,23 +49,32 b' class TestArchives(BackendTestMixin):' | |||||
49 | @classmethod |
|
49 | @classmethod | |
50 | def _get_commits(cls): |
|
50 | def _get_commits(cls): | |
51 | start_date = datetime.datetime(2010, 1, 1, 20) |
|
51 | start_date = datetime.datetime(2010, 1, 1, 20) | |
|
52 | yield { | |||
|
53 | 'message': 'Initial Commit', | |||
|
54 | 'author': 'Joe Doe <joe.doe@example.com>', | |||
|
55 | 'date': start_date + datetime.timedelta(hours=12), | |||
|
56 | 'added': [ | |||
|
57 | FileNode('executable_0o100755', '...', mode=0o100755), | |||
|
58 | FileNode('executable_0o100500', '...', mode=0o100500), | |||
|
59 | FileNode('not_executable', '...', mode=0o100644), | |||
|
60 | ], | |||
|
61 | } | |||
52 | for x in range(5): |
|
62 | for x in range(5): | |
53 | yield { |
|
63 | yield { | |
54 | 'message': 'Commit %d' % x, |
|
64 | 'message': 'Commit %d' % x, | |
55 | 'author': 'Joe Doe <joe.doe@example.com>', |
|
65 | 'author': 'Joe Doe <joe.doe@example.com>', | |
56 | 'date': start_date + datetime.timedelta(hours=12 * x), |
|
66 | 'date': start_date + datetime.timedelta(hours=12 * x), | |
57 | 'added': [ |
|
67 | 'added': [ | |
58 | FileNode( |
|
68 | FileNode('%d/file_%d.txt' % (x, x), content='Foobar %d' % x), | |
59 | '%d/file_%d.txt' % (x, x), content='Foobar %d' % x), |
|
|||
60 | ], |
|
69 | ], | |
61 | } |
|
70 | } | |
62 |
|
71 | |||
63 | @pytest.mark.parametrize('compressor', ['gz', 'bz2']) |
|
72 | @pytest.mark.parametrize('compressor', ['gz', 'bz2']) | |
64 | def test_archive_tar(self, compressor): |
|
73 | def test_archive_tar(self, compressor): | |
65 | self.tip.archive_repo( |
|
74 | self.tip.archive_repo( | |
66 |
self.temp_file, kind='t' |
|
75 | self.temp_file, kind='t{}'.format(compressor), archive_dir_name='repo') | |
67 | out_dir = tempfile.mkdtemp() |
|
76 | out_dir = tempfile.mkdtemp() | |
68 |
out_file = tarfile.open(self.temp_file, 'r|' |
|
77 | out_file = tarfile.open(self.temp_file, 'r|{}'.format(compressor)) | |
69 | out_file.extractall(out_dir) |
|
78 | out_file.extractall(out_dir) | |
70 | out_file.close() |
|
79 | out_file.close() | |
71 |
|
80 | |||
@@ -77,8 +86,24 b' class TestArchives(BackendTestMixin):' | |||||
77 |
|
86 | |||
78 | shutil.rmtree(out_dir) |
|
87 | shutil.rmtree(out_dir) | |
79 |
|
88 | |||
|
89 | @pytest.mark.parametrize('compressor', ['gz', 'bz2']) | |||
|
90 | def test_archive_tar_symlink(self, compressor): | |||
|
91 | return False | |||
|
92 | ||||
|
93 | @pytest.mark.parametrize('compressor', ['gz', 'bz2']) | |||
|
94 | def test_archive_tar_file_modes(self, compressor): | |||
|
95 | self.tip.archive_repo( | |||
|
96 | self.temp_file, kind='t{}'.format(compressor), archive_dir_name='repo') | |||
|
97 | out_dir = tempfile.mkdtemp() | |||
|
98 | out_file = tarfile.open(self.temp_file, 'r|{}'.format(compressor)) | |||
|
99 | out_file.extractall(out_dir) | |||
|
100 | out_file.close() | |||
|
101 | dest = lambda inp: os.path.join(out_dir, 'repo/' + inp) | |||
|
102 | ||||
|
103 | assert oct(os.stat(dest('not_executable')).st_mode) == '0100644' | |||
|
104 | ||||
80 | def test_archive_zip(self): |
|
105 | def test_archive_zip(self): | |
81 |
self.tip.archive_repo(self.temp_file, kind='zip', |
|
106 | self.tip.archive_repo(self.temp_file, kind='zip', archive_dir_name='repo') | |
82 | out = zipfile.ZipFile(self.temp_file) |
|
107 | out = zipfile.ZipFile(self.temp_file) | |
83 |
|
108 | |||
84 | for x in range(5): |
|
109 | for x in range(5): | |
@@ -91,10 +116,10 b' class TestArchives(BackendTestMixin):' | |||||
91 |
|
116 | |||
92 | def test_archive_zip_with_metadata(self): |
|
117 | def test_archive_zip_with_metadata(self): | |
93 | self.tip.archive_repo(self.temp_file, kind='zip', |
|
118 | self.tip.archive_repo(self.temp_file, kind='zip', | |
94 |
|
|
119 | archive_dir_name='repo', write_metadata=True) | |
95 |
|
120 | |||
96 | out = zipfile.ZipFile(self.temp_file) |
|
121 | out = zipfile.ZipFile(self.temp_file) | |
97 | metafile = out.read('.archival.txt') |
|
122 | metafile = out.read('repo/.archival.txt') | |
98 |
|
123 | |||
99 | raw_id = self.tip.raw_id |
|
124 | raw_id = self.tip.raw_id | |
100 | assert 'commit_id:%s' % raw_id in metafile |
|
125 | assert 'commit_id:%s' % raw_id in metafile |
General Comments 0
You need to be logged in to leave comments.
Login now