##// END OF EJS Templates
comments: introduce new draft comments....
milka -
r4540:25406ecd default
parent child Browse files
Show More
@@ -0,0 +1,53 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4 from sqlalchemy import *
5
6 from alembic.migration import MigrationContext
7 from alembic.operations import Operations
8
9 from rhodecode.lib.dbmigrate.versions import _reset_base
10 from rhodecode.model import meta, init_model_encryption
11
12
13 log = logging.getLogger(__name__)
14
15
16 def upgrade(migrate_engine):
17 """
18 Upgrade operations go here.
19 Don't create your own engine; bind migrate_engine to your metadata
20 """
21 _reset_base(migrate_engine)
22 from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db
23
24 init_model_encryption(db)
25
26 context = MigrationContext.configure(migrate_engine.connect())
27 op = Operations(context)
28
29 table = db.ChangesetComment.__table__
30 with op.batch_alter_table(table.name) as batch_op:
31 new_column = Column('draft', Boolean(), nullable=True)
32 batch_op.add_column(new_column)
33
34 _set_default_as_non_draft(op, meta.Session)
35
36
37 def downgrade(migrate_engine):
38 meta = MetaData()
39 meta.bind = migrate_engine
40
41
42 def fixups(models, _SESSION):
43 pass
44
45
46 def _set_default_as_non_draft(op, session):
47 params = {'draft': False}
48 query = text(
49 'UPDATE changeset_comments SET draft = :draft'
50 ).bindparams(**params)
51 op.execute(query)
52 session().commit()
53
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}'
48 48 EXTENSIONS = {}
49 49
50 50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
51 __dbversion__ = 110 # defines current db version for migrations
51 __dbversion__ = 111 # defines current db version for migrations
52 52 __platform__ = platform.system()
53 53 __license__ = 'AGPLv3, and Commercial License'
54 54 __author__ = 'RhodeCode GmbH'
@@ -47,7 +47,7 b' from rhodecode.lib.vcs.exceptions import'
47 47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 48 from rhodecode.model.comment import CommentsModel
49 49 from rhodecode.model.db import (
50 func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
51 51 PullRequestReviewers)
52 52 from rhodecode.model.forms import PullRequestForm
53 53 from rhodecode.model.meta import Session
@@ -268,12 +268,14 b' class RepoPullRequestsView(RepoAppView, '
268 268
269 269 return diffset
270 270
271 def register_comments_vars(self, c, pull_request, versions):
271 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
272 272 comments_model = CommentsModel()
273 273
274 274 # GENERAL COMMENTS with versions #
275 275 q = comments_model._all_general_comments_of_pull_request(pull_request)
276 276 q = q.order_by(ChangesetComment.comment_id.asc())
277 if not include_drafts:
278 q = q.filter(ChangesetComment.draft == false())
277 279 general_comments = q
278 280
279 281 # pick comments we want to render at current version
@@ -283,6 +285,8 b' class RepoPullRequestsView(RepoAppView, '
283 285 # INLINE COMMENTS with versions #
284 286 q = comments_model._all_inline_comments_of_pull_request(pull_request)
285 287 q = q.order_by(ChangesetComment.comment_id.asc())
288 if not include_drafts:
289 q = q.filter(ChangesetComment.draft == false())
286 290 inline_comments = q
287 291
288 292 c.inline_versions = comments_model.aggregate_comments(
@@ -1015,7 +1019,7 b' class RepoPullRequestsView(RepoAppView, '
1015 1019 if at_version and at_version != PullRequest.LATEST_VER
1016 1020 else None)
1017 1021
1018 self.register_comments_vars(c, pull_request_latest, versions)
1022 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1019 1023 all_comments = c.inline_comments_flat + c.comments
1020 1024
1021 1025 existing_ids = self._get_existing_ids(self.request.POST)
@@ -1055,9 +1059,9 b' class RepoPullRequestsView(RepoAppView, '
1055 1059 else None)
1056 1060
1057 1061 c.unresolved_comments = CommentsModel() \
1058 .get_pull_request_unresolved_todos(pull_request)
1062 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1059 1063 c.resolved_comments = CommentsModel() \
1060 .get_pull_request_resolved_todos(pull_request)
1064 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1061 1065
1062 1066 all_comments = c.unresolved_comments + c.resolved_comments
1063 1067 existing_ids = self._get_existing_ids(self.request.POST)
@@ -1544,6 +1548,7 b' class RepoPullRequestsView(RepoAppView, '
1544 1548 status = self.request.POST.get('changeset_status', None)
1545 1549 text = self.request.POST.get('text')
1546 1550 comment_type = self.request.POST.get('comment_type')
1551 is_draft = str2bool(self.request.POST.get('draft'))
1547 1552 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1548 1553 close_pull_request = self.request.POST.get('close_pull_request')
1549 1554
@@ -1574,9 +1579,9 b' class RepoPullRequestsView(RepoAppView, '
1574 1579 else:
1575 1580 # regular comment case, could be inline, or one with status.
1576 1581 # for that one we check also permissions
1577
1582 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1578 1583 allowed_to_change_status = PullRequestModel().check_user_change_status(
1579 pull_request, self._rhodecode_user)
1584 pull_request, self._rhodecode_user) and not is_draft
1580 1585
1581 1586 if status and allowed_to_change_status:
1582 1587 message = (_('Status change %(transition_icon)s %(status)s')
@@ -1596,8 +1601,10 b' class RepoPullRequestsView(RepoAppView, '
1596 1601 status_change_type=(status
1597 1602 if status and allowed_to_change_status else None),
1598 1603 comment_type=comment_type,
1604 is_draft=is_draft,
1599 1605 resolves_comment_id=resolves_comment_id,
1600 auth_user=self._rhodecode_user
1606 auth_user=self._rhodecode_user,
1607 send_email=not is_draft, # skip notification for draft comments
1601 1608 )
1602 1609 is_inline = comment.is_inline
1603 1610
@@ -1638,6 +1645,7 b' class RepoPullRequestsView(RepoAppView, '
1638 1645 'target_id': h.safeid(h.safe_unicode(
1639 1646 self.request.POST.get('f_path'))),
1640 1647 }
1648
1641 1649 if comment:
1642 1650 c.co = comment
1643 1651 c.at_version_num = None
@@ -1648,15 +1656,17 b' class RepoPullRequestsView(RepoAppView, '
1648 1656 data.update(comment.get_dict())
1649 1657 data.update({'rendered_text': rendered_comment})
1650 1658
1651 comment_broadcast_channel = channelstream.comment_channel(
1652 self.db_repo_name, pull_request_obj=pull_request)
1659 # skip channelstream for draft comments
1660 if not is_draft:
1661 comment_broadcast_channel = channelstream.comment_channel(
1662 self.db_repo_name, pull_request_obj=pull_request)
1653 1663
1654 comment_data = data
1655 comment_type = 'inline' if is_inline else 'general'
1656 channelstream.comment_channelstream_push(
1657 self.request, comment_broadcast_channel, self._rhodecode_user,
1658 _('posted a new {} comment').format(comment_type),
1659 comment_data=comment_data)
1664 comment_data = data
1665 comment_type = 'inline' if is_inline else 'general'
1666 channelstream.comment_channelstream_push(
1667 self.request, comment_broadcast_channel, self._rhodecode_user,
1668 _('posted a new {} comment').format(comment_type),
1669 comment_data=comment_data)
1660 1670
1661 1671 return data
1662 1672
@@ -37,6 +37,7 b' from rhodecode.lib.exceptions import Com'
37 37 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import (
40 false,
40 41 ChangesetComment,
41 42 User,
42 43 Notification,
@@ -160,7 +161,7 b' class CommentsModel(BaseModel):'
160 161
161 162 return todos
162 163
163 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
164 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
164 165
165 166 todos = Session().query(ChangesetComment) \
166 167 .filter(ChangesetComment.pull_request == pull_request) \
@@ -168,6 +169,9 b' class CommentsModel(BaseModel):'
168 169 .filter(ChangesetComment.comment_type
169 170 == ChangesetComment.COMMENT_TYPE_TODO)
170 171
172 if not include_drafts:
173 todos = todos.filter(ChangesetComment.draft == false())
174
171 175 if not show_outdated:
172 176 todos = todos.filter(
173 177 coalesce(ChangesetComment.display_state, '') !=
@@ -177,7 +181,7 b' class CommentsModel(BaseModel):'
177 181
178 182 return todos
179 183
180 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
184 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
181 185
182 186 todos = Session().query(ChangesetComment) \
183 187 .filter(ChangesetComment.pull_request == pull_request) \
@@ -185,6 +189,9 b' class CommentsModel(BaseModel):'
185 189 .filter(ChangesetComment.comment_type
186 190 == ChangesetComment.COMMENT_TYPE_TODO)
187 191
192 if not include_drafts:
193 todos = todos.filter(ChangesetComment.draft == false())
194
188 195 if not show_outdated:
189 196 todos = todos.filter(
190 197 coalesce(ChangesetComment.display_state, '') !=
@@ -194,7 +201,7 b' class CommentsModel(BaseModel):'
194 201
195 202 return todos
196 203
197 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
204 def get_commit_unresolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
198 205
199 206 todos = Session().query(ChangesetComment) \
200 207 .filter(ChangesetComment.revision == commit_id) \
@@ -202,6 +209,9 b' class CommentsModel(BaseModel):'
202 209 .filter(ChangesetComment.comment_type
203 210 == ChangesetComment.COMMENT_TYPE_TODO)
204 211
212 if not include_drafts:
213 todos = todos.filter(ChangesetComment.draft == false())
214
205 215 if not show_outdated:
206 216 todos = todos.filter(
207 217 coalesce(ChangesetComment.display_state, '') !=
@@ -211,7 +221,7 b' class CommentsModel(BaseModel):'
211 221
212 222 return todos
213 223
214 def get_commit_resolved_todos(self, commit_id, show_outdated=True):
224 def get_commit_resolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
215 225
216 226 todos = Session().query(ChangesetComment) \
217 227 .filter(ChangesetComment.revision == commit_id) \
@@ -219,6 +229,9 b' class CommentsModel(BaseModel):'
219 229 .filter(ChangesetComment.comment_type
220 230 == ChangesetComment.COMMENT_TYPE_TODO)
221 231
232 if not include_drafts:
233 todos = todos.filter(ChangesetComment.draft == false())
234
222 235 if not show_outdated:
223 236 todos = todos.filter(
224 237 coalesce(ChangesetComment.display_state, '') !=
@@ -228,11 +241,15 b' class CommentsModel(BaseModel):'
228 241
229 242 return todos
230 243
231 def get_commit_inline_comments(self, commit_id):
244 def get_commit_inline_comments(self, commit_id, include_drafts=True):
232 245 inline_comments = Session().query(ChangesetComment) \
233 246 .filter(ChangesetComment.line_no != None) \
234 247 .filter(ChangesetComment.f_path != None) \
235 248 .filter(ChangesetComment.revision == commit_id)
249
250 if not include_drafts:
251 inline_comments = inline_comments.filter(ChangesetComment.draft == false())
252
236 253 inline_comments = inline_comments.all()
237 254 return inline_comments
238 255
@@ -245,7 +262,7 b' class CommentsModel(BaseModel):'
245 262
246 263 def create(self, text, repo, user, commit_id=None, pull_request=None,
247 264 f_path=None, line_no=None, status_change=None,
248 status_change_type=None, comment_type=None,
265 status_change_type=None, comment_type=None, is_draft=False,
249 266 resolves_comment_id=None, closing_pr=False, send_email=True,
250 267 renderer=None, auth_user=None, extra_recipients=None):
251 268 """
@@ -262,6 +279,7 b' class CommentsModel(BaseModel):'
262 279 :param line_no:
263 280 :param status_change: Label for status change
264 281 :param comment_type: Type of comment
282 :param is_draft: is comment a draft only
265 283 :param resolves_comment_id: id of comment which this one will resolve
266 284 :param status_change_type: type of status change
267 285 :param closing_pr:
@@ -288,6 +306,7 b' class CommentsModel(BaseModel):'
288 306 validated_kwargs = schema.deserialize(dict(
289 307 comment_body=text,
290 308 comment_type=comment_type,
309 is_draft=is_draft,
291 310 comment_file=f_path,
292 311 comment_line=line_no,
293 312 renderer_type=renderer,
@@ -296,6 +315,7 b' class CommentsModel(BaseModel):'
296 315 repo=repo.repo_id,
297 316 user=user.user_id,
298 317 ))
318 is_draft = validated_kwargs['is_draft']
299 319
300 320 comment = ChangesetComment()
301 321 comment.renderer = validated_kwargs['renderer_type']
@@ -303,6 +323,7 b' class CommentsModel(BaseModel):'
303 323 comment.f_path = validated_kwargs['comment_file']
304 324 comment.line_no = validated_kwargs['comment_line']
305 325 comment.comment_type = validated_kwargs['comment_type']
326 comment.draft = is_draft
306 327
307 328 comment.repo = repo
308 329 comment.author = user
@@ -462,10 +483,11 b' class CommentsModel(BaseModel):'
462 483 else:
463 484 action = 'repo.commit.comment.create'
464 485
465 comment_data = comment.get_api_data()
486 if not is_draft:
487 comment_data = comment.get_api_data()
466 488
467 self._log_audit_action(
468 action, {'data': comment_data}, auth_user, comment)
489 self._log_audit_action(
490 action, {'data': comment_data}, auth_user, comment)
469 491
470 492 return comment
471 493
@@ -3767,6 +3767,7 b' class ChangesetComment(Base, BaseModel):'
3767 3767 renderer = Column('renderer', Unicode(64), nullable=True)
3768 3768 display_state = Column('display_state', Unicode(128), nullable=True)
3769 3769 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3770 draft = Column('draft', Boolean(), nullable=True, default=False)
3770 3771
3771 3772 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3772 3773 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
@@ -60,7 +60,7 b' class CommentSchema(colander.MappingSche'
60 60 colander.String(),
61 61 validator=colander.OneOf(ChangesetComment.COMMENT_TYPES),
62 62 missing=ChangesetComment.COMMENT_TYPE_NOTE)
63
63 is_draft = colander.SchemaNode(colander.Boolean(),missing=False)
64 64 comment_file = colander.SchemaNode(colander.String(), missing=None)
65 65 comment_line = colander.SchemaNode(colander.String(), missing=None)
66 66 status_change = colander.SchemaNode(
@@ -162,7 +162,6 b' input[type="button"] {'
162 162 }
163 163 }
164 164
165 .btn-warning,
166 165 .btn-danger,
167 166 .revoke_perm,
168 167 .btn-x,
@@ -196,6 +195,36 b' input[type="button"] {'
196 195 }
197 196 }
198 197
198 .btn-warning {
199 .border ( @border-thickness, @alert3 );
200 background-color: white;
201 color: @alert3;
202
203 a {
204 color: @alert3;
205 }
206
207 &:hover,
208 &.active {
209 .border ( @border-thickness, @alert3 );
210 color: white;
211 background-color: @alert3;
212
213 a {
214 color: white;
215 }
216 }
217
218 i {
219 display:none;
220 }
221
222 &:disabled {
223 background-color: white;
224 color: @alert3;
225 }
226 }
227
199 228 .btn-approved-status {
200 229 .border ( @border-thickness, @alert1 );
201 230 background-color: white;
@@ -401,6 +430,37 b' input[type="button"] {'
401 430 }
402 431
403 432
433 input[type="submit"].btn-warning {
434 &:extend(.btn-warning);
435
436 &:focus {
437 outline: 0;
438 }
439
440 &:hover {
441 &:extend(.btn-warning:hover);
442 }
443
444 &.btn-link {
445 &:extend(.btn-link);
446 color: @alert3;
447
448 &:disabled {
449 color: @alert3;
450 background-color: transparent;
451 }
452 }
453
454 &:disabled {
455 .border ( @border-thickness-buttons, @alert3 );
456 background-color: white;
457 color: @alert3;
458 opacity: 0.5;
459 }
460 }
461
462
463
404 464 // TODO: johbo: Form button tweaks, check if we can use the classes instead
405 465 input[type="submit"] {
406 466 &:extend(.btn-primary);
@@ -61,6 +61,13 b' tr.inline-comments div {'
61 61 visibility: hidden;
62 62 }
63 63
64 .comment-draft {
65 float: left;
66 margin-right: 10px;
67 font-weight: 600;
68 color: @alert3;
69 }
70
64 71 .comment-label {
65 72 float: left;
66 73
@@ -349,7 +349,12 b' var initCommentBoxCodeMirror = function('
349 349 };
350 350
351 351 var submitForm = function(cm, pred) {
352 $(cm.display.input.textarea.form).submit();
352 $(cm.display.input.textarea.form).find('.submit-comment-action').click();
353 return CodeMirror.Pass;
354 };
355
356 var submitFormAsDraft = function(cm, pred) {
357 $(cm.display.input.textarea.form).find('.submit-draft-action').click();
353 358 return CodeMirror.Pass;
354 359 };
355 360
@@ -475,9 +480,11 b' var initCommentBoxCodeMirror = function('
475 480 // submit form on Meta-Enter
476 481 if (OSType === "mac") {
477 482 extraKeys["Cmd-Enter"] = submitForm;
483 extraKeys["Shift-Cmd-Enter"] = submitFormAsDraft;
478 484 }
479 485 else {
480 486 extraKeys["Ctrl-Enter"] = submitForm;
487 extraKeys["Shift-Ctrl-Enter"] = submitFormAsDraft;
481 488 }
482 489
483 490 if (triggerActions) {
@@ -124,16 +124,20 b' var _submitAjaxPOST = function(url, post'
124 124 this.statusChange = this.withLineNo('#change_status');
125 125
126 126 this.submitForm = formElement;
127 this.submitButton = $(this.submitForm).find('input[type="submit"]');
127
128 this.submitButton = $(this.submitForm).find('.submit-comment-action');
128 129 this.submitButtonText = this.submitButton.val();
129 130
131 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
132 this.submitDraftButtonText = this.submitDraftButton.val();
130 133
131 134 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
132 135 {'repo_name': templateContext.repo_name,
133 136 'commit_id': templateContext.commit_data.commit_id});
134 137
135 138 if (edit){
136 this.submitButtonText = _gettext('Updated Comment');
139 this.submitDraftButton.hide();
140 this.submitButtonText = _gettext('Update Comment');
137 141 $(this.commentType).prop('disabled', true);
138 142 $(this.commentType).addClass('disabled');
139 143 var editInfo =
@@ -215,10 +219,17 b' var _submitAjaxPOST = function(url, post'
215 219 this.getCommentStatus = function() {
216 220 return $(this.submitForm).find(this.statusChange).val();
217 221 };
222
218 223 this.getCommentType = function() {
219 224 return $(this.submitForm).find(this.commentType).val();
220 225 };
221 226
227 this.getDraftState = function () {
228 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
229 var data = $(submitterElem).data('isDraft');
230 return data
231 }
232
222 233 this.getResolvesId = function() {
223 234 return $(this.submitForm).find(this.resolvesId).val() || null;
224 235 };
@@ -233,7 +244,9 b' var _submitAjaxPOST = function(url, post'
233 244 };
234 245
235 246 this.isAllowedToSubmit = function() {
236 return !$(this.submitButton).prop('disabled');
247 var commentDisabled = $(this.submitButton).prop('disabled');
248 var draftDisabled = $(this.submitDraftButton).prop('disabled');
249 return !commentDisabled && !draftDisabled;
237 250 };
238 251
239 252 this.initStatusChangeSelector = function(){
@@ -259,11 +272,13 b' var _submitAjaxPOST = function(url, post'
259 272 dropdownAutoWidth: true,
260 273 minimumResultsForSearch: -1
261 274 });
275
262 276 $(this.submitForm).find(this.statusChange).on('change', function() {
263 277 var status = self.getCommentStatus();
264 278
265 279 if (status && !self.isInline()) {
266 280 $(self.submitButton).prop('disabled', false);
281 $(self.submitDraftButton).prop('disabled', false);
267 282 }
268 283
269 284 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
@@ -295,10 +310,10 b' var _submitAjaxPOST = function(url, post'
295 310 $(this.statusChange).select2('readonly', false);
296 311 };
297 312
298 this.globalSubmitSuccessCallback = function(){
313 this.globalSubmitSuccessCallback = function(comment){
299 314 // default behaviour is to call GLOBAL hook, if it's registered.
300 315 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
301 commentFormGlobalSubmitSuccessCallback();
316 commentFormGlobalSubmitSuccessCallback(comment);
302 317 }
303 318 };
304 319
@@ -321,6 +336,7 b' var _submitAjaxPOST = function(url, post'
321 336 var text = self.cm.getValue();
322 337 var status = self.getCommentStatus();
323 338 var commentType = self.getCommentType();
339 var isDraft = self.getDraftState();
324 340 var resolvesCommentId = self.getResolvesId();
325 341 var closePullRequest = self.getClosePr();
326 342
@@ -365,7 +381,7 b' var _submitAjaxPOST = function(url, post'
365 381 }
366 382
367 383 // run global callback on submit
368 self.globalSubmitSuccessCallback();
384 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
369 385
370 386 };
371 387 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
@@ -409,10 +425,20 b' var _submitAjaxPOST = function(url, post'
409 425 }
410 426
411 427 $(this.submitButton).prop('disabled', submitState);
428 $(this.submitDraftButton).prop('disabled', submitState);
429
412 430 if (submitEvent) {
413 $(this.submitButton).val(_gettext('Submitting...'));
431 var isDraft = self.getDraftState();
432
433 if (isDraft) {
434 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
435 } else {
436 $(this.submitButton).val(_gettext('Submitting...'));
437 }
438
414 439 } else {
415 440 $(this.submitButton).val(this.submitButtonText);
441 $(this.submitDraftButton).val(this.submitDraftButtonText);
416 442 }
417 443
418 444 };
@@ -488,6 +514,7 b' var _submitAjaxPOST = function(url, post'
488 514 if (!allowedToSubmit){
489 515 return false;
490 516 }
517
491 518 self.handleFormSubmit();
492 519 });
493 520
@@ -659,7 +686,8 b' var CommentsController = function() {'
659 686 var $node = $(node);
660 687 var $td = $node.closest('td');
661 688 var $comment = $node.closest('.comment');
662 var comment_id = $comment.attr('data-comment-id');
689 var comment_id = $($comment).data('commentId');
690 var isDraft = $($comment).data('commentDraft');
663 691 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
664 692 var postData = {
665 693 'csrf_token': CSRF_TOKEN
@@ -677,7 +705,7 b' var CommentsController = function() {'
677 705 updateSticky()
678 706 }
679 707
680 if (window.refreshAllComments !== undefined) {
708 if (window.refreshAllComments !== undefined && !isDraft) {
681 709 // if we have this handler, run it, and refresh all comments boxes
682 710 refreshAllComments()
683 711 }
@@ -716,6 +744,25 b' var CommentsController = function() {'
716 744 })
717 745 };
718 746
747 this._finalizeDrafts = function(commentIds) {
748 window.finalizeDrafts(commentIds)
749 }
750
751 this.finalizeDrafts = function(commentIds) {
752
753 SwalNoAnimation.fire({
754 title: _ngettext('Submit {0} draft comment', 'Submit {0} draft comments', commentIds.length).format(commentIds.length),
755 icon: 'warning',
756 showCancelButton: true,
757 confirmButtonText: _gettext('Yes, finalize drafts'),
758
759 }).then(function(result) {
760 if (result.value) {
761 self._finalizeDrafts(commentIds);
762 }
763 })
764 };
765
719 766 this.toggleWideMode = function (node) {
720 767 if ($('#content').hasClass('wrapper')) {
721 768 $('#content').removeClass("wrapper");
@@ -916,7 +963,8 b' var CommentsController = function() {'
916 963 this.editComment = function(node) {
917 964 var $node = $(node);
918 965 var $comment = $(node).closest('.comment');
919 var comment_id = $comment.attr('data-comment-id');
966 var comment_id = $($comment).data('commentId');
967 var isDraft = $($comment).data('commentDraft');
920 968 var $form = null
921 969
922 970 var $comments = $node.closest('div.inline-comments');
@@ -1002,6 +1050,7 b' var CommentsController = function() {'
1002 1050 'f_path': f_path,
1003 1051 'line': lineno,
1004 1052 'comment_type': commentType,
1053 'draft': isDraft,
1005 1054 'version': version,
1006 1055 'csrf_token': CSRF_TOKEN
1007 1056 };
@@ -1084,7 +1133,7 b' var CommentsController = function() {'
1084 1133 $comments.find('.cb-comment-add-button').before(html);
1085 1134
1086 1135 // run global callback on submit
1087 commentForm.globalSubmitSuccessCallback();
1136 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1088 1137
1089 1138 } catch (e) {
1090 1139 console.error(e);
@@ -1101,7 +1150,7 b' var CommentsController = function() {'
1101 1150 updateSticky()
1102 1151 }
1103 1152
1104 if (window.refreshAllComments !== undefined) {
1153 if (window.refreshAllComments !== undefined && !isDraft) {
1105 1154 // if we have this handler, run it, and refresh all comments boxes
1106 1155 refreshAllComments()
1107 1156 }
@@ -1178,6 +1227,7 b' var CommentsController = function() {'
1178 1227 var text = commentForm.cm.getValue();
1179 1228 var commentType = commentForm.getCommentType();
1180 1229 var resolvesCommentId = commentForm.getResolvesId();
1230 var isDraft = commentForm.getDraftState();
1181 1231
1182 1232 if (text === "") {
1183 1233 return;
@@ -1201,6 +1251,7 b' var CommentsController = function() {'
1201 1251 'f_path': f_path,
1202 1252 'line': lineno,
1203 1253 'comment_type': commentType,
1254 'draft': isDraft,
1204 1255 'csrf_token': CSRF_TOKEN
1205 1256 };
1206 1257 if (resolvesCommentId){
@@ -1222,7 +1273,7 b' var CommentsController = function() {'
1222 1273 }
1223 1274
1224 1275 // run global callback on submit
1225 commentForm.globalSubmitSuccessCallback();
1276 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1226 1277
1227 1278 } catch (e) {
1228 1279 console.error(e);
@@ -1239,7 +1290,7 b' var CommentsController = function() {'
1239 1290 updateSticky()
1240 1291 }
1241 1292
1242 if (window.refreshAllComments !== undefined) {
1293 if (window.refreshAllComments !== undefined && !isDraft) {
1243 1294 // if we have this handler, run it, and refresh all comments boxes
1244 1295 refreshAllComments()
1245 1296 }
@@ -1225,6 +1225,14 b''
1225 1225 (function () {
1226 1226 "use sctrict";
1227 1227
1228 // details block auto-hide menu
1229 $(document).mouseup(function(e) {
1230 var container = $('.details-inline-block');
1231 if (!container.is(e.target) && container.has(e.target).length === 0) {
1232 $('.details-inline-block[open]').removeAttr('open')
1233 }
1234 });
1235
1228 1236 var $sideBar = $('.right-sidebar');
1229 1237 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1230 1238 var sidebarState = templateContext.session_attrs.sidebarState;
@@ -31,8 +31,12 b''
31 31 <%
32 32 display = ''
33 33 _cls = ''
34 ## Extra precaution to not show drafts in the sidebar for todo/comments
35 if comment_obj.draft:
36 continue
34 37 %>
35 38
39
36 40 <%
37 41 comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', []))
38 42 prev_comment_ver_index = 0
@@ -3,20 +3,25 b''
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 4 ## ${comment.comment_block(comment)}
5 5 ##
6 <%namespace name="base" file="/base/base.mako"/>
6 7
7 8 <%!
8 9 from rhodecode.lib import html_filters
9 10 %>
10 11
11 <%namespace name="base" file="/base/base.mako"/>
12
12 13 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
13 14
14 15 <%
15 from rhodecode.model.comment import CommentsModel
16 comment_model = CommentsModel()
16 from rhodecode.model.comment import CommentsModel
17 comment_model = CommentsModel()
18
19 comment_ver = comment.get_index_version(getattr(c, 'versions', []))
20 latest_ver = len(getattr(c, 'versions', []))
21 visible_for_user = True
22 if comment.draft:
23 visible_for_user = comment.user_id == c.rhodecode_user.user_id
17 24 %>
18 <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
19 <% latest_ver = len(getattr(c, 'versions', [])) %>
20 25
21 26 % if inline:
22 27 <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
@@ -24,6 +29,7 b''
24 29 <% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
25 30 % endif
26 31
32 % if visible_for_user:
27 33 <div class="comment
28 34 ${'comment-inline' if inline else 'comment-general'}
29 35 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
@@ -31,6 +37,7 b''
31 37 line="${comment.line_no}"
32 38 data-comment-id="${comment.comment_id}"
33 39 data-comment-type="${comment.comment_type}"
40 data-comment-draft=${h.json.dumps(comment.draft)}
34 41 data-comment-renderer="${comment.renderer}"
35 42 data-comment-text="${comment.text | html_filters.base64,n}"
36 43 data-comment-line-no="${comment.line_no}"
@@ -39,6 +46,9 b''
39 46
40 47 <div class="meta">
41 48 <div class="comment-type-label">
49 % if comment.draft:
50 <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">DRAFT</div>
51 % endif
42 52 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
43 53
44 54 ## TODO COMMENT
@@ -90,7 +100,7 b''
90 100
91 101 </div>
92 102 </div>
93
103 ## NOTE 0 and .. => because we disable it for now until UI ready
94 104 % if 0 and comment.status_change:
95 105 <div class="pull-left">
96 106 <span class="tag authortag tooltip" title="${_('Status from pull request.')}">
@@ -100,10 +110,12 b''
100 110 </span>
101 111 </div>
102 112 % endif
103
113 ## Since only author can see drafts, we don't show it
114 % if not comment.draft:
104 115 <div class="author ${'author-inline' if inline else 'author-general'}">
105 116 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
106 117 </div>
118 % endif
107 119
108 120 <div class="date">
109 121 ${h.age_component(comment.modified_at, time_is_local=True)}
@@ -215,6 +227,11 b''
215 227 <div class="dropdown-item">
216 228 <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
217 229 </div>
230 % if comment.draft:
231 <div class="dropdown-item">
232 <a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a>
233 </div>
234 % endif
218 235 %else:
219 236 <div class="dropdown-divider"></div>
220 237 <div class="dropdown-item">
@@ -252,6 +269,7 b''
252 269 </div>
253 270
254 271 </div>
272 % endif
255 273 </%def>
256 274
257 275 ## generate main comments
@@ -311,6 +329,7 b''
311 329 var text = self.cm.getValue();
312 330 var status = self.getCommentStatus();
313 331 var commentType = self.getCommentType();
332 var isDraft = self.getDraftState();
314 333
315 334 if (text === "" && !status) {
316 335 return;
@@ -337,6 +356,7 b''
337 356 'text': text,
338 357 'changeset_status': status,
339 358 'comment_type': commentType,
359 'draft': isDraft,
340 360 'commit_ids': commitIds,
341 361 'csrf_token': CSRF_TOKEN
342 362 };
@@ -477,14 +497,18 b''
477 497 <div class="action-buttons-extra"></div>
478 498 % endif
479 499
480 <input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
500 <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')">
501
502 % if form_type == 'inline':
503 <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')">
504 % endif
481 505
482 506 ## inline for has a file, and line-number together with cancel hide button.
483 507 % if form_type == 'inline':
484 508 <input type="hidden" name="f_path" value="{0}">
485 509 <input type="hidden" name="line" value="${lineno_id}">
486 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
487 ${_('Cancel')}
510 <button type="button" class="tooltip cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);" title="Hide comment box">
511 <i class="icon-cancel-circled2"></i>
488 512 </button>
489 513 % endif
490 514 </div>
@@ -711,16 +711,19 b' def get_comments_for(diff_type, comments'
711 711 data-line-no="${line.original.lineno}"
712 712 >
713 713
714 <% line_old_comments = None %>
714 <% line_old_comments, line_old_comments_no_drafts = None, None %>
715 715 %if line.original.get_comment_args:
716 <% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %>
716 <%
717 line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args)
718 line_old_comments_no_drafts = [c for c in line_old_comments if not c.draft] if line_old_comments else []
719 has_outdated = any([x.outdated for x in line_old_comments_no_drafts])
720 %>
717 721 %endif
718 %if line_old_comments:
719 <% has_outdated = any([x.outdated for x in line_old_comments]) %>
722 %if line_old_comments_no_drafts:
720 723 % if has_outdated:
721 <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>
724 <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>
722 725 % else:
723 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
726 <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>
724 727 % endif
725 728 %endif
726 729 </td>
@@ -752,18 +755,20 b' def get_comments_for(diff_type, comments'
752 755 >
753 756 <div>
754 757
758 <% line_new_comments, line_new_comments_no_drafts = None, None %>
755 759 %if line.modified.get_comment_args:
756 <% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %>
757 %else:
758 <% line_new_comments = None%>
760 <%
761 line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args)
762 line_new_comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
763 has_outdated = any([x.outdated for x in line_new_comments_no_drafts])
764 %>
759 765 %endif
760 %if line_new_comments:
761 766
762 <% has_outdated = any([x.outdated for x in line_new_comments]) %>
767 %if line_new_comments_no_drafts:
763 768 % if has_outdated:
764 <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>
769 <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>
765 770 % else:
766 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_new_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
771 <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>
767 772 % endif
768 773 %endif
769 774 </div>
@@ -814,20 +819,22 b' def get_comments_for(diff_type, comments'
814 819 <td class="cb-data ${action_class(action)}">
815 820 <div>
816 821
817 %if comments_args:
818 <% comments = get_comments_for('unified', inline_comments, *comments_args) %>
819 %else:
820 <% comments = None %>
821 %endif
822 <% comments, comments_no_drafts = None, None %>
823 %if comments_args:
824 <%
825 comments = get_comments_for('unified', inline_comments, *comments_args)
826 comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
827 has_outdated = any([x.outdated for x in comments_no_drafts])
828 %>
829 %endif
822 830
823 % if comments:
824 <% has_outdated = any([x.outdated for x in comments]) %>
825 % if has_outdated:
826 <i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
827 % else:
828 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
831 % if comments_no_drafts:
832 % if has_outdated:
833 <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>
834 % else:
835 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
836 % endif
829 837 % endif
830 % endif
831 838 </div>
832 839 </td>
833 840 <td class="cb-lineno ${action_class(action)}"
@@ -980,9 +980,11 b' window.setObserversData = ${c.pull_reque'
980 980 $(btns).each(fn_display);
981 981 });
982 982
983 // register submit callback on commentForm form to track TODOs
984 window.commentFormGlobalSubmitSuccessCallback = function () {
985 refreshMergeChecks();
983 // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
984 window.commentFormGlobalSubmitSuccessCallback = function (comment) {
985 if (!comment.draft) {
986 refreshMergeChecks();
987 }
986 988 };
987 989
988 990 ReviewerAutoComplete('#user', reviewersController);
@@ -995,6 +997,12 b' window.setObserversData = ${c.pull_reque'
995 997 var channel = '${c.pr_broadcast_channel}';
996 998 new ReviewerPresenceController(channel)
997 999
1000
1001 window.finalizeDrafts = function(commentIds) {
1002 alert('okok !' + commentIds)
1003
1004 }
1005
998 1006 })
999 1007 </script>
1000 1008
General Comments 0
You need to be logged in to leave comments. Login now