Add "Replace" and "Upload" features
Refactor upload and replace functionality
Rename file and move CSS
Fix typo
Make dropzone a div
Remove unnecessary file
Change color of "upload existing one"
Add missing changes
This commit is contained in:
parent
7abb744eb9
commit
e2ece2bc35
|
|
@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
|
||||||
|
|
||||||
v 8.0.0 (unreleased)
|
v 8.0.0 (unreleased)
|
||||||
- Gracefully handle errors in syntax highlighting by leaving the block unformatted (Stan Hu)
|
- Gracefully handle errors in syntax highlighting by leaving the block unformatted (Stan Hu)
|
||||||
|
- Add "replace" and "upload" functionalities to allow user replace existing file and upload new file into current repository
|
||||||
- Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu)
|
- Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu)
|
||||||
- Fix emoji URLs in Markdown when relative_url_root is used (Stan Hu)
|
- Fix emoji URLs in Markdown when relative_url_root is used (Stan Hu)
|
||||||
- Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU)
|
- Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
class @BlobFileDropzone
|
||||||
|
constructor: (form, method) ->
|
||||||
|
form_dropzone = form.find('.dropzone')
|
||||||
|
Dropzone.autoDiscover = false
|
||||||
|
dropzone = form_dropzone.dropzone(
|
||||||
|
autoDiscover: false
|
||||||
|
autoProcessQueue: false
|
||||||
|
url: form.attr('action')
|
||||||
|
# Rails uses a hidden input field for PUT
|
||||||
|
# http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
|
||||||
|
method: method
|
||||||
|
clickable: true
|
||||||
|
uploadMultiple: false
|
||||||
|
paramName: "file"
|
||||||
|
maxFilesize: gon.max_file_size or 10
|
||||||
|
parallelUploads: 1
|
||||||
|
maxFiles: 1
|
||||||
|
addRemoveLinks: true
|
||||||
|
previewsContainer: '.dropzone-previews'
|
||||||
|
headers:
|
||||||
|
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
|
||||||
|
|
||||||
|
success: (header, response) ->
|
||||||
|
window.location.href = response.filePath
|
||||||
|
return
|
||||||
|
|
||||||
|
error: (temp, errorMessage) ->
|
||||||
|
stripped = $("<div/>").html(errorMessage).text();
|
||||||
|
$('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show()
|
||||||
|
return
|
||||||
|
|
||||||
|
maxfilesexceeded: (file) ->
|
||||||
|
@removeFile file
|
||||||
|
return
|
||||||
|
|
||||||
|
removedfile: (file) ->
|
||||||
|
$('.dropzone-previews')[0].removeChild(file.previewTemplate)
|
||||||
|
$('.dropzone-alerts').html('').hide()
|
||||||
|
return true
|
||||||
|
|
||||||
|
sending: (file, xhr, formData) ->
|
||||||
|
formData.append('commit_message', form.find('#commit_message').val())
|
||||||
|
return
|
||||||
|
)
|
||||||
|
|
||||||
|
submitButton = form.find('#submit-all')[0]
|
||||||
|
submitButton.addEventListener 'click', (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
alert "Please select a file" if dropzone[0].dropzone.getQueuedFiles().length == 0
|
||||||
|
dropzone[0].dropzone.processQueue()
|
||||||
|
return false
|
||||||
|
|
@ -116,3 +116,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#modal-remove-blob > .modal-dialog { width: 850px; }
|
#modal-remove-blob > .modal-dialog { width: 850px; }
|
||||||
|
|
||||||
|
.blob-upload-dropzone-previews {
|
||||||
|
text-align: center;
|
||||||
|
border: 2px;
|
||||||
|
border-style: dashed;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-link {
|
||||||
|
font-weight: normal;
|
||||||
|
color: #0000EE;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,16 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
flash[:notice] = "Your changes have been successfully committed"
|
flash[:notice] = "Your changes have been successfully committed"
|
||||||
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path))
|
respond_to do |format|
|
||||||
|
format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }
|
||||||
|
format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } }
|
||||||
|
end
|
||||||
else
|
else
|
||||||
flash[:alert] = result[:message]
|
flash[:alert] = result[:message]
|
||||||
render :new
|
respond_to do |format|
|
||||||
|
format.html { render :new }
|
||||||
|
format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -45,10 +51,16 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
flash[:notice] = "Your changes have been successfully committed"
|
flash[:notice] = "Your changes have been successfully committed"
|
||||||
redirect_to after_edit_path
|
respond_to do |format|
|
||||||
|
format.html { redirect_to after_edit_path }
|
||||||
|
format.json { render json: { message: "success", filePath: after_edit_path } }
|
||||||
|
end
|
||||||
else
|
else
|
||||||
flash[:alert] = result[:message]
|
flash[:alert] = result[:message]
|
||||||
render :edit
|
respond_to do |format|
|
||||||
|
format.html { render :edit }
|
||||||
|
format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -146,11 +158,19 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
|
|
||||||
@file_path =
|
@file_path =
|
||||||
if action_name.to_s == 'create'
|
if action_name.to_s == 'create'
|
||||||
|
if params[:file].present?
|
||||||
|
params[:file_name] = params[:file].original_filename
|
||||||
|
end
|
||||||
File.join(@path, File.basename(params[:file_name]))
|
File.join(@path, File.basename(params[:file_name]))
|
||||||
else
|
else
|
||||||
@path
|
@path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if params[:file].present?
|
||||||
|
params[:content] = Base64.encode64(params[:file].read)
|
||||||
|
params[:encoding] = 'base64'
|
||||||
|
end
|
||||||
|
|
||||||
@commit_params = {
|
@commit_params = {
|
||||||
file_path: @file_path,
|
file_path: @file_path,
|
||||||
current_branch: @current_branch,
|
current_branch: @current_branch,
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@ module Files
|
||||||
end
|
end
|
||||||
|
|
||||||
unless project.empty_repo?
|
unless project.empty_repo?
|
||||||
|
@file_path.slice!(0) if @file_path.start_with?('/')
|
||||||
|
|
||||||
blob = repository.blob_at_branch(@current_branch, @file_path)
|
blob = repository.blob_at_branch(@current_branch, @file_path)
|
||||||
|
|
||||||
if blob
|
if blob
|
||||||
raise_error("Your changes could not be committed, because file with such name exists")
|
raise_error("Your changes could not be committed because a file with the same name already exists")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@
|
||||||
tree_join(@commit.sha, @path)), class: 'btn btn-sm'
|
tree_join(@commit.sha, @path)), class: 'btn btn-sm'
|
||||||
|
|
||||||
- if allowed_tree_edit?
|
- if allowed_tree_edit?
|
||||||
= button_tag class: 'remove-blob btn btn-sm btn-remove',
|
.btn-group{:role => "group"}
|
||||||
'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do
|
%button.btn.btn-default{class: 'btn-primary', href: '#modal-replace-blob', 'data-target' => '#modal-replace-blob', 'data-toggle' => 'modal'} Replace
|
||||||
Remove
|
%button.btn.btn-default{class: 'btn-remove', href: '#modal-remove-blob', 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal'} Remove
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#modal-replace-blob.modal
|
||||||
|
.modal-dialog
|
||||||
|
.modal-content
|
||||||
|
.modal-header
|
||||||
|
%a.close{href: "#", "data-dismiss" => "modal"} ×
|
||||||
|
%h3.page-title Replace #{@blob.name}
|
||||||
|
%p.light
|
||||||
|
From branch
|
||||||
|
%strong= @ref
|
||||||
|
.modal-body
|
||||||
|
= form_tag namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'blob-file-upload-form-js form-horizontal' do
|
||||||
|
.dropzone
|
||||||
|
.dropzone-previews{class: "blob-upload-dropzone-previews"}
|
||||||
|
%p.dz-message{class: "hint"}<
|
||||||
|
Attach files by dragging & dropping or
|
||||||
|
%a{href: '#', class: "markdown-selector"}>click to upload
|
||||||
|
%br
|
||||||
|
.dropzone-alerts{class: "alert alert-danger data", "data-dismiss" => "alert", style: "display:none"}
|
||||||
|
= render 'shared/commit_message_container', params: params,
|
||||||
|
placeholder: 'Replace this file because...'
|
||||||
|
.form-group
|
||||||
|
.col-sm-offset-2.col-sm-10
|
||||||
|
= button_tag 'Replace file', class: 'btn btn-small btn-primary btn-replace-file', id: 'submit-all'
|
||||||
|
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
|
||||||
|
|
||||||
|
:coffeescript
|
||||||
|
disableButtonIfEmptyField $('.blob-file-upload-form-js').find('#commit_message'), '.btn-replace-file'
|
||||||
|
new BlobFileDropzone($('.blob-file-upload-form-js'), 'put')
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#modal-upload-blob.modal
|
||||||
|
.modal-dialog
|
||||||
|
.modal-content
|
||||||
|
.modal-header
|
||||||
|
%a.close{href: "#", "data-dismiss" => "modal"} ×
|
||||||
|
%h3.page-title Upload
|
||||||
|
%p.light
|
||||||
|
From branch
|
||||||
|
%strong= @ref
|
||||||
|
.modal-body
|
||||||
|
= form_tag namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'blob-file-upload-form-js form-horizontal' do
|
||||||
|
.dropzone
|
||||||
|
.dropzone-previews{class: "blob-upload-dropzone-previews"}
|
||||||
|
%p.dz-message{class: "hint"}<
|
||||||
|
Attach files by dragging & dropping or
|
||||||
|
%a{href: '#', class: "markdown-selector"}>click to upload
|
||||||
|
%br
|
||||||
|
.dropzone-alerts{class: "alert alert-danger data", "data-dismiss" => "alert", style: "display:none"}
|
||||||
|
= render 'shared/commit_message_container', params: params,
|
||||||
|
placeholder: 'Upload this file because...'
|
||||||
|
.form-group
|
||||||
|
.col-sm-offset-2.col-sm-10
|
||||||
|
= button_tag 'Upload file', class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
|
||||||
|
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
|
||||||
|
|
||||||
|
:coffeescript
|
||||||
|
disableButtonIfEmptyField $('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file'
|
||||||
|
new BlobFileDropzone($('.blob-file-upload-form-js'), 'post')
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
- page_title "New File", @ref
|
%h3.page-title<
|
||||||
%h3.page-title New file
|
Create new file or
|
||||||
|
%a.upload-link{href: '#modal-upload-blob', 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'}>upload existing one
|
||||||
|
|
||||||
|
.file-title
|
||||||
|
= render 'projects/blob/upload'
|
||||||
|
%br
|
||||||
|
|
||||||
.file-editor
|
.file-editor
|
||||||
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
|
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
|
||||||
= render 'projects/blob/editor', ref: @ref
|
= render 'projects/blob/editor', ref: @ref
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@
|
||||||
|
|
||||||
- if allowed_tree_edit?
|
- if allowed_tree_edit?
|
||||||
= render 'projects/blob/remove'
|
= render 'projects/blob/remove'
|
||||||
|
= render 'projects/blob/replace'
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,16 @@ Gitlab::Application.routes.draw do
|
||||||
to: 'blob#destroy',
|
to: 'blob#destroy',
|
||||||
constraints: { id: /.+/, format: false }
|
constraints: { id: /.+/, format: false }
|
||||||
)
|
)
|
||||||
|
put(
|
||||||
|
'/blob/*id',
|
||||||
|
to: 'blob#update',
|
||||||
|
constraints: { id: /.+/, format: false }
|
||||||
|
)
|
||||||
|
post(
|
||||||
|
'/blob/*id',
|
||||||
|
to: 'blob#create',
|
||||||
|
constraints: { id: /.+/, format: false }
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope do
|
scope do
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,29 @@ Feature: Project Source Browse Files
|
||||||
And I click on "Commit Changes"
|
And I click on "Commit Changes"
|
||||||
Then I am redirected to the new file
|
Then I am redirected to the new file
|
||||||
And I should see its new content
|
And I should see its new content
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can upload file and commit
|
||||||
|
Given I click on "new file" link in repo
|
||||||
|
Then I can see new file page
|
||||||
|
And I can see "upload existing one"
|
||||||
|
And I click on "upload existing one"
|
||||||
|
And I upload a new text file
|
||||||
|
And I fill the upload file commit message
|
||||||
|
And I click on "Upload file"
|
||||||
|
Then I can see the new text file
|
||||||
|
And I can see the new commit message
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: I can replace file and commit
|
||||||
|
Given I click on ".gitignore" file in repo
|
||||||
|
And I see the ".gitignore"
|
||||||
|
And I click on "Replace"
|
||||||
|
And I replace it with a text file
|
||||||
|
And I fill the replace file commit message
|
||||||
|
And I click on "Replace file"
|
||||||
|
Then I can see the new text file
|
||||||
|
And I can see the replacement commit message
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: I can create and commit file and specify new branch
|
Scenario: I can create and commit file and specify new branch
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# coding: utf-8
|
||||||
class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
||||||
include SharedAuthentication
|
include SharedAuthentication
|
||||||
include SharedProject
|
include SharedProject
|
||||||
|
|
@ -78,7 +79,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I fill the commit message' do
|
step 'I fill the commit message' do
|
||||||
fill_in :commit_message, with: 'Not yet a commit message.'
|
fill_in :commit_message, with: 'Not yet a commit message.', visible: true
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I click link "Diff"' do
|
step 'I click link "Diff"' do
|
||||||
|
|
@ -97,6 +98,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
||||||
click_button 'Remove file'
|
click_button 'Remove file'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
step 'I click on "Replace"' do
|
||||||
|
click_button "Replace"
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I click on "Replace file"' do
|
||||||
|
click_button 'Replace file'
|
||||||
|
end
|
||||||
|
|
||||||
step 'I see diff' do
|
step 'I see diff' do
|
||||||
expect(page).to have_css '.line_holder.new'
|
expect(page).to have_css '.line_holder.new'
|
||||||
end
|
end
|
||||||
|
|
@ -106,10 +115,55 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I can see new file page' do
|
step 'I can see new file page' do
|
||||||
expect(page).to have_content "New file"
|
expect(page).to have_content "new file"
|
||||||
expect(page).to have_content "Commit message"
|
expect(page).to have_content "Commit message"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
step 'I can see "upload existing one"' do
|
||||||
|
expect(page).to have_content "upload existing one"
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I click on "upload existing one"' do
|
||||||
|
click_link 'upload existing one'
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I click on "Upload file"' do
|
||||||
|
click_button 'Upload file'
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I can see the new commit message' do
|
||||||
|
expect(page).to have_content "New upload commit message"
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I upload a new text file' do
|
||||||
|
drop_in_dropzone test_text_file
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I fill the upload file commit message' do
|
||||||
|
page.within('#modal-upload-blob') do
|
||||||
|
fill_in :commit_message, with: 'New upload commit message'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I replace it with a text file' do
|
||||||
|
drop_in_dropzone test_text_file
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I fill the replace file commit message' do
|
||||||
|
page.within('#modal-replace-blob') do
|
||||||
|
fill_in :commit_message, with: 'Replacement file commit message'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I can see the replacement commit message' do
|
||||||
|
expect(page).to have_content "Replacement file commit message"
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I can see the new text file' do
|
||||||
|
expect(page).to have_content "Lorem ipsum dolor sit amet"
|
||||||
|
expect(page).to have_content "Sed ut perspiciatis unde omnis"
|
||||||
|
end
|
||||||
|
|
||||||
step 'I click on files directory' do
|
step 'I click on files directory' do
|
||||||
click_link 'files'
|
click_link 'files'
|
||||||
end
|
end
|
||||||
|
|
@ -232,4 +286,29 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
||||||
def new_file_name
|
def new_file_name
|
||||||
'not_a_file.md'
|
'not_a_file.md'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def drop_in_dropzone(file_path)
|
||||||
|
# Generate a fake input selector
|
||||||
|
page.execute_script <<-JS
|
||||||
|
var fakeFileInput = window.$('<input/>').attr(
|
||||||
|
{id: 'fakeFileInput', type: 'file'}
|
||||||
|
).appendTo('body');
|
||||||
|
JS
|
||||||
|
# Attach the file to the fake input selector with Capybara
|
||||||
|
attach_file("fakeFileInput", file_path)
|
||||||
|
# Add the file to a fileList array and trigger the fake drop event
|
||||||
|
page.execute_script <<-JS
|
||||||
|
var fileList = [$('#fakeFileInput')[0].files[0]];
|
||||||
|
var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
|
||||||
|
$('.dropzone')[0].dropzone.listeners[0].events.drop(e);
|
||||||
|
JS
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_text_file
|
||||||
|
File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_image_file
|
||||||
|
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue