Merge branch 'master' into 6-0-dev

Conflicts:
	app/views/dashboard/projects.html.haml
	app/views/layouts/_head_panel.html.haml
	config/routes.rb
This commit is contained in:
Dmitriy Zaporozhets 2013-07-02 11:47:09 +03:00
commit ee890f2b2a
15 changed files with 324 additions and 24 deletions

View File

@ -56,6 +56,26 @@ var NoteList = {
".js-note-delete",
NoteList.removeNote);
// show the edit note form
$(document).on("click",
".js-note-edit",
NoteList.showEditNoteForm);
// cancel note editing
$(document).on("click",
".note-edit-cancel",
NoteList.cancelNoteEdit);
// delete note attachment
$(document).on("click",
".js-note-attachment-delete",
NoteList.deleteNoteAttachment);
// update the note after editing
$(document).on("ajax:complete",
"form.edit_note",
NoteList.updateNote);
// reset main target form after submit
$(document).on("ajax:complete",
".js-main-target-form",
@ -63,12 +83,12 @@ var NoteList = {
$(document).on("click",
".js-choose-note-attachment-button",
NoteList.chooseNoteAttachment);
".js-choose-note-attachment-button",
NoteList.chooseNoteAttachment);
$(document).on("click",
".js-show-outdated-discussion",
function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() });
".js-show-outdated-discussion",
function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() });
},
@ -107,8 +127,8 @@ var NoteList = {
/**
* Called when clicking the "Choose File" button.
*
* Opesn the file selection dialog.
*
* Opens the file selection dialog.
*/
chooseNoteAttachment: function() {
var form = $(this).closest("form");
@ -143,7 +163,7 @@ var NoteList = {
/**
* 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.
*/
@ -186,6 +206,59 @@ var NoteList = {
NoteList.updateVotes();
},
/**
* Called in response to clicking the edit note link
*
* Replaces the note text with the note edit form
* Adds a hidden div with the original content of the note to fill the edit note form with
* if the user cancels
*/
showEditNoteForm: function(e) {
e.preventDefault();
var note = $(this).closest(".note");
note.find(".note-text").hide();
// Show the attachment delete link
note.find(".js-note-attachment-delete").show();
var form = note.find(".note-edit-form");
form.show();
var textarea = form.find("textarea");
var p = $("<p></p>").text(textarea.val());
var hidden_div = $('<div class="note-original-content"></div>').append(p);
form.append(hidden_div);
hidden_div.hide();
textarea.focus();
},
/**
* Called in response to clicking the cancel button when editing a note
*
* Resets and hides the note editing form
*/
cancelNoteEdit: function(e) {
e.preventDefault();
var note = $(this).closest(".note");
NoteList.resetNoteEditing(note);
},
/**
* Called in response to clicking the delete attachment link
*
* Removes the attachment wrapper view, including image tag if it exists
* Resets the note editing form
*/
deleteNoteAttachment: function() {
var note = $(this).closest(".note");
note.find(".note-attachment").remove();
NoteList.resetNoteEditing(note);
NoteList.rewriteTimestamp(note.find(".note-last-update"));
},
/**
* Called when clicking on the "reply" button for a diff line.
*
@ -436,5 +509,65 @@ var NoteList = {
votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
}
},
/**
* Called in response to the edit note form being submitted
*
* Updates the current note field.
* Hides the edit note form
*/
updateNote: function(e, xhr, settings) {
response = JSON.parse(xhr.responseText);
if (response.success) {
var note_li = $("#note_" + response.id);
var note_text = note_li.find(".note-text");
note_text.html(response.note).show();
var note_form = note_li.find(".note-edit-form");
note_form.hide();
note_form.find(".btn-save").enableButton();
// Update the "Edited at xxx label" on the note to show it's just been updated
NoteList.rewriteTimestamp(note_li.find(".note-last-update"));
}
},
/**
* Called in response to the 'cancel note' link clicked, or after deleting a note attachment
*
* Hides the edit note form and shows the note
* Resets the edit note form textarea with the original content of the note
*/
resetNoteEditing: function(note) {
note.find(".note-text").show();
// Hide the attachment delete link
note.find(".js-note-attachment-delete").hide();
// Put the original content of the note back into the edit form textarea
var form = note.find(".note-edit-form");
var original_content = form.find(".note-original-content");
form.find("textarea").val(original_content.text());
original_content.remove();
note.find(".note-edit-form").hide();
},
/**
* Utility function to generate new timestamp text for a note
*
*/
rewriteTimestamp: function(element) {
// Strip all newlines from the existing timestamp
var ts = element.text().replace(/\n/g, ' ').trim();
// If the timestamp already has '(Edited xxx ago)' text, remove it
ts = ts.replace(new RegExp("\\(Edited [A-Za-z0-9 ]+\\)$", "gi"), "");
// Append "(Edited just now)"
ts = (ts + " <small>(Edited just now)</small>");
element.html(ts);
}
};

View File

@ -77,6 +77,7 @@ header {
top: -4px;
img {
width: 26px;
height: 26px;
@include border-radius(4px);
}
}

View File

@ -325,3 +325,32 @@ ul.notes {
float: left;
}
}
.note-edit-form {
display: none;
.note_text {
border: 1px solid #DDD;
box-shadow: none;
font-size: 14px;
height: 80px;
width: 98.6%;
}
.form-actions {
padding-left: 20px;
.btn-save {
float: left;
}
.note-form-option {
float: left;
padding: 2px 0 0 25px;
}
}
}
.js-note-attachment-delete {
display: none;
}

View File

@ -38,6 +38,32 @@ class Projects::NotesController < Projects::ApplicationController
end
end
def update
@note = @project.notes.find(params[:id])
return access_denied! unless can?(current_user, :admin_note, @note)
@note.update_attributes(params[:note])
respond_to do |format|
format.js do
render js: { success: @note.valid?, id: @note.id, note: view_context.markdown(@note.note) }.to_json
end
format.html do
redirect_to :back
end
end
end
def delete_attachment
@note = @project.notes.find(params[:id])
@note.remove_attachment!
@note.update_attribute(:attachment, nil)
respond_to do |format|
format.js { render nothing: true }
end
end
def preview
render text: view_context.markdown(params[:note])
end

View File

@ -28,4 +28,11 @@ module NotesHelper
def loading_new_notes?
params[:loading_new].present?
end
def note_timestamp(note)
# Shows the created at time and the updated at time if different
ts = "#{time_ago_in_words(note.created_at)} ago"
ts << content_tag(:small, " (Edited #{time_ago_in_words(note.updated_at)} ago)") if note.updated_at != note.created_at
ts.html_safe
end
end

View File

@ -37,4 +37,4 @@
%i.icon-signout
%li
= link_to current_user, class: "profile-pic", id: 'profile-pic' do
= image_tag gravatar_icon(current_user.email, 26)
= image_tag gravatar_icon(current_user.email, 26), alt: ''

View File

@ -6,13 +6,14 @@
Link here
&nbsp;
- 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
= link_to "#", title: "Edit comment", class: "js-note-edit" do
%i.icon-edit
= link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this 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_member(@project, note.author, avatar: false)
%span.note-last-update
= time_ago_in_words(note.updated_at)
ago
= note_timestamp(note)
- if note.upvote?
%span.vote.upvote.label.label-success
@ -25,13 +26,37 @@
.note-body
= preserve do
= markdown(note.note)
.note-text
= preserve do
= markdown(note.note)
.note-edit-form
= form_for note, url: project_note_path(@project, note), method: :put, remote: true do |f|
= f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on'
.form-actions
= f.submit 'Save changes', class: "btn btn-primary btn-save"
.note-form-option
%a.choose-btn.btn.btn-small.js-choose-note-attachment-button
%i.icon-paper-clip
%span Choose File ...
&nbsp;
%span.file_name.js-attachment-filename File name...
= f.file_field :attachment, class: "js-note-attachment-input hide"
= link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel"
- if note.attachment.url
- if note.attachment.image?
= image_tag note.attachment.url, class: 'note-image-attach'
.attachment.pull-right
= link_to note.attachment.secure_url, target: "_blank" do
%i.icon-paper-clip
= note.attachment_identifier
.note-attachment
- if note.attachment.image?
= image_tag note.attachment.url, class: 'note-image-attach'
.attachment.pull-right
= link_to note.attachment.secure_url, target: "_blank" do
%i.icon-paper-clip
= note.attachment_identifier
= link_to delete_attachment_project_note_path(@project, note),
title: "Delete this attachment", method: :delete, remote: true, confirm: 'Are you sure you want to remove the attachment?', class: "danger js-note-attachment-delete" do
%i.icon-trash.cred
.clear

View File

@ -6,6 +6,7 @@
.btn-group.tree-btn-group.pull-right
- if @snippet.author == current_user
= link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-tiny", title: 'Edit Snippet'
= link_to "Delete", snippet_path(@snippet), method: :delete, confirm: "Are you sure?", class: "btn btn-tiny", title: 'Delete Snippet'
= link_to "Raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank"
.file_content.code
- unless @snippet.content.empty?

View File

@ -1,8 +1,8 @@
%h3.page_title
- if @snippet.private?
%i.icon-lock.cgreen
%i{:class => "icon-lock cgreen has_bottom_tooltip", "data-original-title" => "Private snippet"}
- else
%i.icon-globe.cblue
%i{:class => "icon-globe cblue has_bottom_tooltip", "data-original-title" => "Public snippet"}
= @snippet.title

View File

@ -18,6 +18,7 @@ production: &base
host: localhost
port: 80
https: false
# WARNING: This feature is no longer supported
# Uncomment and customize to run in non-root path
# Note that ENV['RAILS_RELATIVE_URL_ROOT'] in config/puma.rb may need to be changed
# relative_url_root: /gitlab

View File

@ -284,12 +284,16 @@ Gitlab::Application.routes.draw do
end
end
resources :notes, only: [:index, :create, :destroy] do
resources :notes, only: [:index, :create, :destroy, :update] do
member do
delete :delete_attachment
end
collection do
post :preview
end
end
end
end
end
root to: "dashboard#show"

View File

@ -1,3 +1,5 @@
include ActionDispatch::TestProcess
FactoryGirl.define do
sequence :sentence, aliases: [:title, :content] do
Faker::Lorem.sentence
@ -127,6 +129,7 @@ FactoryGirl.define do
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]
factory :note_on_merge_request_with_attachment, traits: [:on_merge_request, :with_attachment]
trait :on_commit do
project factory: :project_with_code
@ -148,6 +151,10 @@ FactoryGirl.define do
noteable_id 1
noteable_type "Issue"
end
trait :with_attachment do
attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") }
end
end
factory :event do

