comments: introduce new draft comments.
- private to author - not triggering any notifications - sidebar doesn't display draft comments - They are just placeholders for longer review.
This commit is contained in:
parent
cf30382fbc
commit
afd3357349
15 changed files with 343 additions and 81 deletions
|
|
@ -48,7 +48,7 @@ PYRAMID_SETTINGS = {}
|
|||
EXTENSIONS = {}
|
||||
|
||||
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
|
||||
__dbversion__ = 110 # defines current db version for migrations
|
||||
__dbversion__ = 111 # defines current db version for migrations
|
||||
__platform__ = platform.system()
|
||||
__license__ = 'AGPLv3, and Commercial License'
|
||||
__author__ = 'RhodeCode GmbH'
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ from rhodecode.lib.vcs.exceptions import (
|
|||
from rhodecode.model.changeset_status import ChangesetStatusModel
|
||||
from rhodecode.model.comment import CommentsModel
|
||||
from rhodecode.model.db import (
|
||||
func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
|
||||
func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
|
||||
PullRequestReviewers)
|
||||
from rhodecode.model.forms import PullRequestForm
|
||||
from rhodecode.model.meta import Session
|
||||
|
|
@ -268,12 +268,14 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
return diffset
|
||||
|
||||
def register_comments_vars(self, c, pull_request, versions):
|
||||
def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
|
||||
comments_model = CommentsModel()
|
||||
|
||||
# GENERAL COMMENTS with versions #
|
||||
q = comments_model._all_general_comments_of_pull_request(pull_request)
|
||||
q = q.order_by(ChangesetComment.comment_id.asc())
|
||||
if not include_drafts:
|
||||
q = q.filter(ChangesetComment.draft == false())
|
||||
general_comments = q
|
||||
|
||||
# pick comments we want to render at current version
|
||||
|
|
@ -283,6 +285,8 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
# INLINE COMMENTS with versions #
|
||||
q = comments_model._all_inline_comments_of_pull_request(pull_request)
|
||||
q = q.order_by(ChangesetComment.comment_id.asc())
|
||||
if not include_drafts:
|
||||
q = q.filter(ChangesetComment.draft == false())
|
||||
inline_comments = q
|
||||
|
||||
c.inline_versions = comments_model.aggregate_comments(
|
||||
|
|
@ -1015,7 +1019,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
if at_version and at_version != PullRequest.LATEST_VER
|
||||
else None)
|
||||
|
||||
self.register_comments_vars(c, pull_request_latest, versions)
|
||||
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)
|
||||
|
|
@ -1055,9 +1059,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
else None)
|
||||
|
||||
c.unresolved_comments = CommentsModel() \
|
||||
.get_pull_request_unresolved_todos(pull_request)
|
||||
.get_pull_request_unresolved_todos(pull_request, include_drafts=False)
|
||||
c.resolved_comments = CommentsModel() \
|
||||
.get_pull_request_resolved_todos(pull_request)
|
||||
.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)
|
||||
|
|
@ -1544,6 +1548,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
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')
|
||||
|
||||
|
|
@ -1574,9 +1579,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
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)
|
||||
pull_request, self._rhodecode_user) and not is_draft
|
||||
|
||||
if status and allowed_to_change_status:
|
||||
message = (_('Status change %(transition_icon)s %(status)s')
|
||||
|
|
@ -1596,8 +1601,10 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
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
|
||||
auth_user=self._rhodecode_user,
|
||||
send_email=not is_draft, # skip notification for draft comments
|
||||
)
|
||||
is_inline = comment.is_inline
|
||||
|
||||
|
|
@ -1638,6 +1645,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'target_id': h.safeid(h.safe_unicode(
|
||||
self.request.POST.get('f_path'))),
|
||||
}
|
||||
|
||||
if comment:
|
||||
c.co = comment
|
||||
c.at_version_num = None
|
||||
|
|
@ -1648,15 +1656,17 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
data.update(comment.get_dict())
|
||||
data.update({'rendered_text': rendered_comment})
|
||||
|
||||
comment_broadcast_channel = channelstream.comment_channel(
|
||||
self.db_repo_name, pull_request_obj=pull_request)
|
||||
# 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)
|
||||
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)
|
||||
|
||||
return data
|
||||
|
||||
|
|
|
|||
53
rhodecode/lib/dbmigrate/versions/111_version_4_23_0.py
Normal file
53
rhodecode/lib/dbmigrate/versions/111_version_4_23_0.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from sqlalchemy import *
|
||||
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
|
||||
from rhodecode.lib.dbmigrate.versions import _reset_base
|
||||
from rhodecode.model import meta, init_model_encryption
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
"""
|
||||
Upgrade operations go here.
|
||||
Don't create your own engine; bind migrate_engine to your metadata
|
||||
"""
|
||||
_reset_base(migrate_engine)
|
||||
from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db
|
||||
|
||||
init_model_encryption(db)
|
||||
|
||||
context = MigrationContext.configure(migrate_engine.connect())
|
||||
op = Operations(context)
|
||||
|
||||
table = db.ChangesetComment.__table__
|
||||
with op.batch_alter_table(table.name) as batch_op:
|
||||
new_column = Column('draft', Boolean(), nullable=True)
|
||||
batch_op.add_column(new_column)
|
||||
|
||||
_set_default_as_non_draft(op, meta.Session)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
|
||||
def fixups(models, _SESSION):
|
||||
pass
|
||||
|
||||
|
||||
def _set_default_as_non_draft(op, session):
|
||||
params = {'draft': False}
|
||||
query = text(
|
||||
'UPDATE changeset_comments SET draft = :draft'
|
||||
).bindparams(**params)
|
||||
op.execute(query)
|
||||
session().commit()
|
||||
|
||||
|
|
@ -37,6 +37,7 @@ from rhodecode.lib.exceptions import CommentVersionMismatch
|
|||
from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int
|
||||
from rhodecode.model import BaseModel
|
||||
from rhodecode.model.db import (
|
||||
false,
|
||||
ChangesetComment,
|
||||
User,
|
||||
Notification,
|
||||
|
|
@ -160,7 +161,7 @@ class CommentsModel(BaseModel):
|
|||
|
||||
return todos
|
||||
|
||||
def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
|
||||
def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
|
||||
|
||||
todos = Session().query(ChangesetComment) \
|
||||
.filter(ChangesetComment.pull_request == pull_request) \
|
||||
|
|
@ -168,6 +169,9 @@ class CommentsModel(BaseModel):
|
|||
.filter(ChangesetComment.comment_type
|
||||
== ChangesetComment.COMMENT_TYPE_TODO)
|
||||
|
||||
if not include_drafts:
|
||||
todos = todos.filter(ChangesetComment.draft == false())
|
||||
|
||||
if not show_outdated:
|
||||
todos = todos.filter(
|
||||
coalesce(ChangesetComment.display_state, '') !=
|
||||
|
|
@ -177,7 +181,7 @@ class CommentsModel(BaseModel):
|
|||
|
||||
return todos
|
||||
|
||||
def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
|
||||
def get_pull_request_resolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
|
||||
|
||||
todos = Session().query(ChangesetComment) \
|
||||
.filter(ChangesetComment.pull_request == pull_request) \
|
||||
|
|
@ -185,6 +189,9 @@ class CommentsModel(BaseModel):
|
|||
.filter(ChangesetComment.comment_type
|
||||
== ChangesetComment.COMMENT_TYPE_TODO)
|
||||
|
||||
if not include_drafts:
|
||||
todos = todos.filter(ChangesetComment.draft == false())
|
||||
|
||||
if not show_outdated:
|
||||
todos = todos.filter(
|
||||
coalesce(ChangesetComment.display_state, '') !=
|
||||
|
|
@ -194,7 +201,7 @@ class CommentsModel(BaseModel):
|
|||
|
||||
return todos
|
||||
|
||||
def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
|
||||
def get_commit_unresolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
|
||||
|
||||
todos = Session().query(ChangesetComment) \
|
||||
.filter(ChangesetComment.revision == commit_id) \
|
||||
|
|
@ -202,6 +209,9 @@ class CommentsModel(BaseModel):
|
|||
.filter(ChangesetComment.comment_type
|
||||
== ChangesetComment.COMMENT_TYPE_TODO)
|
||||
|
||||
if not include_drafts:
|
||||
todos = todos.filter(ChangesetComment.draft == false())
|
||||
|
||||
if not show_outdated:
|
||||
todos = todos.filter(
|
||||
coalesce(ChangesetComment.display_state, '') !=
|
||||
|
|
@ -211,7 +221,7 @@ class CommentsModel(BaseModel):
|
|||
|
||||
return todos
|
||||
|
||||
def get_commit_resolved_todos(self, commit_id, show_outdated=True):
|
||||
def get_commit_resolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
|
||||
|
||||
todos = Session().query(ChangesetComment) \
|
||||
.filter(ChangesetComment.revision == commit_id) \
|
||||
|
|
@ -219,6 +229,9 @@ class CommentsModel(BaseModel):
|
|||
.filter(ChangesetComment.comment_type
|
||||
== ChangesetComment.COMMENT_TYPE_TODO)
|
||||
|
||||
if not include_drafts:
|
||||
todos = todos.filter(ChangesetComment.draft == false())
|
||||
|
||||
if not show_outdated:
|
||||
todos = todos.filter(
|
||||
coalesce(ChangesetComment.display_state, '') !=
|
||||
|
|
@ -228,11 +241,15 @@ class CommentsModel(BaseModel):
|
|||
|
||||
return todos
|
||||
|
||||
def get_commit_inline_comments(self, commit_id):
|
||||
def get_commit_inline_comments(self, commit_id, include_drafts=True):
|
||||
inline_comments = Session().query(ChangesetComment) \
|
||||
.filter(ChangesetComment.line_no != None) \
|
||||
.filter(ChangesetComment.f_path != None) \
|
||||
.filter(ChangesetComment.revision == commit_id)
|
||||
|
||||
if not include_drafts:
|
||||
inline_comments = inline_comments.filter(ChangesetComment.draft == false())
|
||||
|
||||
inline_comments = inline_comments.all()
|
||||
return inline_comments
|
||||
|
||||
|
|
@ -245,7 +262,7 @@ class CommentsModel(BaseModel):
|
|||
|
||||
def create(self, text, repo, user, commit_id=None, pull_request=None,
|
||||
f_path=None, line_no=None, status_change=None,
|
||||
status_change_type=None, comment_type=None,
|
||||
status_change_type=None, comment_type=None, is_draft=False,
|
||||
resolves_comment_id=None, closing_pr=False, send_email=True,
|
||||
renderer=None, auth_user=None, extra_recipients=None):
|
||||
"""
|
||||
|
|
@ -262,6 +279,7 @@ class CommentsModel(BaseModel):
|
|||
:param line_no:
|
||||
:param status_change: Label for status change
|
||||
:param comment_type: Type of comment
|
||||
:param is_draft: is comment a draft only
|
||||
:param resolves_comment_id: id of comment which this one will resolve
|
||||
:param status_change_type: type of status change
|
||||
:param closing_pr:
|
||||
|
|
@ -288,6 +306,7 @@ class CommentsModel(BaseModel):
|
|||
validated_kwargs = schema.deserialize(dict(
|
||||
comment_body=text,
|
||||
comment_type=comment_type,
|
||||
is_draft=is_draft,
|
||||
comment_file=f_path,
|
||||
comment_line=line_no,
|
||||
renderer_type=renderer,
|
||||
|
|
@ -296,6 +315,7 @@ class CommentsModel(BaseModel):
|
|||
repo=repo.repo_id,
|
||||
user=user.user_id,
|
||||
))
|
||||
is_draft = validated_kwargs['is_draft']
|
||||
|
||||
comment = ChangesetComment()
|
||||
comment.renderer = validated_kwargs['renderer_type']
|
||||
|
|
@ -303,6 +323,7 @@ class CommentsModel(BaseModel):
|
|||
comment.f_path = validated_kwargs['comment_file']
|
||||
comment.line_no = validated_kwargs['comment_line']
|
||||
comment.comment_type = validated_kwargs['comment_type']
|
||||
comment.draft = is_draft
|
||||
|
||||
comment.repo = repo
|
||||
comment.author = user
|
||||
|
|
@ -462,10 +483,11 @@ class CommentsModel(BaseModel):
|
|||
else:
|
||||
action = 'repo.commit.comment.create'
|
||||
|
||||
comment_data = comment.get_api_data()
|
||||
if not is_draft:
|
||||
comment_data = comment.get_api_data()
|
||||
|
||||
self._log_audit_action(
|
||||
action, {'data': comment_data}, auth_user, comment)
|
||||
self._log_audit_action(
|
||||
action, {'data': comment_data}, auth_user, comment)
|
||||
|
||||
return comment
|
||||
|
||||
|
|
|
|||
|
|
@ -3767,6 +3767,7 @@ class ChangesetComment(Base, BaseModel):
|
|||
renderer = Column('renderer', Unicode(64), nullable=True)
|
||||
display_state = Column('display_state', Unicode(128), nullable=True)
|
||||
immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
|
||||
draft = Column('draft', Boolean(), nullable=True, default=False)
|
||||
|
||||
comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
|
||||
resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class CommentSchema(colander.MappingSchema):
|
|||
colander.String(),
|
||||
validator=colander.OneOf(ChangesetComment.COMMENT_TYPES),
|
||||
missing=ChangesetComment.COMMENT_TYPE_NOTE)
|
||||
|
||||
is_draft = colander.SchemaNode(colander.Boolean(),missing=False)
|
||||
comment_file = colander.SchemaNode(colander.String(), missing=None)
|
||||
comment_line = colander.SchemaNode(colander.String(), missing=None)
|
||||
status_change = colander.SchemaNode(
|
||||
|
|
|
|||
|
|
@ -162,7 +162,6 @@ input[type="button"] {
|
|||
}
|
||||
}
|
||||
|
||||
.btn-warning,
|
||||
.btn-danger,
|
||||
.revoke_perm,
|
||||
.btn-x,
|
||||
|
|
@ -196,6 +195,36 @@ input[type="button"] {
|
|||
}
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
.border ( @border-thickness, @alert3 );
|
||||
background-color: white;
|
||||
color: @alert3;
|
||||
|
||||
a {
|
||||
color: @alert3;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
.border ( @border-thickness, @alert3 );
|
||||
color: white;
|
||||
background-color: @alert3;
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
display:none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: white;
|
||||
color: @alert3;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-approved-status {
|
||||
.border ( @border-thickness, @alert1 );
|
||||
background-color: white;
|
||||
|
|
@ -401,6 +430,37 @@ input[type="button"] {
|
|||
}
|
||||
|
||||
|
||||
input[type="submit"].btn-warning {
|
||||
&:extend(.btn-warning);
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:extend(.btn-warning:hover);
|
||||
}
|
||||
|
||||
&.btn-link {
|
||||
&:extend(.btn-link);
|
||||
color: @alert3;
|
||||
|
||||
&:disabled {
|
||||
color: @alert3;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
.border ( @border-thickness-buttons, @alert3 );
|
||||
background-color: white;
|
||||
color: @alert3;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: johbo: Form button tweaks, check if we can use the classes instead
|
||||
input[type="submit"] {
|
||||
&:extend(.btn-primary);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,13 @@ tr.inline-comments div {
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.comment-draft {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
font-weight: 600;
|
||||
color: @alert3;
|
||||
}
|
||||
|
||||
.comment-label {
|
||||
float: left;
|
||||
|
||||
|
|
|
|||
|
|
@ -349,7 +349,12 @@ var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions)
|
|||
};
|
||||
|
||||
var submitForm = function(cm, pred) {
|
||||
$(cm.display.input.textarea.form).submit();
|
||||
$(cm.display.input.textarea.form).find('.submit-comment-action').click();
|
||||
return CodeMirror.Pass;
|
||||
};
|
||||
|
||||
var submitFormAsDraft = function(cm, pred) {
|
||||
$(cm.display.input.textarea.form).find('.submit-draft-action').click();
|
||||
return CodeMirror.Pass;
|
||||
};
|
||||
|
||||
|
|
@ -475,9 +480,11 @@ var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions)
|
|||
// submit form on Meta-Enter
|
||||
if (OSType === "mac") {
|
||||
extraKeys["Cmd-Enter"] = submitForm;
|
||||
extraKeys["Shift-Cmd-Enter"] = submitFormAsDraft;
|
||||
}
|
||||
else {
|
||||
extraKeys["Ctrl-Enter"] = submitForm;
|
||||
extraKeys["Shift-Ctrl-Enter"] = submitFormAsDraft;
|
||||
}
|
||||
|
||||
if (triggerActions) {
|
||||
|
|
|
|||
|
|
@ -124,16 +124,20 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
this.statusChange = this.withLineNo('#change_status');
|
||||
|
||||
this.submitForm = formElement;
|
||||
this.submitButton = $(this.submitForm).find('input[type="submit"]');
|
||||
|
||||
this.submitButton = $(this.submitForm).find('.submit-comment-action');
|
||||
this.submitButtonText = this.submitButton.val();
|
||||
|
||||
this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
|
||||
this.submitDraftButtonText = this.submitDraftButton.val();
|
||||
|
||||
this.previewUrl = pyroutes.url('repo_commit_comment_preview',
|
||||
{'repo_name': templateContext.repo_name,
|
||||
'commit_id': templateContext.commit_data.commit_id});
|
||||
|
||||
if (edit){
|
||||
this.submitButtonText = _gettext('Updated Comment');
|
||||
this.submitDraftButton.hide();
|
||||
this.submitButtonText = _gettext('Update Comment');
|
||||
$(this.commentType).prop('disabled', true);
|
||||
$(this.commentType).addClass('disabled');
|
||||
var editInfo =
|
||||
|
|
@ -215,10 +219,17 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
this.getCommentStatus = function() {
|
||||
return $(this.submitForm).find(this.statusChange).val();
|
||||
};
|
||||
|
||||
this.getCommentType = function() {
|
||||
return $(this.submitForm).find(this.commentType).val();
|
||||
};
|
||||
|
||||
this.getDraftState = function () {
|
||||
var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
|
||||
var data = $(submitterElem).data('isDraft');
|
||||
return data
|
||||
}
|
||||
|
||||
this.getResolvesId = function() {
|
||||
return $(this.submitForm).find(this.resolvesId).val() || null;
|
||||
};
|
||||
|
|
@ -233,7 +244,9 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
};
|
||||
|
||||
this.isAllowedToSubmit = function() {
|
||||
return !$(this.submitButton).prop('disabled');
|
||||
var commentDisabled = $(this.submitButton).prop('disabled');
|
||||
var draftDisabled = $(this.submitDraftButton).prop('disabled');
|
||||
return !commentDisabled && !draftDisabled;
|
||||
};
|
||||
|
||||
this.initStatusChangeSelector = function(){
|
||||
|
|
@ -259,11 +272,13 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
dropdownAutoWidth: true,
|
||||
minimumResultsForSearch: -1
|
||||
});
|
||||
|
||||
$(this.submitForm).find(this.statusChange).on('change', function() {
|
||||
var status = self.getCommentStatus();
|
||||
|
||||
if (status && !self.isInline()) {
|
||||
$(self.submitButton).prop('disabled', false);
|
||||
$(self.submitDraftButton).prop('disabled', false);
|
||||
}
|
||||
|
||||
var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
|
||||
|
|
@ -295,10 +310,10 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
$(this.statusChange).select2('readonly', false);
|
||||
};
|
||||
|
||||
this.globalSubmitSuccessCallback = function(){
|
||||
this.globalSubmitSuccessCallback = function(comment){
|
||||
// default behaviour is to call GLOBAL hook, if it's registered.
|
||||
if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
|
||||
commentFormGlobalSubmitSuccessCallback();
|
||||
commentFormGlobalSubmitSuccessCallback(comment);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -321,6 +336,7 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
var text = self.cm.getValue();
|
||||
var status = self.getCommentStatus();
|
||||
var commentType = self.getCommentType();
|
||||
var isDraft = self.getDraftState();
|
||||
var resolvesCommentId = self.getResolvesId();
|
||||
var closePullRequest = self.getClosePr();
|
||||
|
||||
|
|
@ -365,7 +381,7 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
}
|
||||
|
||||
// run global callback on submit
|
||||
self.globalSubmitSuccessCallback();
|
||||
self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
|
||||
|
||||
};
|
||||
var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
|
||||
|
|
@ -409,10 +425,20 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
}
|
||||
|
||||
$(this.submitButton).prop('disabled', submitState);
|
||||
$(this.submitDraftButton).prop('disabled', submitState);
|
||||
|
||||
if (submitEvent) {
|
||||
$(this.submitButton).val(_gettext('Submitting...'));
|
||||
var isDraft = self.getDraftState();
|
||||
|
||||
if (isDraft) {
|
||||
$(this.submitDraftButton).val(_gettext('Saving Draft...'));
|
||||
} else {
|
||||
$(this.submitButton).val(_gettext('Submitting...'));
|
||||
}
|
||||
|
||||
} else {
|
||||
$(this.submitButton).val(this.submitButtonText);
|
||||
$(this.submitDraftButton).val(this.submitDraftButtonText);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -488,6 +514,7 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
|||
if (!allowedToSubmit){
|
||||
return false;
|
||||
}
|
||||
|
||||
self.handleFormSubmit();
|
||||
});
|
||||
|
||||
|
|
@ -659,7 +686,8 @@ var CommentsController = function() {
|
|||
var $node = $(node);
|
||||
var $td = $node.closest('td');
|
||||
var $comment = $node.closest('.comment');
|
||||
var comment_id = $comment.attr('data-comment-id');
|
||||
var comment_id = $($comment).data('commentId');
|
||||
var isDraft = $($comment).data('commentDraft');
|
||||
var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
|
||||
var postData = {
|
||||
'csrf_token': CSRF_TOKEN
|
||||
|
|
@ -677,7 +705,7 @@ var CommentsController = function() {
|
|||
updateSticky()
|
||||
}
|
||||
|
||||
if (window.refreshAllComments !== undefined) {
|
||||
if (window.refreshAllComments !== undefined && !isDraft) {
|
||||
// if we have this handler, run it, and refresh all comments boxes
|
||||
refreshAllComments()
|
||||
}
|
||||
|
|
@ -716,6 +744,25 @@ var CommentsController = function() {
|
|||
})
|
||||
};
|
||||
|
||||
this._finalizeDrafts = function(commentIds) {
|
||||
window.finalizeDrafts(commentIds)
|
||||
}
|
||||
|
||||
this.finalizeDrafts = function(commentIds) {
|
||||
|
||||
SwalNoAnimation.fire({
|
||||
title: _ngettext('Submit {0} draft comment', 'Submit {0} draft comments', commentIds.length).format(commentIds.length),
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: _gettext('Yes, finalize drafts'),
|
||||
|
||||
}).then(function(result) {
|
||||
if (result.value) {
|
||||
self._finalizeDrafts(commentIds);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
this.toggleWideMode = function (node) {
|
||||
if ($('#content').hasClass('wrapper')) {
|
||||
$('#content').removeClass("wrapper");
|
||||
|
|
@ -916,7 +963,8 @@ var CommentsController = function() {
|
|||
this.editComment = function(node) {
|
||||
var $node = $(node);
|
||||
var $comment = $(node).closest('.comment');
|
||||
var comment_id = $comment.attr('data-comment-id');
|
||||
var comment_id = $($comment).data('commentId');
|
||||
var isDraft = $($comment).data('commentDraft');
|
||||
var $form = null
|
||||
|
||||
var $comments = $node.closest('div.inline-comments');
|
||||
|
|
@ -1002,6 +1050,7 @@ var CommentsController = function() {
|
|||
'f_path': f_path,
|
||||
'line': lineno,
|
||||
'comment_type': commentType,
|
||||
'draft': isDraft,
|
||||
'version': version,
|
||||
'csrf_token': CSRF_TOKEN
|
||||
};
|
||||
|
|
@ -1084,7 +1133,7 @@ var CommentsController = function() {
|
|||
$comments.find('.cb-comment-add-button').before(html);
|
||||
|
||||
// run global callback on submit
|
||||
commentForm.globalSubmitSuccessCallback();
|
||||
commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
@ -1101,7 +1150,7 @@ var CommentsController = function() {
|
|||
updateSticky()
|
||||
}
|
||||
|
||||
if (window.refreshAllComments !== undefined) {
|
||||
if (window.refreshAllComments !== undefined && !isDraft) {
|
||||
// if we have this handler, run it, and refresh all comments boxes
|
||||
refreshAllComments()
|
||||
}
|
||||
|
|
@ -1178,6 +1227,7 @@ var CommentsController = function() {
|
|||
var text = commentForm.cm.getValue();
|
||||
var commentType = commentForm.getCommentType();
|
||||
var resolvesCommentId = commentForm.getResolvesId();
|
||||
var isDraft = commentForm.getDraftState();
|
||||
|
||||
if (text === "") {
|
||||
return;
|
||||
|
|
@ -1201,6 +1251,7 @@ var CommentsController = function() {
|
|||
'f_path': f_path,
|
||||
'line': lineno,
|
||||
'comment_type': commentType,
|
||||
'draft': isDraft,
|
||||
'csrf_token': CSRF_TOKEN
|
||||
};
|
||||
if (resolvesCommentId){
|
||||
|
|
@ -1222,7 +1273,7 @@ var CommentsController = function() {
|
|||
}
|
||||
|
||||
// run global callback on submit
|
||||
commentForm.globalSubmitSuccessCallback();
|
||||
commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
@ -1239,7 +1290,7 @@ var CommentsController = function() {
|
|||
updateSticky()
|
||||
}
|
||||
|
||||
if (window.refreshAllComments !== undefined) {
|
||||
if (window.refreshAllComments !== undefined && !isDraft) {
|
||||
// if we have this handler, run it, and refresh all comments boxes
|
||||
refreshAllComments()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1225,6 +1225,14 @@
|
|||
(function () {
|
||||
"use sctrict";
|
||||
|
||||
// details block auto-hide menu
|
||||
$(document).mouseup(function(e) {
|
||||
var container = $('.details-inline-block');
|
||||
if (!container.is(e.target) && container.has(e.target).length === 0) {
|
||||
$('.details-inline-block[open]').removeAttr('open')
|
||||
}
|
||||
});
|
||||
|
||||
var $sideBar = $('.right-sidebar');
|
||||
var expanded = $sideBar.hasClass('right-sidebar-expanded');
|
||||
var sidebarState = templateContext.session_attrs.sidebarState;
|
||||
|
|
|
|||
|
|
@ -31,8 +31,12 @@
|
|||
<%
|
||||
display = ''
|
||||
_cls = ''
|
||||
## Extra precaution to not show drafts in the sidebar for todo/comments
|
||||
if comment_obj.draft:
|
||||
continue
|
||||
%>
|
||||
|
||||
|
||||
<%
|
||||
comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', []))
|
||||
prev_comment_ver_index = 0
|
||||
|
|
|
|||
|
|
@ -3,20 +3,25 @@
|
|||
## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
|
||||
## ${comment.comment_block(comment)}
|
||||
##
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
|
||||
<%!
|
||||
from rhodecode.lib import html_filters
|
||||
%>
|
||||
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
|
||||
<%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
|
||||
|
||||
<%
|
||||
from rhodecode.model.comment import CommentsModel
|
||||
comment_model = CommentsModel()
|
||||
from rhodecode.model.comment import CommentsModel
|
||||
comment_model = CommentsModel()
|
||||
|
||||
comment_ver = comment.get_index_version(getattr(c, 'versions', []))
|
||||
latest_ver = len(getattr(c, 'versions', []))
|
||||
visible_for_user = True
|
||||
if comment.draft:
|
||||
visible_for_user = comment.user_id == c.rhodecode_user.user_id
|
||||
%>
|
||||
<% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
|
||||
<% latest_ver = len(getattr(c, 'versions', [])) %>
|
||||
|
||||
% if inline:
|
||||
<% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
|
||||
|
|
@ -24,6 +29,7 @@
|
|||
<% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
|
||||
% endif
|
||||
|
||||
% if visible_for_user:
|
||||
<div class="comment
|
||||
${'comment-inline' if inline else 'comment-general'}
|
||||
${'comment-outdated' if outdated_at_ver else 'comment-current'}"
|
||||
|
|
@ -31,6 +37,7 @@
|
|||
line="${comment.line_no}"
|
||||
data-comment-id="${comment.comment_id}"
|
||||
data-comment-type="${comment.comment_type}"
|
||||
data-comment-draft=${h.json.dumps(comment.draft)}
|
||||
data-comment-renderer="${comment.renderer}"
|
||||
data-comment-text="${comment.text | html_filters.base64,n}"
|
||||
data-comment-line-no="${comment.line_no}"
|
||||
|
|
@ -39,6 +46,9 @@
|
|||
|
||||
<div class="meta">
|
||||
<div class="comment-type-label">
|
||||
% if comment.draft:
|
||||
<div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">DRAFT</div>
|
||||
% endif
|
||||
<div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
|
||||
|
||||
## TODO COMMENT
|
||||
|
|
@ -90,7 +100,7 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## NOTE 0 and .. => because we disable it for now until UI ready
|
||||
% if 0 and comment.status_change:
|
||||
<div class="pull-left">
|
||||
<span class="tag authortag tooltip" title="${_('Status from pull request.')}">
|
||||
|
|
@ -100,10 +110,12 @@
|
|||
</span>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
## Since only author can see drafts, we don't show it
|
||||
% if not comment.draft:
|
||||
<div class="author ${'author-inline' if inline else 'author-general'}">
|
||||
${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<div class="date">
|
||||
${h.age_component(comment.modified_at, time_is_local=True)}
|
||||
|
|
@ -215,6 +227,11 @@
|
|||
<div class="dropdown-item">
|
||||
<a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
|
||||
</div>
|
||||
% if comment.draft:
|
||||
<div class="dropdown-item">
|
||||
<a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a>
|
||||
</div>
|
||||
% endif
|
||||
%else:
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="dropdown-item">
|
||||
|
|
@ -252,6 +269,7 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
## generate main comments
|
||||
|
|
@ -311,6 +329,7 @@
|
|||
var text = self.cm.getValue();
|
||||
var status = self.getCommentStatus();
|
||||
var commentType = self.getCommentType();
|
||||
var isDraft = self.getDraftState();
|
||||
|
||||
if (text === "" && !status) {
|
||||
return;
|
||||
|
|
@ -337,6 +356,7 @@
|
|||
'text': text,
|
||||
'changeset_status': status,
|
||||
'comment_type': commentType,
|
||||
'draft': isDraft,
|
||||
'commit_ids': commitIds,
|
||||
'csrf_token': CSRF_TOKEN
|
||||
};
|
||||
|
|
@ -477,14 +497,18 @@
|
|||
<div class="action-buttons-extra"></div>
|
||||
% endif
|
||||
|
||||
<input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
|
||||
<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')">
|
||||
|
||||
% if form_type == 'inline':
|
||||
<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')">
|
||||
% endif
|
||||
|
||||
## inline for has a file, and line-number together with cancel hide button.
|
||||
% if form_type == 'inline':
|
||||
<input type="hidden" name="f_path" value="{0}">
|
||||
<input type="hidden" name="line" value="${lineno_id}">
|
||||
<button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
|
||||
${_('Cancel')}
|
||||
<button type="button" class="tooltip cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);" title="Hide comment box">
|
||||
<i class="icon-cancel-circled2"></i>
|
||||
</button>
|
||||
% endif
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -711,16 +711,19 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
data-line-no="${line.original.lineno}"
|
||||
>
|
||||
|
||||
<% line_old_comments = None %>
|
||||
<% line_old_comments, line_old_comments_no_drafts = None, None %>
|
||||
%if line.original.get_comment_args:
|
||||
<% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %>
|
||||
<%
|
||||
line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args)
|
||||
line_old_comments_no_drafts = [c for c in line_old_comments if not c.draft] if line_old_comments else []
|
||||
has_outdated = any([x.outdated for x in line_old_comments_no_drafts])
|
||||
%>
|
||||
%endif
|
||||
%if line_old_comments:
|
||||
<% has_outdated = any([x.outdated for x in line_old_comments]) %>
|
||||
%if line_old_comments_no_drafts:
|
||||
% if has_outdated:
|
||||
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% else:
|
||||
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% endif
|
||||
%endif
|
||||
</td>
|
||||
|
|
@ -752,18 +755,20 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
>
|
||||
<div>
|
||||
|
||||
<% line_new_comments, line_new_comments_no_drafts = None, None %>
|
||||
%if line.modified.get_comment_args:
|
||||
<% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %>
|
||||
%else:
|
||||
<% line_new_comments = None%>
|
||||
<%
|
||||
line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args)
|
||||
line_new_comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
|
||||
has_outdated = any([x.outdated for x in line_new_comments_no_drafts])
|
||||
%>
|
||||
%endif
|
||||
%if line_new_comments:
|
||||
|
||||
<% has_outdated = any([x.outdated for x in line_new_comments]) %>
|
||||
%if line_new_comments_no_drafts:
|
||||
% if has_outdated:
|
||||
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_new_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% else:
|
||||
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_new_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% endif
|
||||
%endif
|
||||
</div>
|
||||
|
|
@ -814,20 +819,22 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
<td class="cb-data ${action_class(action)}">
|
||||
<div>
|
||||
|
||||
%if comments_args:
|
||||
<% comments = get_comments_for('unified', inline_comments, *comments_args) %>
|
||||
%else:
|
||||
<% comments = None %>
|
||||
%endif
|
||||
<% comments, comments_no_drafts = None, None %>
|
||||
%if comments_args:
|
||||
<%
|
||||
comments = get_comments_for('unified', inline_comments, *comments_args)
|
||||
comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
|
||||
has_outdated = any([x.outdated for x in comments_no_drafts])
|
||||
%>
|
||||
%endif
|
||||
|
||||
% if comments:
|
||||
<% has_outdated = any([x.outdated for x in comments]) %>
|
||||
% if has_outdated:
|
||||
<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>
|
||||
% else:
|
||||
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% if comments_no_drafts:
|
||||
% if has_outdated:
|
||||
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% else:
|
||||
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% endif
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="cb-lineno ${action_class(action)}"
|
||||
|
|
|
|||
|
|
@ -980,9 +980,11 @@ window.setObserversData = ${c.pull_request_set_observers_data_json | n};
|
|||
$(btns).each(fn_display);
|
||||
});
|
||||
|
||||
// register submit callback on commentForm form to track TODOs
|
||||
window.commentFormGlobalSubmitSuccessCallback = function () {
|
||||
refreshMergeChecks();
|
||||
// register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
|
||||
window.commentFormGlobalSubmitSuccessCallback = function (comment) {
|
||||
if (!comment.draft) {
|
||||
refreshMergeChecks();
|
||||
}
|
||||
};
|
||||
|
||||
ReviewerAutoComplete('#user', reviewersController);
|
||||
|
|
@ -995,6 +997,12 @@ $(document).ready(function () {
|
|||
var channel = '${c.pr_broadcast_channel}';
|
||||
new ReviewerPresenceController(channel)
|
||||
|
||||
|
||||
window.finalizeDrafts = function(commentIds) {
|
||||
alert('okok !' + commentIds)
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue