# HG changeset patch # User Milka Kuzminski # Date 2020-10-21 15:53:23 # Node ID 25406ecd5a4e5e3744a12a3a2b3408077c5126ec # Parent 12a7b92ddeed044c779a035362768b3c0d874ad5 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. diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py --- a/rhodecode/__init__.py +++ b/rhodecode/__init__.py @@ -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' 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 @@ -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, 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, # 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, 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, 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, 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, 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, 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, '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, 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 diff --git a/rhodecode/lib/dbmigrate/versions/111_version_4_23_0.py b/rhodecode/lib/dbmigrate/versions/111_version_4_23_0.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/versions/111_version_4_23_0.py @@ -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() + diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -37,6 +37,7 @@ from rhodecode.lib.exceptions import Com 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 diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -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) diff --git a/rhodecode/model/validation_schema/schemas/comment_schema.py b/rhodecode/model/validation_schema/schemas/comment_schema.py --- a/rhodecode/model/validation_schema/schemas/comment_schema.py +++ b/rhodecode/model/validation_schema/schemas/comment_schema.py @@ -60,7 +60,7 @@ class CommentSchema(colander.MappingSche 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( 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 @@ -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); 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 @@ -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; diff --git a/rhodecode/public/js/src/rhodecode/codemirror.js b/rhodecode/public/js/src/rhodecode/codemirror.js --- a/rhodecode/public/js/src/rhodecode/codemirror.js +++ b/rhodecode/public/js/src/rhodecode/codemirror.js @@ -349,7 +349,12 @@ var initCommentBoxCodeMirror = function( }; 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( // 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) { 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 @@ -124,16 +124,20 @@ var _submitAjaxPOST = function(url, post 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, post 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, post }; 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, post 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, post $(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, post 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, post } // 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, post } $(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, post 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() } diff --git a/rhodecode/templates/base/base.mako b/rhodecode/templates/base/base.mako --- a/rhodecode/templates/base/base.mako +++ b/rhodecode/templates/base/base.mako @@ -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; diff --git a/rhodecode/templates/base/sidebar.mako b/rhodecode/templates/base/sidebar.mako --- a/rhodecode/templates/base/sidebar.mako +++ b/rhodecode/templates/base/sidebar.mako @@ -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 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 @@ -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:
+ % if comment.draft: +
DRAFT
+ % endif
## TODO COMMENT @@ -90,7 +100,7 @@
- + ## NOTE 0 and .. => because we disable it for now until UI ready % if 0 and comment.status_change:
@@ -100,10 +110,12 @@
% endif - + ## Since only author can see drafts, we don't show it + % if not comment.draft:
${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
+ % endif
${h.age_component(comment.modified_at, time_is_local=True)} @@ -215,6 +227,11 @@ + % if comment.draft: + + % endif %else:
+ % endif ## 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 @@
% endif - + + + % if form_type == 'inline': + + % endif ## inline for has a file, and line-number together with cancel hide button. % if form_type == 'inline': - % endif
diff --git a/rhodecode/templates/codeblocks/diffs.mako b/rhodecode/templates/codeblocks/diffs.mako --- a/rhodecode/templates/codeblocks/diffs.mako +++ b/rhodecode/templates/codeblocks/diffs.mako @@ -711,16 +711,19 @@ def get_comments_for(diff_type, comments 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: - + % else: - + % endif %endif @@ -752,18 +755,20 @@ def get_comments_for(diff_type, comments >
+ <% 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: - + % else: - + % endif %endif
@@ -814,20 +819,22 @@ def get_comments_for(diff_type, comments
- %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: - - % else: - + % if comments_no_drafts: + % if has_outdated: + + % else: + + % endif % endif - % endif