View File

@ -3,6 +3,7 @@ require 'spec_helper'
describe "On a merge request", js: true do
let!(:project) { create(:project_with_code) }
let!(:merge_request) { create(:merge_request, project: project) }
let!(:note) { create(:note_on_merge_request_with_attachment, project: project) }
before do
login_as :user
@ -72,6 +73,71 @@ describe "On a merge request", js: true do
should_not have_css(".note")
end
end
describe "when editing a note", js: true do
it "should contain the hidden edit form" do
within("#note_#{note.id}") { should have_css(".note-edit-form", visible: false) }
end
describe "editing the note" do
before do
find('.note').hover
find(".js-note-edit").click
end
it "should show the note edit form and hide the note body" do
within("#note_#{note.id}") do
find(".note-edit-form", visible: true).should be_visible
find(".note-text", visible: false).should_not be_visible
end
end
it "should reset the edit note form textarea with the original content of the note if cancelled" do
find('.note').hover
find(".js-note-edit").click
within(".note-edit-form") do
fill_in "note[note]", with: "Some new content"
find(".btn-cancel").click
find(".js-note-text", visible: false).text.should == note.note
end
end
it "appends the edited at time to the note" do
find('.note').hover
find(".js-note-edit").click
within(".note-edit-form") do
fill_in "note[note]", with: "Some new content"
find(".btn-save").click
end
within("#note_#{note.id}") do
should have_css(".note-last-update small")
find(".note-last-update small").text.should match(/Edited just now/)
end
end
end
describe "deleting an attachment" do
before do
find('.note').hover
find(".js-note-edit").click
end
it "shows the delete link" do
within(".note-attachment") do
should have_css(".js-note-attachment-delete")
end
end
it "removes the attachment div and resets the edit form" do
find(".js-note-attachment-delete").click
should_not have_css(".note-attachment")
find(".note-edit-form", visible: false).should_not be_visible
end
end
end
end
describe "On a merge request diff", js: true, focus: true do

BIN
spec/fixtures/dk.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -237,7 +237,7 @@ end
# project_snippet GET /:project_id/snippets/:id(.:format) snippets#show
# PUT /:project_id/snippets/:id(.:format) snippets#update
# DELETE /:project_id/snippets/:id(.:format) snippets#destroy
describe Projects::SnippetsController, "routing" do
describe SnippetsController, "routing" do
it "to #raw" do
get("/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlabhq', id: '1')
end