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) | ||||
|   - 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 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) | ||||
|  |  | |||
|  | @ -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; } | ||||
| 
 | ||||
| .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 | ||||
|       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 | ||||
|       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 | ||||
| 
 | ||||
|  | @ -45,10 +51,16 @@ class Projects::BlobController < Projects::ApplicationController | |||
| 
 | ||||
|     if result[:status] == :success | ||||
|       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 | ||||
|       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 | ||||
| 
 | ||||
|  | @ -146,11 +158,19 @@ class Projects::BlobController < Projects::ApplicationController | |||
| 
 | ||||
|     @file_path = | ||||
|       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])) | ||||
|       else | ||||
|         @path | ||||
|       end | ||||
| 
 | ||||
|     if params[:file].present? | ||||
|       params[:content] = Base64.encode64(params[:file].read) | ||||
|       params[:encoding] = 'base64' | ||||
|     end | ||||
| 
 | ||||
|     @commit_params = { | ||||
|       file_path: @file_path, | ||||
|       current_branch: @current_branch, | ||||
|  |  | |||
|  | @ -19,10 +19,12 @@ module Files | |||
|       end | ||||
| 
 | ||||
|       unless project.empty_repo? | ||||
|         @file_path.slice!(0) if @file_path.start_with?('/') | ||||
| 
 | ||||
|         blob = repository.blob_at_branch(@current_branch, @file_path) | ||||
| 
 | ||||
|         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 | ||||
|  |  | |||
|  | @ -17,6 +17,6 @@ | |||
|         tree_join(@commit.sha, @path)), class: 'btn btn-sm' | ||||
| 
 | ||||
| - if allowed_tree_edit? | ||||
|   = button_tag class: 'remove-blob btn btn-sm btn-remove', | ||||
|       'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do | ||||
|     Remove | ||||
|   .btn-group{:role => "group"} | ||||
|     %button.btn.btn-default{class: 'btn-primary', href: '#modal-replace-blob', 'data-target' => '#modal-replace-blob', 'data-toggle' => 'modal'} Replace | ||||
|     %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 New file | ||||
| %h3.page-title< | ||||
|   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 | ||||
|   = 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 | ||||
|  |  | |||
|  | @ -10,3 +10,4 @@ | |||
| 
 | ||||
| - if allowed_tree_edit? | ||||
|   = render 'projects/blob/remove' | ||||
|   = render 'projects/blob/replace' | ||||
|  |  | |||
|  | @ -356,6 +356,16 @@ Gitlab::Application.routes.draw do | |||
|             to: 'blob#destroy', | ||||
|             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 | ||||
| 
 | ||||
|         scope do | ||||
|  |  | |||
|  | @ -33,6 +33,29 @@ Feature: Project Source Browse Files | |||
|     And I click on "Commit Changes" | ||||
|     Then I am redirected to the new file | ||||
|     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 | ||||
|   Scenario: I can create and commit file and specify new branch | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| # coding: utf-8 | ||||
| class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps | ||||
|   include SharedAuthentication | ||||
|   include SharedProject | ||||
|  | @ -78,7 +79,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps | |||
|   end | ||||
| 
 | ||||
|   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 | ||||
| 
 | ||||
|   step 'I click link "Diff"' do | ||||
|  | @ -97,6 +98,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps | |||
|     click_button 'Remove file' | ||||
|   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 | ||||
|     expect(page).to have_css '.line_holder.new' | ||||
|   end | ||||
|  | @ -106,10 +115,55 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps | |||
|   end | ||||
| 
 | ||||
|   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" | ||||
|   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 | ||||
|     click_link 'files' | ||||
|   end | ||||
|  | @ -232,4 +286,29 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps | |||
|   def new_file_name | ||||
|     'not_a_file.md' | ||||
|   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 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue