Merge branch 'riyad-discussions'
This commit is contained in:
commit
6ddacaf9bf
71 changed files with 2274 additions and 880 deletions
Binary file not shown.
Before Width: | Height: | Size: 781 B |
BIN
app/assets/images/diff_note_add.png
Normal file
BIN
app/assets/images/diff_note_add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 691 B |
5
app/assets/javascripts/behaviors/details_behavior.coffee
Normal file
5
app/assets/javascripts/behaviors/details_behavior.coffee
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
$ ->
|
||||||
|
$("body").on "click", ".js-details-target", ->
|
||||||
|
container = $(@).closest(".js-details-container")
|
||||||
|
|
||||||
|
container.toggleClass("open")
|
5
app/assets/javascripts/behaviors/toggler_behavior.coffee
Normal file
5
app/assets/javascripts/behaviors/toggler_behavior.coffee
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
$ ->
|
||||||
|
$("body").on "click", ".js-toggler-target", ->
|
||||||
|
container = $(@).closest(".js-toggler-container")
|
||||||
|
|
||||||
|
container.toggleClass("on")
|
7
app/assets/javascripts/extensions/array.js
Normal file
7
app/assets/javascripts/extensions/array.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Array.prototype.first = function() {
|
||||||
|
return this[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.prototype.last = function() {
|
||||||
|
return this[this.length-1];
|
||||||
|
}
|
|
@ -9,71 +9,314 @@ var NoteList = {
|
||||||
loading_more_disabled: false,
|
loading_more_disabled: false,
|
||||||
reversed: false,
|
reversed: false,
|
||||||
|
|
||||||
init:
|
init: function(tid, tt, path) {
|
||||||
function(tid, tt, path) {
|
NoteList.notes_path = path + ".js";
|
||||||
this.notes_path = path + ".js";
|
NoteList.target_id = tid;
|
||||||
this.target_id = tid;
|
NoteList.target_type = tt;
|
||||||
this.target_type = tt;
|
NoteList.reversed = $("#notes-list").is(".reversed");
|
||||||
this.reversed = $("#notes-list").is(".reversed");
|
NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id;
|
||||||
this.target_params = "target_type=" + this.target_type + "&target_id=" + this.target_id;
|
|
||||||
|
|
||||||
// get initial set of notes
|
NoteList.setupMainTargetNoteForm();
|
||||||
this.getContent();
|
|
||||||
|
|
||||||
$("#notes-list, #new-notes-list").on("ajax:success", ".delete-note", function() {
|
if(NoteList.reversed) {
|
||||||
$(this).closest('li').fadeOut(function() {
|
var form = $(".js-main-target-form");
|
||||||
$(this).remove();
|
form.find(".buttons, .note_options").hide();
|
||||||
NoteList.updateVotes();
|
var textarea = form.find(".js-note-text");
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".note-form-holder").on("ajax:before", function(){
|
|
||||||
$(".submit_note").disable();
|
|
||||||
})
|
|
||||||
|
|
||||||
$(".note-form-holder").on("ajax:complete", function(){
|
|
||||||
$(".submit_note").enable();
|
|
||||||
$('#preview-note').hide();
|
|
||||||
$('#note_note').show();
|
|
||||||
})
|
|
||||||
|
|
||||||
disableButtonIfEmptyField(".note-text", ".submit_note");
|
|
||||||
|
|
||||||
$("#note_attachment").change(function(e){
|
|
||||||
var val = $('.input-file').val();
|
|
||||||
var filename = val.replace(/^.*[\\\/]/, '');
|
|
||||||
$(".file_name").text(filename);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(this.reversed) {
|
|
||||||
var textarea = $(".note-text");
|
|
||||||
$('.note_advanced_opts').hide();
|
|
||||||
textarea.css("height", "40px");
|
textarea.css("height", "40px");
|
||||||
textarea.on("focus", function(){
|
textarea.on("focus", function(){
|
||||||
$(this).css("height", "80px");
|
textarea.css("height", "80px");
|
||||||
$('.note_advanced_opts').show();
|
form.find(".buttons, .note_options").show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup note preview
|
// get initial set of notes
|
||||||
$(document).on('click', '#preview-link', function(e) {
|
NoteList.getContent();
|
||||||
$('#preview-note').text('Loading...');
|
|
||||||
|
|
||||||
$(this).text($(this).text() === "Edit" ? "Preview" : "Edit");
|
// add a new diff note
|
||||||
|
$(document).on("click",
|
||||||
|
".js-add-diff-note-button",
|
||||||
|
NoteList.addDiffNote);
|
||||||
|
|
||||||
var note_text = $('#note_note').val();
|
// reply to diff/discussion notes
|
||||||
|
$(document).on("click",
|
||||||
|
".js-discussion-reply-button",
|
||||||
|
NoteList.replyToDiscussionNote);
|
||||||
|
|
||||||
if(note_text.trim().length === 0) {
|
// setup note preview
|
||||||
$('#preview-note').text('Nothing to preview.');
|
$(document).on("click",
|
||||||
} else {
|
".js-note-preview-button",
|
||||||
$.post($(this).attr('href'), {note: note_text}).success(function(data) {
|
NoteList.previewNote);
|
||||||
$('#preview-note').html(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#preview-note, #note_note').toggle();
|
// update the file name when an attachment is selected
|
||||||
|
$(document).on("change",
|
||||||
|
".js-note-attachment-input",
|
||||||
|
NoteList.updateFormAttachment);
|
||||||
|
|
||||||
|
// hide diff note form
|
||||||
|
$(document).on("click",
|
||||||
|
".js-close-discussion-note-form",
|
||||||
|
NoteList.removeDiscussionNoteForm);
|
||||||
|
|
||||||
|
// remove a note (in general)
|
||||||
|
$(document).on("click",
|
||||||
|
".js-note-delete",
|
||||||
|
NoteList.removeNote);
|
||||||
|
|
||||||
|
// reset main target form after submit
|
||||||
|
$(document).on("ajax:complete",
|
||||||
|
".js-main-target-form",
|
||||||
|
NoteList.resetMainTargetForm);
|
||||||
|
|
||||||
|
|
||||||
|
$(document).on("click",
|
||||||
|
".js-choose-note-attachment-button",
|
||||||
|
NoteList.chooseNoteAttachment);
|
||||||
|
|
||||||
|
$(document).on("click",
|
||||||
|
".js-show-outdated-discussion",
|
||||||
|
function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() });
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When clicking on buttons
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when clicking on the "add a comment" button on the side of a diff line.
|
||||||
|
*
|
||||||
|
* Inserts a temporary row for the form below the line.
|
||||||
|
* Sets up the form and shows it.
|
||||||
|
*/
|
||||||
|
addDiffNote: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// find the form
|
||||||
|
var form = $(".js-new-note-form");
|
||||||
|
var row = $(this).closest("tr");
|
||||||
|
var nextRow = row.next();
|
||||||
|
|
||||||
|
// does it already have notes?
|
||||||
|
if (nextRow.is(".notes_holder")) {
|
||||||
|
$.proxy(NoteList.replyToDiscussionNote,
|
||||||
|
nextRow.find(".js-discussion-reply-button")
|
||||||
|
).call();
|
||||||
|
} else {
|
||||||
|
// add a notes row and insert the form
|
||||||
|
row.after('<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"></td></tr>');
|
||||||
|
form.clone().appendTo(row.next().find(".notes_content"));
|
||||||
|
|
||||||
|
// show the form
|
||||||
|
NoteList.setupDiscussionNoteForm($(this), row.next().find("form"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when clicking the "Choose File" button.
|
||||||
|
*
|
||||||
|
* Opesn the file selection dialog.
|
||||||
|
*/
|
||||||
|
chooseNoteAttachment: function() {
|
||||||
|
var form = $(this).closest("form");
|
||||||
|
|
||||||
|
form.find(".js-note-attachment-input").click();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the note preview.
|
||||||
|
*
|
||||||
|
* Lets the server render GFM into Html and displays it.
|
||||||
|
*
|
||||||
|
* Note: uses the Toggler behavior to toggle preview/edit views/buttons
|
||||||
|
*/
|
||||||
|
previewNote: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var form = $(this).closest("form");
|
||||||
|
var preview = form.find('.js-note-preview');
|
||||||
|
var noteText = form.find('.js-note-text').val();
|
||||||
|
|
||||||
|
if(noteText.trim().length === 0) {
|
||||||
|
preview.text('Nothing to preview.');
|
||||||
|
} else {
|
||||||
|
preview.text('Loading...');
|
||||||
|
$.post($(this).data('url'), {note: noteText})
|
||||||
|
.success(function(previewData) {
|
||||||
|
preview.html(previewData);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in response to "cancel" on a diff note form.
|
||||||
|
*
|
||||||
|
* Shows the reply button again.
|
||||||
|
* Removes the form and if necessary it's temporary row.
|
||||||
|
*/
|
||||||
|
removeDiscussionNoteForm: function() {
|
||||||
|
var form = $(this).closest("form");
|
||||||
|
var row = form.closest("tr");
|
||||||
|
|
||||||
|
// show the reply button (will only work for replys)
|
||||||
|
form.prev(".js-discussion-reply-button").show();
|
||||||
|
|
||||||
|
if (row.is(".js-temp-notes-holder")) {
|
||||||
|
// remove temporary row for diff lines
|
||||||
|
row.remove();
|
||||||
|
} else {
|
||||||
|
// only remove the form
|
||||||
|
form.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in response to deleting a note of any kind.
|
||||||
|
*
|
||||||
|
* Removes the actual note from view.
|
||||||
|
* Removes the whole discussion if the last note is being removed.
|
||||||
|
*/
|
||||||
|
removeNote: function() {
|
||||||
|
var note = $(this).closest(".note");
|
||||||
|
var notes = note.closest(".notes");
|
||||||
|
|
||||||
|
// check if this is the last note for this line
|
||||||
|
if (notes.find(".note").length === 1) {
|
||||||
|
// for discussions
|
||||||
|
notes.closest(".discussion").remove();
|
||||||
|
|
||||||
|
// for diff lines
|
||||||
|
notes.closest("tr").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
note.remove();
|
||||||
|
NoteList.updateVotes();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when clicking on the "reply" button for a diff line.
|
||||||
|
*
|
||||||
|
* Shows the note form below the notes.
|
||||||
|
*/
|
||||||
|
replyToDiscussionNote: function() {
|
||||||
|
// find the form
|
||||||
|
var form = $(".js-new-note-form");
|
||||||
|
|
||||||
|
// hide reply button
|
||||||
|
$(this).hide();
|
||||||
|
// insert the form after the button
|
||||||
|
form.clone().insertAfter($(this));
|
||||||
|
|
||||||
|
// show the form
|
||||||
|
NoteList.setupDiscussionNoteForm($(this), $(this).next("form"));
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for inserting and setting up note forms.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in response to creating a note failing validation.
|
||||||
|
*
|
||||||
|
* Adds the rendered errors to the respective form.
|
||||||
|
* If "discussionId" is null or undefined, the main target form is assumed.
|
||||||
|
*/
|
||||||
|
errorsOnForm: function(errorsHtml, discussionId) {
|
||||||
|
// find the form
|
||||||
|
if (discussionId) {
|
||||||
|
var form = $("form[rel='"+discussionId+"']");
|
||||||
|
} else {
|
||||||
|
var form = $(".js-main-target-form");
|
||||||
|
}
|
||||||
|
|
||||||
|
form.find(".js-errors").remove();
|
||||||
|
form.prepend(errorsHtml);
|
||||||
|
|
||||||
|
form.find(".js-note-text").focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the diff/discussion form and does some setup on it.
|
||||||
|
*
|
||||||
|
* Sets some hidden fields in the form.
|
||||||
|
*
|
||||||
|
* Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
|
||||||
|
* and "noteableId" data attributes set.
|
||||||
|
*/
|
||||||
|
setupDiscussionNoteForm: function(dataHolder, form) {
|
||||||
|
// setup note target
|
||||||
|
form.attr("rel", dataHolder.data("discussionId"));
|
||||||
|
form.find("#note_commit_id").val(dataHolder.data("commitId"));
|
||||||
|
form.find("#note_line_code").val(dataHolder.data("lineCode"));
|
||||||
|
form.find("#note_noteable_type").val(dataHolder.data("noteableType"));
|
||||||
|
form.find("#note_noteable_id").val(dataHolder.data("noteableId"));
|
||||||
|
|
||||||
|
NoteList.setupNoteForm(form);
|
||||||
|
|
||||||
|
form.find(".js-note-text").focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the main form and does some setup on it.
|
||||||
|
*
|
||||||
|
* Sets some hidden fields in the form.
|
||||||
|
*/
|
||||||
|
setupMainTargetNoteForm: function() {
|
||||||
|
// find the form
|
||||||
|
var form = $(".js-new-note-form");
|
||||||
|
// insert the form after the button
|
||||||
|
form.clone().replaceAll($(".js-main-target-form"));
|
||||||
|
|
||||||
|
form = form.prev("form");
|
||||||
|
|
||||||
|
// show the form
|
||||||
|
NoteList.setupNoteForm(form);
|
||||||
|
|
||||||
|
// fix classes
|
||||||
|
form.removeClass("js-new-note-form");
|
||||||
|
form.addClass("js-main-target-form");
|
||||||
|
|
||||||
|
// remove unnecessary fields and buttons
|
||||||
|
form.find("#note_line_code").remove();
|
||||||
|
form.find(".js-close-discussion-note-form").remove();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General note form setup.
|
||||||
|
*
|
||||||
|
* * deactivates the submit button when text is empty
|
||||||
|
* * hides the preview button when text is empty
|
||||||
|
* * setup GFM auto complete
|
||||||
|
* * show the form
|
||||||
|
*/
|
||||||
|
setupNoteForm: function(form) {
|
||||||
|
disableButtonIfEmptyField(form.find(".js-note-text"), form.find(".js-comment-button"));
|
||||||
|
|
||||||
|
form.removeClass("js-new-note-form");
|
||||||
|
|
||||||
|
// setup preview buttons
|
||||||
|
form.find(".js-note-edit-button, .js-note-preview-button")
|
||||||
|
.tooltip({ placement: 'left' });
|
||||||
|
|
||||||
|
previewButton = form.find(".js-note-preview-button");
|
||||||
|
form.find(".js-note-text").on("input", function() {
|
||||||
|
if ($(this).val().trim() !== "") {
|
||||||
|
previewButton.removeClass("turn-off").addClass("turn-on");
|
||||||
|
} else {
|
||||||
|
previewButton.removeClass("turn-on").addClass("turn-off");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove notify commit author checkbox for non-commit notes
|
||||||
|
if (form.find("#note_noteable_type").val() !== "Commit") {
|
||||||
|
form.find(".js-notify-commit-author").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
GitLab.GfmAutoComplete.setup();
|
||||||
|
|
||||||
|
form.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,40 +329,39 @@ var NoteList = {
|
||||||
/**
|
/**
|
||||||
* Gets an inital set of notes.
|
* Gets an inital set of notes.
|
||||||
*/
|
*/
|
||||||
getContent:
|
getContent: function() {
|
||||||
function() {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
url: NoteList.notes_path,
|
||||||
url: this.notes_path,
|
data: NoteList.target_params,
|
||||||
data: this.target_params,
|
complete: function(){ $('.js-notes-busy').removeClass("loading")},
|
||||||
complete: function(){ $('.notes-status').removeClass("loading")},
|
beforeSend: function() { $('.js-notes-busy').addClass("loading") },
|
||||||
beforeSend: function() { $('.notes-status').addClass("loading") },
|
dataType: "script"
|
||||||
dataType: "script"});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called in response to getContent().
|
* Called in response to getContent().
|
||||||
* Replaces the content of #notes-list with the given html.
|
* Replaces the content of #notes-list with the given html.
|
||||||
*/
|
*/
|
||||||
setContent:
|
setContent: function(newNoteIds, html) {
|
||||||
function(first_id, last_id, html) {
|
NoteList.top_id = newNoteIds.first();
|
||||||
this.top_id = first_id;
|
NoteList.bottom_id = newNoteIds.last();
|
||||||
this.bottom_id = last_id;
|
|
||||||
$("#notes-list").html(html);
|
$("#notes-list").html(html);
|
||||||
|
|
||||||
|
// for the wall
|
||||||
|
if (NoteList.reversed) {
|
||||||
// init infinite scrolling
|
// init infinite scrolling
|
||||||
this.initLoadMore();
|
NoteList.initLoadMore();
|
||||||
|
|
||||||
// init getting new notes
|
// init getting new notes
|
||||||
if (this.reversed) {
|
NoteList.initRefreshNew();
|
||||||
this.initRefreshNew();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle loading more notes when scrolling to the bottom of the page.
|
* Handle loading more notes when scrolling to the bottom of the page.
|
||||||
* The id of the last note in the list is in this.bottom_id.
|
* The id of the last note in the list is in NoteList.bottom_id.
|
||||||
*
|
*
|
||||||
* Set up refreshing only new notes after all notes have been loaded.
|
* Set up refreshing only new notes after all notes have been loaded.
|
||||||
*/
|
*/
|
||||||
|
@ -128,8 +370,7 @@ var NoteList = {
|
||||||
/**
|
/**
|
||||||
* Initializes loading more notes when scrolling to the bottom of the page.
|
* Initializes loading more notes when scrolling to the bottom of the page.
|
||||||
*/
|
*/
|
||||||
initLoadMore:
|
initLoadMore: function() {
|
||||||
function() {
|
|
||||||
$(document).endlessScroll({
|
$(document).endlessScroll({
|
||||||
bottomPixels: 400,
|
bottomPixels: 400,
|
||||||
fireDelay: 1000,
|
fireDelay: 1000,
|
||||||
|
@ -146,27 +387,26 @@ var NoteList = {
|
||||||
/**
|
/**
|
||||||
* Gets an additional set of notes.
|
* Gets an additional set of notes.
|
||||||
*/
|
*/
|
||||||
getMore:
|
getMore: function() {
|
||||||
function() {
|
|
||||||
// only load more notes if there are no "new" notes
|
// only load more notes if there are no "new" notes
|
||||||
$('.loading').show();
|
$('.loading').show();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
url: NoteList.notes_path,
|
||||||
url: this.notes_path,
|
data: NoteList.target_params + "&loading_more=1&" + (NoteList.reversed ? "before_id" : "after_id") + "=" + NoteList.bottom_id,
|
||||||
data: this.target_params + "&loading_more=1&" + (this.reversed ? "before_id" : "after_id") + "=" + this.bottom_id,
|
complete: function(){ $('.js-notes-busy').removeClass("loading")},
|
||||||
complete: function(){ $('.notes-status').removeClass("loading")},
|
beforeSend: function() { $('.js-notes-busy').addClass("loading") },
|
||||||
beforeSend: function() { $('.notes-status').addClass("loading") },
|
dataType: "script"
|
||||||
dataType: "script"});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called in response to getMore().
|
* Called in response to getMore().
|
||||||
* Append notes to #notes-list.
|
* Append notes to #notes-list.
|
||||||
*/
|
*/
|
||||||
appendMoreNotes:
|
appendMoreNotes: function(newNoteIds, html) {
|
||||||
function(id, html) {
|
var lastNewNoteId = newNoteIds.last();
|
||||||
if(id != this.bottom_id) {
|
if(lastNewNoteId != NoteList.bottom_id) {
|
||||||
this.bottom_id = id;
|
NoteList.bottom_id = lastNewNoteId;
|
||||||
$("#notes-list").append(html);
|
$("#notes-list").append(html);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -174,18 +414,12 @@ var NoteList = {
|
||||||
/**
|
/**
|
||||||
* Called in response to getMore().
|
* Called in response to getMore().
|
||||||
* Disables loading more notes when scrolling to the bottom of the page.
|
* Disables loading more notes when scrolling to the bottom of the page.
|
||||||
* Initalizes refreshing new notes.
|
|
||||||
*/
|
*/
|
||||||
finishedLoadingMore:
|
finishedLoadingMore: function() {
|
||||||
function() {
|
NoteList.loading_more_disabled = true;
|
||||||
this.loading_more_disabled = true;
|
|
||||||
|
|
||||||
// from now on only get new notes
|
|
||||||
if (!this.reversed) {
|
|
||||||
this.initRefreshNew();
|
|
||||||
}
|
|
||||||
// make sure we are up to date
|
// make sure we are up to date
|
||||||
this.updateVotes();
|
NoteList.updateVotes();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -194,51 +428,117 @@ var NoteList = {
|
||||||
*
|
*
|
||||||
* New notes are all notes that are created after the site has been loaded.
|
* New notes are all notes that are created after the site has been loaded.
|
||||||
* The "old" notes are in #notes-list the "new" ones will be in #new-notes-list.
|
* The "old" notes are in #notes-list the "new" ones will be in #new-notes-list.
|
||||||
* The id of the last "old" note is in this.bottom_id.
|
* The id of the last "old" note is in NoteList.bottom_id.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes getting new notes every n seconds.
|
* Initializes getting new notes every n seconds.
|
||||||
|
*
|
||||||
|
* Note: only used on wall.
|
||||||
*/
|
*/
|
||||||
initRefreshNew:
|
initRefreshNew: function() {
|
||||||
function() {
|
|
||||||
setInterval("NoteList.getNew()", 10000);
|
setInterval("NoteList.getNew()", 10000);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the new set of notes.
|
* Gets the new set of notes.
|
||||||
|
*
|
||||||
|
* Note: only used on wall.
|
||||||
*/
|
*/
|
||||||
getNew:
|
getNew: function() {
|
||||||
function() {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
url: NoteList.notes_path,
|
||||||
url: this.notes_path,
|
data: NoteList.target_params + "&loading_new=1&after_id=" + (NoteList.reversed ? NoteList.top_id : NoteList.bottom_id),
|
||||||
data: this.target_params + "&loading_new=1&after_id=" + (this.reversed ? this.top_id : this.bottom_id),
|
dataType: "script"
|
||||||
dataType: "script"});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called in response to getNew().
|
* Called in response to getNew().
|
||||||
* Replaces the content of #new-notes-list with the given html.
|
* Replaces the content of #new-notes-list with the given html.
|
||||||
|
*
|
||||||
|
* Note: only used on wall.
|
||||||
*/
|
*/
|
||||||
replaceNewNotes:
|
replaceNewNotes: function(newNoteIds, html) {
|
||||||
function(html) {
|
|
||||||
$("#new-notes-list").html(html);
|
$("#new-notes-list").html(html);
|
||||||
this.updateVotes();
|
NoteList.updateVotes();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a single note to #new-notes-list.
|
* Adds a single common note to #notes-list.
|
||||||
*/
|
*/
|
||||||
appendNewNote:
|
appendNewNote: function(id, html) {
|
||||||
function(id, html) {
|
$("#notes-list").append(html);
|
||||||
if (this.reversed) {
|
NoteList.updateVotes();
|
||||||
$("#new-notes-list").prepend(html);
|
},
|
||||||
} else {
|
|
||||||
$("#new-notes-list").append(html);
|
/**
|
||||||
|
* Adds a single discussion note to #notes-list.
|
||||||
|
*
|
||||||
|
* Also removes the corresponding form.
|
||||||
|
*/
|
||||||
|
appendNewDiscussionNote: function(discussionId, diffRowHtml, noteHtml) {
|
||||||
|
var form = $("form[rel='"+discussionId+"']");
|
||||||
|
var row = form.closest("tr");
|
||||||
|
|
||||||
|
// is this the first note of discussion?
|
||||||
|
if (row.is(".js-temp-notes-holder")) {
|
||||||
|
// insert the note and the reply button after the temp row
|
||||||
|
row.after(diffRowHtml);
|
||||||
|
// remove the note (will be added again below)
|
||||||
|
row.next().find(".note").remove();
|
||||||
}
|
}
|
||||||
this.updateVotes();
|
|
||||||
|
// append new note to all matching discussions
|
||||||
|
$(".notes[rel='"+discussionId+"']").append(noteHtml);
|
||||||
|
|
||||||
|
// cleanup after successfully creating a diff/discussion note
|
||||||
|
$.proxy(NoteList.removeDiscussionNoteForm, form).call();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single wall note to #new-notes-list.
|
||||||
|
*
|
||||||
|
* Note: only used on wall.
|
||||||
|
*/
|
||||||
|
appendNewWallNote: function(id, html) {
|
||||||
|
$("#new-notes-list").prepend(html);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in response the main target form has been successfully submitted.
|
||||||
|
*
|
||||||
|
* Removes any errors.
|
||||||
|
* Resets text and preview.
|
||||||
|
* Resets buttons.
|
||||||
|
*/
|
||||||
|
resetMainTargetForm: function(){
|
||||||
|
var form = $(this);
|
||||||
|
|
||||||
|
// remove validation errors
|
||||||
|
form.find(".js-errors").remove();
|
||||||
|
|
||||||
|
// reset text and preview
|
||||||
|
var previewContainer = form.find(".js-toggler-container.note_text_and_preview");
|
||||||
|
if (previewContainer.is(".on")) {
|
||||||
|
previewContainer.removeClass("on");
|
||||||
|
}
|
||||||
|
form.find(".js-note-text").val("").trigger("input");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after an attachment file has been selected.
|
||||||
|
*
|
||||||
|
* Updates the file name for the selected attachment.
|
||||||
|
*/
|
||||||
|
updateFormAttachment: function() {
|
||||||
|
var form = $(this).closest("form");
|
||||||
|
|
||||||
|
// get only the basename
|
||||||
|
var filename = $(this).val().replace(/^.*[\\\/]/, '');
|
||||||
|
|
||||||
|
form.find(".js-attachment-filename").text(filename);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -249,10 +549,9 @@ var NoteList = {
|
||||||
* Might produce inaccurate results when not all notes have been loaded and a
|
* Might produce inaccurate results when not all notes have been loaded and a
|
||||||
* recalculation is triggered (e.g. when deleting a note).
|
* recalculation is triggered (e.g. when deleting a note).
|
||||||
*/
|
*/
|
||||||
updateVotes:
|
updateVotes: function() {
|
||||||
function() {
|
|
||||||
var votes = $("#votes .votes");
|
var votes = $("#votes .votes");
|
||||||
var notes = $("#notes-list, #new-notes-list").find(".note .vote");
|
var notes = $("#notes-list .note .vote");
|
||||||
|
|
||||||
// only update if there is a vote display
|
// only update if there is a vote display
|
||||||
if (votes.size()) {
|
if (votes.size()) {
|
||||||
|
@ -271,45 +570,3 @@ var NoteList = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var PerLineNotes = {
|
|
||||||
init:
|
|
||||||
function() {
|
|
||||||
/**
|
|
||||||
* Called when clicking on the "add note" or "reply" button for a diff line.
|
|
||||||
*
|
|
||||||
* Shows the note form below the line.
|
|
||||||
* Sets some hidden fields in the form.
|
|
||||||
*/
|
|
||||||
$(".diff_file_content").on("click", ".line_note_link, .line_note_reply_link", function(e) {
|
|
||||||
var form = $(".per_line_form");
|
|
||||||
$(this).closest("tr").after(form);
|
|
||||||
form.find("#note_line_code").val($(this).data("lineCode"));
|
|
||||||
form.show();
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
disableButtonIfEmptyField(".line-note-text", ".submit_inline_note");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called in response to successfully deleting a note on a diff line.
|
|
||||||
*
|
|
||||||
* Removes the actual note from view.
|
|
||||||
* Removes the reply button if the last note for that line has been removed.
|
|
||||||
*/
|
|
||||||
$(".diff_file_content").on("ajax:success", ".delete-note", function() {
|
|
||||||
var trNote = $(this).closest("tr");
|
|
||||||
trNote.fadeOut(function() {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
// check if this is the last note for this line
|
|
||||||
// elements must really be removed for this to work reliably
|
|
||||||
var trLine = trNote.prev();
|
|
||||||
var trRpl = trNote.next();
|
|
||||||
if (trLine.is(".line_holder") && trRpl.is(".reply")) {
|
|
||||||
trRpl.fadeOut(function() { $(this).remove(); });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -46,3 +46,8 @@
|
||||||
@import "themes/ui_gray.scss";
|
@import "themes/ui_gray.scss";
|
||||||
@import "themes/ui_color.scss";
|
@import "themes/ui_color.scss";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles for JS behaviors.
|
||||||
|
*/
|
||||||
|
@import "behaviors.scss";
|
||||||
|
|
||||||
|
|
14
app/assets/stylesheets/behaviors.scss
Normal file
14
app/assets/stylesheets/behaviors.scss
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Details
|
||||||
|
//--------
|
||||||
|
.js-details-container .content { display: none; }
|
||||||
|
.js-details-container .content.hide { display: block; }
|
||||||
|
.js-details-container.open .content { display: block; }
|
||||||
|
.js-details-container.open .content.hide { display: none; }
|
||||||
|
|
||||||
|
|
||||||
|
// Toggler
|
||||||
|
//--------
|
||||||
|
.js-toggler-container .turn-on { display: inherit; }
|
||||||
|
.js-toggler-container .turn-off { display: none; }
|
||||||
|
.js-toggler-container.on .turn-on { display: none; }
|
||||||
|
.js-toggler-container.on .turn-off { display: inherit; }
|
|
@ -546,3 +546,9 @@ h1.http_status_code {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img.emoji {
|
||||||
|
height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
|
@ -119,7 +119,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.old_line, .new_line {
|
.new_line,
|
||||||
|
.old_line,
|
||||||
|
.notes_line {
|
||||||
margin:0px;
|
margin:0px;
|
||||||
padding:0px;
|
padding:0px;
|
||||||
border:none;
|
border:none;
|
||||||
|
@ -134,6 +136,7 @@
|
||||||
moz-user-select: none;
|
moz-user-select: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
float: left;
|
float: left;
|
||||||
width: 35px;
|
width: 35px;
|
||||||
|
|
|
@ -1,42 +1,71 @@
|
||||||
/**
|
/**
|
||||||
* Notes
|
* Notes
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
#notes-list,
|
ul.notes {
|
||||||
#new-notes-list {
|
|
||||||
display: block;
|
display: block;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
|
||||||
|
|
||||||
.issue_notes,
|
.discussion-header,
|
||||||
.wiki_notes {
|
.note-header {
|
||||||
.note_content {
|
@extend .cgray;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
float: left;
|
float: left;
|
||||||
width: 400px;
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion-last-update,
|
||||||
|
.note-last-update {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.note-author {
|
||||||
|
color: $style_color;
|
||||||
|
font-weight: bold;
|
||||||
|
&:hover {
|
||||||
|
color: $primary_color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note textare */
|
.discussion {
|
||||||
#note_note {
|
padding: 8px 0;
|
||||||
height: 80px;
|
overflow: hidden;
|
||||||
width: 98%;
|
display: block;
|
||||||
font-size: 14px;
|
position:relative;
|
||||||
}
|
|
||||||
|
|
||||||
#new_note {
|
.discussion-body {
|
||||||
.attach_holder {
|
margin-left: 50px;
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview_note {
|
.diff_file,
|
||||||
margin: 2px;
|
.discussion-hidden,
|
||||||
border: 1px solid #ddd;
|
.notes {
|
||||||
padding: 10px;
|
@extend .borders;
|
||||||
min-height: 60px;
|
background-color: #F9F9F9;
|
||||||
background: #f5f5f5;
|
}
|
||||||
|
.diff_file .notes {
|
||||||
|
/* reset */
|
||||||
|
background: inherit;
|
||||||
|
border: none;
|
||||||
|
@include box-shadow(none);
|
||||||
|
|
||||||
|
}
|
||||||
|
.discussion-hidden .note {
|
||||||
|
@extend .cgray;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.notes .note {
|
||||||
|
border-color: #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.reply-btn {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note {
|
.note {
|
||||||
|
@ -44,125 +73,125 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
position:relative;
|
position:relative;
|
||||||
img {float: left; margin-right: 10px;}
|
|
||||||
img.emoji {float: none;margin: 0;}
|
|
||||||
.note-author cite{font-style: italic;}
|
|
||||||
p { color: $style_color; }
|
p { color: $style_color; }
|
||||||
.note-author { color: $style_color;}
|
|
||||||
|
|
||||||
.note-title { margin-left: 45px; padding-top: 5px;}
|
|
||||||
.avatar {
|
.avatar {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
.attachment {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: -20px;
|
||||||
|
|
||||||
.delete-note {
|
.icon-attachment {
|
||||||
|
@extend .icon-paper-clip;
|
||||||
|
font-size: 24px;
|
||||||
|
position: relative;
|
||||||
|
text-align: right;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.note-body {
|
||||||
|
margin-left: 45px;
|
||||||
|
}
|
||||||
|
.note-header {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// paint top or bottom borders depending on notes direction
|
||||||
|
&:not(.reversed) .note,
|
||||||
|
&:not(.reversed) .discussion {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
&.reversed .note,
|
||||||
|
&.reversed .discussion {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff_file .notes_holder {
|
||||||
|
font-family: $sansFontFamily;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-left: none;
|
||||||
|
|
||||||
|
&.notes_line {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
&.notes_content {
|
||||||
|
background-color: $white;
|
||||||
|
border-width: 1px 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-btn {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions for Discussions/Notes
|
||||||
|
*/
|
||||||
|
|
||||||
|
.discussion,
|
||||||
|
.note {
|
||||||
|
&.note:hover {
|
||||||
|
.note-actions { display: block; }
|
||||||
|
}
|
||||||
|
.discussion-header:hover {
|
||||||
|
.discussion-actions { display: block; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion-actions,
|
||||||
|
.note-actions {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
float: right;
|
||||||
|
|
||||||
|
[class^="icon-"],
|
||||||
|
[class*="icon-"] {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@extend .cgray;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary_color;
|
||||||
|
&.danger { @extend .cred; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.diff_file .note .note-actions {
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.delete-note { display: block; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#notes-list:not(.reversed) .note,
|
|
||||||
#new-notes-list:not(.reversed) .note {
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
#notes-list.reversed .note,
|
|
||||||
#new-notes-list.reversed .note {
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* mark vote notes */
|
|
||||||
.voting_notes .note {
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes-status {
|
|
||||||
margin: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
p.notify_controls input{
|
/**
|
||||||
margin: 5px;
|
* Line note button on the side of diffs
|
||||||
}
|
*/
|
||||||
|
|
||||||
p.notify_controls span{
|
.diff_file tr.line_holder {
|
||||||
font-weight: 700;
|
.add-diff-note {
|
||||||
}
|
background: url("diff_note_add.png") no-repeat left 0;
|
||||||
|
height: 22px;
|
||||||
tr.line_notes_row {
|
margin-left: -65px;
|
||||||
border-bottom: 1px solid #DDD;
|
|
||||||
border-left: 7px solid #2A79A3;
|
|
||||||
|
|
||||||
&.reply {
|
|
||||||
background: #eee;
|
|
||||||
border-left: 7px solid #2A79A3;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
td {
|
|
||||||
padding: 7px 10px;
|
|
||||||
}
|
|
||||||
a.line_note_reply_link {
|
|
||||||
border: 1px solid #eaeaea;
|
|
||||||
@include border-radius(4px);
|
|
||||||
padding: 3px 10px;
|
|
||||||
margin-left: 5px;
|
|
||||||
color: white;
|
|
||||||
background: #2A79A3;
|
|
||||||
border-color: #2A79A3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
li {
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
|
|
||||||
|
|
||||||
.per_line_form {
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
form { margin: 0; }
|
|
||||||
td {
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
.note_actions {
|
|
||||||
margin: 0;
|
|
||||||
padding-top: 10px;
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
float: left;
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
.options {
|
|
||||||
.labels {
|
|
||||||
float: left;
|
|
||||||
padding-left: 10px;
|
|
||||||
label {
|
|
||||||
padding: 6px 0;
|
|
||||||
margin: 0;
|
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td .line_note_link {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left:-70px;
|
width: 22px;
|
||||||
margin-top:-10px;
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
background: url("comment_add.png") no-repeat left 0;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
|
// "hide" it by default
|
||||||
opacity: 0.0;
|
opacity: 0.0;
|
||||||
filter: alpha(opacity=0);
|
filter: alpha(opacity=0);
|
||||||
|
|
||||||
|
@ -172,62 +201,114 @@ td .line_note_link {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff_file_content tr.line_holder:hover > td { background: $hover !important; }
|
// "show" the icon also if we just hover somwhere over the line
|
||||||
.diff_file_content tr.line_holder:hover > td .line_note_link {
|
&:hover > td {
|
||||||
|
background: $hover !important;
|
||||||
|
|
||||||
|
.add-diff-note {
|
||||||
opacity: 1.0;
|
opacity: 1.0;
|
||||||
filter: alpha(opacity=100);
|
filter: alpha(opacity=100);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note Form
|
||||||
|
*/
|
||||||
|
|
||||||
|
.comment-btn,
|
||||||
|
.reply-btn {
|
||||||
|
@extend .save-btn;
|
||||||
|
}
|
||||||
|
.diff_file,
|
||||||
|
.discussion {
|
||||||
.new_note {
|
.new_note {
|
||||||
.input-file {
|
margin: 8px 5px 8px 0;
|
||||||
font: 500px monospace;
|
|
||||||
opacity: 0;
|
|
||||||
filter: alpha(opacity=0);
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note_advanced_opts {
|
.note_options {
|
||||||
|
// because of the smaller width and the extra "cancel" button
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.new_note {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
float: left;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.clearfix {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.note_options {
|
||||||
h6 {
|
h6 {
|
||||||
line-height: 32px;
|
@extend .left;
|
||||||
padding-right: 15px;
|
line-height: 20px;
|
||||||
|
padding-right: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
label {
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachments {
|
.attachment {
|
||||||
|
@extend .right;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
overflow: hidden;
|
|
||||||
margin:0 0 5px !important;
|
margin:0 0 5px !important;
|
||||||
|
|
||||||
.input_file {
|
// hide the actual file field
|
||||||
.file_upload {
|
input {
|
||||||
position: absolute;
|
display: none;
|
||||||
right: 14px;
|
|
||||||
top: 7px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file_name {
|
.choose-btn {
|
||||||
line-height: 30px;
|
|
||||||
width: 240px;
|
|
||||||
height: 28px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.input-file {
|
|
||||||
width: 260px;
|
|
||||||
height: 41px;
|
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.notify_options {
|
||||||
|
@extend .right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.note_text_and_preview {
|
||||||
|
// makes the "absolute" position for links relative to this
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
// preview/edit buttons
|
||||||
|
> a {
|
||||||
|
font-size: 24px;
|
||||||
|
padding: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
.note_preview {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
@include border-radius(4px);
|
||||||
|
min-height: 80px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
.note_text {
|
||||||
|
border: 1px solid #DDD;
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 80px;
|
||||||
|
width: 98.6%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-text {
|
/* loading indicator */
|
||||||
border: 1px solid #DDD;
|
.notes-busy {
|
||||||
box-shadow: none;
|
margin: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-image-attach {
|
||||||
|
@extend .span4;
|
||||||
|
@extend .thumbnail;
|
||||||
|
margin-left: 45px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ module Notes
|
||||||
def execute
|
def execute
|
||||||
note = project.notes.new(params[:note])
|
note = project.notes.new(params[:note])
|
||||||
note.author = current_user
|
note.author = current_user
|
||||||
note.notify = true if params[:notify] == '1'
|
note.notify = params[:notify].present?
|
||||||
note.notify_author = true if params[:notify_author] == '1'
|
note.notify_author = params[:notify_author].present?
|
||||||
note.save
|
note.save
|
||||||
note
|
note
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,11 +9,11 @@ module Notes
|
||||||
|
|
||||||
@notes = case target_type
|
@notes = case target_type
|
||||||
when "commit"
|
when "commit"
|
||||||
project.notes.for_commit_id(target_id).not_inline.fresh.limit(20)
|
project.notes.for_commit_id(target_id).not_inline.fresh
|
||||||
when "issue"
|
when "issue"
|
||||||
project.issues.find(target_id).notes.inc_author.fresh.limit(20)
|
project.issues.find(target_id).notes.inc_author.fresh
|
||||||
when "merge_request"
|
when "merge_request"
|
||||||
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh.limit(20)
|
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
|
||||||
when "snippet"
|
when "snippet"
|
||||||
project.snippets.find(target_id).notes.fresh
|
project.snippets.find(target_id).notes.fresh
|
||||||
when "wall"
|
when "wall"
|
||||||
|
|
|
@ -14,10 +14,16 @@ class CommitController < ProjectResourceController
|
||||||
git_not_found! unless @commit
|
git_not_found! unless @commit
|
||||||
|
|
||||||
@suppress_diff = result[:suppress_diff]
|
@suppress_diff = result[:suppress_diff]
|
||||||
|
|
||||||
@note = result[:note]
|
@note = result[:note]
|
||||||
@line_notes = result[:line_notes]
|
@line_notes = result[:line_notes]
|
||||||
@notes_count = result[:notes_count]
|
@notes_count = result[:notes_count]
|
||||||
@comments_allowed = true
|
@target_type = :commit
|
||||||
|
@target_id = @commit.id
|
||||||
|
|
||||||
|
@comments_allowed = @reply_allowed = true
|
||||||
|
@comments_target = { noteable_type: 'Commit',
|
||||||
|
commit_id: @commit.id }
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
|
|
|
@ -35,6 +35,8 @@ class IssuesController < ProjectResourceController
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@note = @project.notes.new(noteable: @issue)
|
@note = @project.notes.new(noteable: @issue)
|
||||||
|
@target_type = :issue
|
||||||
|
@target_id = @issue.id
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
|
|
|
@ -18,6 +18,9 @@ class MergeRequestsController < ProjectResourceController
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@target_type = :merge_request
|
||||||
|
@target_id = @merge_request.id
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
format.js
|
format.js
|
||||||
|
@ -31,7 +34,9 @@ class MergeRequestsController < ProjectResourceController
|
||||||
@diffs = @merge_request.diffs
|
@diffs = @merge_request.diffs
|
||||||
@commit = @merge_request.last_commit
|
@commit = @merge_request.last_commit
|
||||||
|
|
||||||
@comments_allowed = true
|
@comments_allowed = @reply_allowed = true
|
||||||
|
@comments_target = { noteable_type: 'MergeRequest',
|
||||||
|
noteable_id: @merge_request.id }
|
||||||
@line_notes = @merge_request.notes.where("line_code is not null")
|
@line_notes = @merge_request.notes.where("line_code is not null")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,12 @@ class NotesController < ProjectResourceController
|
||||||
respond_to :js
|
respond_to :js
|
||||||
|
|
||||||
def index
|
def index
|
||||||
notes
|
@notes = Notes::LoadContext.new(project, current_user, params).execute
|
||||||
|
@target_type = params[:target_type].camelize
|
||||||
|
@target_id = params[:target_id]
|
||||||
|
|
||||||
if params[:target_type] == "merge_request"
|
if params[:target_type] == "merge_request"
|
||||||
@mixed_targets = true
|
@discussions = discussions_from_notes
|
||||||
@main_target_type = params[:target_type].camelize
|
|
||||||
end
|
end
|
||||||
|
|
||||||
respond_with(@notes)
|
respond_with(@notes)
|
||||||
|
@ -17,6 +19,8 @@ class NotesController < ProjectResourceController
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@note = Notes::CreateContext.new(project, current_user, params).execute
|
@note = Notes::CreateContext.new(project, current_user, params).execute
|
||||||
|
@target_type = params[:target_type].camelize
|
||||||
|
@target_id = params[:target_id]
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html {redirect_to :back}
|
format.html {redirect_to :back}
|
||||||
|
@ -40,7 +44,34 @@ class NotesController < ProjectResourceController
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def notes
|
def discussion_notes_for(note)
|
||||||
@notes = Notes::LoadContext.new(project, current_user, params).execute
|
@notes.select do |other_note|
|
||||||
|
note.discussion_id == other_note.discussion_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def discussions_from_notes
|
||||||
|
discussion_ids = []
|
||||||
|
discussions = []
|
||||||
|
|
||||||
|
@notes.each do |note|
|
||||||
|
next if discussion_ids.include?(note.discussion_id)
|
||||||
|
|
||||||
|
# don't group notes for the main target
|
||||||
|
if note_for_main_target?(note)
|
||||||
|
discussions << [note]
|
||||||
|
else
|
||||||
|
discussions << discussion_notes_for(note)
|
||||||
|
discussion_ids << note.discussion_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
discussions
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helps to distinguish e.g. commit notes in mr notes list
|
||||||
|
def note_for_main_target?(note)
|
||||||
|
note.for_wall? ||
|
||||||
|
(@target_type.camelize == note.noteable_type && !note.for_diff_line?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,7 +80,10 @@ class ProjectsController < ProjectResourceController
|
||||||
|
|
||||||
def wall
|
def wall
|
||||||
return render_404 unless @project.wall_enabled
|
return render_404 unless @project.wall_enabled
|
||||||
@note = Note.new
|
|
||||||
|
@target_type = :wall
|
||||||
|
@target_id = nil
|
||||||
|
@note = @project.notes.new
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
|
|
|
@ -50,6 +50,8 @@ class SnippetsController < ProjectResourceController
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@note = @project.notes.new(noteable: @snippet)
|
@note = @project.notes.new(noteable: @snippet)
|
||||||
|
@target_type = :snippet
|
||||||
|
@target_id = @snippet.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|
|
@ -9,11 +9,13 @@ module CommitsHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_line_anchor(index, line_new, line_old)
|
def build_line_anchor(diff, line_new, line_old)
|
||||||
"#{index}_#{line_old}_#{line_new}"
|
"#{hexdigest(diff.new_path)}_#{line_old}_#{line_new}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def each_diff_line(diff_arr, index)
|
def each_diff_line(diff, index)
|
||||||
|
diff_arr = diff.diff.lines.to_a
|
||||||
|
|
||||||
line_old = 1
|
line_old = 1
|
||||||
line_new = 1
|
line_new = 1
|
||||||
type = nil
|
type = nil
|
||||||
|
@ -39,7 +41,7 @@ module CommitsHelper
|
||||||
next
|
next
|
||||||
else
|
else
|
||||||
type = identification_type(line)
|
type = identification_type(line)
|
||||||
line_code = build_line_anchor(index, line_new, line_old)
|
line_code = build_line_anchor(diff, line_new, line_old)
|
||||||
yield(full_line, type, line_code, line_new, line_old)
|
yield(full_line, type, line_code, line_new, line_old)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,27 @@
|
||||||
module NotesHelper
|
module NotesHelper
|
||||||
|
# Helps to distinguish e.g. commit notes in mr notes list
|
||||||
|
def note_for_main_target?(note)
|
||||||
|
note.for_wall? ||
|
||||||
|
(@target_type.camelize == note.noteable_type && !note.for_diff_line?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def note_target_fields
|
||||||
|
hidden_field_tag(:target_type, @target_type) +
|
||||||
|
hidden_field_tag(:target_id, @target_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def link_to_commit_diff_line_note(note)
|
||||||
|
if note.for_commit_diff_line?
|
||||||
|
link_to "#{note.diff_file_name}:L#{note.diff_new_line}", project_commit_path(@project, note.noteable, anchor: note.line_code)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def link_to_merge_request_diff_line_note(note)
|
||||||
|
if note.for_merge_request_diff_line? and note.diff
|
||||||
|
link_to "#{note.diff_file_name}:L#{note.diff_new_line}", diffs_project_merge_request_path(note.project, note.noteable_id, anchor: note.line_code)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def loading_more_notes?
|
def loading_more_notes?
|
||||||
params[:loading_more].present?
|
params[:loading_more].present?
|
||||||
end
|
end
|
||||||
|
@ -6,19 +29,4 @@ module NotesHelper
|
||||||
def loading_new_notes?
|
def loading_new_notes?
|
||||||
params[:loading_new].present?
|
params[:loading_new].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Helps to distinguish e.g. commit notes in mr notes list
|
|
||||||
def note_for_main_target?(note)
|
|
||||||
!@mixed_targets || @main_target_type == note.noteable_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def link_to_commit_diff_line_note(note)
|
|
||||||
commit = note.noteable
|
|
||||||
diff_index, diff_old_line, diff_new_line = note.line_code.split('_')
|
|
||||||
|
|
||||||
link_file = commit.diffs[diff_index.to_i].new_path
|
|
||||||
link_line = diff_new_line
|
|
||||||
|
|
||||||
link_to "#{link_file}:L#{link_line}", project_commit_path(@project, commit, anchor: note.line_code)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,12 +63,12 @@ class Notify < ActionMailer::Base
|
||||||
# Note
|
# Note
|
||||||
#
|
#
|
||||||
|
|
||||||
def note_commit_email(recipient_id, note_id)
|
def note_commit_email(commit_autor_email, note_id)
|
||||||
@note = Note.find(note_id)
|
@note = Note.find(note_id)
|
||||||
@commit = @note.noteable
|
@commit = @note.noteable
|
||||||
@commit = CommitDecorator.decorate(@commit)
|
@commit = CommitDecorator.decorate(@commit)
|
||||||
@project = @note.project
|
@project = @note.project
|
||||||
mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title))
|
mail(to: recipient(commit_autor_email), subject: subject("note for commit #{@commit.short_id}", @commit.title))
|
||||||
end
|
end
|
||||||
|
|
||||||
def note_issue_email(recipient_id, note_id)
|
def note_issue_email(recipient_id, note_id)
|
||||||
|
|
|
@ -69,18 +69,9 @@ module Issuable
|
||||||
closed_changed? && !closed
|
closed_changed? && !closed
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the number of +1 comments (upvotes)
|
#
|
||||||
def upvotes
|
# Votes
|
||||||
notes.select(&:upvote?).size
|
#
|
||||||
end
|
|
||||||
|
|
||||||
def upvotes_in_percent
|
|
||||||
if votes_count.zero?
|
|
||||||
0
|
|
||||||
else
|
|
||||||
100.0 / votes_count * upvotes
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the number of -1 comments (downvotes)
|
# Return the number of -1 comments (downvotes)
|
||||||
def downvotes
|
def downvotes
|
||||||
|
@ -95,6 +86,19 @@ module Issuable
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return the number of +1 comments (upvotes)
|
||||||
|
def upvotes
|
||||||
|
notes.select(&:upvote?).size
|
||||||
|
end
|
||||||
|
|
||||||
|
def upvotes_in_percent
|
||||||
|
if votes_count.zero?
|
||||||
|
0
|
||||||
|
else
|
||||||
|
100.0 / votes_count * upvotes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Return the total number of votes
|
# Return the total number of votes
|
||||||
def votes_count
|
def votes_count
|
||||||
upvotes + downvotes
|
upvotes + downvotes
|
||||||
|
|
|
@ -19,7 +19,6 @@ require 'carrierwave/orm/activerecord'
|
||||||
require 'file_size_validator'
|
require 'file_size_validator'
|
||||||
|
|
||||||
class Note < ActiveRecord::Base
|
class Note < ActiveRecord::Base
|
||||||
|
|
||||||
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
|
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
|
||||||
:attachment, :line_code, :commit_id
|
:attachment, :line_code, :commit_id
|
||||||
|
|
||||||
|
@ -34,6 +33,7 @@ class Note < ActiveRecord::Base
|
||||||
delegate :name, :email, to: :author, prefix: true
|
delegate :name, :email, to: :author, prefix: true
|
||||||
|
|
||||||
validates :note, :project, presence: true
|
validates :note, :project, presence: true
|
||||||
|
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
|
||||||
validates :attachment, file_size: { maximum: 10.megabytes.to_i }
|
validates :attachment, file_size: { maximum: 10.megabytes.to_i }
|
||||||
|
|
||||||
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
|
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
|
||||||
|
@ -60,12 +60,74 @@ class Note < ActiveRecord::Base
|
||||||
}, without_protection: true)
|
}, without_protection: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def notify
|
def commit_author
|
||||||
@notify ||= false
|
@commit_author ||=
|
||||||
|
project.users.find_by_email(noteable.author_email) ||
|
||||||
|
project.users.find_by_name(noteable.author_name)
|
||||||
|
rescue
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def notify_author
|
def diff
|
||||||
@notify_author ||= false
|
if noteable.diffs.present?
|
||||||
|
noteable.diffs.select do |d|
|
||||||
|
if d.b_path
|
||||||
|
Digest::SHA1.hexdigest(d.b_path) == diff_file_index
|
||||||
|
end
|
||||||
|
end.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def diff_file_index
|
||||||
|
line_code.split('_')[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def diff_file_name
|
||||||
|
diff.b_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def diff_new_line
|
||||||
|
line_code.split('_')[2].to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def discussion_id
|
||||||
|
@discussion_id ||= [:discussion, noteable_type.try(:underscore), noteable_id, line_code].join("-").to_sym
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns true if this is a downvote note,
|
||||||
|
# otherwise false is returned
|
||||||
|
def downvote?
|
||||||
|
votable? && (note.start_with?('-1') ||
|
||||||
|
note.start_with?(':-1:')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_commit?
|
||||||
|
noteable_type == "Commit"
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_commit_diff_line?
|
||||||
|
for_commit? && for_diff_line?
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_diff_line?
|
||||||
|
line_code.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_issue?
|
||||||
|
noteable_type == "Issue"
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_merge_request?
|
||||||
|
noteable_type == "MergeRequest"
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_merge_request_diff_line?
|
||||||
|
for_merge_request? && for_diff_line?
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_wall?
|
||||||
|
noteable_type.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
# override to return commits, which are not active record
|
# override to return commits, which are not active record
|
||||||
|
@ -81,50 +143,24 @@ class Note < ActiveRecord::Base
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check if we can notify commit author
|
def notify
|
||||||
# with email about our comment
|
@notify ||= false
|
||||||
#
|
|
||||||
# If commit author email exist in project
|
|
||||||
# and commit author is not passed user we can
|
|
||||||
# send email to him
|
|
||||||
#
|
|
||||||
# params:
|
|
||||||
# user - current user
|
|
||||||
#
|
|
||||||
# return:
|
|
||||||
# Boolean
|
|
||||||
#
|
|
||||||
def notify_only_author?(user)
|
|
||||||
for_commit? && commit_author &&
|
|
||||||
commit_author.email != user.email
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def for_commit?
|
def notify_author
|
||||||
noteable_type == "Commit"
|
@notify_author ||= false
|
||||||
end
|
|
||||||
|
|
||||||
def for_diff_line?
|
|
||||||
line_code.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def commit_author
|
|
||||||
@commit_author ||=
|
|
||||||
project.users.find_by_email(noteable.author_email) ||
|
|
||||||
project.users.find_by_name(noteable.author_name)
|
|
||||||
rescue
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if this is an upvote note,
|
# Returns true if this is an upvote note,
|
||||||
# otherwise false is returned
|
# otherwise false is returned
|
||||||
def upvote?
|
def upvote?
|
||||||
note.start_with?('+1') || note.start_with?(':+1:')
|
votable? && (note.start_with?('+1') ||
|
||||||
|
note.start_with?(':+1:')
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if this is a downvote note,
|
def votable?
|
||||||
# otherwise false is returned
|
for_issue? || (for_merge_request? && !for_diff_line?)
|
||||||
def downvote?
|
|
||||||
note.start_with?('-1') || note.start_with?(':-1:')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def noteable_type_name
|
def noteable_type_name
|
||||||
|
|
|
@ -11,7 +11,7 @@ class NoteObserver < ActiveRecord::Observer
|
||||||
notify_team(note)
|
notify_team(note)
|
||||||
elsif note.notify_author
|
elsif note.notify_author
|
||||||
# Notify only author of resource
|
# Notify only author of resource
|
||||||
Notify.delay.note_commit_email(note.commit_author.id, note.id)
|
Notify.delay.note_commit_email(note.noteable.author_email, note.id)
|
||||||
else
|
else
|
||||||
# Otherwise ignore it
|
# Otherwise ignore it
|
||||||
nil
|
nil
|
||||||
|
|
|
@ -7,12 +7,10 @@
|
||||||
%span.cred #{@commit.stats.deletions} deletions
|
%span.cred #{@commit.stats.deletions} deletions
|
||||||
|
|
||||||
= render "commits/diffs", diffs: @commit.diffs
|
= render "commits/diffs", diffs: @commit.diffs
|
||||||
= render "notes/notes_with_form", tid: @commit.id, tt: "commit"
|
= render "notes/notes_with_form"
|
||||||
= render "notes/per_line_form"
|
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
$(function(){
|
$(function(){
|
||||||
PerLineNotes.init();
|
|
||||||
var w, h;
|
var w, h;
|
||||||
$('.diff_file').each(function(){
|
$('.diff_file').each(function(){
|
||||||
$('.image.diff_removed img', this).on('load', $.proxy(function(event){
|
$('.image.diff_removed img', this).on('load', $.proxy(function(event){
|
||||||
|
|
|
@ -38,10 +38,10 @@
|
||||||
|
|
||||||
%br/
|
%br/
|
||||||
.diff_file_content
|
.diff_file_content
|
||||||
-# Skipp all non non-supported blobs
|
-# Skip all non-supported blobs
|
||||||
- next unless file.respond_to?('text?')
|
- next unless file.respond_to?('text?')
|
||||||
- if file.text?
|
- if file.text?
|
||||||
= render "commits/text_file", diff: diff, index: i
|
= render "commits/text_diff", diff: diff, index: i
|
||||||
- elsif file.image?
|
- elsif file.image?
|
||||||
- old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil?
|
- old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil?
|
||||||
- if diff.renamed_file || diff.new_file || diff.deleted_file
|
- if diff.renamed_file || diff.new_file || diff.deleted_file
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
%a.supp_diff_link Diff suppressed. Click to show
|
%a.supp_diff_link Diff suppressed. Click to show
|
||||||
|
|
||||||
%table{class: "#{'hide' if too_big}"}
|
%table{class: "#{'hide' if too_big}"}
|
||||||
- each_diff_line(diff.diff.lines.to_a, index) do |line, type, line_code, line_new, line_old|
|
- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old|
|
||||||
%tr.line_holder{ id: line_code }
|
%tr.line_holder{ id: line_code }
|
||||||
- if type == "match"
|
- if type == "match"
|
||||||
%td.old_line= "..."
|
%td.old_line= "..."
|
||||||
|
@ -13,11 +13,11 @@
|
||||||
%td.old_line
|
%td.old_line
|
||||||
= link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
|
= link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
|
||||||
- if @comments_allowed
|
- if @comments_allowed
|
||||||
= render "notes/per_line_note_link", line_code: line_code
|
= render "notes/diff_note_link", line_code: line_code
|
||||||
%td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code
|
%td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code
|
||||||
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
|
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
|
||||||
|
|
||||||
- if @comments_allowed
|
- if @reply_allowed
|
||||||
- comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
|
- comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
|
||||||
- unless comments.empty?
|
- unless comments.empty?
|
||||||
= render "notes/per_line_notes_with_reply", notes: comments
|
= render "notes/diff_notes_with_reply", notes: comments
|
|
@ -56,4 +56,4 @@
|
||||||
= markdown @issue.description
|
= markdown @issue.description
|
||||||
|
|
||||||
|
|
||||||
.issue_notes.voting_notes#notes= render "notes/notes_with_form", tid: @issue.id, tt: "issue"
|
.voting_notes#notes= render "notes/notes_with_form"
|
||||||
|
|
|
@ -12,20 +12,18 @@
|
||||||
%li.notes-tab{data: {action: 'notes'}}
|
%li.notes-tab{data: {action: 'notes'}}
|
||||||
= link_to project_merge_request_path(@project, @merge_request) do
|
= link_to project_merge_request_path(@project, @merge_request) do
|
||||||
%i.icon-comment
|
%i.icon-comment
|
||||||
Comments
|
Discussion
|
||||||
%li.diffs-tab{data: {action: 'diffs'}}
|
%li.diffs-tab{data: {action: 'diffs'}}
|
||||||
= link_to diffs_project_merge_request_path(@project, @merge_request) do
|
= link_to diffs_project_merge_request_path(@project, @merge_request) do
|
||||||
%i.icon-list-alt
|
%i.icon-list-alt
|
||||||
Diff
|
Diff
|
||||||
|
|
||||||
.notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
|
.notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
|
||||||
= render("notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")
|
= render "notes/notes_with_form"
|
||||||
.diffs.tab-content
|
.diffs.tab-content
|
||||||
= render "merge_requests/show/diffs" if @diffs
|
= render "merge_requests/show/diffs" if @diffs
|
||||||
.status
|
.status
|
||||||
|
|
||||||
= render "notes/per_line_form"
|
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
var merge_request;
|
var merge_request;
|
||||||
$(function(){
|
$(function(){
|
||||||
|
|
|
@ -1,6 +1 @@
|
||||||
= render "show"
|
= render "show"
|
||||||
|
|
||||||
:javascript
|
|
||||||
$(function(){
|
|
||||||
PerLineNotes.init();
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
:plain
|
:plain
|
||||||
merge_request.$(".diffs").html("#{escape_javascript(render(partial: "merge_requests/show/diffs"))}");
|
merge_request.$(".diffs").html("#{escape_javascript(render(partial: "merge_requests/show/diffs"))}");
|
||||||
|
|
||||||
PerLineNotes.init();
|
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
:plain
|
:plain
|
||||||
merge_request.$(".notes").html("#{escape_javascript(render "notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")}");
|
merge_request.$(".notes").html("#{escape_javascript(render "notes/notes_with_form")}");
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
.note-form-holder
|
|
||||||
= form_for [@project, @note], remote: "true", multipart: true do |f|
|
|
||||||
%h3.page_title Leave a comment
|
|
||||||
-if @note.errors.any?
|
|
||||||
.alert-message.block-message.error
|
|
||||||
- @note.errors.full_messages.each do |msg|
|
|
||||||
%div= msg
|
|
||||||
|
|
||||||
= f.hidden_field :noteable_id
|
|
||||||
= f.hidden_field :commit_id
|
|
||||||
= f.hidden_field :noteable_type
|
|
||||||
= f.text_area :note, size: 255, class: 'note-text js-gfm-input'
|
|
||||||
#preview-note.preview_note.hide
|
|
||||||
.hint
|
|
||||||
.right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
|
|
||||||
.clearfix
|
|
||||||
|
|
||||||
.row.note_advanced_opts
|
|
||||||
.span3
|
|
||||||
= f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"
|
|
||||||
= link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'
|
|
||||||
.span4.notify_opts
|
|
||||||
%h6.left Notify via email:
|
|
||||||
= label_tag :notify do
|
|
||||||
= check_box_tag :notify, 1, @note.noteable_type != "Commit"
|
|
||||||
%span Project team
|
|
||||||
|
|
||||||
- if @note.notify_only_author?(current_user)
|
|
||||||
= label_tag :notify_author do
|
|
||||||
= check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
|
|
||||||
%span Commit author
|
|
||||||
.span5.attachments
|
|
||||||
%h6.left Attachment:
|
|
||||||
%span.file_name File name...
|
|
||||||
|
|
||||||
.input.input_file
|
|
||||||
%a.file_upload.btn.small Upload File
|
|
||||||
= f.file_field :attachment, class: "input-file"
|
|
||||||
%span.hint Any file less than 10 MB
|
|
|
@ -1,13 +0,0 @@
|
||||||
- if note.valid?
|
|
||||||
:plain
|
|
||||||
$(".note-form-holder .error").remove();
|
|
||||||
$('.note-form-holder textarea').val("");
|
|
||||||
$('.note-form-holder #preview-link').text('Preview');
|
|
||||||
$('.note-form-holder #preview-note').hide();
|
|
||||||
$('.note-form-holder').show();
|
|
||||||
NoteList.appendNewNote(#{note.id}, "#{escape_javascript(render "notes/note", note: note)}");
|
|
||||||
|
|
||||||
- else
|
|
||||||
:plain
|
|
||||||
$(".note-form-holder").replaceWith("#{escape_javascript(render 'notes/common_form')}");
|
|
||||||
GitLab.GfmAutoComplete.setup();
|
|
|
@ -1,19 +0,0 @@
|
||||||
- if note.valid?
|
|
||||||
:plain
|
|
||||||
// hide and reset the form
|
|
||||||
$(".per_line_form").hide();
|
|
||||||
$('.line-note-form-holder textarea').val("");
|
|
||||||
|
|
||||||
// find the reply button for this line
|
|
||||||
// (might not be there if this is the first note)
|
|
||||||
var trRpl = $("a.line_note_reply_link[data-line-code='#{note.line_code}']").closest("tr");
|
|
||||||
if (trRpl.size() == 0) {
|
|
||||||
// find the commented line ...
|
|
||||||
var trEl = $(".#{note.line_code}").parent();
|
|
||||||
// ... and insert the note and the reply button after it
|
|
||||||
trEl.after("#{escape_javascript(render "notes/per_line_reply_button", line_code: note.line_code)}");
|
|
||||||
trEl.after("#{escape_javascript(render "notes/per_line_note", note: note)}");
|
|
||||||
} else {
|
|
||||||
// instert new note before reply button
|
|
||||||
trRpl.before("#{escape_javascript(render "notes/per_line_note", note: note)}");
|
|
||||||
}
|
|
10
app/views/notes/_diff_note_link.html.haml
Normal file
10
app/views/notes/_diff_note_link.html.haml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
- note = @project.notes.new(@comments_target.merge({ line_code: line_code }))
|
||||||
|
= link_to "",
|
||||||
|
"javascript:;",
|
||||||
|
class: "add-diff-note js-add-diff-note-button",
|
||||||
|
data: { noteable_type: note.noteable_type,
|
||||||
|
noteable_id: note.noteable_id,
|
||||||
|
commit_id: note.commit_id,
|
||||||
|
line_code: note.line_code,
|
||||||
|
discussion_id: note.discussion_id },
|
||||||
|
title: "Add a comment to this line"
|
11
app/views/notes/_diff_notes_with_reply.html.haml
Normal file
11
app/views/notes/_diff_notes_with_reply.html.haml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
- note = notes.first # example note
|
||||||
|
%tr.notes_holder
|
||||||
|
%td.notes_line{ colspan: 2 }
|
||||||
|
%span.btn.disabled
|
||||||
|
%i.icon-comment
|
||||||
|
= notes.count
|
||||||
|
%td.notes_content
|
||||||
|
%ul.notes{ rel: note.discussion_id }
|
||||||
|
= render notes
|
||||||
|
|
||||||
|
= render "notes/discussion_reply_button", note: note
|
63
app/views/notes/_discussion.html.haml
Normal file
63
app/views/notes/_discussion.html.haml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
- note = discussion_notes.first
|
||||||
|
.discussion.js-details-container.js-toggler-container.open{ class: note.discussion_id }
|
||||||
|
.discussion-header
|
||||||
|
.discussion-actions
|
||||||
|
= link_to "javascript:;", class: "js-details-target turn-on js-toggler-target" do
|
||||||
|
%i.icon-eye-close
|
||||||
|
Hide discussion
|
||||||
|
= link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do
|
||||||
|
%i.icon-eye-open
|
||||||
|
Show discussion
|
||||||
|
= image_tag gravatar_icon(note.author.email), class: "avatar s32"
|
||||||
|
%div
|
||||||
|
= link_to note.author_name, project_team_member_path(@project, @project.team_member_by_id(note.author)), class: "note-author"
|
||||||
|
- if note.for_merge_request?
|
||||||
|
- if note.diff
|
||||||
|
started a discussion on this merge request diff
|
||||||
|
= link_to_merge_request_diff_line_note(note)
|
||||||
|
- else
|
||||||
|
started
|
||||||
|
%strong
|
||||||
|
%i.icon-remove
|
||||||
|
outdated
|
||||||
|
discussion on this merge request diff
|
||||||
|
- elsif note.for_commit?
|
||||||
|
started a discussion on commit
|
||||||
|
#{link_to note.noteable.short_id, project_commit_path(@project, note.noteable)}
|
||||||
|
= link_to_commit_diff_line_note(note) if note.for_diff_line?
|
||||||
|
- else
|
||||||
|
%cite.cgray started a discussion
|
||||||
|
%div
|
||||||
|
- last_note = discussion_notes.last
|
||||||
|
last updated by
|
||||||
|
= link_to last_note.author_name, project_team_member_path(@project, @project.team_member_by_id(last_note.author)), class: "note-author"
|
||||||
|
%span.discussion-last-update
|
||||||
|
= time_ago_in_words(last_note.updated_at)
|
||||||
|
ago
|
||||||
|
.discussion-body
|
||||||
|
- if note.for_diff_line?
|
||||||
|
- if note.diff
|
||||||
|
.content
|
||||||
|
.diff_file= render "notes/discussion_diff", discussion_notes: discussion_notes, note: note
|
||||||
|
- else
|
||||||
|
= link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion'
|
||||||
|
%div.hide.outdated-discussion
|
||||||
|
.content
|
||||||
|
.notes{ rel: discussion_notes.first.discussion_id }
|
||||||
|
= render discussion_notes
|
||||||
|
|
||||||
|
|
||||||
|
- else
|
||||||
|
.content
|
||||||
|
.notes{ rel: discussion_notes.first.discussion_id }
|
||||||
|
= render discussion_notes
|
||||||
|
= render "notes/discussion_reply_button", note: discussion_notes.first
|
||||||
|
|
||||||
|
-# will be shown when the other one is hidden
|
||||||
|
.discussion-hidden.content.hide
|
||||||
|
.note
|
||||||
|
%em Hidden discussion.
|
||||||
|
= link_to "javascript:;", class: "js-details-target js-toggler-target" do
|
||||||
|
%i.icon-eye-open
|
||||||
|
Show
|
||||||
|
|
25
app/views/notes/_discussion_diff.html.haml
Normal file
25
app/views/notes/_discussion_diff.html.haml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
- diff = note.diff
|
||||||
|
.diff_file_header
|
||||||
|
- if diff.deleted_file
|
||||||
|
%span= diff.old_path
|
||||||
|
- else
|
||||||
|
%span= diff.new_path
|
||||||
|
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
|
||||||
|
%span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
|
||||||
|
%br/
|
||||||
|
.diff_file_content
|
||||||
|
%table
|
||||||
|
- each_diff_line(diff, note.diff_file_index) do |line, type, line_code, line_new, line_old|
|
||||||
|
%tr.line_holder{ id: line_code }
|
||||||
|
- if type == "match"
|
||||||
|
%td.old_line= "..."
|
||||||
|
%td.new_line= "..."
|
||||||
|
%td.line_content.matched= line
|
||||||
|
- else
|
||||||
|
%td.old_line= raw(type == "new" ? " " : line_old)
|
||||||
|
%td.new_line= raw(type == "old" ? " " : line_new)
|
||||||
|
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} "
|
||||||
|
|
||||||
|
- if line_code == note.line_code
|
||||||
|
= render "notes/diff_notes_with_reply", notes: discussion_notes
|
||||||
|
- break # cut off diff after notes
|
10
app/views/notes/_discussion_reply_button.html.haml
Normal file
10
app/views/notes/_discussion_reply_button.html.haml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
= link_to "javascript:;",
|
||||||
|
class: "btn reply-btn js-discussion-reply-button",
|
||||||
|
data: { noteable_type: note.noteable_type,
|
||||||
|
noteable_id: note.noteable_id,
|
||||||
|
commit_id: note.commit_id,
|
||||||
|
line_code: note.line_code,
|
||||||
|
discussion_id: note.discussion_id },
|
||||||
|
title: "Add a reply" do
|
||||||
|
%i.icon-comment
|
||||||
|
Reply
|
44
app/views/notes/_form.html.haml
Normal file
44
app/views/notes/_form.html.haml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form" } do |f|
|
||||||
|
|
||||||
|
= note_target_fields
|
||||||
|
= f.hidden_field :commit_id
|
||||||
|
= f.hidden_field :line_code
|
||||||
|
= f.hidden_field :noteable_id
|
||||||
|
= f.hidden_field :noteable_type
|
||||||
|
|
||||||
|
.note_text_and_preview.js-toggler-container
|
||||||
|
%a.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", title: "Preview", data: {url: preview_project_notes_path(@project)} }
|
||||||
|
%i.icon-eye-open
|
||||||
|
%a.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;", title: "Edit" }
|
||||||
|
%i.icon-edit
|
||||||
|
|
||||||
|
= f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on'
|
||||||
|
.note_preview.js-note-preview.turn-off
|
||||||
|
|
||||||
|
.buttons
|
||||||
|
= f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button"
|
||||||
|
%a.btn.grouped.js-close-discussion-note-form Cancel
|
||||||
|
.hint
|
||||||
|
.right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
|
||||||
|
.clearfix
|
||||||
|
|
||||||
|
.note_options
|
||||||
|
.attachment
|
||||||
|
%h6 Attachment:
|
||||||
|
.file_name.js-attachment-filename File name...
|
||||||
|
%a.choose-btn.btn.small.js-choose-note-attachment-button Choose File ...
|
||||||
|
.hint Any file up to 10 MB
|
||||||
|
|
||||||
|
= f.file_field :attachment, class: "js-note-attachment-input"
|
||||||
|
|
||||||
|
.notify_options
|
||||||
|
%h6 Notify via email:
|
||||||
|
= label_tag :notify do
|
||||||
|
= check_box_tag :notify, 1, !@note.for_commit?
|
||||||
|
Project team
|
||||||
|
|
||||||
|
.js-notify-commit-author
|
||||||
|
= label_tag :notify_author do
|
||||||
|
= check_box_tag :notify_author, 1 , @note.for_commit?
|
||||||
|
Commit author
|
||||||
|
.clearfix
|
3
app/views/notes/_form_errors.html.haml
Normal file
3
app/views/notes/_form_errors.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.error_message.js-errors
|
||||||
|
- note.errors.full_messages.each do |msg|
|
||||||
|
%div= msg
|
|
@ -1,20 +1,19 @@
|
||||||
%li{id: dom_id(note), class: "note"}
|
%li{ id: dom_id(note), class: dom_class(note), data: { discussion: note.discussion_id } }
|
||||||
= image_tag gravatar_icon(note.author.email), class: "avatar s32"
|
.note-header
|
||||||
%div.note-author
|
.note-actions
|
||||||
%strong= note.author_name
|
|
||||||
= link_to "##{dom_id(note)}", name: dom_id(note) do
|
= link_to "##{dom_id(note)}", name: dom_id(note) do
|
||||||
%cite.cgray
|
%i.icon-link
|
||||||
|
Link here
|
||||||
|
|
||||||
|
- if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project)
|
||||||
|
= link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove comment?', remote: true, class: "danger js-note-delete" do
|
||||||
|
%i.icon-trash.cred
|
||||||
|
= image_tag gravatar_icon(note.author.email), class: "avatar s32"
|
||||||
|
= link_to note.author_name, project_team_member_path(@project, @project.team_member_by_id(note.author)), class: "note-author"
|
||||||
|
%span.note-last-update
|
||||||
= time_ago_in_words(note.updated_at)
|
= time_ago_in_words(note.updated_at)
|
||||||
ago
|
ago
|
||||||
|
|
||||||
- unless note_for_main_target?(note)
|
|
||||||
- if note.for_commit?
|
|
||||||
%span.cgray
|
|
||||||
on #{link_to note.noteable.short_id, project_commit_path(@project, note.noteable)}
|
|
||||||
= link_to_commit_diff_line_note(note) if note.for_diff_line?
|
|
||||||
|
|
||||||
-# only show vote if it's a note for the main target
|
|
||||||
- if note_for_main_target?(note)
|
|
||||||
- if note.upvote?
|
- if note.upvote?
|
||||||
%span.vote.upvote.label.label-success
|
%span.vote.upvote.label.label-success
|
||||||
%i.icon-thumbs-up
|
%i.icon-thumbs-up
|
||||||
|
@ -24,19 +23,15 @@
|
||||||
%i.icon-thumbs-down
|
%i.icon-thumbs-down
|
||||||
\-1
|
\-1
|
||||||
|
|
||||||
-# remove button
|
|
||||||
- if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project)
|
|
||||||
= link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do
|
|
||||||
%i.icon-trash
|
|
||||||
Remove
|
|
||||||
|
|
||||||
%div.note-title
|
.note-body
|
||||||
= preserve do
|
= preserve do
|
||||||
= markdown(note.note)
|
= markdown(note.note)
|
||||||
- if note.attachment.url
|
- if note.attachment.url
|
||||||
- if note.attachment.image?
|
- if note.attachment.image?
|
||||||
= image_tag note.attachment.url, class: 'thumbnail span4'
|
= image_tag note.attachment.url, class: 'note-image-attach'
|
||||||
.right
|
.attachment.right
|
||||||
%div.file
|
= link_to note.attachment.url, target: "_blank" do
|
||||||
= link_to note.attachment_identifier, note.attachment.url, target: "_blank"
|
%i.icon-attachment
|
||||||
|
= note.attachment_identifier
|
||||||
.clear
|
.clear
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
|
- if @discussions.present?
|
||||||
|
- @discussions.each do |discussion_notes|
|
||||||
|
- note = discussion_notes.first
|
||||||
|
- if note_for_main_target?(note)
|
||||||
|
= render discussion_notes
|
||||||
|
- else
|
||||||
|
= render 'discussion', discussion_notes: discussion_notes
|
||||||
|
- else
|
||||||
- @notes.each do |note|
|
- @notes.each do |note|
|
||||||
- next unless note.author
|
- next unless note.author
|
||||||
= render "note", note: note
|
= render 'note', note: note
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
%ul#notes-list
|
%ul#notes-list.notes
|
||||||
%ul#new-notes-list
|
.js-notes-busy
|
||||||
.notes-status
|
|
||||||
|
|
||||||
|
.js-main-target-form
|
||||||
- if can? current_user, :write_note, @project
|
- if can? current_user, :write_note, @project
|
||||||
= render "notes/common_form"
|
= render "notes/form"
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
$(function(){
|
$(function(){
|
||||||
NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}");
|
NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}");
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
%table{style: "display:none;"}
|
|
||||||
%tr.per_line_form
|
|
||||||
%td{colspan: 3 }
|
|
||||||
.line-note-form-holder
|
|
||||||
= form_for [@project, @note], remote: "true", multipart: true do |f|
|
|
||||||
%h3.page_title Leave a note
|
|
||||||
%div.span10
|
|
||||||
-if @note.errors.any?
|
|
||||||
.alert-message.block-message.error
|
|
||||||
- @note.errors.full_messages.each do |msg|
|
|
||||||
%div= msg
|
|
||||||
|
|
||||||
= f.hidden_field :noteable_id
|
|
||||||
= f.hidden_field :commit_id
|
|
||||||
= f.hidden_field :noteable_type
|
|
||||||
= f.hidden_field :line_code
|
|
||||||
= f.text_area :note, size: 255, class: 'line-note-text js-gfm-input'
|
|
||||||
.note_actions
|
|
||||||
.buttons
|
|
||||||
= f.submit 'Add note', class: "btn save-btn submit_note submit_inline_note", id: "submit_note"
|
|
||||||
= link_to "Cancel", "#", class: "btn hide-button"
|
|
||||||
.options
|
|
||||||
%h6.left Notify via email:
|
|
||||||
.labels
|
|
||||||
= label_tag :notify do
|
|
||||||
= check_box_tag :notify, 1, @note.noteable_type != "Commit"
|
|
||||||
%span Project team
|
|
||||||
|
|
||||||
- if @note.notify_only_author?(current_user)
|
|
||||||
= label_tag :notify_author do
|
|
||||||
= check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
|
|
||||||
%span Commit author
|
|
||||||
|
|
||||||
:javascript
|
|
||||||
$(function(){
|
|
||||||
$(".per_line_form .hide-button").bind("click", function(){
|
|
||||||
$('.per_line_form').hide();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,5 +0,0 @@
|
||||||
%tr.line_notes_row
|
|
||||||
%td{colspan: 3}
|
|
||||||
%ul
|
|
||||||
= render "notes/note", note: note
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
= link_to "", "#", class: "line_note_link", data: { line_code: line_code }, title: "Add note for this line"
|
|
|
@ -1,3 +0,0 @@
|
||||||
- notes.each do |note|
|
|
||||||
= render "notes/per_line_note", note: note
|
|
||||||
= render "notes/per_line_reply_button", line_code: notes.first.line_code
|
|
|
@ -1,4 +0,0 @@
|
||||||
%tr.line_notes_row.reply
|
|
||||||
%td{colspan: 3}
|
|
||||||
%i.icon-comment
|
|
||||||
= link_to "Reply", "#", class: "line_note_reply_link", data: { line_code: line_code }, title: "Add note for this line"
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
.js-main-target-form
|
||||||
- if can? current_user, :write_note, @project
|
- if can? current_user, :write_note, @project
|
||||||
= render "notes/common_form"
|
= render "notes/form"
|
||||||
|
|
||||||
%ul.reversed#new-notes-list
|
%ul#new-notes-list.reversed.notes
|
||||||
%ul.reversed#notes-list
|
%ul#notes-list.reversed.notes
|
||||||
.notes-status
|
.notes-busy.js-notes-busy
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
$(function(){
|
$(function(){
|
||||||
NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}");
|
NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}");
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,21 @@
|
||||||
- if @note.line_code
|
- if @note.valid?
|
||||||
= render "create_per_line_note", note: @note
|
var noteHtml = "#{escape_javascript(render "notes/note", note: @note)}";
|
||||||
- else
|
|
||||||
= render "create_common_note", note: @note
|
|
||||||
|
|
||||||
-# Enable submit button
|
- if note_for_main_target?(@note)
|
||||||
|
- if @note.for_wall?
|
||||||
|
NoteList.appendNewWallNote(#{@note.id}, noteHtml);
|
||||||
|
- else
|
||||||
|
NoteList.appendNewNote(#{@note.id}, noteHtml);
|
||||||
|
- else
|
||||||
:plain
|
:plain
|
||||||
$("#submit_note").removeAttr("disabled");
|
var firstDiscussionNoteHtml = "#{escape_javascript(render "notes/diff_notes_with_reply", notes: [@note])}";
|
||||||
|
NoteList.appendNewDiscussionNote("#{@note.discussion_id}",
|
||||||
|
firstDiscussionNoteHtml,
|
||||||
|
noteHtml);
|
||||||
|
|
||||||
|
- else
|
||||||
|
var errorsHtml = "#{escape_javascript(render 'notes/form_errors', note: @note)}";
|
||||||
|
- if note_for_main_target?(@note)
|
||||||
|
NoteList.errorsOnForm(errorsHtml);
|
||||||
|
- else
|
||||||
|
NoteList.errorsOnForm(errorsHtml, "#{@note.discussion_id}");
|
|
@ -1,17 +1,15 @@
|
||||||
- unless @notes.blank?
|
- unless @notes.blank?
|
||||||
|
var notesHtml = "#{escape_javascript(render 'notes/notes')}";
|
||||||
|
- new_note_ids = @notes.map(&:id)
|
||||||
- if loading_more_notes?
|
- if loading_more_notes?
|
||||||
:plain
|
NoteList.appendMoreNotes(#{new_note_ids}, notesHtml);
|
||||||
NoteList.appendMoreNotes(#{@notes.last.id}, "#{escape_javascript(render 'notes/notes')}");
|
|
||||||
|
|
||||||
- elsif loading_new_notes?
|
- elsif loading_new_notes?
|
||||||
:plain
|
NoteList.replaceNewNotes(#{new_note_ids}, notesHtml);
|
||||||
NoteList.replaceNewNotes("#{escape_javascript(render 'notes/notes')}");
|
|
||||||
|
|
||||||
- else
|
- else
|
||||||
:plain
|
NoteList.setContent(#{new_note_ids}, notesHtml);
|
||||||
NoteList.setContent(#{@notes.first.id}, #{@notes.last.id}, "#{escape_javascript(render 'notes/notes')}");
|
|
||||||
|
|
||||||
- else
|
- else
|
||||||
- if loading_more_notes?
|
- if loading_more_notes?
|
||||||
:plain
|
|
||||||
NoteList.finishedLoadingMore();
|
NoteList.finishedLoadingMore();
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
%div.wall_page
|
%div.wall_page
|
||||||
= render "notes/reversed_notes_with_form", tid: nil, tt: "wall"
|
= render "notes/reversed_notes_with_form"
|
||||||
|
|
|
@ -8,4 +8,4 @@
|
||||||
|
|
||||||
%br
|
%br
|
||||||
%div= render 'blob'
|
%div= render 'blob'
|
||||||
%div#notes= render "notes/notes_with_form", tid: @snippet.id, tt: "snippet"
|
%div#notes= render "notes/notes_with_form"
|
||||||
|
|
|
@ -1,10 +1,48 @@
|
||||||
Feature: Project Comment commit
|
Feature: Comments on commits
|
||||||
Background:
|
Background:
|
||||||
Given I sign in as a user
|
Given I sign in as a user
|
||||||
And I own project "Shop"
|
And I own project "Shop"
|
||||||
Given I visit project commit page
|
And I visit project commit page
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: I comment commit
|
Scenario: I can comment on a commit
|
||||||
Given I leave a comment like "XML attached"
|
Given I leave a comment like "XML attached"
|
||||||
Then I should see comment "XML attached"
|
Then I should see a comment saying "XML attached"
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can't cancel the main form
|
||||||
|
Then I should not see the cancel comment button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can't preview without text
|
||||||
|
Given I haven't written any comment text
|
||||||
|
Then I should not see the comment preview button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can preview with text
|
||||||
|
Given I write a comment like "Nice"
|
||||||
|
Then I should see the comment preview button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I preview a comment
|
||||||
|
Given I preview a comment text like "Bug fixed :smile:"
|
||||||
|
Then I should see the comment preview
|
||||||
|
And I should not see the comment text field
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can edit after preview
|
||||||
|
Given I preview a comment text like "Bug fixed :smile:"
|
||||||
|
Then I should see the comment edit button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I have a reset form after posting from preview
|
||||||
|
Given I preview a comment text like "Bug fixed :smile:"
|
||||||
|
And I submit the comment
|
||||||
|
Then I should see an empty comment text field
|
||||||
|
And I should not see the comment preview
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can delete a comment
|
||||||
|
Given I leave a comment like "XML attached"
|
||||||
|
And I delete a comment
|
||||||
|
Then I should not see a comment saying "XML attached"
|
||||||
|
|
91
features/project/commits/commit_diff_comments.feature
Normal file
91
features/project/commits/commit_diff_comments.feature
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
Feature: Comments on commit diffs
|
||||||
|
Background:
|
||||||
|
Given I sign in as a user
|
||||||
|
And I own project "Shop"
|
||||||
|
And I visit project commit page
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can access add diff comment buttons
|
||||||
|
Then I should see add a diff comment button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can comment on a commit diff
|
||||||
|
Given I leave a diff comment like "Typo, please fix"
|
||||||
|
Then I should see a diff comment saying "Typo, please fix"
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I get a temporary form for the first comment on a diff line
|
||||||
|
Given I open a diff comment form
|
||||||
|
Then I should see a temporary diff comment form
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I have a cancel button on the diff form
|
||||||
|
Given I open a diff comment form
|
||||||
|
Then I should see the cancel comment button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can cancel a diff form
|
||||||
|
Given I open a diff comment form
|
||||||
|
And I cancel the diff comment
|
||||||
|
Then I should not see the diff comment form
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can't open a second form for a diff line
|
||||||
|
Given I open a diff comment form
|
||||||
|
And I open a diff comment form
|
||||||
|
Then I should only see one diff form
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can have multiple forms
|
||||||
|
Given I open a diff comment form
|
||||||
|
And I write a diff comment like ":-1: I don't like this"
|
||||||
|
And I open another diff comment form
|
||||||
|
Then I should see a diff comment form with ":-1: I don't like this"
|
||||||
|
And I should see an empty diff comment form
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can preview multiple forms separately
|
||||||
|
Given I preview a diff comment text like "Should fix it :smile:"
|
||||||
|
And I preview another diff comment text like "DRY this up"
|
||||||
|
Then I should see two separate previews
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I have a reply button in discussions
|
||||||
|
Given I leave a diff comment like "Typo, please fix"
|
||||||
|
Then I should see a discussion reply button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can't preview without text
|
||||||
|
Given I open a diff comment form
|
||||||
|
And I haven't written any diff comment text
|
||||||
|
Then I should not see the diff comment preview button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can preview with text
|
||||||
|
Given I open a diff comment form
|
||||||
|
And I write a diff comment like ":-1: I don't like this"
|
||||||
|
Then I should see the diff comment preview button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I preview a diff comment
|
||||||
|
Given I preview a diff comment text like "Should fix it :smile:"
|
||||||
|
Then I should see the diff comment preview
|
||||||
|
And I should not see the diff comment text field
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can edit after preview
|
||||||
|
Given I preview a diff comment text like "Should fix it :smile:"
|
||||||
|
Then I should see the diff comment edit button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: The form gets removed after posting
|
||||||
|
Given I preview a diff comment text like "Should fix it :smile:"
|
||||||
|
And I submit the diff comment
|
||||||
|
Then I should not see the diff comment form
|
||||||
|
And I should see a discussion reply button
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can delete a discussion comment
|
||||||
|
Given I leave a diff comment like "Typo, please fix"
|
||||||
|
And I delete a diff comment
|
||||||
|
Then I should not see a diff comment saying "Typo, please fix"
|
|
@ -35,8 +35,34 @@ Feature: Project Merge Requests
|
||||||
Then I should see merge request "Wiki Feature"
|
Then I should see merge request "Wiki Feature"
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: I comment merge request
|
Scenario: I comment on a merge request
|
||||||
Given I visit merge request page "Bug NS-04"
|
Given I visit merge request page "Bug NS-04"
|
||||||
And I leave a comment like "XML attached"
|
And I leave a comment like "XML attached"
|
||||||
Then I should see comment "XML attached"
|
Then I should see comment "XML attached"
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I comment on a merge request diff
|
||||||
|
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
|
||||||
|
And I visit merge request page "Bug NS-05"
|
||||||
|
And I switch to the diff tab
|
||||||
|
And I leave a comment like "Line is wrong" on line 185 of the first file
|
||||||
|
And I switch to the merge request's comments tab
|
||||||
|
Then I should see a discussion has started on line 185
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I comment on a line of a commit in merge request
|
||||||
|
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
|
||||||
|
And I visit merge request page "Bug NS-05"
|
||||||
|
And I click on the first commit in the merge request
|
||||||
|
And I leave a comment like "Line is wrong" on line 185 of the first file
|
||||||
|
And I switch to the merge request's comments tab
|
||||||
|
Then I should see a discussion has started on commit bcf03b5de6c:L185
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I comment on a commit in merge request
|
||||||
|
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
|
||||||
|
And I visit merge request page "Bug NS-05"
|
||||||
|
And I click on the first commit in the merge request
|
||||||
|
And I leave a comment on the diff page
|
||||||
|
And I switch to the merge request's comments tab
|
||||||
|
Then I should see a discussion has started on commit bcf03b5de6c
|
||||||
|
|
6
features/steps/project/comments_on_commit_diffs.rb
Normal file
6
features/steps/project/comments_on_commit_diffs.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class CommentsOnCommitDiffs < Spinach::FeatureSteps
|
||||||
|
include SharedAuthentication
|
||||||
|
include SharedDiffNote
|
||||||
|
include SharedPaths
|
||||||
|
include SharedProject
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
class ProjectCommentCommit < Spinach::FeatureSteps
|
class CommentsOnCommits < Spinach::FeatureSteps
|
||||||
include SharedAuthentication
|
include SharedAuthentication
|
||||||
include SharedProject
|
|
||||||
include SharedNote
|
include SharedNote
|
||||||
include SharedPaths
|
include SharedPaths
|
||||||
|
include SharedProject
|
||||||
end
|
end
|
|
@ -4,40 +4,24 @@ class ProjectMergeRequests < Spinach::FeatureSteps
|
||||||
include SharedNote
|
include SharedNote
|
||||||
include SharedPaths
|
include SharedPaths
|
||||||
|
|
||||||
Then 'I should see "Bug NS-04" in merge requests' do
|
Given 'I click link "New Merge Request"' do
|
||||||
page.should have_content "Bug NS-04"
|
click_link "New Merge Request"
|
||||||
end
|
|
||||||
|
|
||||||
And 'I should not see "Feature NS-03" in merge requests' do
|
|
||||||
page.should_not have_content "Feature NS-03"
|
|
||||||
end
|
|
||||||
|
|
||||||
Given 'I click link "Closed"' do
|
|
||||||
click_link "Closed"
|
|
||||||
end
|
|
||||||
|
|
||||||
Then 'I should see "Feature NS-03" in merge requests' do
|
|
||||||
page.should have_content "Feature NS-03"
|
|
||||||
end
|
|
||||||
|
|
||||||
And 'I should not see "Bug NS-04" in merge requests' do
|
|
||||||
page.should_not have_content "Bug NS-04"
|
|
||||||
end
|
|
||||||
|
|
||||||
Given 'I click link "All"' do
|
|
||||||
click_link "All"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Given 'I click link "Bug NS-04"' do
|
Given 'I click link "Bug NS-04"' do
|
||||||
click_link "Bug NS-04"
|
click_link "Bug NS-04"
|
||||||
end
|
end
|
||||||
|
|
||||||
Then 'I should see merge request "Bug NS-04"' do
|
Given 'I click link "All"' do
|
||||||
page.should have_content "Bug NS-04"
|
click_link "All"
|
||||||
end
|
end
|
||||||
|
|
||||||
And 'I click link "Close"' do
|
Given 'I click link "Closed"' do
|
||||||
click_link "Close"
|
click_link "Closed"
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see merge request "Wiki Feature"' do
|
||||||
|
page.should have_content "Wiki Feature"
|
||||||
end
|
end
|
||||||
|
|
||||||
Then 'I should see closed merge request "Bug NS-04"' do
|
Then 'I should see closed merge request "Bug NS-04"' do
|
||||||
|
@ -46,8 +30,29 @@ class ProjectMergeRequests < Spinach::FeatureSteps
|
||||||
page.should have_content "Closed by"
|
page.should have_content "Closed by"
|
||||||
end
|
end
|
||||||
|
|
||||||
Given 'I click link "New Merge Request"' do
|
Then 'I should see merge request "Bug NS-04"' do
|
||||||
click_link "New Merge Request"
|
page.should have_content "Bug NS-04"
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see "Bug NS-04" in merge requests' do
|
||||||
|
page.should have_content "Bug NS-04"
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see "Feature NS-03" in merge requests' do
|
||||||
|
page.should have_content "Feature NS-03"
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'I should not see "Feature NS-03" in merge requests' do
|
||||||
|
page.should_not have_content "Feature NS-03"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
And 'I should not see "Bug NS-04" in merge requests' do
|
||||||
|
page.should_not have_content "Bug NS-04"
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'I click link "Close"' do
|
||||||
|
click_link "Close"
|
||||||
end
|
end
|
||||||
|
|
||||||
And 'I submit new merge request "Wiki Feature"' do
|
And 'I submit new merge request "Wiki Feature"' do
|
||||||
|
@ -57,24 +62,91 @@ class ProjectMergeRequests < Spinach::FeatureSteps
|
||||||
click_button "Submit merge request"
|
click_button "Submit merge request"
|
||||||
end
|
end
|
||||||
|
|
||||||
Then 'I should see merge request "Wiki Feature"' do
|
|
||||||
page.should have_content "Wiki Feature"
|
|
||||||
end
|
|
||||||
|
|
||||||
And 'project "Shop" have "Bug NS-04" open merge request' do
|
And 'project "Shop" have "Bug NS-04" open merge request' do
|
||||||
project = Project.find_by_name("Shop")
|
project = Project.find_by_name("Shop")
|
||||||
create(:merge_request,
|
create(:merge_request,
|
||||||
:title => "Bug NS-04",
|
title: "Bug NS-04",
|
||||||
:project => project,
|
project: project,
|
||||||
:author => project.users.first)
|
author: project.users.first)
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
|
||||||
|
project = Project.find_by_name("Shop")
|
||||||
|
create(:merge_request_with_diffs,
|
||||||
|
title: "Bug NS-05",
|
||||||
|
project: project,
|
||||||
|
author: project.users.first)
|
||||||
end
|
end
|
||||||
|
|
||||||
And 'project "Shop" have "Feature NS-03" closed merge request' do
|
And 'project "Shop" have "Feature NS-03" closed merge request' do
|
||||||
project = Project.find_by_name("Shop")
|
project = Project.find_by_name("Shop")
|
||||||
create(:merge_request,
|
create(:merge_request,
|
||||||
:title => "Feature NS-03",
|
title: "Feature NS-03",
|
||||||
:project => project,
|
project: project,
|
||||||
:author => project.users.first,
|
author: project.users.first,
|
||||||
:closed => true)
|
closed: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'I switch to the diff tab' do
|
||||||
|
mr = MergeRequest.find_by_title("Bug NS-05")
|
||||||
|
visit diffs_project_merge_request_path(mr.project, mr)
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'I switch to the merge request\'s comments tab' do
|
||||||
|
mr = MergeRequest.find_by_title("Bug NS-05")
|
||||||
|
visit project_merge_request_path(mr.project, mr)
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'I click on the first commit in the merge request' do
|
||||||
|
mr = MergeRequest.find_by_title("Bug NS-05")
|
||||||
|
click_link mr.commits.first.short_id(8)
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'I leave a comment on the diff page' do
|
||||||
|
within(:xpath, "//div[@class='note-form-holder']") do
|
||||||
|
fill_in "note_note", with: "One comment to rule them all"
|
||||||
|
click_button "Add Comment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
And 'I leave a comment like "Line is wrong" on line 185 of the first file' do
|
||||||
|
save_and_open_page
|
||||||
|
within(:xpath, "//div[@class='diff_file'][1]") do
|
||||||
|
click_link "add-diff-line-note-0_185_185"
|
||||||
|
end
|
||||||
|
|
||||||
|
within(:xpath, "//div[@class='line-note-form-holder']") do
|
||||||
|
fill_in "note_note", with: "Line is wrong"
|
||||||
|
click_button "Add Comment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a discussion has started on line 185' do
|
||||||
|
mr = MergeRequest.find_by_title("Bug NS-05")
|
||||||
|
first_commit = mr.commits.first
|
||||||
|
first_diff = mr.diffs.first
|
||||||
|
page.should have_content "#{current_user.name} started a discussion on this merge request diff"
|
||||||
|
page.should have_content "#{first_diff.b_path}:L185"
|
||||||
|
page.should have_content "Line is wrong"
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a discussion has started on commit bcf03b5de6c:L185' do
|
||||||
|
mr = MergeRequest.find_by_title("Bug NS-05")
|
||||||
|
first_commit = mr.commits.first
|
||||||
|
first_diff = mr.diffs.first
|
||||||
|
page.should have_content "#{current_user.name} started a discussion on commit"
|
||||||
|
page.should have_content first_commit.short_id(8)
|
||||||
|
page.should have_content "#{first_diff.b_path}:L185"
|
||||||
|
page.should have_content "Line is wrong"
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a discussion has started on commit bcf03b5de6c' do
|
||||||
|
mr = MergeRequest.find_by_title("Bug NS-05")
|
||||||
|
first_commit = mr.st_commits.first
|
||||||
|
first_diff = mr.diffs.first
|
||||||
|
page.should have_content "#{current_user.name} started a discussion on commit"
|
||||||
|
page.should have_content first_commit.short_id(8)
|
||||||
|
page.should have_content "One comment to rule them all"
|
||||||
|
page.should_not have_content "#{first_diff.b_path}:L185"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
158
features/steps/shared/diff_note.rb
Normal file
158
features/steps/shared/diff_note.rb
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
module SharedDiffNote
|
||||||
|
include Spinach::DSL
|
||||||
|
|
||||||
|
Given 'I cancel the diff comment' do
|
||||||
|
within(".diff_file") do
|
||||||
|
find(".js-close-discussion-note-form").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I delete a diff comment' do
|
||||||
|
within(".diff_file") do
|
||||||
|
first(".js-note-delete").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I haven\'t written any diff comment text' do
|
||||||
|
within(".diff_file") do
|
||||||
|
fill_in "note[note]", with: ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I leave a diff comment like "Typo, please fix"' do
|
||||||
|
find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
within(".diff_file") do
|
||||||
|
fill_in "note[note]", with: "Typo, please fix"
|
||||||
|
#click_button("Add Comment")
|
||||||
|
find(".js-comment-button").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I preview a diff comment text like "Should fix it :smile:"' do
|
||||||
|
find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
within(".diff_file") do
|
||||||
|
fill_in "note[note]", with: "Should fix it :smile:"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I preview another diff comment text like "DRY this up"' do
|
||||||
|
find("#586fb7c4e1add2d4d24e27566ed7064680098646_57_41.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
within(".diff_file") do
|
||||||
|
fill_in "note[note]", with: "DRY this up"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I open a diff comment form' do
|
||||||
|
find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I open another diff comment form' do
|
||||||
|
find("#586fb7c4e1add2d4d24e27566ed7064680098646_57_41.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I write a diff comment like ":-1: I don\'t like this"' do
|
||||||
|
within(".diff_file") do
|
||||||
|
fill_in "note[note]", with: ":-1: I don\'t like this"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I submit the diff comment' do
|
||||||
|
within(".diff_file") do
|
||||||
|
click_button("Add Comment")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Then 'I should not see the diff comment form' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should_not have_css("form.new_note")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should not see the diff comment preview button' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_css(".js-note-preview-button", visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should not see the diff comment text field' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_css(".js-note-text", visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should only see one diff form' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_css("form.new_note", count: 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a diff comment form with ":-1: I don\'t like this"' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_field("note[note]", with: ":-1: I don\'t like this")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a diff comment saying "Typo, please fix"' do
|
||||||
|
within(".diff_file .note") do
|
||||||
|
page.should have_content("Typo, please fix")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a discussion reply button' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_link("Reply")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a temporary diff comment form' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_css(".js-temp-notes-holder form.new_note")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see add a diff comment button' do
|
||||||
|
page.should have_css(".js-add-diff-note-button", visible: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see an empty diff comment form' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_field("note[note]", with: "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see the cancel comment button' do
|
||||||
|
within(".diff_file form") do
|
||||||
|
page.should have_css(".js-close-discussion-note-form", text: "Cancel")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see the diff comment preview' do
|
||||||
|
within(".diff_file form") do
|
||||||
|
page.should have_css(".js-note-preview", visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see the diff comment edit button' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_css(".js-note-edit-button", visible: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see the diff comment preview button' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_css(".js-note-preview-button", visible: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see two separate previews' do
|
||||||
|
within(".diff_file") do
|
||||||
|
page.should have_css(".js-note-preview", visible: true, count: 2)
|
||||||
|
page.should have_content("Should fix it")
|
||||||
|
page.should have_content("DRY this up")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,19 +1,112 @@
|
||||||
module SharedNote
|
module SharedNote
|
||||||
include Spinach::DSL
|
include Spinach::DSL
|
||||||
|
|
||||||
Given 'I leave a comment like "XML attached"' do
|
Given 'I delete a comment' do
|
||||||
fill_in "note_note", :with => "XML attached"
|
first(".js-note-delete").trigger("click")
|
||||||
click_button "Add Comment"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Then 'I should see comment "XML attached"' do
|
Given 'I haven\'t written any comment text' do
|
||||||
page.should have_content "XML attached"
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: ""
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I leave a comment like "XML attached"' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "XML attached"
|
||||||
|
click_button "Add Comment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I preview a comment text like "Bug fixed :smile:"' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "Bug fixed :smile:"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I submit the comment' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
click_button "Add Comment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Given 'I write a comment like "Nice"' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "Nice"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Then 'I should not see a comment saying "XML attached"' do
|
||||||
|
page.should_not have_css(".note")
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should not see the cancel comment button' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
should_not have_link("Cancel")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should not see the comment preview' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
page.should have_css(".js-note-preview", visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should not see the comment preview button' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
page.should have_css(".js-note-preview-button", visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should not see the comment text field' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
page.should have_css(".js-note-text", visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see a comment saying "XML attached"' do
|
||||||
|
within(".note") do
|
||||||
|
page.should have_content("XML attached")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see an empty comment text field' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
page.should have_field("note[note]", with: "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see the comment edit button' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
page.should have_css(".js-note-edit-button", visible: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see the comment preview' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
page.should have_css(".js-note-preview", visible: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then 'I should see the comment preview button' do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
page.should have_css(".js-note-preview-button", visible: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Wall
|
||||||
|
|
||||||
Given 'I write new comment "my special test message"' do
|
Given 'I write new comment "my special test message"' do
|
||||||
fill_in "note_note", :with => "my special test message"
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "my special test message"
|
||||||
click_button "Add Comment"
|
click_button "Add Comment"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Then 'I should see project wall note "my special test message"' do
|
Then 'I should see project wall note "my special test message"' do
|
||||||
page.should have_content "my special test message"
|
page.should have_content "my special test message"
|
||||||
|
|
|
@ -224,6 +224,11 @@ module SharedPaths
|
||||||
visit project_merge_request_path(mr.project, mr)
|
visit project_merge_request_path(mr.project, mr)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Given 'I visit merge request page "Bug NS-05"' do
|
||||||
|
mr = MergeRequest.find_by_title("Bug NS-05")
|
||||||
|
visit project_merge_request_path(mr.project, mr)
|
||||||
|
end
|
||||||
|
|
||||||
And 'I visit project "Shop" merge requests page' do
|
And 'I visit project "Shop" merge requests page' do
|
||||||
visit project_merge_requests_path(Project.find_by_name("Shop"))
|
visit project_merge_requests_path(Project.find_by_name("Shop"))
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,22 +25,6 @@ module Gitlab
|
||||||
# >> gfm(":trollface:")
|
# >> gfm(":trollface:")
|
||||||
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
|
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
|
||||||
module Markdown
|
module Markdown
|
||||||
REFERENCE_PATTERN = %r{
|
|
||||||
(?<prefix>\W)? # Prefix
|
|
||||||
( # Reference
|
|
||||||
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
|
|
||||||
|\#(?<issue>\d+) # Issue ID
|
|
||||||
|!(?<merge_request>\d+) # MR ID
|
|
||||||
|\$(?<snippet>\d+) # Snippet ID
|
|
||||||
|(?<commit>[\h]{6,40}) # Commit ID
|
|
||||||
)
|
|
||||||
(?<suffix>\W)? # Suffix
|
|
||||||
}x.freeze
|
|
||||||
|
|
||||||
TYPES = [:user, :issue, :merge_request, :snippet, :commit].freeze
|
|
||||||
|
|
||||||
EMOJI_PATTERN = %r{(:(\S+):)}.freeze
|
|
||||||
|
|
||||||
attr_reader :html_options
|
attr_reader :html_options
|
||||||
|
|
||||||
# Public: Parse the provided text with GitLab-Flavored Markdown
|
# Public: Parse the provided text with GitLab-Flavored Markdown
|
||||||
|
@ -96,6 +80,20 @@ module Gitlab
|
||||||
text
|
text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
REFERENCE_PATTERN = %r{
|
||||||
|
(?<prefix>\W)? # Prefix
|
||||||
|
( # Reference
|
||||||
|
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
|
||||||
|
|\#(?<issue>\d+) # Issue ID
|
||||||
|
|!(?<merge_request>\d+) # MR ID
|
||||||
|
|\$(?<snippet>\d+) # Snippet ID
|
||||||
|
|(?<commit>[\h]{6,40}) # Commit ID
|
||||||
|
)
|
||||||
|
(?<suffix>\W)? # Suffix
|
||||||
|
}x.freeze
|
||||||
|
|
||||||
|
TYPES = [:user, :issue, :merge_request, :snippet, :commit].freeze
|
||||||
|
|
||||||
def parse_references(text)
|
def parse_references(text)
|
||||||
# parse reference links
|
# parse reference links
|
||||||
text.gsub!(REFERENCE_PATTERN) do |match|
|
text.gsub!(REFERENCE_PATTERN) do |match|
|
||||||
|
@ -115,11 +113,13 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
EMOJI_PATTERN = %r{(:(\S+):)}.freeze
|
||||||
|
|
||||||
def parse_emoji(text)
|
def parse_emoji(text)
|
||||||
# parse emoji
|
# parse emoji
|
||||||
text.gsub!(EMOJI_PATTERN) do |match|
|
text.gsub!(EMOJI_PATTERN) do |match|
|
||||||
if valid_emoji?($2)
|
if valid_emoji?($2)
|
||||||
image_tag("emoji/#{$2}.png", size: "20x20", class: 'emoji', title: $1, alt: $1)
|
image_tag("emoji/#{$2}.png", class: 'emoji', title: $1, alt: $1, size: "20x20")
|
||||||
else
|
else
|
||||||
match
|
match
|
||||||
end
|
end
|
||||||
|
|
|
@ -73,8 +73,8 @@ FactoryGirl.define do
|
||||||
|
|
||||||
# pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
|
# pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
|
||||||
trait :with_diffs do
|
trait :with_diffs do
|
||||||
target_branch "bcf03b5d~3"
|
target_branch "master" # pretend bcf03b5d~3
|
||||||
source_branch "bcf03b5d"
|
source_branch "stable" # pretend bcf03b5d
|
||||||
st_commits do
|
st_commits do
|
||||||
[Commit.new(project.repo.commit('bcf03b5d')),
|
[Commit.new(project.repo.commit('bcf03b5d')),
|
||||||
Commit.new(project.repo.commit('bcf03b5d~1')),
|
Commit.new(project.repo.commit('bcf03b5d~1')),
|
||||||
|
@ -92,6 +92,32 @@ FactoryGirl.define do
|
||||||
factory :note do
|
factory :note do
|
||||||
project
|
project
|
||||||
note "Note"
|
note "Note"
|
||||||
|
author
|
||||||
|
|
||||||
|
factory :note_on_commit, traits: [:on_commit]
|
||||||
|
factory :note_on_commit_diff, traits: [:on_commit, :on_diff]
|
||||||
|
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
|
||||||
|
factory :note_on_merge_request, traits: [:on_merge_request]
|
||||||
|
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
|
||||||
|
|
||||||
|
trait :on_commit do
|
||||||
|
commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
|
||||||
|
noteable_type "Commit"
|
||||||
|
end
|
||||||
|
|
||||||
|
trait :on_diff do
|
||||||
|
line_code "0_184_184"
|
||||||
|
end
|
||||||
|
|
||||||
|
trait :on_merge_request do
|
||||||
|
noteable_id 1
|
||||||
|
noteable_type "MergeRequest"
|
||||||
|
end
|
||||||
|
|
||||||
|
trait :on_issue do
|
||||||
|
noteable_id 1
|
||||||
|
noteable_type "Issue"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :event do
|
factory :event do
|
||||||
|
|
|
@ -1,132 +1,138 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Issue do
|
describe Issue do
|
||||||
let(:issue) { create(:issue) }
|
it { should include_module(Votes) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe MergeRequest do
|
||||||
|
let(:merge_request) { FactoryGirl.create(:merge_request_with_diffs) }
|
||||||
|
|
||||||
|
it { should include_module(Votes) }
|
||||||
|
|
||||||
describe "#upvotes" do
|
describe "#upvotes" do
|
||||||
it "with no notes has a 0/0 score" do
|
it "with no notes has a 0/0 score" do
|
||||||
issue.upvotes.should == 0
|
merge_request.upvotes.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize non-+1 notes" do
|
it "should recognize non-+1 notes" do
|
||||||
issue.notes << create(:note, note: "No +1 here")
|
merge_request.notes << create(:note, note: "No +1 here")
|
||||||
issue.should have(1).note
|
merge_request.should have(1).note
|
||||||
issue.notes.first.upvote?.should be_false
|
merge_request.notes.first.upvote?.should be_false
|
||||||
issue.upvotes.should == 0
|
merge_request.upvotes.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize a single +1 note" do
|
it "should recognize a single +1 note" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.upvotes.should == 1
|
merge_request.upvotes.should == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize multiple +1 notes" do
|
it "should recognize multiple +1 notes" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.notes << create(:note, note: "+1 I want this")
|
merge_request.notes << create(:note, note: "+1 I want this")
|
||||||
issue.upvotes.should == 2
|
merge_request.upvotes.should == 2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#downvotes" do
|
describe "#downvotes" do
|
||||||
it "with no notes has a 0/0 score" do
|
it "with no notes has a 0/0 score" do
|
||||||
issue.downvotes.should == 0
|
merge_request.downvotes.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize non--1 notes" do
|
it "should recognize non--1 notes" do
|
||||||
issue.notes << create(:note, note: "Almost got a -1")
|
merge_request.notes << create(:note, note: "Almost got a -1")
|
||||||
issue.should have(1).note
|
merge_request.should have(1).note
|
||||||
issue.notes.first.downvote?.should be_false
|
merge_request.notes.first.downvote?.should be_false
|
||||||
issue.downvotes.should == 0
|
merge_request.downvotes.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize a single -1 note" do
|
it "should recognize a single -1 note" do
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.downvotes.should == 1
|
merge_request.downvotes.should == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize multiple -1 notes" do
|
it "should recognize multiple -1 notes" do
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.notes << create(:note, note: "-1 Away with this")
|
merge_request.notes << create(:note, note: "-1 Away with this")
|
||||||
issue.downvotes.should == 2
|
merge_request.downvotes.should == 2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#votes_count" do
|
describe "#votes_count" do
|
||||||
it "with no notes has a 0/0 score" do
|
it "with no notes has a 0/0 score" do
|
||||||
issue.votes_count.should == 0
|
merge_request.votes_count.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize non notes" do
|
it "should recognize non notes" do
|
||||||
issue.notes << create(:note, note: "No +1 here")
|
merge_request.notes << create(:note, note: "No +1 here")
|
||||||
issue.should have(1).note
|
merge_request.should have(1).note
|
||||||
issue.votes_count.should == 0
|
merge_request.votes_count.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize a single +1 note" do
|
it "should recognize a single +1 note" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.votes_count.should == 1
|
merge_request.votes_count.should == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize a single -1 note" do
|
it "should recognize a single -1 note" do
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.votes_count.should == 1
|
merge_request.votes_count.should == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize multiple notes" do
|
it "should recognize multiple notes" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.notes << create(:note, note: "+1 I want this")
|
merge_request.notes << create(:note, note: "+1 I want this")
|
||||||
issue.votes_count.should == 3
|
merge_request.votes_count.should == 3
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#upvotes_in_percent" do
|
describe "#upvotes_in_percent" do
|
||||||
it "with no notes has a 0% score" do
|
it "with no notes has a 0% score" do
|
||||||
issue.upvotes_in_percent.should == 0
|
merge_request.upvotes_in_percent.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should count a single 1 note as 100%" do
|
it "should count a single 1 note as 100%" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.upvotes_in_percent.should == 100
|
merge_request.upvotes_in_percent.should == 100
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should count multiple +1 notes as 100%" do
|
it "should count multiple +1 notes as 100%" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.notes << create(:note, note: "+1 I want this")
|
merge_request.notes << create(:note, note: "+1 I want this")
|
||||||
issue.upvotes_in_percent.should == 100
|
merge_request.upvotes_in_percent.should == 100
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should count fractions for multiple +1 and -1 notes correctly" do
|
it "should count fractions for multiple +1 and -1 notes correctly" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.notes << create(:note, note: "+1 I want this")
|
merge_request.notes << create(:note, note: "+1 I want this")
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.notes << create(:note, note: "+1 me too")
|
merge_request.notes << create(:note, note: "+1 me too")
|
||||||
issue.upvotes_in_percent.should == 75
|
merge_request.upvotes_in_percent.should == 75
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#downvotes_in_percent" do
|
describe "#downvotes_in_percent" do
|
||||||
it "with no notes has a 0% score" do
|
it "with no notes has a 0% score" do
|
||||||
issue.downvotes_in_percent.should == 0
|
merge_request.downvotes_in_percent.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should count a single -1 note as 100%" do
|
it "should count a single -1 note as 100%" do
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.downvotes_in_percent.should == 100
|
merge_request.downvotes_in_percent.should == 100
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should count multiple -1 notes as 100%" do
|
it "should count multiple -1 notes as 100%" do
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.notes << create(:note, note: "-1 Away with this")
|
merge_request.notes << create(:note, note: "-1 Away with this")
|
||||||
issue.downvotes_in_percent.should == 100
|
merge_request.downvotes_in_percent.should == 100
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should count fractions for multiple +1 and -1 notes correctly" do
|
it "should count fractions for multiple +1 and -1 notes correctly" do
|
||||||
issue.notes << create(:note, note: "+1 This is awesome")
|
merge_request.notes << create(:note, note: "+1 This is awesome")
|
||||||
issue.notes << create(:note, note: "+1 I want this")
|
merge_request.notes << create(:note, note: "+1 I want this")
|
||||||
issue.notes << create(:note, note: "-1 This is bad")
|
merge_request.notes << create(:note, note: "-1 This is bad")
|
||||||
issue.notes << create(:note, note: "+1 me too")
|
merge_request.notes << create(:note, note: "+1 me too")
|
||||||
issue.downvotes_in_percent.should == 25
|
merge_request.downvotes_in_percent.should == 25
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,34 +38,34 @@ describe Note do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
|
|
||||||
it "recognizes a neutral note" do
|
it "recognizes a neutral note" do
|
||||||
note = create(:note, note: "This is not a +1 note")
|
note = create(:votable_note, note: "This is not a +1 note")
|
||||||
note.should_not be_upvote
|
note.should_not be_upvote
|
||||||
note.should_not be_downvote
|
note.should_not be_downvote
|
||||||
end
|
end
|
||||||
|
|
||||||
it "recognizes a neutral emoji note" do
|
it "recognizes a neutral emoji note" do
|
||||||
note = build(:note, note: "I would :+1: this, but I don't want to")
|
note = build(:votable_note, note: "I would :+1: this, but I don't want to")
|
||||||
note.should_not be_upvote
|
note.should_not be_upvote
|
||||||
note.should_not be_downvote
|
note.should_not be_downvote
|
||||||
end
|
end
|
||||||
|
|
||||||
it "recognizes a +1 note" do
|
it "recognizes a +1 note" do
|
||||||
note = create(:note, note: "+1 for this")
|
note = create(:votable_note, note: "+1 for this")
|
||||||
note.should be_upvote
|
note.should be_upvote
|
||||||
end
|
end
|
||||||
|
|
||||||
it "recognizes a +1 emoji as a vote" do
|
it "recognizes a +1 emoji as a vote" do
|
||||||
note = build(:note, note: ":+1: for this")
|
note = build(:votable_note, note: ":+1: for this")
|
||||||
note.should be_upvote
|
note.should be_upvote
|
||||||
end
|
end
|
||||||
|
|
||||||
it "recognizes a -1 note" do
|
it "recognizes a -1 note" do
|
||||||
note = create(:note, note: "-1 for this")
|
note = create(:votable_note, note: "-1 for this")
|
||||||
note.should be_downvote
|
note.should be_downvote
|
||||||
end
|
end
|
||||||
|
|
||||||
it "recognizes a -1 emoji as a vote" do
|
it "recognizes a -1 emoji as a vote" do
|
||||||
note = build(:note, note: ":-1: for this")
|
note = build(:votable_note, note: ":-1: for this")
|
||||||
note.should be_downvote
|
note.should be_downvote
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -74,43 +74,72 @@ describe Note do
|
||||||
let(:commit) { project.repository.commit }
|
let(:commit) { project.repository.commit }
|
||||||
|
|
||||||
describe "Commit notes" do
|
describe "Commit notes" do
|
||||||
before do
|
let!(:note) { create(:note_on_commit, note: "+1 from me") }
|
||||||
@note = create(:note,
|
let!(:commit) { note.noteable }
|
||||||
commit_id: commit.id,
|
|
||||||
noteable_type: "Commit")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be accessible through #noteable" do
|
it "should be accessible through #noteable" do
|
||||||
@note.commit_id.should == commit.id
|
note.commit_id.should == commit.id
|
||||||
@note.noteable.should be_a(Commit)
|
note.noteable.should be_a(Commit)
|
||||||
@note.noteable.should == commit
|
note.noteable.should == commit
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should save a valid note" do
|
it "should save a valid note" do
|
||||||
@note.commit_id.should == commit.id
|
note.commit_id.should == commit.id
|
||||||
@note.noteable == commit
|
note.noteable == commit
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be recognized by #for_commit?" do
|
it "should be recognized by #for_commit?" do
|
||||||
@note.should be_for_commit
|
note.should be_for_commit
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be votable" do
|
||||||
|
note.should_not be_votable
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Pre-line commit notes" do
|
describe "Commit diff line notes" do
|
||||||
before do
|
let!(:note) { create(:note_on_commit_line, note: "+1 from me") }
|
||||||
@note = create(:note,
|
let!(:commit) { note.noteable }
|
||||||
commit_id: commit.id,
|
|
||||||
noteable_type: "Commit",
|
|
||||||
line_code: "0_16_1")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should save a valid note" do
|
it "should save a valid note" do
|
||||||
@note.commit_id.should == commit.id
|
note.commit_id.should == commit.id
|
||||||
@note.noteable.id.should == commit.id
|
note.noteable.id.should == commit.id
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be recognized by #for_diff_line?" do
|
it "should be recognized by #for_diff_line?" do
|
||||||
@note.should be_for_diff_line
|
note.should be_for_diff_line
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be recognized by #for_commit_diff_line?" do
|
||||||
|
note.should be_for_commit_diff_line
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be votable" do
|
||||||
|
note.should_not be_votable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Issue notes" do
|
||||||
|
let!(:note) { create(:note_on_issue, note: "+1 from me") }
|
||||||
|
|
||||||
|
it "should not be votable" do
|
||||||
|
note.should be_votable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Merge request notes" do
|
||||||
|
let!(:note) { create(:note_on_merge_request, note: "+1 from me") }
|
||||||
|
|
||||||
|
it "should not be votable" do
|
||||||
|
note.should be_votable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Merge request diff line notes" do
|
||||||
|
let!(:note) { create(:note_on_merge_request_line, note: "+1 from me") }
|
||||||
|
|
||||||
|
it "should not be votable" do
|
||||||
|
note.should_not be_votable
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
233
spec/requests/notes_on_merge_requests_spec.rb
Normal file
233
spec/requests/notes_on_merge_requests_spec.rb
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe "On a merge request", js: true do
|
||||||
|
let!(:project) { create(:project) }
|
||||||
|
let!(:merge_request) { create(:merge_request, project: project) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
login_as :user
|
||||||
|
project.team << [@user, :master]
|
||||||
|
|
||||||
|
visit project_merge_request_path(project, merge_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { page }
|
||||||
|
|
||||||
|
describe "the note form" do
|
||||||
|
# main target form creation
|
||||||
|
it { should have_css(".js-main-target-form", visible: true, count: 1) }
|
||||||
|
|
||||||
|
# button initalization
|
||||||
|
it { within(".js-main-target-form") { should have_button("Add Comment") } }
|
||||||
|
it { within(".js-main-target-form") { should_not have_link("Cancel") } }
|
||||||
|
|
||||||
|
# notifiactions
|
||||||
|
it { within(".js-main-target-form") { should have_checked_field("Project team") } }
|
||||||
|
it { within(".js-main-target-form") { should_not have_checked_field("Commit author") } }
|
||||||
|
it { within(".js-main-target-form") { should_not have_unchecked_field("Commit author") } }
|
||||||
|
|
||||||
|
describe "without text" do
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with text" do
|
||||||
|
before do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "This is awesome"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") } }
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) } }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with preview" do
|
||||||
|
before do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "This is awesome"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview", text: "This is awesome", visible: true) } }
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-edit-button", visible: true) } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when posting a note" do
|
||||||
|
before do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "This is awsome!"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
click_button "Add Comment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# note added
|
||||||
|
it { within(".js-main-target-form") { should have_content("This is awsome!") } }
|
||||||
|
|
||||||
|
# reset form
|
||||||
|
it { within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } }
|
||||||
|
|
||||||
|
# return from preview
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } }
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } }
|
||||||
|
|
||||||
|
|
||||||
|
it "should be removable" do
|
||||||
|
find(".js-note-delete").trigger("click")
|
||||||
|
|
||||||
|
should_not have_css(".note")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe "On a merge request diff", js: true, focus: true do
|
||||||
|
let!(:project) { create(:project) }
|
||||||
|
let!(:merge_request) { create(:merge_request_with_diffs, project: project) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
login_as :user
|
||||||
|
project.team << [@user, :master]
|
||||||
|
|
||||||
|
visit diffs_project_merge_request_path(project, merge_request)
|
||||||
|
|
||||||
|
click_link("Diff")
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { page }
|
||||||
|
|
||||||
|
describe "when adding a note" do
|
||||||
|
before do
|
||||||
|
find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "the notes holder" do
|
||||||
|
it { should have_css("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder") }
|
||||||
|
|
||||||
|
it { within(".js-temp-notes-holder") { should have_css(".new_note") } }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "the note form" do
|
||||||
|
# set up hidden fields correctly
|
||||||
|
it { within(".js-temp-notes-holder") { find("#note_noteable_type").value.should == "MergeRequest" } }
|
||||||
|
it { within(".js-temp-notes-holder") { find("#note_noteable_id").value.should == "" } }
|
||||||
|
it { within(".js-temp-notes-holder") { find("#note_commit_id").value.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" } }
|
||||||
|
it { within(".js-temp-notes-holder") { find("#note_line_code").value.should == "4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185" } }
|
||||||
|
|
||||||
|
# buttons
|
||||||
|
it { should have_button("Add Comment") }
|
||||||
|
it { should have_css(".js-close-discussion-note-form", text: "Cancel") }
|
||||||
|
|
||||||
|
# notification options
|
||||||
|
it { should have_unchecked_field("Project team") }
|
||||||
|
it { should have_checked_field("Commit author") }
|
||||||
|
|
||||||
|
it "shouldn't add a second form for same row" do
|
||||||
|
find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
|
||||||
|
should have_css("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder form", count: 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be removed when canceled" do
|
||||||
|
find(".js-close-discussion-note-form").trigger("click")
|
||||||
|
|
||||||
|
should have_no_css(".js-temp-notes-holder")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with muliple note forms" do
|
||||||
|
before do
|
||||||
|
find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
find("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
end
|
||||||
|
|
||||||
|
# has two line forms
|
||||||
|
it { should have_css(".js-temp-notes-holder", count: 2) }
|
||||||
|
|
||||||
|
describe "previewing them separately" do
|
||||||
|
before do
|
||||||
|
# add two separate texts and trigger previews on both
|
||||||
|
within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder") do
|
||||||
|
fill_in "note[note]", with: "One comment on line 185"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
end
|
||||||
|
within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") do
|
||||||
|
fill_in "note[note]", with: "Another comment on line 17"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# check if previews were rendered separately
|
||||||
|
it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder") { should have_css(".js-note-preview", text: "One comment on line 185") } }
|
||||||
|
it { within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") { should have_css(".js-note-preview", text: "Another comment on line 17") } }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "posting a note" do
|
||||||
|
before do
|
||||||
|
within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") do
|
||||||
|
fill_in "note[note]", with: "Another comment on line 17"
|
||||||
|
click_button("Add Comment")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# removed form after submit
|
||||||
|
it { should have_no_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") }
|
||||||
|
|
||||||
|
# added discussion
|
||||||
|
it { should have_content("Another comment on line 17") }
|
||||||
|
it { should have_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .notes_holder") }
|
||||||
|
it { should have_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .notes_holder .note", count: 1) }
|
||||||
|
it { should have_link("Reply") }
|
||||||
|
|
||||||
|
it "should remove last note of a discussion" do
|
||||||
|
within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .notes_holder") do
|
||||||
|
find(".js-note-delete").trigger("click")
|
||||||
|
end
|
||||||
|
|
||||||
|
# removed whole discussion
|
||||||
|
should_not have_css(".note_holder")
|
||||||
|
should have_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + #342e16cbbd482ac2047dc679b2749d248cc1428f_18_18.line_holder")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when replying to a note" do
|
||||||
|
before do
|
||||||
|
# create first note
|
||||||
|
find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder .js-add-diff-note-button").trigger("click")
|
||||||
|
within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .js-temp-notes-holder") do
|
||||||
|
fill_in "note[note]", with: "One comment on line 184"
|
||||||
|
click_button("Add Comment")
|
||||||
|
end
|
||||||
|
# create second note
|
||||||
|
within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") do
|
||||||
|
find(".js-discussion-reply-button").trigger("click")
|
||||||
|
fill_in "note[note]", with: "An additional comment in reply"
|
||||||
|
click_button("Add Comment")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# inserted note
|
||||||
|
it { should have_content("An additional comment in reply") }
|
||||||
|
it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") { should have_css(".note", count: 2) } }
|
||||||
|
|
||||||
|
# removed form after reply
|
||||||
|
it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") { should have_no_css("form") } }
|
||||||
|
it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") { should have_link("Reply") } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe "On merge request discussion", js: true do
|
||||||
|
describe "with merge request diff note"
|
||||||
|
describe "with commit note"
|
||||||
|
describe "with commit diff note"
|
||||||
|
end
|
85
spec/requests/notes_on_wall_spec.rb
Normal file
85
spec/requests/notes_on_wall_spec.rb
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe "On the project wall", js: true do
|
||||||
|
let!(:project) { create(:project) }
|
||||||
|
let!(:commit) { project.commit("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") }
|
||||||
|
|
||||||
|
before do
|
||||||
|
login_as :user
|
||||||
|
project.team << [@user, :master]
|
||||||
|
visit wall_project_path(project)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { page }
|
||||||
|
|
||||||
|
describe "the note form" do
|
||||||
|
# main target form creation
|
||||||
|
it { should have_css(".js-main-target-form", visible: true, count: 1) }
|
||||||
|
|
||||||
|
# button initalization
|
||||||
|
it { within(".js-main-target-form") { should have_button("Add Comment") } }
|
||||||
|
it { within(".js-main-target-form") { should_not have_link("Cancel") } }
|
||||||
|
|
||||||
|
# notifiactions
|
||||||
|
it { within(".js-main-target-form") { should have_checked_field("Project team") } }
|
||||||
|
it { within(".js-main-target-form") { should_not have_checked_field("Commit author") } }
|
||||||
|
it { within(".js-main-target-form") { should_not have_unchecked_field("Commit author") } }
|
||||||
|
|
||||||
|
describe "without text" do
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with text" do
|
||||||
|
before do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "This is awesome"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") } }
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) } }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with preview" do
|
||||||
|
before do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "This is awesome"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview", text: "This is awesome", visible: true) } }
|
||||||
|
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-edit-button", visible: true) } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when posting a note" do
|
||||||
|
before do
|
||||||
|
within(".js-main-target-form") do
|
||||||
|
fill_in "note[note]", with: "This is awsome!"
|
||||||
|
find(".js-note-preview-button").trigger("click")
|
||||||
|
click_button "Add Comment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# note added
|
||||||
|
it { within(".js-main-target-form") { should have_content("This is awsome!") } }
|
||||||
|
|
||||||
|
# reset form
|
||||||
|
it { within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } }
|
||||||
|
|
||||||
|
# return from preview
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } }
|
||||||
|
it { within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } }
|
||||||
|
|
||||||
|
|
||||||
|
it "should be removable" do
|
||||||
|
find(".js-note-delete").trigger("click")
|
||||||
|
|
||||||
|
should_not have_css(".note")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue