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
|
||||
el: '#notes'
|
||||
el: '#diff-comments-app'
|
||||
components:
|
||||
'resolve-btn': ResolveBtn
|
||||
'resolve-all': ResolveAll
|
||||
|
||||
new Vue
|
||||
el: '#resolve-all-app'
|
||||
el: '#resolve-count-app'
|
||||
components:
|
||||
'resolve-all': ResolveAll
|
||||
'resolve-count': ResolveCount
|
||||
|
|
|
@ -1,27 +1,22 @@
|
|||
@ResolveAll = Vue.extend
|
||||
props:
|
||||
discussionId: String
|
||||
namespace: String
|
||||
data: ->
|
||||
comments: CommentsStore.state
|
||||
loading: false
|
||||
props:
|
||||
namespace: String
|
||||
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: ->
|
||||
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:
|
||||
updateAll: ->
|
||||
ids = CommentsStore.getAllForState(this.allResolved)
|
||||
resolve: ->
|
||||
this.loading = true
|
||||
|
||||
ResolveService
|
||||
.resolveAll(this.namespace, ids, !this.allResolved)
|
||||
.resolveAll(this.namespace, this.discussionId, this.allResolved)
|
||||
.then =>
|
||||
this.loading = false
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@ResolveBtn = Vue.extend
|
||||
props:
|
||||
noteId: Number
|
||||
discussionId: String
|
||||
resolved: Boolean
|
||||
namespace: String
|
||||
data: ->
|
||||
|
@ -9,7 +10,7 @@
|
|||
computed:
|
||||
buttonText: ->
|
||||
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:
|
||||
updateTooltip: ->
|
||||
$(this.$els.button)
|
||||
|
@ -18,13 +19,13 @@
|
|||
resolve: ->
|
||||
this.loading = true
|
||||
ResolveService
|
||||
.resolve(this.namespace, this.noteId, !this.isResolved)
|
||||
.resolve(this.namespace, this.discussionId, this.noteId, !this.isResolved)
|
||||
.then =>
|
||||
this.loading = false
|
||||
this.$nextTick this.updateTooltip
|
||||
compiled: ->
|
||||
$(this.$els.button).tooltip()
|
||||
destroyed: ->
|
||||
CommentsStore.delete(this.noteId)
|
||||
CommentsStore.delete(this.discussionId, this.noteId)
|
||||
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()
|
||||
@resource = Vue.resource('notes{/id}', {}, actions)
|
||||
|
||||
resolve: (namespace, id, resolve) ->
|
||||
resolve: (namespace, discussionId, noteId, resolve) ->
|
||||
Vue.http.options.root = "/#{namespace}"
|
||||
@resource
|
||||
.resolve({ id: id }, { resolved: resolve })
|
||||
.resolve({ id: noteId }, { discussion: discussionId, resolved: resolve })
|
||||
.then (response) ->
|
||||
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}"
|
||||
|
||||
ids = []
|
||||
for noteId, resolved of CommentsStore.state[discussionId]
|
||||
ids.push(noteId) if resolved is allResolve
|
||||
|
||||
@resource
|
||||
.all({}, { ids: ids, resolve: resolve })
|
||||
.all({}, { ids: ids, discussion: discussionId, resolved: !allResolve })
|
||||
.then (response) ->
|
||||
CommentsStore.updateAll(resolve)
|
||||
if response.status is 200
|
||||
for noteId in ids
|
||||
CommentsStore.update(discussionId, noteId, !allResolve)
|
||||
|
||||
$ ->
|
||||
@ResolveService = new ResolveService()
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
@CommentsStore =
|
||||
state: {}
|
||||
create: (id, resolved) ->
|
||||
Vue.set(this.state, id, resolved)
|
||||
update: (id, resolved) ->
|
||||
this.state[id] = resolved
|
||||
delete: (id) ->
|
||||
Vue.delete(this.state, id)
|
||||
updateAll: (state) ->
|
||||
for id,resolved of this.state
|
||||
this.update(id, state) if resolved isnt state
|
||||
getAllForState: (state) ->
|
||||
ids = []
|
||||
for id,resolved of this.state
|
||||
ids.push(id) if resolved is state
|
||||
ids
|
||||
get: (discussionId, noteId) ->
|
||||
this.state[discussionId][noteId]
|
||||
create: (discussionId, noteId, resolved) ->
|
||||
unless this.state[discussionId]?
|
||||
Vue.set(this.state, discussionId, {})
|
||||
|
||||
Vue.set(this.state[discussionId], noteId, resolved)
|
||||
update: (discussionId, noteId, resolved) ->
|
||||
this.state[discussionId][noteId] = resolved
|
||||
delete: (discussionId, noteId) ->
|
||||
Vue.delete(this.state[discussionId], noteId)
|
||||
|
||||
if Object.keys(this.state[discussionId]).length is 0
|
||||
Vue.delete(this.state, discussionId)
|
||||
|
|
|
@ -157,6 +157,11 @@ class @MergeRequestTabs
|
|||
url: "#{source}.json" + @_location.search
|
||||
success: (data) =>
|
||||
$('#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'))
|
||||
$('#diffs .js-syntax-highlight').syntaxHighlight()
|
||||
$('#diffs .diff-file').singleFileDiff()
|
||||
|
|
|
@ -271,8 +271,8 @@ class @Notes
|
|||
# append new note to all matching discussions
|
||||
discussionContainer.append note_html
|
||||
|
||||
if $('resolve-btn').length and DiffNotesApp?
|
||||
$('resolve-btn').each ->
|
||||
if $('resolve-btn, resolve-all').length and DiffNotesApp?
|
||||
$('resolve-btn, resolve-all').each ->
|
||||
DiffNotesApp.$compile $(this).get(0)
|
||||
|
||||
gl.utils.localTimeAgo($('.js-timeago', note_html), false)
|
||||
|
@ -507,10 +507,10 @@ class @Notes
|
|||
replyToDiscussionNote: (e) =>
|
||||
form = @formClone.clone()
|
||||
replyLink = $(e.target).closest(".js-discussion-reply-button")
|
||||
replyLink.hide()
|
||||
|
||||
# insert the form after the button
|
||||
replyLink.after form
|
||||
replyLink
|
||||
.closest('.discussion-reply-holder')
|
||||
.hide()
|
||||
.after form
|
||||
|
||||
# show the form
|
||||
@setupDiscussionNoteForm(replyLink, form)
|
||||
|
@ -606,7 +606,9 @@ class @Notes
|
|||
form.find(".js-note-text").data("autosave").reset()
|
||||
|
||||
# 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")
|
||||
# remove temporary row for diff lines
|
||||
row.remove()
|
||||
|
|
|
@ -156,6 +156,27 @@
|
|||
.discussion-reply-holder {
|
||||
background-color: $white-light;
|
||||
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,11 +81,9 @@ module NotesHelper
|
|||
|
||||
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',
|
||||
data: data, title: 'Add a reply'
|
||||
end
|
||||
end
|
||||
|
||||
def note_max_access_for_user(note)
|
||||
@max_access_by_user_id ||= Hash.new do |hash, key|
|
||||
|
|
|
@ -3,4 +3,10 @@
|
|||
%td.notes_content
|
||||
%ul.notes{ data: { discussion_id: discussion.id } }
|
||||
= 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 } }
|
||||
= 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
|
||||
%td.notes_line.old= ""
|
||||
%td.notes_content.parallel.old= ""
|
||||
|
@ -16,7 +21,12 @@
|
|||
%ul.notes{ data: { discussion_id: discussion_right.id } }
|
||||
= 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
|
||||
%td.notes_line.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"
|
||||
|
||||
- if current_user
|
||||
#resolve-all-app{ "v-cloak" => true }
|
||||
%resolve-all{ ":namespace" => "'#{@project.namespace.path}/#{@project.path}'", "inline-template" => true }
|
||||
#resolve-count-app{ "v-cloak" => true }
|
||||
%resolve-count{ "inline-template" => true }
|
||||
.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
|
||||
{{ resolved }}/{{ commentsCount }} comments resolved
|
||||
{{ resolved }}/{{ commentsCount }} discussions resolved
|
||||
|
||||
- if @commits_count.nonzero?
|
||||
%ul.merge-request-tabs.nav-links.no-top.no-bottom
|
||||
|
@ -74,7 +71,7 @@
|
|||
Changes
|
||||
%span.badge= @merge_request.diff_size
|
||||
|
||||
.tab-content
|
||||
.tab-content#diff-comments-app
|
||||
#notes.notes.tab-pane.voting_notes
|
||||
.content-block.content-block-small.oneline-block
|
||||
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
- if access and not note.system
|
||||
%span.note-role.hidden-xs= access
|
||||
- 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
|
||||
= 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 }
|
||||
|
|
|
@ -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