diff --git a/rhodecode/apps/debug_style/views.py b/rhodecode/apps/debug_style/views.py
--- a/rhodecode/apps/debug_style/views.py
+++ b/rhodecode/apps/debug_style/views.py
@@ -252,7 +252,7 @@ But please check this code
var comment = $('#comment-'+commentId);
var commentData = comment.data();
if (commentData.commentInline) {
- this.createComment(comment, commentId)
+ this.createComment(comment, f_path, line_no, commentId)
} else {
Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
}
diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py
--- a/rhodecode/apps/repository/views/repo_commits.py
+++ b/rhodecode/apps/repository/views/repo_commits.py
@@ -383,6 +383,9 @@ class RepoCommitsView(RepoAppView):
text = self.request.POST.get('text')
comment_type = self.request.POST.get('comment_type')
resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
+ f_path = self.request.POST.get('f_path')
+ line_no = self.request.POST.get('line')
+ target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
if status:
text = text or (_('Status change %(transition_icon)s %(status)s')
@@ -404,8 +407,8 @@ class RepoCommitsView(RepoAppView):
repo=self.db_repo.repo_id,
user=self._rhodecode_db_user.user_id,
commit_id=current_id,
- f_path=self.request.POST.get('f_path'),
- line_no=self.request.POST.get('line'),
+ f_path=f_path,
+ line_no=line_no,
status_change=(ChangesetStatus.get_status_lbl(status)
if status else None),
status_change_type=status,
@@ -447,19 +450,20 @@ class RepoCommitsView(RepoAppView):
# finalize, commit and redirect
Session().commit()
- data = {
- 'target_id': h.safeid(h.safe_unicode(
- self.request.POST.get('f_path'))),
- }
+ data = {}
if comment:
+ comment_id = comment.comment_id
+ data[comment_id] = {
+ 'target_id': target_elem_id
+ }
c.co = comment
c.at_version_num = 0
rendered_comment = render(
'rhodecode:templates/changeset/changeset_comment_block.mako',
self._get_template_context(c), self.request)
- data.update(comment.get_dict())
- data.update({'rendered_text': rendered_comment})
+ data[comment_id].update(comment.get_dict())
+ data[comment_id].update({'rendered_text': rendered_comment})
comment_broadcast_channel = channelstream.comment_channel(
self.db_repo_name, commit_obj=commit)
diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py
--- a/rhodecode/apps/repository/views/repo_pull_requests.py
+++ b/rhodecode/apps/repository/views/repo_pull_requests.py
@@ -983,8 +983,9 @@ class RepoPullRequestsView(RepoAppView,
}
return data
- def _get_existing_ids(self, post_data):
- return filter(lambda e: e, map(safe_int, aslist(post_data.get('comments'), ',')))
+ @classmethod
+ def get_comment_ids(cls, post_data):
+ return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
@LoginRequired()
@NotAnonymous()
@@ -1022,7 +1023,7 @@ class RepoPullRequestsView(RepoAppView,
self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
all_comments = c.inline_comments_flat + c.comments
- existing_ids = self._get_existing_ids(self.request.POST)
+ existing_ids = self.get_comment_ids(self.request.POST)
return _render('comments_table', all_comments, len(all_comments),
existing_ids=existing_ids)
@@ -1064,7 +1065,7 @@ class RepoPullRequestsView(RepoAppView,
.get_pull_request_resolved_todos(pull_request, include_drafts=False)
all_comments = c.unresolved_comments + c.resolved_comments
- existing_ids = self._get_existing_ids(self.request.POST)
+ existing_ids = self.get_comment_ids(self.request.POST)
return _render('comments_table', all_comments, len(c.unresolved_comments),
todo_comments=True, existing_ids=existing_ids)
@@ -1518,6 +1519,150 @@ class RepoPullRequestsView(RepoAppView,
self._rhodecode_user)
raise HTTPNotFound()
+ def _pull_request_comments_create(self, pull_request, comments):
+ _ = self.request.translate
+ data = {}
+ pull_request_id = pull_request.pull_request_id
+ if not comments:
+ return
+
+ all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
+
+ for entry in comments:
+ c = self.load_default_context()
+ comment_type = entry['comment_type']
+ text = entry['text']
+ status = entry['status']
+ is_draft = str2bool(entry['is_draft'])
+ resolves_comment_id = entry['resolves_comment_id']
+ close_pull_request = entry['close_pull_request']
+ f_path = entry['f_path']
+ line_no = entry['line']
+ target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
+
+ # the logic here should work like following, if we submit close
+ # pr comment, use `close_pull_request_with_comment` function
+ # else handle regular comment logic
+
+ if close_pull_request:
+ # only owner or admin or person with write permissions
+ allowed_to_close = PullRequestModel().check_user_update(
+ pull_request, self._rhodecode_user)
+ if not allowed_to_close:
+ log.debug('comment: forbidden because not allowed to close '
+ 'pull request %s', pull_request_id)
+ raise HTTPForbidden()
+
+ # This also triggers `review_status_change`
+ comment, status = PullRequestModel().close_pull_request_with_comment(
+ pull_request, self._rhodecode_user, self.db_repo, message=text,
+ auth_user=self._rhodecode_user)
+ Session().flush()
+ is_inline = comment.is_inline
+
+ PullRequestModel().trigger_pull_request_hook(
+ pull_request, self._rhodecode_user, 'comment',
+ data={'comment': comment})
+
+ else:
+ # regular comment case, could be inline, or one with status.
+ # for that one we check also permissions
+ # Additionally ENSURE if somehow draft is sent we're then unable to change status
+ allowed_to_change_status = PullRequestModel().check_user_change_status(
+ pull_request, self._rhodecode_user) and not is_draft
+
+ if status and allowed_to_change_status:
+ message = (_('Status change %(transition_icon)s %(status)s')
+ % {'transition_icon': '>',
+ 'status': ChangesetStatus.get_status_lbl(status)})
+ text = text or message
+
+ comment = CommentsModel().create(
+ text=text,
+ repo=self.db_repo.repo_id,
+ user=self._rhodecode_user.user_id,
+ pull_request=pull_request,
+ f_path=f_path,
+ line_no=line_no,
+ status_change=(ChangesetStatus.get_status_lbl(status)
+ if status and allowed_to_change_status else None),
+ status_change_type=(status
+ if status and allowed_to_change_status else None),
+ comment_type=comment_type,
+ is_draft=is_draft,
+ resolves_comment_id=resolves_comment_id,
+ auth_user=self._rhodecode_user,
+ send_email=not is_draft, # skip notification for draft comments
+ )
+ is_inline = comment.is_inline
+
+ if allowed_to_change_status:
+ # calculate old status before we change it
+ old_calculated_status = pull_request.calculated_review_status()
+
+ # get status if set !
+ if status:
+ ChangesetStatusModel().set_status(
+ self.db_repo.repo_id,
+ status,
+ self._rhodecode_user.user_id,
+ comment,
+ pull_request=pull_request
+ )
+
+ Session().flush()
+ # this is somehow required to get access to some relationship
+ # loaded on comment
+ Session().refresh(comment)
+
+ PullRequestModel().trigger_pull_request_hook(
+ pull_request, self._rhodecode_user, 'comment',
+ data={'comment': comment})
+
+ # we now calculate the status of pull request, and based on that
+ # calculation we set the commits status
+ calculated_status = pull_request.calculated_review_status()
+ if old_calculated_status != calculated_status:
+ PullRequestModel().trigger_pull_request_hook(
+ pull_request, self._rhodecode_user, 'review_status_change',
+ data={'status': calculated_status})
+
+ comment_id = comment.comment_id
+ data[comment_id] = {
+ 'target_id': target_elem_id
+ }
+ Session().flush()
+
+ c.co = comment
+ c.at_version_num = None
+ c.is_new = True
+ rendered_comment = render(
+ 'rhodecode:templates/changeset/changeset_comment_block.mako',
+ self._get_template_context(c), self.request)
+
+ data[comment_id].update(comment.get_dict())
+ data[comment_id].update({'rendered_text': rendered_comment})
+
+ Session().commit()
+
+ # skip channelstream for draft comments
+ if all_drafts:
+ comment_broadcast_channel = channelstream.comment_channel(
+ self.db_repo_name, pull_request_obj=pull_request)
+
+ comment_data = data
+ comment_type = 'inline' if is_inline else 'general'
+ if len(data) == 1:
+ msg = _('posted {} new {} comment').format(len(data), comment_type)
+ else:
+ msg = _('posted {} new {} comments').format(len(data), comment_type)
+
+ channelstream.comment_channelstream_push(
+ self.request, comment_broadcast_channel, self._rhodecode_user, msg,
+ comment_data=comment_data)
+
+ return data
+
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator(
@@ -1529,9 +1674,7 @@ class RepoPullRequestsView(RepoAppView,
def pull_request_comment_create(self):
_ = self.request.translate
- pull_request = PullRequest.get_or_404(
- self.request.matchdict['pull_request_id'])
- pull_request_id = pull_request.pull_request_id
+ pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
if pull_request.is_closed():
log.debug('comment: forbidden because pull request is closed')
@@ -1543,130 +1686,17 @@ class RepoPullRequestsView(RepoAppView,
log.debug('comment: forbidden because pull request is from forbidden repo')
raise HTTPForbidden()
- c = self.load_default_context()
-
- status = self.request.POST.get('changeset_status', None)
- text = self.request.POST.get('text')
- comment_type = self.request.POST.get('comment_type')
- is_draft = str2bool(self.request.POST.get('draft'))
- resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
- close_pull_request = self.request.POST.get('close_pull_request')
-
- # the logic here should work like following, if we submit close
- # pr comment, use `close_pull_request_with_comment` function
- # else handle regular comment logic
-
- if close_pull_request:
- # only owner or admin or person with write permissions
- allowed_to_close = PullRequestModel().check_user_update(
- pull_request, self._rhodecode_user)
- if not allowed_to_close:
- log.debug('comment: forbidden because not allowed to close '
- 'pull request %s', pull_request_id)
- raise HTTPForbidden()
-
- # This also triggers `review_status_change`
- comment, status = PullRequestModel().close_pull_request_with_comment(
- pull_request, self._rhodecode_user, self.db_repo, message=text,
- auth_user=self._rhodecode_user)
- Session().flush()
- is_inline = comment.is_inline
-
- PullRequestModel().trigger_pull_request_hook(
- pull_request, self._rhodecode_user, 'comment',
- data={'comment': comment})
-
- else:
- # regular comment case, could be inline, or one with status.
- # for that one we check also permissions
- # Additionally ENSURE if somehow draft is sent we're then unable to change status
- allowed_to_change_status = PullRequestModel().check_user_change_status(
- pull_request, self._rhodecode_user) and not is_draft
-
- if status and allowed_to_change_status:
- message = (_('Status change %(transition_icon)s %(status)s')
- % {'transition_icon': '>',
- 'status': ChangesetStatus.get_status_lbl(status)})
- text = text or message
-
- comment = CommentsModel().create(
- text=text,
- repo=self.db_repo.repo_id,
- user=self._rhodecode_user.user_id,
- pull_request=pull_request,
- f_path=self.request.POST.get('f_path'),
- line_no=self.request.POST.get('line'),
- status_change=(ChangesetStatus.get_status_lbl(status)
- if status and allowed_to_change_status else None),
- status_change_type=(status
- if status and allowed_to_change_status else None),
- comment_type=comment_type,
- is_draft=is_draft,
- resolves_comment_id=resolves_comment_id,
- auth_user=self._rhodecode_user,
- send_email=not is_draft, # skip notification for draft comments
- )
- is_inline = comment.is_inline
-
- if allowed_to_change_status:
- # calculate old status before we change it
- old_calculated_status = pull_request.calculated_review_status()
-
- # get status if set !
- if status:
- ChangesetStatusModel().set_status(
- self.db_repo.repo_id,
- status,
- self._rhodecode_user.user_id,
- comment,
- pull_request=pull_request
- )
-
- Session().flush()
- # this is somehow required to get access to some relationship
- # loaded on comment
- Session().refresh(comment)
-
- PullRequestModel().trigger_pull_request_hook(
- pull_request, self._rhodecode_user, 'comment',
- data={'comment': comment})
-
- # we now calculate the status of pull request, and based on that
- # calculation we set the commits status
- calculated_status = pull_request.calculated_review_status()
- if old_calculated_status != calculated_status:
- PullRequestModel().trigger_pull_request_hook(
- pull_request, self._rhodecode_user, 'review_status_change',
- data={'status': calculated_status})
-
- Session().commit()
-
- data = {
- 'target_id': h.safeid(h.safe_unicode(
- self.request.POST.get('f_path'))),
+ comment_data = {
+ 'comment_type': self.request.POST.get('comment_type'),
+ 'text': self.request.POST.get('text'),
+ 'status': self.request.POST.get('changeset_status', None),
+ 'is_draft': self.request.POST.get('draft'),
+ 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
+ 'close_pull_request': self.request.POST.get('close_pull_request'),
+ 'f_path': self.request.POST.get('f_path'),
+ 'line': self.request.POST.get('line'),
}
-
- if comment:
- c.co = comment
- c.at_version_num = None
- rendered_comment = render(
- 'rhodecode:templates/changeset/changeset_comment_block.mako',
- self._get_template_context(c), self.request)
-
- data.update(comment.get_dict())
- data.update({'rendered_text': rendered_comment})
-
- # skip channelstream for draft comments
- if not is_draft:
- comment_broadcast_channel = channelstream.comment_channel(
- self.db_repo_name, pull_request_obj=pull_request)
-
- comment_data = data
- comment_type = 'inline' if is_inline else 'general'
- channelstream.comment_channelstream_push(
- self.request, comment_broadcast_channel, self._rhodecode_user,
- _('posted a new {} comment').format(comment_type),
- comment_data=comment_data)
+ data = self._pull_request_comments_create(pull_request, [comment_data])
return data
diff --git a/rhodecode/lib/channelstream.py b/rhodecode/lib/channelstream.py
--- a/rhodecode/lib/channelstream.py
+++ b/rhodecode/lib/channelstream.py
@@ -339,13 +339,12 @@ def comment_channelstream_push(request,
comment_data = kwargs.pop('comment_data', {})
user_data = kwargs.pop('user_data', {})
- comment_id = comment_data.get('comment_id')
+ comment_id = comment_data.keys()[0] if comment_data else ''
- message = '{} {} #{}, {}'.format(
+ message = '{} {} #{}'.format(
user.username,
msg,
comment_id,
- _reload_link(_('Reload page to see new comments')),
)
message_obj = {
diff --git a/rhodecode/lib/diffs.py b/rhodecode/lib/diffs.py
--- a/rhodecode/lib/diffs.py
+++ b/rhodecode/lib/diffs.py
@@ -1148,7 +1148,7 @@ class DiffLimitExceeded(Exception):
# NOTE(marcink): if diffs.mako change, probably this
# needs a bump to next version
-CURRENT_DIFF_VERSION = 'v4'
+CURRENT_DIFF_VERSION = 'v5'
def _cleanup_cache_file(cached_diff_file):
diff --git a/rhodecode/public/css/buttons.less b/rhodecode/public/css/buttons.less
--- a/rhodecode/public/css/buttons.less
+++ b/rhodecode/public/css/buttons.less
@@ -370,7 +370,7 @@ input[type="button"] {
color: @alert2;
&:hover {
- color: darken(@alert2,30%);
+ color: darken(@alert2, 30%);
}
&:disabled {
diff --git a/rhodecode/public/css/code-block.less b/rhodecode/public/css/code-block.less
--- a/rhodecode/public/css/code-block.less
+++ b/rhodecode/public/css/code-block.less
@@ -1002,7 +1002,7 @@ input.filediff-collapse-state {
.nav-chunk {
position: absolute;
right: 20px;
- margin-top: -17px;
+ margin-top: -15px;
}
.nav-chunk.selected {
diff --git a/rhodecode/public/css/comments.less b/rhodecode/public/css/comments.less
--- a/rhodecode/public/css/comments.less
+++ b/rhodecode/public/css/comments.less
@@ -4,7 +4,7 @@
// Comments
-@comment-outdated-opacity: 0.6;
+@comment-outdated-opacity: 1.0;
.comments {
width: 100%;
@@ -64,32 +64,34 @@ tr.inline-comments div {
.comment-draft {
float: left;
margin-right: 10px;
- font-weight: 600;
- color: @alert3;
+ font-weight: 400;
+ color: @color-draft;
+}
+
+.comment-new {
+ float: left;
+ margin-right: 10px;
+ font-weight: 400;
+ color: @color-new;
}
.comment-label {
float: left;
- padding: 0.4em 0.4em;
- margin: 2px 4px 0px 0px;
- display: inline-block;
+ padding: 0 8px 0 0;
min-height: 0;
text-align: center;
font-size: 10px;
- line-height: .8em;
font-family: @text-italic;
font-style: italic;
background: #fff none;
color: @grey3;
- border: 1px solid @grey4;
white-space: nowrap;
text-transform: uppercase;
min-width: 50px;
- border-radius: 4px;
&.todo {
color: @color5;
@@ -277,64 +279,165 @@ tr.inline-comments div {
.comment-outdated {
opacity: @comment-outdated-opacity;
}
+
+ .comment-outdated-label {
+ color: @grey3;
+ padding-right: 4px;
+ }
}
.inline-comments {
- border-radius: @border-radius;
+
.comment {
margin: 0;
- border-radius: @border-radius;
}
+
.comment-outdated {
opacity: @comment-outdated-opacity;
}
+ .comment-outdated-label {
+ color: @grey3;
+ padding-right: 4px;
+ }
+
.comment-inline {
+
+ &:first-child {
+ margin: 4px 4px 0 4px;
+ border-top: 1px solid @grey5;
+ border-bottom: 0 solid @grey5;
+ border-left: 1px solid @grey5;
+ border-right: 1px solid @grey5;
+ .border-radius-top(4px);
+ }
+
+ &:only-child {
+ margin: 4px 4px 0 4px;
+ border-top: 1px solid @grey5;
+ border-bottom: 0 solid @grey5;
+ border-left: 1px solid @grey5;
+ border-right: 1px solid @grey5;
+ .border-radius-top(4px);
+ }
+
background: white;
padding: @comment-padding @comment-padding;
- border: @comment-padding solid @grey6;
+ margin: 0 4px 0 4px;
+ border-top: 0 solid @grey5;
+ border-bottom: 0 solid @grey5;
+ border-left: 1px solid @grey5;
+ border-right: 1px solid @grey5;
.text {
border: none;
}
+
.meta {
border-bottom: 1px solid @grey6;
margin: -5px 0px;
line-height: 24px;
}
+
}
.comment-selected {
border-left: 6px solid @comment-highlight-color;
}
+
+ .comment-inline-form-open {
+ display: block !important;
+ }
+
.comment-inline-form {
- padding: @comment-padding;
display: none;
}
- .cb-comment-add-button {
- margin: @comment-padding;
+
+ .comment-inline-form-edit {
+ padding: 0;
+ margin: 0px 4px 2px 4px;
+ }
+
+ .reply-thread-container {
+ display: table;
+ width: 100%;
+ padding: 0px 4px 4px 4px;
+ }
+
+ .reply-thread-container-wrapper {
+ margin: 0 4px 4px 4px;
+ border-top: 0 solid @grey5;
+ border-bottom: 1px solid @grey5;
+ border-left: 1px solid @grey5;
+ border-right: 1px solid @grey5;
+ .border-radius-bottom(4px);
+ }
+
+ .reply-thread-gravatar {
+ display: table-cell;
+ width: 24px;
+ height: 24px;
+ padding-top: 10px;
+ padding-left: 10px;
+ background-color: #eeeeee;
+ vertical-align: top;
}
- /* hide add comment button when form is open */
+
+ .reply-thread-reply-button {
+ display: table-cell;
+ width: 100%;
+ height: 33px;
+ padding: 3px 8px;
+ margin-left: 8px;
+ background-color: #eeeeee;
+ }
+
+ .reply-thread-reply-button .cb-comment-add-button {
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px 2px;
+ text-align: left;
+ cursor: text;
+ color: @grey3;
+ }
+ .reply-thread-reply-button .cb-comment-add-button:hover {
+ background-color: white;
+ color: @grey2;
+ }
+
+ .reply-thread-last {
+ display: table-cell;
+ width: 10px;
+ }
+
+ /* Hide reply box when it's a first element,
+ can happen when drafts are saved but not shown to specific user,
+ or there are outdated comments hidden
+ */
+ .reply-thread-container-wrapper:first-child:not(.comment-form-active) {
+ display: none;
+ }
+
+ .reply-thread-container-wrapper.comment-outdated {
+ display: none
+ }
+
+ /* hide add comment button when form is open */
.comment-inline-form-open ~ .cb-comment-add-button {
display: none;
}
- .comment-inline-form-open {
- display: block;
- }
- /* hide add comment button when form but no comments */
- .comment-inline-form:first-child + .cb-comment-add-button {
- display: none;
- }
- /* hide add comment button when no comments or form */
- .cb-comment-add-button:first-child {
- display: none;
- }
+
/* hide add comment button when only comment is being deleted */
.comment-deleting:first-child + .cb-comment-add-button {
display: none;
}
+
+ /* hide add comment button when form but no comments */
+ .comment-inline-form:first-child + .cb-comment-add-button {
+ display: none;
+ }
+
}
-
.show-outdated-comments {
display: inline;
color: @rcblue;
@@ -387,23 +490,40 @@ form.comment-form {
}
.comment-footer {
- position: relative;
+ display: table;
width: 100%;
- min-height: 42px;
+ height: 42px;
- .status_box,
+ .comment-status-box,
.cancel-button {
- float: left;
display: inline-block;
}
- .status_box {
+ .comment-status-box {
margin-left: 10px;
}
.action-buttons {
- float: left;
- display: inline-block;
+ display: table-cell;
+ padding: 5px 0 5px 2px;
+ }
+
+ .toolbar-text {
+ height: 42px;
+ display: table-cell;
+ vertical-align: bottom;
+ font-size: 11px;
+ color: @grey4;
+ text-align: right;
+
+ a {
+ color: @grey4;
+ }
+
+ p {
+ padding: 0;
+ margin: 0;
+ }
}
.action-buttons-extra {
@@ -434,10 +554,6 @@ form.comment-form {
margin-right: 0;
}
- .comment-footer {
- margin-bottom: 50px;
- margin-top: 10px;
- }
}
@@ -489,8 +605,8 @@ form.comment-form {
.injected_diff .comment-inline-form,
.comment-inline-form {
background-color: white;
- margin-top: 10px;
- margin-bottom: 20px;
+ margin-top: 4px;
+ margin-bottom: 10px;
}
.inline-form {
@@ -526,9 +642,6 @@ form.comment-form {
margin: 0px;
}
-.comment-inline-form .comment-footer {
- margin: 10px 0px 0px 0px;
-}
.hide-inline-form-button {
margin-left: 5px;
@@ -554,6 +667,7 @@ comment-area-text {
.comment-area-header {
height: 35px;
+ border-bottom: 1px solid @grey5;
}
.comment-area-header .nav-links {
@@ -561,6 +675,7 @@ comment-area-text {
flex-flow: row wrap;
-webkit-flex-flow: row wrap;
width: 100%;
+ border: none;
}
.comment-area-footer {
@@ -629,14 +744,3 @@ comment-area-text {
border-bottom: 2px solid transparent;
}
-.toolbar-text {
- float: right;
- font-size: 11px;
- color: @grey4;
- text-align: right;
-
- a {
- color: @grey4;
- }
-}
-
diff --git a/rhodecode/public/css/main.less b/rhodecode/public/css/main.less
--- a/rhodecode/public/css/main.less
+++ b/rhodecode/public/css/main.less
@@ -3212,7 +3212,12 @@ details:not([open]) > :not(summary) {
.sidebar-element {
margin-top: 20px;
-}
+
+ .icon-draft {
+ color: @color-draft
+ }
+}
+
.right-sidebar-collapsed-state {
display: flex;
@@ -3235,5 +3240,4 @@ details:not([open]) > :not(summary) {
.old-comments-marker td {
padding-top: 15px;
- border-bottom: 1px solid @grey5;
-}
+}
diff --git a/rhodecode/public/css/variables.less b/rhodecode/public/css/variables.less
--- a/rhodecode/public/css/variables.less
+++ b/rhodecode/public/css/variables.less
@@ -47,6 +47,8 @@
// Highlight color for lines and colors
@comment-highlight-color: #ffd887;
+@color-draft: darken(@alert3, 30%);
+@color-new: darken(@alert1, 5%);
// FONTS
@basefontsize: 13px;
diff --git a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js
--- a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js
+++ b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js
@@ -71,14 +71,20 @@ export class RhodecodeApp extends Polyme
if (elem) {
elem.handleNotification(data);
}
-
}
handleComment(data) {
- if (data.message.comment_id) {
+
+ if (data.message.comment_data.length !== 0) {
if (window.refreshAllComments !== undefined) {
refreshAllComments()
}
+ var json_data = data.message.comment_data;
+
+ if (window.commentsController !== undefined) {
+
+ window.commentsController.attachComment(json_data)
+ }
}
}
diff --git a/rhodecode/public/js/src/rhodecode/comments.js b/rhodecode/public/js/src/rhodecode/comments.js
--- a/rhodecode/public/js/src/rhodecode/comments.js
+++ b/rhodecode/public/js/src/rhodecode/comments.js
@@ -364,12 +364,15 @@ var _submitAjaxPOST = function(url, post
postData['close_pull_request'] = true;
}
- var submitSuccessCallback = function(o) {
+ // submitSuccess for general comments
+ var submitSuccessCallback = function(json_data) {
// reload page if we change status for single commit.
if (status && self.commitId) {
location.reload(true);
} else {
- $('#injected_page_comments').append(o.rendered_text);
+ // inject newly created comments, json_data is {: {}}
+ self.attachGeneralComment(json_data)
+
self.resetCommentFormState();
timeagoActivate();
tooltipActivate();
@@ -565,26 +568,6 @@ var CommentsController = function() {
var mainComment = '#text';
var self = this;
- this.cancelComment = function (node) {
- var $node = $(node);
- var edit = $(this).attr('edit');
- if (edit) {
- var $general_comments = null;
- var $inline_comments = $node.closest('div.inline-comments');
- if (!$inline_comments.length) {
- $general_comments = $('#comments');
- var $comment = $general_comments.parent().find('div.comment:hidden');
- // show hidden general comment form
- $('#cb-comment-general-form-placeholder').show();
- } else {
- var $comment = $inline_comments.find('div.comment:hidden');
- }
- $comment.show();
- }
- $node.closest('.comment-inline-form').remove();
- return false;
- };
-
this.showVersion = function (comment_id, comment_history_id) {
var historyViewUrl = pyroutes.url(
@@ -682,6 +665,35 @@ var CommentsController = function() {
return self.scrollToComment(node, -1, true);
};
+ this.cancelComment = function (node) {
+ var $node = $(node);
+ var edit = $(this).attr('edit');
+ var $inlineComments = $node.closest('div.inline-comments');
+
+ if (edit) {
+ var $general_comments = null;
+ if (!$inlineComments.length) {
+ $general_comments = $('#comments');
+ var $comment = $general_comments.parent().find('div.comment:hidden');
+ // show hidden general comment form
+ $('#cb-comment-general-form-placeholder').show();
+ } else {
+ var $comment = $inlineComments.find('div.comment:hidden');
+ }
+ $comment.show();
+ }
+ var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
+ $replyWrapper.removeClass('comment-form-active');
+
+ var lastComment = $inlineComments.find('.comment-inline').last();
+ if ($(lastComment).hasClass('comment-outdated')) {
+ $replyWrapper.hide();
+ }
+
+ $node.closest('.comment-inline-form').remove();
+ return false;
+ };
+
this._deleteComment = function(node) {
var $node = $(node);
var $td = $node.closest('td');
@@ -751,7 +763,7 @@ var CommentsController = function() {
this.finalizeDrafts = function(commentIds) {
SwalNoAnimation.fire({
- title: _ngettext('Submit {0} draft comment', 'Submit {0} draft comments', commentIds.length).format(commentIds.length),
+ title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
icon: 'warning',
showCancelButton: true,
confirmButtonText: _gettext('Yes, finalize drafts'),
@@ -764,6 +776,7 @@ var CommentsController = function() {
};
this.toggleWideMode = function (node) {
+
if ($('#content').hasClass('wrapper')) {
$('#content').removeClass("wrapper");
$('#content').addClass("wide-mode-wrapper");
@@ -778,16 +791,49 @@ var CommentsController = function() {
};
- this.toggleComments = function(node, show) {
+ /**
+ * Turn off/on all comments in file diff
+ */
+ this.toggleDiffComments = function(node) {
+ // Find closes filediff container
var $filediff = $(node).closest('.filediff');
+ if ($(node).hasClass('toggle-on')) {
+ var show = false;
+ } else if ($(node).hasClass('toggle-off')) {
+ var show = true;
+ }
+
+ // Toggle each individual comment block, so we can un-toggle single ones
+ $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
+ self.toggleLineComments($(val), show)
+ })
+
+ // since we change the height of the diff container that has anchor points for upper
+ // sticky header, we need to tell it to re-calculate those
+ if (window.updateSticky !== undefined) {
+ // potentially our comments change the active window size, so we
+ // notify sticky elements
+ updateSticky()
+ }
+
+ return false;
+ }
+
+ this.toggleLineComments = function(node, show) {
+
+ var trElem = $(node).closest('tr')
+
if (show === true) {
- $filediff.removeClass('hide-comments');
+ // mark outdated comments as visible before the toggle;
+ $(trElem).find('.comment-outdated').show();
+ $(trElem).removeClass('hide-line-comments');
} else if (show === false) {
- $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
- $filediff.addClass('hide-comments');
+ $(trElem).find('.comment-outdated').hide();
+ $(trElem).addClass('hide-line-comments');
} else {
- $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
- $filediff.toggleClass('hide-comments');
+ // mark outdated comments as visible before the toggle;
+ $(trElem).find('.comment-outdated').show();
+ $(trElem).toggleClass('hide-line-comments');
}
// since we change the height of the diff container that has anchor points for upper
@@ -798,15 +844,6 @@ var CommentsController = function() {
updateSticky()
}
- return false;
- };
-
- this.toggleLineComments = function(node) {
- self.toggleComments(node, true);
- var $node = $(node);
- // mark outdated comments as visible before the toggle;
- $(node.closest('tr')).find('.comment-outdated').show();
- $node.closest('tr').toggleClass('hide-line-comments');
};
this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
@@ -960,64 +997,58 @@ var CommentsController = function() {
return commentForm;
};
- this.editComment = function(node) {
+ this.editComment = function(node, line_no, f_path) {
+ self.edit = true;
var $node = $(node);
+ var $td = $node.closest('td');
+
var $comment = $(node).closest('.comment');
var comment_id = $($comment).data('commentId');
var isDraft = $($comment).data('commentDraft');
- var $form = null
+ var $editForm = null
var $comments = $node.closest('div.inline-comments');
var $general_comments = null;
- var lineno = null;
if($comments.length){
// inline comments setup
- $form = $comments.find('.comment-inline-form');
- lineno = self.getLineNumber(node)
+ $editForm = $comments.find('.comment-inline-form');
+ line_no = self.getLineNumber(node)
}
else{
// general comments setup
$comments = $('#comments');
- $form = $comments.find('.comment-inline-form');
- lineno = $comment[0].id
+ $editForm = $comments.find('.comment-inline-form');
+ line_no = $comment[0].id
$('#cb-comment-general-form-placeholder').hide();
}
- this.edit = true;
+ if ($editForm.length === 0) {
- if (!$form.length) {
-
+ // unhide all comments if they are hidden for a proper REPLY mode
var $filediff = $node.closest('.filediff');
$filediff.removeClass('hide-comments');
- var f_path = $filediff.attr('data-f-path');
-
- // create a new HTML from template
- var tmpl = $('#cb-comment-inline-form-template').html();
- tmpl = tmpl.format(escapeHtml(f_path), lineno);
- $form = $(tmpl);
- $comment.after($form)
+ $editForm = self.createNewFormWrapper(f_path, line_no);
+ if(f_path && line_no) {
+ $editForm.addClass('comment-inline-form-edit')
+ }
- var _form = $($form[0]).find('form');
+ $comment.after($editForm)
+
+ var _form = $($editForm[0]).find('form');
var autocompleteActions = ['as_note',];
var commentForm = this.createCommentForm(
- _form, lineno, '', autocompleteActions, resolvesCommentId,
+ _form, line_no, '', autocompleteActions, resolvesCommentId,
this.edit, comment_id);
var old_comment_text_binary = $comment.attr('data-comment-text');
var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
commentForm.cm.setValue(old_comment_text);
$comment.hide();
+ tooltipActivate();
- $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
- form: _form,
- parent: $comments,
- lineno: lineno,
- f_path: f_path}
- );
-
- // set a CUSTOM submit handler for inline comments.
- commentForm.setHandleFormSubmit(function(o) {
+ // set a CUSTOM submit handler for inline comment edit action.
+ commentForm.setHandleFormSubmit(function(o) {
var text = commentForm.cm.getValue();
var commentType = commentForm.getCommentType();
@@ -1048,7 +1079,7 @@ var CommentsController = function() {
var postData = {
'text': text,
'f_path': f_path,
- 'line': lineno,
+ 'line': line_no,
'comment_type': commentType,
'draft': isDraft,
'version': version,
@@ -1056,7 +1087,7 @@ var CommentsController = function() {
};
var submitSuccessCallback = function(json_data) {
- $form.remove();
+ $editForm.remove();
$comment.show();
var postData = {
'text': text,
@@ -1121,8 +1152,7 @@ var CommentsController = function() {
'commit_id': templateContext.commit_data.commit_id});
_submitAjaxPOST(
- previewUrl, postData, successRenderCommit,
- failRenderCommit
+ previewUrl, postData, successRenderCommit, failRenderCommit
);
try {
@@ -1178,49 +1208,103 @@ var CommentsController = function() {
});
}
- $form.addClass('comment-inline-form-open');
+ $editForm.addClass('comment-inline-form-open');
};
- this.createComment = function(node, resolutionComment) {
- var resolvesCommentId = resolutionComment || null;
+ this.attachComment = function(json_data) {
+ var self = this;
+ $.each(json_data, function(idx, val) {
+ var json_data_elem = [val]
+ var isInline = val.comment_f_path && val.comment_lineno
+
+ if (isInline) {
+ self.attachInlineComment(json_data_elem)
+ } else {
+ self.attachGeneralComment(json_data_elem)
+ }
+ })
+
+ }
+
+ this.attachGeneralComment = function(json_data) {
+ $.each(json_data, function(idx, val) {
+ $('#injected_page_comments').append(val.rendered_text);
+ })
+ }
+
+ this.attachInlineComment = function(json_data) {
+
+ $.each(json_data, function (idx, val) {
+ var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
+ var html = val.rendered_text;
+ var $inlineComments = $('#' + val.target_id)
+ .find(line_qry)
+ .find('.inline-comments');
+
+ var lastComment = $inlineComments.find('.comment-inline').last();
+
+ if (lastComment.length === 0) {
+ // first comment, we append simply
+ $inlineComments.find('.reply-thread-container-wrapper').before(html);
+ } else {
+ $(lastComment).after(html)
+ }
+
+ })
+
+ };
+
+ this.createNewFormWrapper = function(f_path, line_no) {
+ // create a new reply HTML form from template
+ var tmpl = $('#cb-comment-inline-form-template').html();
+ tmpl = tmpl.format(escapeHtml(f_path), line_no);
+ return $(tmpl);
+ }
+
+ this.createComment = function(node, f_path, line_no, resolutionComment) {
+ self.edit = false;
var $node = $(node);
var $td = $node.closest('td');
- var $form = $td.find('.comment-inline-form');
- this.edit = false;
+ var resolvesCommentId = resolutionComment || null;
- if (!$form.length) {
+ var $replyForm = $td.find('.comment-inline-form');
- var $filediff = $node.closest('.filediff');
- $filediff.removeClass('hide-comments');
- var f_path = $filediff.attr('data-f-path');
- var lineno = self.getLineNumber(node);
- // create a new HTML from template
- var tmpl = $('#cb-comment-inline-form-template').html();
- tmpl = tmpl.format(escapeHtml(f_path), lineno);
- $form = $(tmpl);
+ // if form isn't existing, we're generating a new one and injecting it.
+ if ($replyForm.length === 0) {
+
+ // unhide/expand all comments if they are hidden for a proper REPLY mode
+ self.toggleLineComments($node, true);
+
+ $replyForm = self.createNewFormWrapper(f_path, line_no);
var $comments = $td.find('.inline-comments');
- if (!$comments.length) {
- $comments = $(
- $('#cb-comments-inline-container-template').html());
- $td.append($comments);
+
+ // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
+ if ($comments.length===0) {
+ var replBtn = ''.format(f_path, line_no)
+ var $reply_container = $('#cb-comments-inline-container-template')
+ $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
+ $td.append($($reply_container).html());
}
- $td.find('.cb-comment-add-button').before($form);
+ // default comment button exists, so we prepend the form for leaving initial comment
+ $td.find('.cb-comment-add-button').before($replyForm);
+ // set marker, that we have a open form
+ var $replyWrapper = $td.find('.reply-thread-container-wrapper')
+ $replyWrapper.addClass('comment-form-active');
- var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
- var _form = $($form[0]).find('form');
+ var lastComment = $comments.find('.comment-inline').last();
+ if ($(lastComment).hasClass('comment-outdated')) {
+ $replyWrapper.show();
+ }
+
+ var _form = $($replyForm[0]).find('form');
var autocompleteActions = ['as_note', 'as_todo'];
var comment_id=null;
- var commentForm = this.createCommentForm(
- _form, lineno, placeholderText, autocompleteActions, resolvesCommentId, this.edit, comment_id);
-
- $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
- form: _form,
- parent: $td[0],
- lineno: lineno,
- f_path: f_path}
- );
+ var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
+ var commentForm = self.createCommentForm(
+ _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
+ self.edit, comment_id);
// set a CUSTOM submit handler for inline comments.
commentForm.setHandleFormSubmit(function(o) {
@@ -1233,12 +1317,13 @@ var CommentsController = function() {
return;
}
- if (lineno === undefined) {
- alert('missing line !');
+ if (line_no === undefined) {
+ alert('Error: unable to fetch line number for this inline comment !');
return;
}
+
if (f_path === undefined) {
- alert('missing file path !');
+ alert('Error: unable to fetch file path for this inline comment !');
return;
}
@@ -1249,7 +1334,7 @@ var CommentsController = function() {
var postData = {
'text': text,
'f_path': f_path,
- 'line': lineno,
+ 'line': line_no,
'comment_type': commentType,
'draft': isDraft,
'csrf_token': CSRF_TOKEN
@@ -1258,32 +1343,32 @@ var CommentsController = function() {
postData['resolves_comment_id'] = resolvesCommentId;
}
+ // submitSuccess for inline commits
var submitSuccessCallback = function(json_data) {
- $form.remove();
- try {
- var html = json_data.rendered_text;
- var lineno = json_data.line_no;
- var target_id = json_data.target_id;
+
+ $replyForm.remove();
+ $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
+
+ try {
+
+ // inject newly created comments, json_data is {: {}}
+ self.attachInlineComment(json_data)
- $comments.find('.cb-comment-add-button').before(html);
+ //mark visually which comment was resolved
+ if (resolvesCommentId) {
+ commentForm.markCommentResolved(resolvesCommentId);
+ }
- //mark visually which comment was resolved
- if (resolvesCommentId) {
- commentForm.markCommentResolved(resolvesCommentId);
+ // run global callback on submit
+ commentForm.globalSubmitSuccessCallback({
+ draft: isDraft,
+ comment_id: comment_id
+ });
+
+ } catch (e) {
+ console.error(e);
}
- // run global callback on submit
- commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
-
- } catch (e) {
- console.error(e);
- }
-
- // re trigger the linkification of next/prev navigation
- linkifyComments($('.inline-comment-injected'));
- timeagoActivate();
- tooltipActivate();
-
if (window.updateSticky !== undefined) {
// potentially our comments change the active window size, so we
// notify sticky elements
@@ -1297,19 +1382,27 @@ var CommentsController = function() {
commentForm.setActionButtonsDisabled(false);
+ // re trigger the linkification of next/prev navigation
+ linkifyComments($('.inline-comment-injected'));
+ timeagoActivate();
+ tooltipActivate();
};
+
var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
var prefix = "Error while submitting comment.\n"
var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
ajaxErrorSwal(message);
commentForm.resetCommentFormState(text)
};
+
commentForm.submitAjaxPOST(
commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
});
}
- $form.addClass('comment-inline-form-open');
+ // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
+ $replyForm.addClass('comment-inline-form-open');
+ tooltipActivate();
};
this.createResolutionComment = function(commentId){
@@ -1319,9 +1412,12 @@ var CommentsController = function() {
var comment = $('#comment-'+commentId);
var commentData = comment.data();
if (commentData.commentInline) {
- this.createComment(comment, commentId)
+ var f_path = commentData.fPath;
+ var line_no = commentData.lineNo;
+ //TODO check this if we need to give f_path/line_no
+ this.createComment(comment, f_path, line_no, commentId)
} else {
- Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
+ this.createGeneralComment('general', "$placeholder", commentId)
}
return false;
@@ -1347,3 +1443,8 @@ var CommentsController = function() {
};
};
+
+window.commentHelp = function(renderer) {
+ var funcData = {'renderer': renderer}
+ return renderTemplate('commentHelpHovercard', funcData)
+}
\ No newline at end of file
diff --git a/rhodecode/public/js/src/rhodecode/menus.js b/rhodecode/public/js/src/rhodecode/menus.js
--- a/rhodecode/public/js/src/rhodecode/menus.js
+++ b/rhodecode/public/js/src/rhodecode/menus.js
@@ -42,12 +42,22 @@ window.toggleElement = function (elem, t
var $elem = $(elem);
var $target = $(target);
- if ($target.is(':visible') || $target.length === 0) {
+ if (target !== undefined) {
+ var show = $target.is(':visible') || $target.length === 0;
+ } else {
+ var show = $elem.hasClass('toggle-off')
+ }
+
+ if (show) {
$target.hide();
$elem.html($elem.data('toggleOn'))
+ $elem.addClass('toggle-on')
+ $elem.removeClass('toggle-off')
} else {
$target.show();
$elem.html($elem.data('toggleOff'))
+ $elem.addClass('toggle-off')
+ $elem.removeClass('toggle-on')
}
return false
diff --git a/rhodecode/public/js/topics_list.txt b/rhodecode/public/js/topics_list.txt
--- a/rhodecode/public/js/topics_list.txt
+++ b/rhodecode/public/js/topics_list.txt
@@ -1,7 +1,5 @@
/__MAIN_APP__ - launched when rhodecode-app element is attached to DOM
/plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed
-/ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview
-/ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created
/notifications - shows new event notifications
/connection_controller/subscribe - subscribes user to new channels
/connection_controller/presence - receives presence change messages
diff --git a/rhodecode/templates/changeset/changeset_comment_block.mako b/rhodecode/templates/changeset/changeset_comment_block.mako
--- a/rhodecode/templates/changeset/changeset_comment_block.mako
+++ b/rhodecode/templates/changeset/changeset_comment_block.mako
@@ -1,4 +1,4 @@
## this is a dummy html file for partial rendering on server and sending
## generated output via ajax after comment submit
<%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
-${comment.comment_block(c.co, inline=c.co.is_inline)}
+${comment.comment_block(c.co, inline=c.co.is_inline, is_new=c.is_new)}
diff --git a/rhodecode/templates/changeset/changeset_file_comment.mako b/rhodecode/templates/changeset/changeset_file_comment.mako
--- a/rhodecode/templates/changeset/changeset_file_comment.mako
+++ b/rhodecode/templates/changeset/changeset_file_comment.mako
@@ -10,7 +10,7 @@
%>
-<%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
+<%def name="comment_block(comment, inline=False, active_pattern_entries=None, is_new=False)">
<%
from rhodecode.model.comment import CommentsModel
@@ -40,6 +40,7 @@
data-comment-draft=${h.json.dumps(comment.draft)}
data-comment-renderer="${comment.renderer}"
data-comment-text="${comment.text | html_filters.base64,n}"
+ data-comment-f-path="${comment.f_path}"
data-comment-line-no="${comment.line_no}"
data-comment-inline=${h.json.dumps(inline)}
style="${'display: none;' if outdated_at_ver else ''}">
@@ -47,8 +48,17 @@
outdated ${'v{}'.format(comment_ver)}
+ outdated${'v{}'.format(comment_ver)}
|
% elif comment_ver:${'v{}'.format(comment_ver)}
@@ -222,12 +232,13 @@ %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):- @mention - ${_('and')} - `/` autocomplete - ${_('actions supported.')} +
${_('Styling with {} is supported.').format(renderer_url)|n} + + +