Added resolve button to discussions
Top count displays how many resolved discussions
This commit is contained in:
parent
43131802a9
commit
c1fe066b4b
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
$ =>
|
$ =>
|
||||||
@DiffNotesApp = new Vue
|
@DiffNotesApp = new Vue
|
||||||
el: '#notes'
|
el: '#diff-comments-app'
|
||||||
components:
|
components:
|
||||||
'resolve-btn': ResolveBtn
|
'resolve-btn': ResolveBtn
|
||||||
|
'resolve-all': ResolveAll
|
||||||
|
|
||||||
new Vue
|
new Vue
|
||||||
el: '#resolve-all-app'
|
el: '#resolve-count-app'
|
||||||
components:
|
components:
|
||||||
'resolve-all': ResolveAll
|
'resolve-count': ResolveCount
|
||||||
|
|
|
@ -1,27 +1,22 @@
|
||||||
@ResolveAll = Vue.extend
|
@ResolveAll = Vue.extend
|
||||||
|
props:
|
||||||
|
discussionId: String
|
||||||
|
namespace: String
|
||||||
data: ->
|
data: ->
|
||||||
comments: CommentsStore.state
|
comments: CommentsStore.state
|
||||||
loading: false
|
loading: false
|
||||||
props:
|
|
||||||
namespace: String
|
|
||||||
computed:
|
computed:
|
||||||
resolved: ->
|
|
||||||
resolvedCount = 0
|
|
||||||
for noteId, resolved of this.comments
|
|
||||||
resolvedCount++ if resolved
|
|
||||||
resolvedCount
|
|
||||||
commentsCount: ->
|
|
||||||
Object.keys(this.comments).length
|
|
||||||
buttonText: ->
|
|
||||||
if this.allResolved then 'Un-resolve all' else 'Resolve all'
|
|
||||||
allResolved: ->
|
allResolved: ->
|
||||||
this.resolved is this.commentsCount
|
isResolved = true
|
||||||
|
for noteId, resolved of this.comments[this.discussionId]
|
||||||
|
isResolved = false unless resolved
|
||||||
|
isResolved
|
||||||
|
buttonText: ->
|
||||||
|
if this.allResolved then "Un-resolve all" else "Resolve all"
|
||||||
methods:
|
methods:
|
||||||
updateAll: ->
|
resolve: ->
|
||||||
ids = CommentsStore.getAllForState(this.allResolved)
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
ResolveService
|
ResolveService
|
||||||
.resolveAll(this.namespace, ids, !this.allResolved)
|
.resolveAll(this.namespace, this.discussionId, this.allResolved)
|
||||||
.then =>
|
.then =>
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@ResolveBtn = Vue.extend
|
@ResolveBtn = Vue.extend
|
||||||
props:
|
props:
|
||||||
noteId: Number
|
noteId: Number
|
||||||
|
discussionId: String
|
||||||
resolved: Boolean
|
resolved: Boolean
|
||||||
namespace: String
|
namespace: String
|
||||||
data: ->
|
data: ->
|
||||||
|
@ -9,7 +10,7 @@
|
||||||
computed:
|
computed:
|
||||||
buttonText: ->
|
buttonText: ->
|
||||||
if this.isResolved then "Mark as un-resolved" else "Mark as resolved"
|
if this.isResolved then "Mark as un-resolved" else "Mark as resolved"
|
||||||
isResolved: -> this.comments[this.noteId]
|
isResolved: -> CommentsStore.get(this.discussionId, this.noteId)
|
||||||
methods:
|
methods:
|
||||||
updateTooltip: ->
|
updateTooltip: ->
|
||||||
$(this.$els.button)
|
$(this.$els.button)
|
||||||
|
@ -18,13 +19,13 @@
|
||||||
resolve: ->
|
resolve: ->
|
||||||
this.loading = true
|
this.loading = true
|
||||||
ResolveService
|
ResolveService
|
||||||
.resolve(this.namespace, this.noteId, !this.isResolved)
|
.resolve(this.namespace, this.discussionId, this.noteId, !this.isResolved)
|
||||||
.then =>
|
.then =>
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.$nextTick this.updateTooltip
|
this.$nextTick this.updateTooltip
|
||||||
compiled: ->
|
compiled: ->
|
||||||
$(this.$els.button).tooltip()
|
$(this.$els.button).tooltip()
|
||||||
destroyed: ->
|
destroyed: ->
|
||||||
CommentsStore.delete(this.noteId)
|
CommentsStore.delete(this.discussionId, this.noteId)
|
||||||
created: ->
|
created: ->
|
||||||
CommentsStore.create(this.noteId, this.resolved)
|
CommentsStore.create(this.discussionId, this.noteId, this.resolved)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
@ResolveCount = Vue.extend
|
||||||
|
data: ->
|
||||||
|
comments: CommentsStore.state
|
||||||
|
loading: false
|
||||||
|
computed:
|
||||||
|
resolved: ->
|
||||||
|
resolvedCount = 0
|
||||||
|
for discussionId, comments of this.comments
|
||||||
|
resolved = true
|
||||||
|
for noteId, resolved of comments
|
||||||
|
resolved = false unless resolved
|
||||||
|
resolvedCount++ if resolved
|
||||||
|
resolvedCount
|
||||||
|
commentsCount: ->
|
||||||
|
Object.keys(this.comments).length
|
||||||
|
allResolved: ->
|
||||||
|
this.resolved is this.commentsCount
|
|
@ -12,20 +12,27 @@ class ResolveService
|
||||||
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken()
|
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken()
|
||||||
@resource = Vue.resource('notes{/id}', {}, actions)
|
@resource = Vue.resource('notes{/id}', {}, actions)
|
||||||
|
|
||||||
resolve: (namespace, id, resolve) ->
|
resolve: (namespace, discussionId, noteId, resolve) ->
|
||||||
Vue.http.options.root = "/#{namespace}"
|
Vue.http.options.root = "/#{namespace}"
|
||||||
@resource
|
@resource
|
||||||
.resolve({ id: id }, { resolved: resolve })
|
.resolve({ id: noteId }, { discussion: discussionId, resolved: resolve })
|
||||||
.then (response) ->
|
.then (response) ->
|
||||||
if response.status is 200
|
if response.status is 200
|
||||||
CommentsStore.update(id, resolve)
|
CommentsStore.update(discussionId, noteId, resolve)
|
||||||
|
|
||||||
resolveAll: (namespace, ids, resolve) ->
|
resolveAll: (namespace, discussionId, allResolve) ->
|
||||||
Vue.http.options.root = "/#{namespace}"
|
Vue.http.options.root = "/#{namespace}"
|
||||||
|
|
||||||
|
ids = []
|
||||||
|
for noteId, resolved of CommentsStore.state[discussionId]
|
||||||
|
ids.push(noteId) if resolved is allResolve
|
||||||
|
|
||||||
@resource
|
@resource
|
||||||
.all({}, { ids: ids, resolve: resolve })
|
.all({}, { ids: ids, discussion: discussionId, resolved: !allResolve })
|
||||||
.then (response) ->
|
.then (response) ->
|
||||||
CommentsStore.updateAll(resolve)
|
if response.status is 200
|
||||||
|
for noteId in ids
|
||||||
|
CommentsStore.update(discussionId, noteId, !allResolve)
|
||||||
|
|
||||||
$ ->
|
$ ->
|
||||||
@ResolveService = new ResolveService()
|
@ResolveService = new ResolveService()
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
@CommentsStore =
|
@CommentsStore =
|
||||||
state: {}
|
state: {}
|
||||||
create: (id, resolved) ->
|
get: (discussionId, noteId) ->
|
||||||
Vue.set(this.state, id, resolved)
|
this.state[discussionId][noteId]
|
||||||
update: (id, resolved) ->
|
create: (discussionId, noteId, resolved) ->
|
||||||
this.state[id] = resolved
|
unless this.state[discussionId]?
|
||||||
delete: (id) ->
|
Vue.set(this.state, discussionId, {})
|
||||||
Vue.delete(this.state, id)
|
|
||||||
updateAll: (state) ->
|
Vue.set(this.state[discussionId], noteId, resolved)
|
||||||
for id,resolved of this.state
|
update: (discussionId, noteId, resolved) ->
|
||||||
this.update(id, state) if resolved isnt state
|
this.state[discussionId][noteId] = resolved
|
||||||
getAllForState: (state) ->
|
delete: (discussionId, noteId) ->
|
||||||
ids = []
|
Vue.delete(this.state[discussionId], noteId)
|
||||||
for id,resolved of this.state
|
|
||||||
ids.push(id) if resolved is state
|
if Object.keys(this.state[discussionId]).length is 0
|
||||||
ids
|
Vue.delete(this.state, discussionId)
|
||||||
|
|
|
@ -157,6 +157,11 @@ class @MergeRequestTabs
|
||||||
url: "#{source}.json" + @_location.search
|
url: "#{source}.json" + @_location.search
|
||||||
success: (data) =>
|
success: (data) =>
|
||||||
$('#diffs').html data.html
|
$('#diffs').html data.html
|
||||||
|
|
||||||
|
if $('resolve-btn, resolve-all').length and DiffNotesApp?
|
||||||
|
$('resolve-btn, resolve-all').each ->
|
||||||
|
DiffNotesApp.$compile $(this).get(0)
|
||||||
|
|
||||||
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
|
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
|
||||||
$('#diffs .js-syntax-highlight').syntaxHighlight()
|
$('#diffs .js-syntax-highlight').syntaxHighlight()
|
||||||
$('#diffs .diff-file').singleFileDiff()
|
$('#diffs .diff-file').singleFileDiff()
|
||||||
|
|
|
@ -271,8 +271,8 @@ class @Notes
|
||||||
# append new note to all matching discussions
|
# append new note to all matching discussions
|
||||||
discussionContainer.append note_html
|
discussionContainer.append note_html
|
||||||
|
|
||||||
if $('resolve-btn').length and DiffNotesApp?
|
if $('resolve-btn, resolve-all').length and DiffNotesApp?
|
||||||
$('resolve-btn').each ->
|
$('resolve-btn, resolve-all').each ->
|
||||||
DiffNotesApp.$compile $(this).get(0)
|
DiffNotesApp.$compile $(this).get(0)
|
||||||
|
|
||||||
gl.utils.localTimeAgo($('.js-timeago', note_html), false)
|
gl.utils.localTimeAgo($('.js-timeago', note_html), false)
|
||||||
|
@ -507,10 +507,10 @@ class @Notes
|
||||||
replyToDiscussionNote: (e) =>
|
replyToDiscussionNote: (e) =>
|
||||||
form = @formClone.clone()
|
form = @formClone.clone()
|
||||||
replyLink = $(e.target).closest(".js-discussion-reply-button")
|
replyLink = $(e.target).closest(".js-discussion-reply-button")
|
||||||
replyLink.hide()
|
replyLink
|
||||||
|
.closest('.discussion-reply-holder')
|
||||||
# insert the form after the button
|
.hide()
|
||||||
replyLink.after form
|
.after form
|
||||||
|
|
||||||
# show the form
|
# show the form
|
||||||
@setupDiscussionNoteForm(replyLink, form)
|
@setupDiscussionNoteForm(replyLink, form)
|
||||||
|
@ -606,7 +606,9 @@ class @Notes
|
||||||
form.find(".js-note-text").data("autosave").reset()
|
form.find(".js-note-text").data("autosave").reset()
|
||||||
|
|
||||||
# show the reply button (will only work for replies)
|
# show the reply button (will only work for replies)
|
||||||
form.prev(".js-discussion-reply-button").show()
|
form
|
||||||
|
.prev('.discussion-reply-holder')
|
||||||
|
.show()
|
||||||
if row.is(".js-temp-notes-holder")
|
if row.is(".js-temp-notes-holder")
|
||||||
# remove temporary row for diff lines
|
# remove temporary row for diff lines
|
||||||
row.remove()
|
row.remove()
|
||||||
|
|
|
@ -156,6 +156,27 @@
|
||||||
.discussion-reply-holder {
|
.discussion-reply-holder {
|
||||||
background-color: $white-light;
|
background-color: $white-light;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
|
||||||
|
.btn-group-justified {
|
||||||
|
table-layout: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group:first-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion-with-resolve-btn {
|
||||||
|
.btn-group:first-child .btn {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group:last-child .btn {
|
||||||
|
margin-left: -1px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,10 +81,8 @@ module NotesHelper
|
||||||
|
|
||||||
data = discussion.reply_attributes.merge(line_type: line_type)
|
data = discussion.reply_attributes.merge(line_type: line_type)
|
||||||
|
|
||||||
content_tag(:div, class: "discussion-reply-holder") do
|
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
|
||||||
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
|
data: data, title: 'Add a reply'
|
||||||
data: data, title: 'Add a reply'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def note_max_access_for_user(note)
|
def note_max_access_for_user(note)
|
||||||
|
|
|
@ -3,4 +3,10 @@
|
||||||
%td.notes_content
|
%td.notes_content
|
||||||
%ul.notes{ data: { discussion_id: discussion.id } }
|
%ul.notes{ data: { discussion_id: discussion.id } }
|
||||||
= render partial: "projects/notes/note", collection: discussion.notes, as: :note
|
= render partial: "projects/notes/note", collection: discussion.notes, as: :note
|
||||||
= link_to_reply_discussion(discussion)
|
|
||||||
|
.discussion-reply-holder
|
||||||
|
.btn-group-justified.discussion-with-resolve-btn{ role: "group" }
|
||||||
|
.btn-group{ role: "group" }
|
||||||
|
= link_to_reply_discussion(note)
|
||||||
|
.btn-group{ role: "group" }
|
||||||
|
= render "projects/notes/discussions/resolve_all", note: note
|
||||||
|
|
|
@ -5,7 +5,12 @@
|
||||||
%ul.notes{ data: { discussion_id: discussion_left.id } }
|
%ul.notes{ data: { discussion_id: discussion_left.id } }
|
||||||
= render partial: "projects/notes/note", collection: discussion_left.notes, as: :note
|
= render partial: "projects/notes/note", collection: discussion_left.notes, as: :note
|
||||||
|
|
||||||
= link_to_reply_discussion(discussion_left, 'old')
|
.discussion-reply-holder
|
||||||
|
.btn-group-justified.discussion-with-resolve-btn{ role: "group" }
|
||||||
|
.btn-group{ role: "group" }
|
||||||
|
= link_to_reply_discussion(note_left, 'old')
|
||||||
|
.btn-group{ role: "group" }
|
||||||
|
= render "projects/notes/discussions/resolve_all", note: note_left
|
||||||
- else
|
- else
|
||||||
%td.notes_line.old= ""
|
%td.notes_line.old= ""
|
||||||
%td.notes_content.parallel.old= ""
|
%td.notes_content.parallel.old= ""
|
||||||
|
@ -16,7 +21,12 @@
|
||||||
%ul.notes{ data: { discussion_id: discussion_right.id } }
|
%ul.notes{ data: { discussion_id: discussion_right.id } }
|
||||||
= render partial: "projects/notes/note", collection: discussion_right.notes, as: :note
|
= render partial: "projects/notes/note", collection: discussion_right.notes, as: :note
|
||||||
|
|
||||||
= link_to_reply_discussion(discussion_right, 'new')
|
.discussion-reply-holder
|
||||||
|
.btn-group-justified.discussion-with-resolve-btn{ role: "group" }
|
||||||
|
.btn-group{ role: "group" }
|
||||||
|
= link_to_reply_discussion(note_right, 'new')
|
||||||
|
.btn-group{ role: "group" }
|
||||||
|
= render "projects/notes/discussions/resolve_all", note: note_right
|
||||||
- else
|
- else
|
||||||
%td.notes_line.new= ""
|
%td.notes_line.new= ""
|
||||||
%td.notes_content.parallel.new= ""
|
%td.notes_content.parallel.new= ""
|
||||||
|
|
|
@ -45,14 +45,11 @@
|
||||||
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
|
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
|
||||||
|
|
||||||
- if current_user
|
- if current_user
|
||||||
#resolve-all-app{ "v-cloak" => true }
|
#resolve-count-app{ "v-cloak" => true }
|
||||||
%resolve-all{ ":namespace" => "'#{@project.namespace.path}/#{@project.path}'", "inline-template" => true }
|
%resolve-count{ "inline-template" => true }
|
||||||
.line-resolve-all{ "v-show" => "commentsCount > 0" }
|
.line-resolve-all{ "v-show" => "commentsCount > 0" }
|
||||||
%button.btn.btn-gray{ type: "button", "aria-label" => "Resolve all", "@click" => "updateAll", ":disabled" => "loading" }
|
|
||||||
= icon("spinner spin", "v-show" => "loading")
|
|
||||||
{{ buttonText }}
|
|
||||||
%span.line-resolve-text
|
%span.line-resolve-text
|
||||||
{{ resolved }}/{{ commentsCount }} comments resolved
|
{{ resolved }}/{{ commentsCount }} discussions resolved
|
||||||
|
|
||||||
- if @commits_count.nonzero?
|
- if @commits_count.nonzero?
|
||||||
%ul.merge-request-tabs.nav-links.no-top.no-bottom
|
%ul.merge-request-tabs.nav-links.no-top.no-bottom
|
||||||
|
@ -74,7 +71,7 @@
|
||||||
Changes
|
Changes
|
||||||
%span.badge= @merge_request.diff_size
|
%span.badge= @merge_request.diff_size
|
||||||
|
|
||||||
.tab-content
|
.tab-content#diff-comments-app
|
||||||
#notes.notes.tab-pane.voting_notes
|
#notes.notes.tab-pane.voting_notes
|
||||||
.content-block.content-block-small.oneline-block
|
.content-block.content-block-small.oneline-block
|
||||||
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true
|
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
- if access and not note.system
|
- if access and not note.system
|
||||||
%span.note-role.hidden-xs= access
|
%span.note-role.hidden-xs= access
|
||||||
- if !note.system && current_user
|
- if !note.system && current_user
|
||||||
%resolve-btn{ ":namespace" => "'#{note.project.namespace.path}/#{note.project.path}'", ":note-id" => note.id, ":resolved" => "false", "inline-template" => true, "v-ref:note_#{note.id}" => true }
|
%resolve-btn{ ":namespace" => "'#{note.project.namespace.path}/#{note.project.path}'", ":discussion-id" => "'#{note.discussion_id}'", ":note-id" => note.id, ":resolved" => "false", "inline-template" => true, "v-ref:note_#{note.id}" => true }
|
||||||
.note-action-button
|
.note-action-button
|
||||||
= icon("spin spinner", "v-show" => "loading")
|
= icon("spin spinner", "v-show" => "loading")
|
||||||
%button.line-resolve-btn{ type: "button", ":class" => "{ 'is-active': isResolved }", ":aria-label" => "buttonText", "@click" => "resolve", ":title" => "buttonText", "v-show" => "!loading", "v-el:button" => true }
|
%button.line-resolve-btn{ type: "button", ":class" => "{ 'is-active': isResolved }", ":aria-label" => "buttonText", "@click" => "resolve", ":title" => "buttonText", "v-show" => "!loading", "v-el:button" => true }
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
%resolve-all{ ":namespace" => "'#{note.project.namespace.path}/#{note.project.path}'", ":discussion-id" => "'#{note.discussion_id}'", "inline-template" => true, "v-cloak" => true }
|
||||||
|
%button.btn.btn-success{ type: "button", "@click" => "resolve", ":disabled" => "loading" }
|
||||||
|
= icon("spinner spin", "v-show" => "loading")
|
||||||
|
{{ buttonText }}
|
Loading…
Reference in New Issue