Commit c51cbc1f authored by Phil Hughes's avatar Phil Hughes

Updated issues with jump tp button

Fixed styling bugs
parent db8c4bce
...@@ -32,12 +32,12 @@ ...@@ -32,12 +32,12 @@
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`); const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`);
this.textareaVal = $textarea.val(); this.textareaVal = $textarea.val();
$textarea.on('input', () => { $textarea.on('input.comment-and-resolve-btn', () => {
this.textareaVal = $textarea.val(); this.textareaVal = $textarea.val();
}); });
}, },
destroyed: function () { destroyed: function () {
$(`#new-discussion-note-form-${this.discussionId} .note-textarea`).off('input'); $(`#new-discussion-note-form-${this.discussionId} .note-textarea`).off('input.comment-and-resolve-btn');
} }
}); });
})(window); })(window);
...@@ -14,66 +14,77 @@ ...@@ -14,66 +14,77 @@
return discussion.isResolved(); return discussion.isResolved();
}, },
discussionsCount: function () { discussionsCount: function () {
return Object.keys(this.discussions).length; return CommentsStore.discussionCount();
},
unresolvedDiscussionCount: function () {
let unresolvedCount = 0;
for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId];
if (!discussion.isResolved()) {
unresolvedCount++;
}
}
return unresolvedCount;
}, },
showButton: function () { showButton: function () {
return this.discussionsCount > 0 && (this.discussionsCount > 1 || !this.discussionId); if (this.discussionId) {
if (this.unresolvedDiscussionCount > 1) {
return true;
} else {
return this.discussionId !== this.lastResolvedId();
}
} else {
return this.unresolvedDiscussionCount >= 1;
}
} }
}, },
methods: { methods: {
lastResolvedId: function () {
let lastId;
for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId];
if (!discussion.isResolved()) {
lastId = discussion.id;
}
}
return lastId;
},
jumpToNextUnresolvedDiscussion: function () { jumpToNextUnresolvedDiscussion: function () {
let nextUnresolvedDiscussionId, let nextUnresolvedDiscussionId,
firstUnresolvedDiscussionId; firstUnresolvedDiscussionId,
useNextDiscussionId = false,
i = 0;
if (!this.discussionId) { for (const discussionId in this.discussions) {
let i = 0; const discussion = this.discussions[discussionId];
for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId];
const isResolved = discussion.isResolved();
if (!firstUnresolvedDiscussionId && !isResolved) { if (!discussion.isResolved()) {
firstUnresolvedDiscussionId = discussionId; if (i === 0) {
firstUnresolvedDiscussionId = discussion.id;
} }
if (!isResolved) { if (useNextDiscussionId) {
nextUnresolvedDiscussionId = discussionId; nextUnresolvedDiscussionId = discussion.id;
break; break;
} }
i++; if (this.discussionId && discussion.id === this.discussionId) {
} useNextDiscussionId = true;
} else {
let nextDiscussionId;
const discussionKeys = Object.keys(this.discussions),
indexOfDiscussion = discussionKeys.indexOf(this.discussionId);
nextDiscussionIds = discussionKeys.splice(indexOfDiscussion);
nextDiscussionIds.forEach((discussionId) => {
if (discussionId !== this.discussionId) {
const discussion = this.discussions[discussionId];
if (!discussion.isResolved()) {
nextDiscussionId = discussion.id;
}
} }
});
if (nextDiscussionId) { i++;
nextUnresolvedDiscussionId = nextDiscussionId;
} else {
firstUnresolvedDiscussionId = discussionKeys[0];
} }
} }
if (firstUnresolvedDiscussionId) { if (!nextUnresolvedDiscussionId && firstUnresolvedDiscussionId) {
// Jump to first unresolved discussion
nextUnresolvedDiscussionId = firstUnresolvedDiscussionId; nextUnresolvedDiscussionId = firstUnresolvedDiscussionId;
} }
if (nextUnresolvedDiscussionId) { if (nextUnresolvedDiscussionId) {
$('#notes').addClass('active'); mrTabs.activateTab('notes');
$('#commits, #builds, #diffs').removeClass('active');
mrTabs.setCurrentAction('notes');
$.scrollTo(`.discussion[data-discussion-id="${nextUnresolvedDiscussionId}"]`, { $.scrollTo(`.discussion[data-discussion-id="${nextUnresolvedDiscussionId}"]`, {
offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()) offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
return this.note.resolved; return this.note.resolved;
}, },
resolvedByName: function () { resolvedByName: function () {
return this.note.user; return this.note.resolved_by;
}, },
}, },
methods: { methods: {
...@@ -63,13 +63,13 @@ ...@@ -63,13 +63,13 @@
} }
promise.then((response) => { promise.then((response) => {
const data = response.data; const data = response.json();
const user = data ? data.resolved_by : null;
this.loading = false; this.loading = false;
if (response.status === 200) { if (response.status === 200) {
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, user); const resolved_by = data ? data.resolved_by : null;
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
ResolveService.updateUpdatedHtml(this.discussionId, data); ResolveService.updateUpdatedHtml(this.discussionId, data);
} else { } else {
new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert');
......
((w) => { ((w) => {
w.ResolveCount = Vue.extend({ w.ResolveCount = Vue.extend({
props: {
loggedOut: Boolean
},
data: function () { data: function () {
return { return {
discussions: CommentsStore.state, discussions: CommentsStore.state,
......
...@@ -15,8 +15,11 @@ ...@@ -15,8 +15,11 @@
}; };
}, },
computed: { computed: {
discussion: function () {
return this.discussions[this.discussionId];
},
allResolved: function () { allResolved: function () {
return this.discussions[this.discussionId].isResolved(); return this.discussion.isResolved();
}, },
buttonText: function () { buttonText: function () {
if (this.allResolved) { if (this.allResolved) {
...@@ -26,7 +29,7 @@ ...@@ -26,7 +29,7 @@
} }
}, },
loading: function () { loading: function () {
return this.discussions[this.discussionId].loading; return this.discussion.loading;
} }
}, },
methods: { methods: {
......
...@@ -5,8 +5,8 @@ class DiscussionModel { ...@@ -5,8 +5,8 @@ class DiscussionModel {
this.loading = false; this.loading = false;
} }
createNote (noteId, resolved, user) { createNote (noteId, resolved, resolved_by) {
Vue.set(this.notes, noteId, new NoteModel(this.id, noteId, resolved, user)); Vue.set(this.notes, noteId, new NoteModel(this.id, noteId, resolved, resolved_by));
} }
deleteNote (noteId) { deleteNote (noteId) {
...@@ -17,6 +17,10 @@ class DiscussionModel { ...@@ -17,6 +17,10 @@ class DiscussionModel {
return this.notes[noteId]; return this.notes[noteId];
} }
notesCount() {
return Object.keys(this.notes).length;
}
isResolved () { isResolved () {
for (const noteId in this.notes) { for (const noteId in this.notes) {
const note = this.notes[noteId]; const note = this.notes[noteId];
...@@ -28,24 +32,24 @@ class DiscussionModel { ...@@ -28,24 +32,24 @@ class DiscussionModel {
return true; return true;
} }
resolveAllNotes (user) { resolveAllNotes (resolved_by) {
for (const noteId in this.notes) { for (const noteId in this.notes) {
const note = this.notes[noteId]; const note = this.notes[noteId];
if (!note.resolved) { if (!note.resolved) {
note.resolved = true; note.resolved = true;
note.user = user; note.resolved_by = resolved_by;
} }
} }
} }
unResolveAllNotes (user) { unResolveAllNotes () {
for (const noteId in this.notes) { for (const noteId in this.notes) {
const note = this.notes[noteId]; const note = this.notes[noteId];
if (note.resolved) { if (note.resolved) {
note.resolved = false; note.resolved = false;
note.user = null; note.resolved_by = null;
} }
} }
} }
......
class NoteModel { class NoteModel {
constructor (discussionId, noteId, resolved, user) { constructor (discussionId, noteId, resolved, resolved_by) {
this.discussionId = discussionId; this.discussionId = discussionId;
this.id = noteId; this.id = noteId;
this.resolved = resolved; this.resolved = resolved;
this.user = user; this.resolved_by = resolved_by;
} }
} }
...@@ -11,14 +11,18 @@ ...@@ -11,14 +11,18 @@
resolve(namespace, noteId) { resolve(namespace, noteId) {
this.setCSRF(); this.setCSRF();
Vue.http.options.root = `/${namespace}`; if (Vue.http.options.root !== `/${namespace}`) {
Vue.http.options.root = `/${namespace}`;
}
return this.noteResource.save({ noteId }, {}); return this.noteResource.save({ noteId }, {});
} }
unresolve(namespace, noteId) { unresolve(namespace, noteId) {
this.setCSRF(); this.setCSRF();
Vue.http.options.root = `/${namespace}`; if (Vue.http.options.root !== `/${namespace}`) {
Vue.http.options.root = `/${namespace}`;
}
return this.noteResource.delete({ noteId }, {}); return this.noteResource.delete({ noteId }, {});
} }
...@@ -37,18 +41,22 @@ ...@@ -37,18 +41,22 @@
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
this.setCSRF(); this.setCSRF();
Vue.http.options.root = `/${namespace}`;
if (Vue.http.options.root !== `/${namespace}`) {
Vue.http.options.root = `/${namespace}`;
}
discussion.loading = true; discussion.loading = true;
console.log(discussion.loading);
return this.discussionResource.save({ return this.discussionResource.save({
mergeRequestId, mergeRequestId,
discussionId discussionId
}, {}).then((response) => { }, {}).then((response) => {
if (response.status === 200) { if (response.status === 200) {
const data = response.data; const data = response.json();
const user = data ? data.resolved_by : null; const resolved_by = data ? data.resolved_by : null;
discussion.resolveAllNotes(user); discussion.resolveAllNotes(resolved_by);
discussion.loading = false; discussion.loading = false;
this.updateUpdatedHtml(discussionId, data); this.updateUpdatedHtml(discussionId, data);
...@@ -71,7 +79,7 @@ ...@@ -71,7 +79,7 @@
discussionId discussionId
}, {}).then((response) => { }, {}).then((response) => {
if (response.status === 200) { if (response.status === 200) {
const data = response.data; const data = response.json();
discussion.unResolveAllNotes(); discussion.unResolveAllNotes();
discussion.loading = false; discussion.loading = false;
......
...@@ -4,28 +4,31 @@ ...@@ -4,28 +4,31 @@
get: function (discussionId, noteId) { get: function (discussionId, noteId) {
return this.state[discussionId].getNote(noteId); return this.state[discussionId].getNote(noteId);
}, },
create: function (discussionId, noteId, resolved, user) { create: function (discussionId, noteId, resolved, resolved_by) {
let discussion = this.state[discussionId]; let discussion = this.state[discussionId];
if (!this.state[discussionId]) { if (!this.state[discussionId]) {
discussion = new DiscussionModel(discussionId); discussion = new DiscussionModel(discussionId);
Vue.set(this.state, discussionId, discussion); Vue.set(this.state, discussionId, discussion);
} }
discussion.createNote(noteId, resolved, user); discussion.createNote(noteId, resolved, resolved_by);
}, },
update: function (discussionId, noteId, resolved, user) { update: function (discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId]; const discussion = this.state[discussionId];
const note = discussion.getNote(noteId); const note = discussion.getNote(noteId);
note.resolved = resolved; note.resolved = resolved;
note.user = user; note.resolved_by = resolved_by;
}, },
delete: function (discussionId, noteId) { delete: function (discussionId, noteId) {
const discussion = this.state[discussionId]; const discussion = this.state[discussionId];
discussion.deleteNote(noteId); discussion.deleteNote(noteId);
if (Object.keys(discussion.notes).length === 0) { if (discussion.notesCount() === 0) {
Vue.delete(this.state, discussionId); Vue.delete(this.state, discussionId);
} }
},
discussionCount: function () {
return Object.keys(this.state).length
} }
}; };
})(window); })(window);
...@@ -514,7 +514,7 @@ ...@@ -514,7 +514,7 @@
notes = note.closest(".notes"); notes = note.closest(".notes");
if (typeof DiffNotesApp !== "undefined" && DiffNotesApp !== null) { if (typeof DiffNotesApp !== "undefined" && DiffNotesApp !== null) {
ref = DiffNotesApp.$refs['' + noteId + '']; ref = DiffNotesApp.$refs[noteId];
if (ref) { if (ref) {
ref.$destroy(true); ref.$destroy(true);
...@@ -591,7 +591,7 @@ ...@@ -591,7 +591,7 @@
form.find('.js-note-target-close').remove(); form.find('.js-note-target-close').remove();
this.setupNoteForm(form); this.setupNoteForm(form);
if (canResolve === 'false') { if (canResolve === 'false' || !form.closest('.notes_content').find('.note:not(.system-note)').length) {
form.find('comment-and-resolve-btn').remove(); form.find('comment-and-resolve-btn').remove();
} else if (DiffNotesApp) { } else if (DiffNotesApp) {
var $commentBtn = form.find('comment-and-resolve-btn'); var $commentBtn = form.find('comment-and-resolve-btn');
......
...@@ -35,10 +35,12 @@ ...@@ -35,10 +35,12 @@
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) { if (!this.isOpen && !this.hasError) {
this.content.hide(); this.content.hide();
return this.collapsedContent.show(); this.collapsedContent.show();
compileVueComponentsForDiffNotes();
} else if (this.content) { } else if (this.content) {
this.collapsedContent.hide(); this.collapsedContent.hide();
return this.content.show(); this.content.show();
compileVueComponentsForDiffNotes();
} else { } else {
return this.getContentHTML(); return this.getContentHTML();
} }
...@@ -53,6 +55,7 @@ ...@@ -53,6 +55,7 @@
if (data.html) { if (data.html) {
_this.content = $(data.html); _this.content = $(data.html);
_this.content.syntaxHighlight(); _this.content.syntaxHighlight();
compileVueComponentsForDiffNotes();
} else { } else {
_this.hasError = true; _this.hasError = true;
_this.content = $(ERROR_HTML); _this.content = $(ERROR_HTML);
......
%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
.discussion-reply-holder - if current_user
- if discussion.diff_discussion? .discussion-reply-holder
- line_type = local_assigns.fetch(:line_type, nil) - if discussion.diff_discussion?
- line_type = local_assigns.fetch(:line_type, nil)
.btn-group-justified.discussion-with-resolve-btn{ role: "group" } .btn-group-justified.discussion-with-resolve-btn{ role: "group" }
.btn-group{ role: "group" } .btn-group{ role: "group" }
= link_to_reply_discussion(discussion, line_type) = link_to_reply_discussion(discussion, line_type)
.btn-group{ role: "group" }
= render "discussions/resolve_all", discussion: discussion = render "discussions/resolve_all", discussion: discussion
= render "discussions/jump_to_next", discussion: discussion = render "discussions/jump_to_next", discussion: discussion
- else - else
= link_to_reply_discussion(discussion) = link_to_reply_discussion(discussion)
- if discussion.can_resolve?(current_user) - if discussion.can_resolve?(current_user)
%resolve-discussion-btn{ ":namespace-path" => "'#{discussion.project.namespace.path}'", .btn-group{ role: "group" }
":project-path" => "'#{discussion.project.path}'", %resolve-discussion-btn{ ":namespace-path" => "'#{discussion.project.namespace.path}'",
":discussion-id" => "'#{discussion.id}'", ":project-path" => "'#{discussion.project.path}'",
":merge-request-id" => "#{discussion.noteable.iid}", ":discussion-id" => "'#{discussion.id}'",
"inline-template" => true, ":merge-request-id" => "#{discussion.noteable.iid}",
"v-cloak" => true } "inline-template" => true,
%button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading" } "v-cloak" => true }
= icon("spinner spin", "v-show" => "loading") %button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading" }
{{ buttonText }} = icon("spinner spin", "v-show" => "loading")
{{ buttonText }}
...@@ -45,9 +45,9 @@ ...@@ -45,9 +45,9 @@
= 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"
#resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true } #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
%resolve-count{ "inline-template" => true } %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
.line-resolve-all{ "v-show" => "discussionCount > 0", .line-resolve-all{ "v-show" => "discussionCount > 0",
":class" => "{ 'has-next-btn': resolved !== discussionCount }" } ":class" => "{ 'has-next-btn': !loggedOut && resolved !== discussionCount }" }
%span.line-resolve-btn.is-disabled{ type: "button", %span.line-resolve-btn.is-disabled{ type: "button",
":class" => "{ 'is-active': resolved === discussionCount }" } ":class" => "{ 'is-active': resolved === discussionCount }" }
= icon("check") = icon("check")
......
- return unless note.author - return unless note.author
- return if note.cross_reference_not_visible_for?(current_user) - return if note.cross_reference_not_visible_for?(current_user)
- can_resolve = can?(current_user, :resolve_note, note)
- note_editable = note_editable?(note) - note_editable = note_editable?(note)
%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} } %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
...@@ -28,16 +29,16 @@ ...@@ -28,16 +29,16 @@
":discussion-id" => "'#{note.discussion_id}'", ":discussion-id" => "'#{note.discussion_id}'",
":note-id" => note.id, ":note-id" => note.id,
":resolved" => note.resolved?, ":resolved" => note.resolved?,
":can-resolve" => can?(current_user, :resolve_note, note), ":can-resolve" => can_resolve,
":resolved-by" => "'#{note.resolved_by.try(:name)}'", ":resolved-by" => "'#{note.resolved_by.try(:name)}'",
"v-show" => "#{(note.resolvable? && can?(current_user, :resolve_note, note)) || (note.resolved? && !can?(current_user, :resolve_note, note))}", "v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true, "inline-template" => true,
"v-ref:note_#{note.id}" => 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", %button.line-resolve-btn{ type: "button",
class: ("is-disabled" unless can?(current_user, :resolve_note, note)), class: ("is-disabled" unless can_resolve),
":class" => "{ 'is-active': isResolved }", ":class" => "{ 'is-active': isResolved }",
":aria-label" => "buttonText", ":aria-label" => "buttonText",
"@click" => "resolve", "@click" => "resolve",
......
...@@ -116,15 +116,16 @@ feature 'Diff notes resolve', feature: true, js: true do ...@@ -116,15 +116,16 @@ feature 'Diff notes resolve', feature: true, js: true do
it 'allows user to unresolve from reply form without a comment' do it 'allows user to unresolve from reply form without a comment' do
page.within '.diff-content' do page.within '.diff-content' do
click_button 'Resolve discussion' click_button 'Resolve discussion'
sleep 1
click_button 'Reply...' click_button 'Reply...'
click_button 'Resolve discussion' click_button 'Unresolve discussion'
end end
page.within '.line-resolve-all-container' do page.within '.line-resolve-all-container' do
expect(page).to have_content('0/1 discussion resolved') expect(page).to have_content('0/1 discussion resolved')
expect(page).to have_selector('.line-resolve-btn.is-active') expect(page).not_to have_selector('.line-resolve-btn.is-active')
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment