WIP mozreview: add inline-comments js, html, css (
bug 1115707); r!davidwalsh,smacleod
Add core support for showing comments inline on the diff view.
MozReview-Commit-ID: HWeg6ZAEt8f
--- a/reviewboard/reviewboard/static/rb/css/pages/diffviewer.less
+++ b/reviewboard/reviewboard/static/rb/css/pages/diffviewer.less
@@ -828,9 +828,72 @@
color: @paginate-text-color;
font-weight: bold;
}
#pagination2 {
margin-top: 1em;
}
+
+/****************************************************************************
+ * Inline Comments
+ ****************************************************************************/
+.inlineCommentRow {
+ .inlineComments {
+ border-top: 1px solid #888;
+ border-bottom: 1px solid #888;
+
+ &:hover {
+ outline: 1px solid #000;
+ }
+
+ .inlineComment {
+ cursor: pointer;
+ background: #f0f0f0;
+ padding: 2px;
+ border-right: 0;
+ font-size: 8pt;
+
+ .meta {
+ display: inline-block;
+ overflow: hidden;
+ vertical-align: bottom;
+ height: 16px; // icon height
+ }
+
+ .comment {
+ display: inline-block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ height: 16px; // icon height
+ width: 250px; // calculated on window.resize
+ color: #888;
+ vertical-align: bottom;
+ margin-left: 4px;
+
+ p {
+ margin: 0;
+ }
+ }
+ }
+ }
+}
+
+.sidebyside {
+ tbody {
+ tr.inlineCommentRow {
+ th, td {
+ padding: 0;
+ cursor: default;
+ }
+ }
+ }
+}
+
+tr.selected-draft {
+ th:nth-child(3) {
+ outline: 1px dotted #000;
+ }
+}
+
// vim: set et ts=2 sw=2:
new file mode 100644
--- /dev/null
+++ b/reviewboard/reviewboard/static/rb/js/views/inlineCommentView.js
@@ -0,0 +1,207 @@
+RB.InlineCommentView = RB.AbstractCommentBlockView.extend({
+ tagName: 'tr',
+ className: 'inlineCommentRow',
+
+ initialize: function() {
+ var view = this;
+ this.rowTemplate = _.template($('#inlineComments-row').html());
+ this.commentTemplate = _.template($('#inlineComments-comment').html());
+ this.draftTemplate = _.template($('#inlineComments-draft').html());
+ this.$beginRow = null;
+ this.$endRow = null;
+ this.isDraft = false;
+ this.contextRows = [];
+ _.bindAll(this, '_updateSize');
+
+ // Detect when chunks are expanded/collapsed to update visible rows.
+ $.each(RB.PageManager.getPage()._diffReviewableViews, function() {
+ this.on('chunkExpansionChanged', function() {
+ view._updateContextRows();
+ view._updateSize();
+ });
+ });
+
+ // Update icon when issue statuses change.
+ RB.PageManager.getPage().commentIssueManager.on('issueStatusUpdated',
+ function(comment) {
+ $('#c' + comment.id + ' img')[0].className =
+ view._commentIconClass(
+ comment.get('issueOpened'), comment.get('issueStatus'));
+ });
+
+ // Don't recalculate the sizes on _every_ window.resize event.
+ this.lazyUpdateSize = _.debounce(this._updateSize, 100);
+ },
+
+ renderContent: function() {
+ var view = this;
+ var commentHtml = [];
+ this.isDraft = !!this.model.get('draftComment');
+ console.log(this); // XXX debugging
+
+ // Render row with each comment.
+ if (this.isDraft) {
+ commentHtml.push(view.draftTemplate({
+ user_name: RB.UserSession.instance.get('username')
+ }));
+ } else {
+ _.each(this.model.get('serializedComments'), function(comment) {
+ commentHtml.push(view.commentTemplate({
+ id: comment.comment_id,
+ user_name: comment.user.username,
+ comment_html: comment.html,
+ icon_class: view._commentIconClass(
+ comment.issue_opened, comment.issue_status)
+ }));
+ });
+ }
+ this.$el
+ .html(this.rowTemplate({
+ commentHtml: commentHtml.join('')
+ }))
+ .prop('id', this.cid);
+
+ this.$('.inlineComments')
+ .click(function() {
+ view.trigger('clicked');
+ })
+ .hover(
+ function() {
+ _.each(view.contextRows, function($row) {
+ $row.addClass('selected');
+ });
+ },
+ function() {
+ _.each(view.contextRows, function($row) {
+ $row.removeClass('selected');
+ });
+ }
+ );
+
+ if (this.isDraft) {
+ this.$el.find('.comment')
+ .bindProperty('text', this.model.get('draftComment'), 'text', {
+ elementToModel: false
+ });
+ }
+
+ $(window).on('resize', this.lazyUpdateSize);
+ this.lazyUpdateSize();
+ },
+
+ remove: function() {
+ Backbone.View.prototype.remove.call(this);
+ $(window).off('resize', this.lazyUpdateSize);
+ },
+
+ setRows: function($beginRow, $endRow) {
+ var view = this;
+ this.$beginRow = $beginRow;
+ this.$endRow = $endRow;
+ this._updateContextRows();
+
+ // Highlight selected rows on drafts.
+ if (this.isDraft) {
+ _.each(view.contextRows, function($row) {
+ $row.addClass('selected-draft');
+ });
+ }
+ },
+
+ _commentIconClass: function(issueOpened, issueStatus) {
+ return 'rb-icon ' +
+ (issueOpened
+ ? 'rb-icon-issue-' + issueStatus
+ : 'rb-icon-datagrid-comment');
+ },
+
+ positionCommentDlg: function(commentDlg) {
+ commentDlg.$el.css({
+ left: $(document).scrollLeft() +
+ ($(window).width() - commentDlg.$el.width()) / 2,
+ top: this.$endRow.offset().top + this.$endRow.height()
+ });
+ },
+
+ buildAnchorName: function() {
+ return 'file' + this.model.get('fileDiffID') + 'line' +
+ this.model.get('beginLineNum');
+ },
+
+ _updateContextRows: function () {
+ if (!(this.$beginRow && this.$endRow)) {
+ return;
+ }
+ // Keep track of the rows this comment refers to.
+ var beginLineNum = this.$beginRow.attr('line');
+ var endLineNum = this.$endRow.attr('line');
+ this.contextRows = [];
+ for (var lineNum = beginLineNum; lineNum <= endLineNum; lineNum++) {
+ var $tr = $('tr[line="' + lineNum + '"]');
+ if ($tr.length) {
+ this.contextRows.push($tr);
+ }
+ }
+ },
+
+ _updateSize: function() {
+ if (!(this.$beginRow && this.$endRow)) {
+ return;
+ }
+ // Show as much comment context as possible.
+ // Half the row, minus line number columns, minus name.
+ var colWidth = $('tr[line]').first().width() / 2 - (25 + 25 + 100);
+ this.$el
+ .find('.inlineComment .comment')
+ .width(colWidth - 20);
+ },
+
+ dispose: function() {
+ if (this.isDraft) {
+ _.each(this.contextRows, function($row) {
+ $row.removeClass('selected-draft');
+ });
+ }
+ this.remove();
+ },
+
+ render: function() {
+ this.renderContent();
+ this.model.on('change:draftComment', this._onDraftCommentChanged, this);
+ this._onDraftCommentChanged();
+ return this;
+ },
+
+ notify: function() {
+ // Ignore requests to show a notification bubble.
+ },
+
+ _onDraftCommentChanged: function() {
+ var self = this,
+ $el = this.$el,
+ comment = this.model.get('draftComment');
+
+ if (!comment) {
+ $el.removeClass('draft');
+ return;
+ }
+
+ comment.on('destroy', function() {
+ // Discard the comment block if empty.
+ if (this.model.isEmpty()) {
+ $el.fadeOut(150, function() { self.dispose(); });
+ } else {
+ $el.removeClass('draft');
+ }
+ }, this);
+
+ comment.on('saved', function() {
+ _.each(self.contextRows, function($row) {
+ $row.removeClass('selected-draft');
+ });
+ RB.DraftReviewBannerView.instance.show();
+ }, this);
+
+ $el.addClass('draft');
+ }
+});
--- a/reviewboard/reviewboard/staticbundles.py
+++ b/reviewboard/reviewboard/staticbundles.py
@@ -230,16 +230,17 @@ PIPELINE_JS = dict({
'rb/js/views/reviewReplyDraftBannerView.js',
'rb/js/views/reviewReplyEditorView.js',
'rb/js/views/reviewRequestEditorView.js',
'rb/js/views/screenshotThumbnailView.js',
'rb/js/views/imageReviewableView.js',
'rb/js/views/dummyReviewableView.js',
'rb/js/views/textBasedCommentBlockView.js',
'rb/js/views/textBasedReviewableView.js',
+ 'rb/js/views/inlineCommentView.js',
'rb/js/views/textCommentRowSelector.js',
'rb/js/views/markdownReviewableView.js',
'rb/js/views/uploadDiffView.js',
'rb/js/views/updateDiffView.js',
'rb/js/diffviewer/models/diffCommentBlockModel.js',
'rb/js/diffviewer/models/diffCommentsHintModel.js',
'rb/js/diffviewer/models/diffFileModel.js',
'rb/js/diffviewer/models/diffReviewableModel.js',
new file mode 100644
--- /dev/null
+++ b/reviewboard/reviewboard/templates/diffviewer/inline_comments.html
@@ -0,0 +1,27 @@
+
+<script type="text/template" id="inlineComments-row">
+ <th></th>
+ <td></td>
+ <th></th>
+ <td class="inlineComments"><%= commentHtml %></td>
+</script>
+
+<script type="text/template" id="inlineComments-comment">
+ <div class="inlineComment" id="c<%- id %>">
+ <div class="meta">
+ <span class="icon"><img class="<%- icon_class %>" width="16" height="16"></span>
+ <span class="user"><%- user_name %></span>
+ </div>
+ <div class="comment"><%= comment_html %></div>
+ </div>
+</script>
+
+<script type="text/template" id="inlineComments-draft">
+ <div class="inlineComment">
+ <div class="meta">
+ <span class="icon"><img class="rb-icon rb-icon-datagrid-comment-draft" width="16" height="16"></span>
+ <span class="user"><%- user_name %></span>
+ </div>
+ <div class="comment"></div>
+ </div>
+</script>