summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/images/comment_add.pngbin781 -> 0 bytes
-rw-r--r--app/assets/images/diff_note_add.pngbin0 -> 691 bytes
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.coffee5
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.coffee5
-rw-r--r--app/assets/javascripts/extensions/array.js7
-rw-r--r--app/assets/javascripts/notes.js670
-rw-r--r--app/assets/stylesheets/application.scss5
-rw-r--r--app/assets/stylesheets/behaviors.scss14
-rw-r--r--app/assets/stylesheets/common.scss6
-rw-r--r--app/assets/stylesheets/sections/commits.scss15
-rw-r--r--app/assets/stylesheets/sections/notes.scss420
11 files changed, 759 insertions, 388 deletions
diff --git a/app/assets/images/comment_add.png b/app/assets/images/comment_add.png
deleted file mode 100644
index 836557ac846..00000000000
--- a/app/assets/images/comment_add.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/diff_note_add.png b/app/assets/images/diff_note_add.png
new file mode 100644
index 00000000000..8ec15b701fc
--- /dev/null
+++ b/app/assets/images/diff_note_add.png
Binary files differ
diff --git a/app/assets/javascripts/behaviors/details_behavior.coffee b/app/assets/javascripts/behaviors/details_behavior.coffee
new file mode 100644
index 00000000000..7ad5c818946
--- /dev/null
+++ b/app/assets/javascripts/behaviors/details_behavior.coffee
@@ -0,0 +1,5 @@
+$ ->
+ $("body").on "click", ".js-details-target", ->
+ container = $(@).closest(".js-details-container")
+
+ container.toggleClass("open")
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
new file mode 100644
index 00000000000..3fefbf8e121
--- /dev/null
+++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee
@@ -0,0 +1,5 @@
+$ ->
+ $("body").on "click", ".js-toggler-target", ->
+ container = $(@).closest(".js-toggler-container")
+
+ container.toggleClass("on")
diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js
new file mode 100644
index 00000000000..7fccc9c9d5f
--- /dev/null
+++ b/app/assets/javascripts/extensions/array.js
@@ -0,0 +1,7 @@
+Array.prototype.first = function() {
+ return this[0];
+}
+
+Array.prototype.last = function() {
+ return this[this.length-1];
+} \ No newline at end of file
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index b6f65b7aa5e..fa0edd2d1f4 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -9,72 +9,310 @@ var NoteList = {
loading_more_disabled: false,
reversed: false,
- init:
- function(tid, tt, path) {
- this.notes_path = path + ".js";
- this.target_id = tid;
- this.target_type = tt;
- this.reversed = $("#notes-list").is(".reversed");
- this.target_params = "target_type=" + this.target_type + "&target_id=" + this.target_id;
-
- // get initial set of notes
- this.getContent();
-
- $("#notes-list, #new-notes-list").on("ajax:success", ".delete-note", function() {
- $(this).closest('li').fadeOut(function() {
- $(this).remove();
- NoteList.updateVotes();
- });
+ init: function(tid, tt, path) {
+ NoteList.notes_path = path + ".js";
+ NoteList.target_id = tid;
+ NoteList.target_type = tt;
+ NoteList.reversed = $("#notes-list").is(".reversed");
+ NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id;
+
+ NoteList.setupMainTargetNoteForm();
+
+ if(NoteList.reversed) {
+ var form = $(".js-main-target-form");
+ form.find(".buttons, .note_options").hide();
+ var textarea = form.find(".js-note-text");
+ textarea.css("height", "40px");
+ textarea.on("focus", function(){
+ textarea.css("height", "80px");
+ form.find(".buttons, .note_options").show();
});
+ }
- $(".note-form-holder").on("ajax:before", function(){
- $(".submit_note").disable();
- })
+ // get initial set of notes
+ NoteList.getContent();
- $(".note-form-holder").on("ajax:complete", function(){
- $(".submit_note").enable();
- $('#preview-note').hide();
- $('#note_note').show();
- })
+ // add a new diff note
+ $(document).on("click",
+ ".js-add-diff-note-button",
+ NoteList.addDiffNote);
- disableButtonIfEmptyField(".note-text", ".submit_note");
+ // reply to diff/discussion notes
+ $(document).on("click",
+ ".js-discussion-reply-button",
+ NoteList.replyToDiscussionNote);
- $("#note_attachment").change(function(e){
- var val = $('.input-file').val();
- var filename = val.replace(/^.*[\\\/]/, '');
- $(".file_name").text(filename);
- });
+ // setup note preview
+ $(document).on("click",
+ ".js-note-preview-button",
+ NoteList.previewNote);
+
+ // 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);
+ },
+
+
+ /**
+ * 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();
+
+ // 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();
+ },
- if(this.reversed) {
- var textarea = $(".note-text");
- $('.note_advanced_opts').hide();
- textarea.css("height", "40px");
- textarea.on("focus", function(){
- $(this).css("height", "80px");
- $('.note_advanced_opts').show();
+ /**
+ * 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();
+ }
+ },
- // Setup note preview
- $(document).on('click', '#preview-link', function(e) {
- $('#preview-note').text('Loading...');
+ /**
+ * 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");
- $(this).text($(this).text() === "Edit" ? "Preview" : "Edit");
+ // check if this is the last note for this line
+ if (notes.find(".note").length === 1) {
+ // for discussions
+ notes.closest(".discussion").remove();
- var note_text = $('#note_note').val();
+ // for diff lines
+ notes.closest("tr").remove();
+ }
- if(note_text.trim().length === 0) {
- $('#preview-note').text('Nothing to preview.');
- } else {
- $.post($(this).attr('href'), {note: note_text}).success(function(data) {
- $('#preview-note').html(data);
- });
- }
+ note.remove();
+ NoteList.updateVotes();
+ },
- $('#preview-note, #note_note').toggle();
- e.preventDefault();
- });
- },
+ /**
+ * 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_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 +324,39 @@ var NoteList = {
/**
* Gets an inital set of notes.
*/
- getContent:
- function() {
- $.ajax({
- type: "GET",
- url: this.notes_path,
- data: this.target_params,
- complete: function(){ $('.notes-status').removeClass("loading")},
- beforeSend: function() { $('.notes-status').addClass("loading") },
- dataType: "script"});
- },
+ getContent: function() {
+ $.ajax({
+ url: NoteList.notes_path,
+ data: NoteList.target_params,
+ complete: function(){ $('.js-notes-busy').removeClass("loading")},
+ beforeSend: function() { $('.js-notes-busy').addClass("loading") },
+ dataType: "script"
+ });
+ },
/**
* Called in response to getContent().
* Replaces the content of #notes-list with the given html.
*/
- setContent:
- function(first_id, last_id, html) {
- this.top_id = first_id;
- this.bottom_id = last_id;
- $("#notes-list").html(html);
+ setContent: function(newNoteIds, html) {
+ NoteList.top_id = newNoteIds.first();
+ NoteList.bottom_id = newNoteIds.last();
+ $("#notes-list").html(html);
+ // for the wall
+ if (NoteList.reversed) {
// init infinite scrolling
- this.initLoadMore();
+ NoteList.initLoadMore();
// init getting new notes
- if (this.reversed) {
- this.initRefreshNew();
- }
- },
+ NoteList.initRefreshNew();
+ }
+ },
/**
* 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.
*/
@@ -128,65 +365,57 @@ var NoteList = {
/**
* Initializes loading more notes when scrolling to the bottom of the page.
*/
- initLoadMore:
- function() {
- $(document).endlessScroll({
- bottomPixels: 400,
- fireDelay: 1000,
- fireOnce:true,
- ceaseFire: function() {
- return NoteList.loading_more_disabled;
- },
- callback: function(i) {
- NoteList.getMore();
- }
- });
- },
+ initLoadMore: function() {
+ $(document).endlessScroll({
+ bottomPixels: 400,
+ fireDelay: 1000,
+ fireOnce:true,
+ ceaseFire: function() {
+ return NoteList.loading_more_disabled;
+ },
+ callback: function(i) {
+ NoteList.getMore();
+ }
+ });
+ },
/**
* Gets an additional set of notes.
*/
- getMore:
- function() {
- // only load more notes if there are no "new" notes
- $('.loading').show();
- $.ajax({
- type: "GET",
- url: this.notes_path,
- data: this.target_params + "&loading_more=1&" + (this.reversed ? "before_id" : "after_id") + "=" + this.bottom_id,
- complete: function(){ $('.notes-status').removeClass("loading")},
- beforeSend: function() { $('.notes-status').addClass("loading") },
- dataType: "script"});
- },
+ getMore: function() {
+ // only load more notes if there are no "new" notes
+ $('.loading').show();
+ $.ajax({
+ url: NoteList.notes_path,
+ data: NoteList.target_params + "&loading_more=1&" + (NoteList.reversed ? "before_id" : "after_id") + "=" + NoteList.bottom_id,
+ complete: function(){ $('.js-notes-busy').removeClass("loading")},
+ beforeSend: function() { $('.js-notes-busy').addClass("loading") },
+ dataType: "script"
+ });
+ },
/**
* Called in response to getMore().
* Append notes to #notes-list.
*/
- appendMoreNotes:
- function(id, html) {
- if(id != this.bottom_id) {
- this.bottom_id = id;
- $("#notes-list").append(html);
- }
- },
+ appendMoreNotes: function(newNoteIds, html) {
+ var lastNewNoteId = newNoteIds.last();
+ if(lastNewNoteId != NoteList.bottom_id) {
+ NoteList.bottom_id = lastNewNoteId;
+ $("#notes-list").append(html);
+ }
+ },
/**
* Called in response to getMore().
* Disables loading more notes when scrolling to the bottom of the page.
- * Initalizes refreshing new notes.
*/
- finishedLoadingMore:
- function() {
- this.loading_more_disabled = true;
+ finishedLoadingMore: function() {
+ NoteList.loading_more_disabled = true;
- // from now on only get new notes
- if (!this.reversed) {
- this.initRefreshNew();
- }
- // make sure we are up to date
- this.updateVotes();
- },
+ // make sure we are up to date
+ NoteList.updateVotes();
+ },
/**
@@ -194,52 +423,118 @@ var NoteList = {
*
* 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 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.
+ *
+ * Note: only used on wall.
*/
- initRefreshNew:
- function() {
- setInterval("NoteList.getNew()", 10000);
- },
+ initRefreshNew: function() {
+ setInterval("NoteList.getNew()", 10000);
+ },
/**
* Gets the new set of notes.
+ *
+ * Note: only used on wall.
*/
- getNew:
- function() {
- $.ajax({
- type: "GET",
- url: this.notes_path,
- data: this.target_params + "&loading_new=1&after_id=" + (this.reversed ? this.top_id : this.bottom_id),
- dataType: "script"});
- },
+ getNew: function() {
+ $.ajax({
+ url: NoteList.notes_path,
+ data: NoteList.target_params + "&loading_new=1&after_id=" + (NoteList.reversed ? NoteList.top_id : NoteList.bottom_id),
+ dataType: "script"
+ });
+ },
/**
* Called in response to getNew().
* Replaces the content of #new-notes-list with the given html.
+ *
+ * Note: only used on wall.
*/
- replaceNewNotes:
- function(html) {
- $("#new-notes-list").html(html);
- this.updateVotes();
- },
+ replaceNewNotes: function(newNoteIds, html) {
+ $("#new-notes-list").html(html);
+ NoteList.updateVotes();
+ },
/**
- * Adds a single note to #new-notes-list.
+ * Adds a single common note to #notes-list.
*/
- appendNewNote:
- function(id, html) {
- if (this.reversed) {
- $("#new-notes-list").prepend(html);
- } else {
- $("#new-notes-list").append(html);
- }
- this.updateVotes();
- },
+ appendNewNote: function(id, html) {
+ $("#notes-list").append(html);
+ NoteList.updateVotes();
+ },
+
+ /**
+ * 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();
+ }
+
+ // 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);
+ },
/**
* Recalculates the votes and updates them (if they are displayed at all).
@@ -249,67 +544,24 @@ var NoteList = {
* Might produce inaccurate results when not all notes have been loaded and a
* recalculation is triggered (e.g. when deleting a note).
*/
- updateVotes:
- function() {
- var votes = $("#votes .votes");
- var notes = $("#notes-list, #new-notes-list").find(".note .vote");
-
- // only update if there is a vote display
- if (votes.size()) {
- var upvotes = notes.filter(".upvote").size();
- var downvotes = notes.filter(".downvote").size();
- var votesCount = upvotes + downvotes;
- var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0;
- var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0;
-
- // change vote bar lengths
- votes.find(".bar-success").css("width", upvotesPercent+"%");
- votes.find(".bar-danger").css("width", downvotesPercent+"%");
- // replace vote numbers
- votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
- votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
- }
+ updateVotes: function() {
+ var votes = $("#votes .votes");
+ var notes = $("#notes-list .note .vote");
+
+ // only update if there is a vote display
+ if (votes.size()) {
+ var upvotes = notes.filter(".upvote").size();
+ var downvotes = notes.filter(".downvote").size();
+ var votesCount = upvotes + downvotes;
+ var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0;
+ var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0;
+
+ // change vote bar lengths
+ votes.find(".bar-success").css("width", upvotesPercent+"%");
+ votes.find(".bar-danger").css("width", downvotesPercent+"%");
+ // replace vote numbers
+ votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
+ votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
}
+ }
};
-
-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(); });
- }
- });
- }
-}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 54690e73f81..f93246c13c7 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -45,3 +45,8 @@
@import "themes/ui_gray.scss";
@import "themes/ui_color.scss";
+/**
+ * Styles for JS behaviors.
+ */
+@import "behaviors.scss";
+
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
new file mode 100644
index 00000000000..3fdc20485f2
--- /dev/null
+++ b/app/assets/stylesheets/behaviors.scss
@@ -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; }
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index dcdfcdb289c..6d4c815106b 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -556,3 +556,9 @@ h1.http_status_code {
}
}
}
+
+img.emoji {
+ height: 20px;
+ vertical-align: middle;
+ width: 20px;
+}
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss
index 7ed53333f8c..1cae7b0c6b1 100644
--- a/app/assets/stylesheets/sections/commits.scss
+++ b/app/assets/stylesheets/sections/commits.scss
@@ -176,12 +176,14 @@
}
}
}
- .old_line, .new_line {
- margin: 0px;
- padding: 0px;
- border: none;
- background: #EEE;
- color: #666;
+ .new_line,
+ .old_line,
+ .notes_line {
+ margin:0px;
+ padding:0px;
+ border:none;
+ background:#EEE;
+ color:#666;
padding: 0px 5px;
border-right: 1px solid #ccc;
text-align: right;
@@ -191,6 +193,7 @@
moz-user-select: none;
-khtml-user-select: none;
user-select: none;
+
a {
float: left;
width: 35px;
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index 0c2a56d62f5..465d578f3b7 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -1,233 +1,307 @@
/**
* Notes
- *
*/
-#notes-list,
-#new-notes-list {
+ul.notes {
display: block;
list-style: none;
margin: 0px;
padding: 0px;
-}
-.issue_notes,
-.wiki_notes {
- .note_content {
- float: left;
- width: 400px;
- }
-}
+ .discussion-header,
+ .note-header {
+ @extend .cgray;
+ padding-top: 5px;
+ padding-bottom: 15px;
-/* Note textare */
-#note_note {
- height: 80px;
- width: 99%;
- font-size: 14px;
-}
+ .avatar {
+ float: left;
+ margin-right: 10px;
+ }
-#new_note {
- .attach_holder {
- display: none;
+ .discussion-last-update,
+ .note-last-update {
+ font-style: italic;
+ }
+ .note-author {
+ color: $style_color;
+ font-weight: bold;
+ &:hover {
+ color: $primary_color;
+ }
+ }
}
-}
-
-.preview_note {
- margin: 2px;
- border: 1px solid #ddd;
- padding: 10px;
- min-height: 60px;
- background: #f5f5f5;
-}
-.note {
- padding: 8px 0;
- overflow: hidden;
- display: block;
- position: relative;
- img {float: left; margin-right: 10px;}
- img.emoji {float: none;margin: 0;}
- .note-author cite{font-style: italic;}
- p { color: $style_color; }
- .note-author { color: $style_color;}
+ .discussion {
+ padding: 8px 0;
+ overflow: hidden;
+ display: block;
+ position:relative;
- .note-title { margin-left: 45px; padding-top: 5px;}
- .avatar {
- margin-top: 3px;
- }
+ .discussion-body {
+ margin-left: 50px;
- .delete-note {
- display: none;
- position: absolute;
- right: 0;
- top: 0;
- }
+ .diff_file,
+ .discussion-hidden,
+ .notes {
+ @extend .borders;
+ background-color: #F9F9F9;
+ }
+ .diff_file .notes {
+ /* reset */
+ background: inherit;
+ border: none;
+ @include box-shadow(none);
- &:hover {
- .delete-note { display: block; }
+ }
+ .discussion-hidden .note {
+ @extend .cgray;
+ padding: 8px;
+ text-align: center;
+ }
+ .notes .note {
+ border-color: #ddd;
+ padding: 8px;
+ }
+ .reply-btn {
+ margin-top: 8px;
+ }
+ }
}
-}
-#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;
-}
+ .note {
+ padding: 8px 0;
+ overflow: hidden;
+ display: block;
+ position:relative;
+ p { color: $style_color; }
-.notes-status {
- margin: 18px;
-}
+ .avatar {
+ margin-top: 3px;
+ }
+ .attachment {
+ font-size: 16px;
+ margin-top: -20px;
+ .icon-attachment {
+ @extend .icon-paper-clip;
+ font-size: 24px;
+ position: relative;
+ text-align: right;
+ top: 6px;
+ }
+ }
+ .note-body {
+ margin-left: 45px;
+ padding-top: 5px;
+ }
+ .note-header {
+ padding-bottom: 5px;
+ }
+ }
-p.notify_controls input{
- margin: 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;
+ }
}
-p.notify_controls span{
- font-weight: 700;
-}
+.diff_file .notes_holder {
+ font-family: $sansFontFamily;
+ font-size: 13px;
+ line-height: 18px;
-tr.line_notes_row {
- border-bottom: 1px solid #DDD;
- border-left: 7px solid #2A79A3;
+ td {
+ border: 1px solid #ddd;
+ border-left: none;
- &.reply {
- background: #eee;
- border-left: 7px solid #2A79A3;
- border-top: 1px solid #ddd;
- td {
- padding: 7px 10px;
+ &.notes_line {
+ text-align: center;
+ padding: 10px 0;
}
- 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;
+ &.notes_content {
+ background-color: $white;
+ border-width: 1px 0;
+ padding-top: 0;
}
}
- ul {
- margin: 0;
- li {
- padding: 0;
- border: none;
- }
+
+ .reply-btn {
+ margin-top: 8px;
}
}
-.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;
+
+/**
+ * Actions for Discussions/Notes
+ */
+
+.discussion,
+.note {
+ &.note:hover {
+ .note-actions { display: block; }
+ }
+ .discussion-header:hover {
+ .discussion-actions { display: block; }
}
- .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;
- }
+ .discussion-actions,
+ .note-actions {
+ display: none;
+ 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;
+ top: 0;
+}
+
+
+
+/**
+ * Line note button on the side of diffs
+ */
-td .line_note_link {
- position: absolute;
- margin-left:-70px;
- margin-top:-10px;
- z-index: 10;
- background: url("comment_add.png") no-repeat left 0;
- width: 32px;
- height: 32px;
+.diff_file tr.line_holder {
+ .add-diff-note {
+ background: url("diff_note_add.png") no-repeat left 0;
+ height: 22px;
+ margin-left: -65px;
+ position: absolute;
+ width: 22px;
+ z-index: 10;
- opacity: 0.0;
- filter: alpha(opacity=0);
+ // "hide" it by default
+ opacity: 0.0;
+ filter: alpha(opacity=0);
- &:hover {
- opacity: 1.0;
- filter: alpha(opacity=100);
+ &:hover {
+ opacity: 1.0;
+ filter: alpha(opacity=100);
+ }
+ }
+
+ // "show" the icon also if we just hover somwhere over the line
+ &:hover > td {
+ background: $hover !important;
+
+ .add-diff-note {
+ opacity: 1.0;
+ filter: alpha(opacity=100);
+ }
}
}
-.diff_file_content tr.line_holder:hover > td { background: $hover !important; }
-.diff_file_content tr.line_holder:hover > td .line_note_link {
- opacity: 1.0;
- filter: alpha(opacity=100);
+
+
+/**
+ * Note Form
+ */
+
+.comment-btn,
+.reply-btn {
+ @extend .save-btn;
}
+.diff_file,
+.discussion {
+ .new_note {
+ margin: 8px 5px 8px 0;
-.new_note {
- .input-file {
- font: 500px monospace;
- opacity: 0;
- filter: alpha(opacity=0);
- position: absolute;
- z-index: 1;
- top: 0;
- right: 0;
- padding: 0;
- margin: 0;
+ .note_options {
+ // because of the smaller width and the extra "cancel" button
+ margin-top: 8px;
+ }
}
+}
+.new_note {
+ display: none;
- .note_advanced_opts {
+ .buttons {
+ float: left;
+ margin-top: 8px;
+ }
+ .clearfix {
+ margin-bottom: 0;
+ }
+ .note_options {
h6 {
- line-height: 32px;
- padding-right: 15px;
+ @extend .left;
+ line-height: 20px;
+ padding-right: 16px;
+ padding-bottom: 16px;
+ }
+ label {
+ padding: 0;
}
- }
- .attachments {
- position: relative;
- width: 350px;
- height: 50px;
- overflow: hidden;
- margin:0 0 5px !important;
+ .attachment {
+ @extend .right;
+ position: relative;
+ width: 350px;
+ height: 50px;
+ margin:0 0 5px !important;
- .input_file {
- .file_upload {
- position: absolute;
- right: 14px;
- top: 7px;
+ // hide the actual file field
+ input {
+ display: none;
}
- .file_name {
- line-height: 30px;
- width: 240px;
- height: 28px;
- overflow: hidden;
- }
- .input-file {
- width: 260px;
- height: 41px;
+ .choose-btn {
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: 0;
+ }
+ .note_preview {
+ background: #f5f5f5;
+ border: 1px solid #ddd;
+ @include border-radius(4px);
+ min-height: 80px;
+ padding: 4px 6px;
+ }
+ .note_text {
+ font-size: 14px;
+ height: 80px;
+ width: 98.6%;
+ }
}
}
-.note-text {
- border: 1px solid #aaa;
- box-shadow: none;
+/* loading indicator */
+.notes-busy {
+ margin: 18px;
}