diff --git a/rhodecode/apps/repository/tests/test_repo_commit_comments.py b/rhodecode/apps/repository/tests/test_repo_commit_comments.py --- a/rhodecode/apps/repository/tests/test_repo_commit_comments.py +++ b/rhodecode/apps/repository/tests/test_repo_commit_comments.py @@ -485,23 +485,10 @@ class TestRepoCommitCommentsView(TestCon def assert_comment_links(response, comments, inline_comments): - if comments == 1: - comments_text = "%d General" % comments - else: - comments_text = "%d General" % comments - - if inline_comments == 1: - inline_comments_text = "%d Inline" % inline_comments - else: - inline_comments_text = "%d Inline" % inline_comments + response.mustcontain( + '{}'.format(comments)) + response.mustcontain( + '{}'.format(inline_comments)) - if comments: - response.mustcontain('%s,' % comments_text) - else: - response.mustcontain(comments_text) - if inline_comments: - response.mustcontain( - 'id="inline-comments-counter">%s' % inline_comments_text) - else: - response.mustcontain(inline_comments_text) + diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py --- a/rhodecode/apps/repository/views/repo_commits.py +++ b/rhodecode/apps/repository/views/repo_commits.py @@ -18,8 +18,8 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ - import logging +import collections from pyramid.httpexceptions import ( HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict) @@ -34,14 +34,14 @@ from rhodecode.apps.file_store.exception from rhodecode.lib import diffs, codeblocks from rhodecode.lib.auth import ( LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) - +from rhodecode.lib.ext_json import json from rhodecode.lib.compat import OrderedDict from rhodecode.lib.diffs import ( cache_diff, load_cached_diff, diff_cache_exist, get_diff_context, get_diff_whitespace_flag) from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch import rhodecode.lib.helpers as h -from rhodecode.lib.utils2 import safe_unicode, str2bool +from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict from rhodecode.lib.vcs.backends.base import EmptyCommit from rhodecode.lib.vcs.exceptions import ( RepositoryError, CommitDoesNotExistError) @@ -87,7 +87,6 @@ class RepoCommitsView(RepoAppView): diff_limit = c.visual.cut_off_limit_diff file_limit = c.visual.cut_off_limit_file - # get ranges of commit ids if preset commit_range = commit_id_range.split('...')[:2] @@ -116,6 +115,7 @@ class RepoCommitsView(RepoAppView): except Exception: log.exception("General failure") raise HTTPNotFound() + single_commit = len(c.commit_ranges) == 1 c.changes = OrderedDict() c.lines_added = 0 @@ -129,23 +129,48 @@ class RepoCommitsView(RepoAppView): c.inline_comments = [] c.files = [] - c.statuses = [] c.comments = [] c.unresolved_comments = [] c.resolved_comments = [] - if len(c.commit_ranges) == 1: + + # Single commit + if single_commit: commit = c.commit_ranges[0] c.comments = CommentsModel().get_comments( self.db_repo.repo_id, revision=commit.raw_id) - c.statuses.append(ChangesetStatusModel().get_status( - self.db_repo.repo_id, commit.raw_id)) + # comments from PR statuses = ChangesetStatusModel().get_statuses( self.db_repo.repo_id, commit.raw_id, with_revisions=True) - prs = set(st.pull_request for st in statuses - if st.pull_request is not None) + + prs = set() + reviewers = list() + reviewers_duplicates = set() # to not have duplicates from multiple votes + for c_status in statuses: + + # extract associated pull-requests from votes + if c_status.pull_request: + prs.add(c_status.pull_request) + + # extract reviewers + _user_id = c_status.author.user_id + if _user_id not in reviewers_duplicates: + reviewers.append( + StrictAttributeDict({ + 'user': c_status.author, + + # fake attributed for commit, page that we don't have + # but we share the display with PR page + 'mandatory': False, + 'reasons': [], + 'rule_user_group_data': lambda: None + }) + ) + reviewers_duplicates.add(_user_id) + + c.allowed_reviewers = reviewers # from associated statuses, check the pull requests, and # show comments from them for pr in prs: @@ -156,6 +181,37 @@ class RepoCommitsView(RepoAppView): c.resolved_comments = CommentsModel()\ .get_commit_resolved_todos(commit.raw_id) + c.inline_comments_flat = CommentsModel()\ + .get_commit_inline_comments(commit.raw_id) + + review_statuses = ChangesetStatusModel().aggregate_votes_by_user( + statuses, reviewers) + + c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED + + c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []}) + + for review_obj, member, reasons, mandatory, status in review_statuses: + member_reviewer = h.reviewer_as_json( + member, reasons=reasons, mandatory=mandatory, + user_group=None + ) + + current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED + member_reviewer['review_status'] = current_review_status + member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status) + member_reviewer['allowed_to_update'] = False + c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer) + + c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json) + + # NOTE(marcink): this uses the same voting logic as in pull-requests + c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses) + c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format( + c.repo_name, + commit.raw_id + ) + diff = None # Iterate over ranges (default commit view is always one commit) for commit in c.commit_ranges: @@ -397,6 +453,7 @@ class RepoCommitsView(RepoAppView): } if comment: c.co = comment + c.at_version_num = 0 rendered_comment = render( 'rhodecode:templates/changeset/changeset_comment_block.mako', self._get_template_context(c), self.request) 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 @@ -39,7 +39,7 @@ from rhodecode.lib.ext_json import json from rhodecode.lib.auth import ( LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) -from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode +from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason from rhodecode.lib.vcs.exceptions import ( CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) @@ -474,9 +474,6 @@ class RepoPullRequestsView(RepoAppView, c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json) - - - general_comments, inline_comments = \ self.register_comments_vars(c, pull_request_latest, versions) @@ -980,7 +977,7 @@ class RepoPullRequestsView(RepoAppView, version = self.request.GET.get('version') _render = self.request.get_partial_renderer( - 'rhodecode:templates/pullrequests/pullrequest_show.mako') + 'rhodecode:templates/base/sidebar.mako') c = _render.get_call_context() (pull_request_latest, @@ -999,7 +996,11 @@ class RepoPullRequestsView(RepoAppView, self.register_comments_vars(c, pull_request_latest, versions) all_comments = c.inline_comments_flat + c.comments - return _render('comments_table', all_comments, len(all_comments)) + + existing_ids = filter( + lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) + return _render('comments_table', all_comments, len(all_comments), + existing_ids=existing_ids) @LoginRequired() @NotAnonymous() @@ -1017,7 +1018,7 @@ class RepoPullRequestsView(RepoAppView, version = self.request.GET.get('version') _render = self.request.get_partial_renderer( - 'rhodecode:templates/pullrequests/pullrequest_show.mako') + 'rhodecode:templates/base/sidebar.mako') c = _render.get_call_context() (pull_request_latest, pull_request_at_ver, @@ -1039,7 +1040,10 @@ class RepoPullRequestsView(RepoAppView, .get_pull_request_resolved_todos(pull_request) all_comments = c.unresolved_comments + c.resolved_comments - return _render('comments_table', all_comments, len(c.unresolved_comments), todo_comments=True) + existing_ids = filter( + lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) + return _render('comments_table', all_comments, len(c.unresolved_comments), + todo_comments=True, existing_ids=existing_ids) @LoginRequired() @NotAnonymous() diff --git a/rhodecode/model/changeset_status.py b/rhodecode/model/changeset_status.py --- a/rhodecode/model/changeset_status.py +++ b/rhodecode/model/changeset_status.py @@ -354,34 +354,37 @@ class ChangesetStatusModel(BaseModel): Session().add(new_status) return new_statuses + def aggregate_votes_by_user(self, commit_statuses, reviewers_data): + + commit_statuses_map = collections.defaultdict(list) + for st in commit_statuses: + commit_statuses_map[st.author.username] += [st] + + reviewers = [] + + def version(commit_status): + return commit_status.version + + for obj in reviewers_data: + if not obj.user: + continue + statuses = commit_statuses_map.get(obj.user.username, None) + if statuses: + status_groups = itertools.groupby( + sorted(statuses, key=version), version) + statuses = [(x, list(y)[0]) for x, y in status_groups] + + reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses)) + + return reviewers + def reviewers_statuses(self, pull_request): _commit_statuses = self.get_statuses( pull_request.source_repo, pull_request=pull_request, with_revisions=True) - commit_statuses = collections.defaultdict(list) - for st in _commit_statuses: - commit_statuses[st.author.username] += [st] - - pull_request_reviewers = [] - - def version(commit_status): - return commit_status.version - - for obj in pull_request.reviewers: - if not obj.user: - continue - statuses = commit_statuses.get(obj.user.username, None) - if statuses: - status_groups = itertools.groupby( - sorted(statuses, key=version), version) - statuses = [(x, list(y)[0]) for x, y in status_groups] - - pull_request_reviewers.append( - (obj, obj.user, obj.reasons, obj.mandatory, statuses)) - - return pull_request_reviewers + return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers) def calculated_review_status(self, pull_request, reviewers_statuses=None): """ diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -228,6 +228,14 @@ class CommentsModel(BaseModel): return todos + def get_commit_inline_comments(self, commit_id): + inline_comments = Session().query(ChangesetComment) \ + .filter(ChangesetComment.line_no != None) \ + .filter(ChangesetComment.f_path != None) \ + .filter(ChangesetComment.revision == commit_id) + inline_comments = inline_comments.all() + return inline_comments + def _log_audit_action(self, action, action_data, auth_user, comment): audit_logger.store( action=action, diff --git a/rhodecode/public/css/alerts.less b/rhodecode/public/css/alerts.less --- a/rhodecode/public/css/alerts.less +++ b/rhodecode/public/css/alerts.less @@ -55,3 +55,16 @@ margin: 0 auto 35px auto; } } + +.alert-text-success { + color: @alert1; + +} + +.alert-text-error { + color: @alert2; +} + +.alert-text-warning { + color: @alert3; +} 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 @@ -254,7 +254,7 @@ input[type="button"] { .btn-group-actions { position: relative; - z-index: 100; + z-index: 50; &:not(.open) .btn-action-switcher-container { display: none; diff --git a/rhodecode/public/css/code-block.less b/rhodecode/public/css/code-block.less --- a/rhodecode/public/css/code-block.less +++ b/rhodecode/public/css/code-block.less @@ -1078,10 +1078,16 @@ input.filediff-collapse-state { background: @color5; color: white; } + &[op="comments"] { /* comments on file */ background: @grey4; color: white; } + + &[op="options"] { /* context menu */ + background: @grey6; + color: black; + } } } diff --git a/rhodecode/public/css/helpers.less b/rhodecode/public/css/helpers.less --- a/rhodecode/public/css/helpers.less +++ b/rhodecode/public/css/helpers.less @@ -31,6 +31,10 @@ a { cursor: pointer; } clear: both; } +.display-none { + display: none; +} + .pull-right { float: right !important; } diff --git a/rhodecode/public/css/main.less b/rhodecode/public/css/main.less --- a/rhodecode/public/css/main.less +++ b/rhodecode/public/css/main.less @@ -83,6 +83,11 @@ body { } } +.flex-container { + display: flex; + justify-content: space-between; +} + .action-link{ margin-left: @padding; padding-left: @padding; @@ -482,6 +487,15 @@ ul.auth_plugins { text-align: left; overflow: hidden; white-space: pre-line; + padding-top: 5px +} + +#add_reviewer { + padding-top: 10px; +} + +#add_reviewer_input { + padding-top: 10px } .pr-details-title-author-pref { @@ -1169,9 +1183,12 @@ label { a { color: @grey5 } - @media screen and (max-width: 1200px) { + + // 1024px or smaller + @media screen and (max-width: 1180px) { display: none; } + } img { @@ -1553,6 +1570,7 @@ table.integrations { width: 16px; padding: 0; color: black; + cursor: pointer; } .reviewer_member_mandatory_remove { @@ -1682,7 +1700,7 @@ table.group_members { } .reviewer_ac .ac-input { - width: 92%; + width: 100%; margin-bottom: 1em; } @@ -2756,7 +2774,7 @@ table.rctable td.td-search-results div { } #help_kb .modal-content{ - max-width: 750px; + max-width: 800px; margin: 10% auto; table{ @@ -3053,4 +3071,141 @@ form.markup-form { .pr-hovercard-title { padding-top: 5px; -} \ No newline at end of file +} + +.action-divider { + opacity: 0.5; +} + +.details-inline-block { + display: inline-block; + position: relative; +} + +.details-inline-block summary { + list-style: none; +} + +details:not([open]) > :not(summary) { + display: none !important; +} + +.details-reset > summary { + list-style: none; +} + +.details-reset > summary::-webkit-details-marker { + display: none; +} + +.details-dropdown { + position: absolute; + top: 100%; + width: 185px; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid @grey5; + box-shadow: 0 8px 24px rgba(149, 157, 165, .2); + left: -150px; + text-align: left; + z-index: 90; +} + +.dropdown-divider { + display: block; + height: 0; + margin: 8px 0; + border-top: 1px solid @grey5; +} + +.dropdown-item { + display: block; + padding: 4px 8px 4px 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: normal; +} + +.right-sidebar { + position: fixed; + top: 0px; + bottom: 0; + right: 0; + + background: #fafafa; + z-index: 50; +} + +.right-sidebar { + border-left: 1px solid @grey5; +} + +.right-sidebar.right-sidebar-expanded { + width: 300px; + overflow: scroll; +} + +.right-sidebar.right-sidebar-collapsed { + width: 40px; + padding: 0; + display: block; + overflow: hidden; +} + +.sidenav { + float: right; + will-change: min-height; + background: #fafafa; + width: 100%; +} + +.sidebar-toggle { + height: 30px; + text-align: center; + margin: 15px 0px 0 0; +} + +.sidebar-toggle a { + +} + +.sidebar-content { + margin-left: 15px; + margin-right: 15px; +} + +.sidebar-heading { + font-size: 1.2em; + font-weight: 700; + margin-top: 10px; +} + +.sidebar-element { + margin-top: 20px; +} + +.right-sidebar-collapsed-state { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 0 10px; + cursor: pointer; + font-size: 1.3em; + margin: 0 -15px; +} + +.right-sidebar-collapsed-state:hover { + background-color: @grey5; +} + +.old-comments-marker { + text-align: left; +} + +.old-comments-marker td { + padding-top: 15px; + border-bottom: 1px solid @grey5; +} diff --git a/rhodecode/public/css/navigation.less b/rhodecode/public/css/navigation.less --- a/rhodecode/public/css/navigation.less +++ b/rhodecode/public/css/navigation.less @@ -790,7 +790,7 @@ input { &.main_filter_input { padding: 5px 10px; - min-width: 340px; + color: @grey7; background: @black; min-height: 18px; @@ -800,11 +800,34 @@ input { color: @grey2 !important; background: white !important; } + &:focus { color: @grey2 !important; background: white !important; } + + min-width: 360px; + + @media screen and (max-width: 1600px) { + min-width: 300px; + } + @media screen and (max-width: 1500px) { + min-width: 280px; + } + @media screen and (max-width: 1400px) { + min-width: 260px; + } + @media screen and (max-width: 1300px) { + min-width: 240px; + } + @media screen and (max-width: 1200px) { + min-width: 220px; + } + @media screen and (max-width: 720px) { + min-width: 140px; + } } + } diff --git a/rhodecode/public/css/rcicons.less b/rhodecode/public/css/rcicons.less --- a/rhodecode/public/css/rcicons.less +++ b/rhodecode/public/css/rcicons.less @@ -168,6 +168,7 @@ .icon-remove:before { content: '\e810'; } /* '' */ .icon-fork:before { content: '\e811'; } /* '' */ .icon-more:before { content: '\e812'; } /* '' */ +.icon-options:before { content: '\e812'; } /* '' */ .icon-search:before { content: '\e813'; } /* '' */ .icon-scissors:before { content: '\e814'; } /* '' */ .icon-download:before { content: '\e815'; } /* '' */ @@ -251,6 +252,7 @@ // TRANSFORM .icon-merge:before {transform: rotate(180deg);} .icon-wide-mode:before {transform: rotate(90deg);} +.icon-options:before {transform: rotate(90deg);} // -- END ICON CLASSES -- // diff --git a/rhodecode/public/js/rhodecode/base/keyboard-bindings.js b/rhodecode/public/js/rhodecode/base/keyboard-bindings.js --- a/rhodecode/public/js/rhodecode/base/keyboard-bindings.js +++ b/rhodecode/public/js/rhodecode/base/keyboard-bindings.js @@ -131,6 +131,11 @@ function setRCMouseBindings(repoName, re window.location = pyroutes.url( 'edit_repo_perms', {'repo_name': repoName}); }); + Mousetrap.bind(['t s'], function(e) { + if (window.toggleSidebar !== undefined) { + window.toggleSidebar(); + } + }); } } diff --git a/rhodecode/public/js/src/rhodecode/menus.js b/rhodecode/public/js/src/rhodecode/menus.js --- a/rhodecode/public/js/src/rhodecode/menus.js +++ b/rhodecode/public/js/src/rhodecode/menus.js @@ -35,4 +35,75 @@ var quick_repo_menu = function() { }, function() { hide_quick_repo_menus(); }); -}; \ No newline at end of file +}; + + +window.toggleElement = function (elem, target) { + var $elem = $(elem); + var $target = $(target); + + if ($target.is(':visible') || $target.length === 0) { + $target.hide(); + $elem.html($elem.data('toggleOn')) + } else { + $target.show(); + $elem.html($elem.data('toggleOff')) + } + + return false +} + +var marginExpVal = '300' // needs a sync with `.right-sidebar.right-sidebar-expanded` value +var marginColVal = '40' // needs a sync with `.right-sidebar.right-sidebar-collapsed` value + +var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)}; +var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)}; + +var updateStickyHeader = function () { + if (window.updateSticky !== undefined) { + // potentially our comments change the active window size, so we + // notify sticky elements + updateSticky() + } +} + +var expandSidebar = function () { + var $sideBar = $('.right-sidebar'); + $('.outerwrapper').css(marginExpanded); + $('.sidebar-toggle a').html(''); + $('.right-sidebar-collapsed-state').hide(); + $('.right-sidebar-expanded-state').show(); + $('.branding').addClass('display-none'); + $sideBar.addClass('right-sidebar-expanded') + $sideBar.removeClass('right-sidebar-collapsed') +} + +var collapseSidebar = function () { + var $sideBar = $('.right-sidebar'); + $('.outerwrapper').css(marginCollapsed); + $('.sidebar-toggle a').html(''); + $('.right-sidebar-collapsed-state').show(); + $('.right-sidebar-expanded-state').hide(); + $('.branding').removeClass('display-none'); + $sideBar.removeClass('right-sidebar-expanded') + $sideBar.addClass('right-sidebar-collapsed') +} + +window.toggleSidebar = function () { + var $sideBar = $('.right-sidebar'); + + if ($sideBar.hasClass('right-sidebar-expanded')) { + // expanded -> collapsed transition + collapseSidebar(); + var sidebarState = 'collapsed'; + + } else { + // collapsed -> expanded + expandSidebar(); + var sidebarState = 'expanded'; + } + + // update our other sticky header in same context + updateStickyHeader(); + storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState); +} diff --git a/rhodecode/public/js/src/rhodecode/pullrequests.js b/rhodecode/public/js/src/rhodecode/pullrequests.js --- a/rhodecode/public/js/src/rhodecode/pullrequests.js +++ b/rhodecode/public/js/src/rhodecode/pullrequests.js @@ -279,8 +279,11 @@ ReviewersController = function () { $('#user').show(); // show user autocomplete after load var commitElements = data["diff_info"]['commits']; + if (commitElements.length === 0) { - prButtonLock(true, _gettext('no commits'), 'all'); + var noCommitsMsg = '{0}'.format( + _gettext('There are no commits to merge.')); + prButtonLock(true, noCommitsMsg, 'all'); } else { // un-lock PR button, so we cannot send PR before it's calculated @@ -324,7 +327,6 @@ ReviewersController = function () { }; this.addReviewMember = function (reviewer_obj, reasons, mandatory) { - var members = self.$reviewMembers.get(0); var id = reviewer_obj.user_id; var username = reviewer_obj.username; @@ -333,10 +335,10 @@ ReviewersController = function () { // register IDS to check if we don't have this ID already in var currentIds = []; - var _els = self.$reviewMembers.find('li').toArray(); - for (el in _els) { - currentIds.push(_els[el].id) - } + + $.each(self.$reviewMembers.find('.reviewer_entry'), function (index, value) { + currentIds.push($(value).data('reviewerUserId')) + }) var userAllowedReview = function (userId) { var allowed = true; @@ -354,12 +356,12 @@ ReviewersController = function () { alert(_gettext('User `{0}` not allowed to be a reviewer').format(username)); } else { // only add if it's not there - var alreadyReviewer = currentIds.indexOf('reviewer_' + id) != -1; + var alreadyReviewer = currentIds.indexOf(id) != -1; if (alreadyReviewer) { alert(_gettext('User `{0}` already in reviewers').format(username)); } else { - members.innerHTML += renderTemplate('reviewMemberEntry', { + var reviewerEntry = renderTemplate('reviewMemberEntry', { 'member': reviewer_obj, 'mandatory': mandatory, 'reasons': reasons, @@ -368,7 +370,9 @@ ReviewersController = function () { 'review_status_label': _gettext('Not Reviewed'), 'user_group': reviewer_obj.user_group, 'create': true, - }); + 'rule_show': true, + }) + $(self.$reviewMembers.selector).append(reviewerEntry); tooltipActivate(); } } @@ -492,7 +496,7 @@ var ReviewerAutoComplete = function(inpu }; -VersionController = function () { +window.VersionController = function () { var self = this; this.$verSource = $('input[name=ver_source]'); this.$verTarget = $('input[name=ver_target]'); @@ -612,25 +616,10 @@ VersionController = function () { return false }; - this.toggleElement = function (elem, target) { - var $elem = $(elem); - var $target = $(target); - - if ($target.is(':visible') || $target.length === 0) { - $target.hide(); - $elem.html($elem.data('toggleOn')) - } else { - $target.show(); - $elem.html($elem.data('toggleOff')) - } - - return false - } - }; -UpdatePrController = function () { +window.UpdatePrController = function () { var self = this; this.$updateCommits = $('#update_commits'); this.$updateCommitsSwitcher = $('#update_commits_switcher'); @@ -672,4 +661,230 @@ UpdatePrController = function () { templateContext.repo_name, templateContext.pull_request_data.pull_request_id, force); }; -}; \ No newline at end of file +}; + +/** + * Reviewer display panel + */ +window.ReviewersPanel = { + editButton: null, + closeButton: null, + addButton: null, + removeButtons: null, + reviewRules: null, + setReviewers: null, + + setSelectors: function () { + var self = this; + self.editButton = $('#open_edit_reviewers'); + self.closeButton =$('#close_edit_reviewers'); + self.addButton = $('#add_reviewer'); + self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove'); + }, + + init: function (reviewRules, setReviewers) { + var self = this; + self.setSelectors(); + + this.reviewRules = reviewRules; + this.setReviewers = setReviewers; + + this.editButton.on('click', function (e) { + self.edit(); + }); + this.closeButton.on('click', function (e) { + self.close(); + self.renderReviewers(); + }); + + self.renderReviewers(); + + }, + + renderReviewers: function () { + + $('#review_members').html('') + $.each(this.setReviewers.reviewers, function (key, val) { + var member = val; + + var entry = renderTemplate('reviewMemberEntry', { + 'member': member, + 'mandatory': member.mandatory, + 'reasons': member.reasons, + 'allowed_to_update': member.allowed_to_update, + 'review_status': member.review_status, + 'review_status_label': member.review_status_label, + 'user_group': member.user_group, + 'create': false + }); + + $('#review_members').append(entry) + }); + tooltipActivate(); + + }, + + edit: function (event) { + this.editButton.hide(); + this.closeButton.show(); + this.addButton.show(); + $(this.removeButtons.selector).css('visibility', 'visible'); + // review rules + reviewersController.loadReviewRules(this.reviewRules); + }, + + close: function (event) { + this.editButton.show(); + this.closeButton.hide(); + this.addButton.hide(); + $(this.removeButtons.selector).css('visibility', 'hidden'); + // hide review rules + reviewersController.hideReviewRules() + } +}; + + +/** + * OnLine presence using channelstream + */ +window.ReviewerPresenceController = function (channel) { + var self = this; + this.channel = channel; + this.users = {}; + + this.storeUsers = function (users) { + self.users = {} + $.each(users, function (index, value) { + var userId = value.state.id; + self.users[userId] = value.state; + }) + } + + this.render = function () { + $.each($('.reviewer_entry'), function (index, value) { + var userData = $(value).data(); + if (self.users[userData.reviewerUserId] !== undefined) { + $(value).find('.presence-state').show(); + } else { + $(value).find('.presence-state').hide(); + } + }) + }; + + this.handlePresence = function (data) { + if (data.type == 'presence' && data.channel === self.channel) { + this.storeUsers(data.users); + this.render() + } + }; + + this.handleChannelUpdate = function (data) { + if (data.channel === this.channel) { + this.storeUsers(data.state.users); + this.render() + } + + }; + + /* subscribe to the current presence */ + $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this)); + /* subscribe to updates e.g connect/disconnect */ + $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this)); + +}; + +window.refreshComments = function (version) { + version = version || templateContext.pull_request_data.pull_request_version || ''; + + // Pull request case + if (templateContext.pull_request_data.pull_request_id !== null) { + var params = { + 'pull_request_id': templateContext.pull_request_data.pull_request_id, + 'repo_name': templateContext.repo_name, + 'version': version, + }; + var loadUrl = pyroutes.url('pullrequest_comments', params); + } // commit case + else { + return + } + + var currentIDs = [] + $.each($('.comment'), function (idx, element) { + currentIDs.push($(element).data('commentId')); + }); + var data = {"comments[]": currentIDs}; + + var $targetElem = $('.comments-content-table'); + $targetElem.css('opacity', 0.3); + $targetElem.load( + loadUrl, data, function (responseText, textStatus, jqXHR) { + if (jqXHR.status !== 200) { + return false; + } + var $counterElem = $('#comments-count'); + var newCount = $(responseText).data('counter'); + if (newCount !== undefined) { + var callback = function () { + $counterElem.animate({'opacity': 1.00}, 200) + $counterElem.html(newCount); + }; + $counterElem.animate({'opacity': 0.15}, 200, callback); + } + + $targetElem.css('opacity', 1); + tooltipActivate(); + } + ); +} + +window.refreshTODOs = function (version) { + version = version || templateContext.pull_request_data.pull_request_version || ''; + // Pull request case + if (templateContext.pull_request_data.pull_request_id !== null) { + var params = { + 'pull_request_id': templateContext.pull_request_data.pull_request_id, + 'repo_name': templateContext.repo_name, + 'version': version, + }; + var loadUrl = pyroutes.url('pullrequest_comments', params); + } // commit case + else { + return + } + + var currentIDs = [] + $.each($('.comment'), function (idx, element) { + currentIDs.push($(element).data('commentId')); + }); + + var data = {"comments[]": currentIDs}; + var $targetElem = $('.todos-content-table'); + $targetElem.css('opacity', 0.3); + $targetElem.load( + loadUrl, data, function (responseText, textStatus, jqXHR) { + if (jqXHR.status !== 200) { + return false; + } + var $counterElem = $('#todos-count') + var newCount = $(responseText).data('counter'); + if (newCount !== undefined) { + var callback = function () { + $counterElem.animate({'opacity': 1.00}, 200) + $counterElem.html(newCount); + }; + $counterElem.animate({'opacity': 0.15}, 200, callback); + } + + $targetElem.css('opacity', 1); + tooltipActivate(); + } + ); +} + +window.refreshAllComments = function (version) { + version = version || templateContext.pull_request_data.pull_request_version || ''; + + refreshComments(version); + refreshTODOs(version); +}; 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 @@ -701,9 +701,6 @@ notice_messages, notice_level = c.rhodecode_user.get_notice_messages() notice_display = 'none' if len(notice_messages) == 0 else '' %> -
${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}
+