From e2ece2bc350bf881e9716e51c01a59eecac65fc9 Mon Sep 17 00:00:00 2001 From: liyakun Date: Thu, 10 Sep 2015 16:18:40 +0200 Subject: 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 --- CHANGELOG | 1 + .../javascripts/blob/blob_file_dropzone.js.coffee | 52 ++++++++++++++ app/assets/stylesheets/pages/tree.scss | 12 ++++ app/controllers/projects/blob_controller.rb | 28 ++++++-- app/services/files/create_service.rb | 4 +- app/views/projects/blob/_actions.html.haml | 6 +- app/views/projects/blob/_replace.html.haml | 28 ++++++++ app/views/projects/blob/_upload.html.haml | 28 ++++++++ app/views/projects/blob/new.html.haml | 10 ++- app/views/projects/blob/show.html.haml | 1 + config/routes.rb | 10 +++ features/project/source/browse_files.feature | 23 ++++++ features/steps/project/source/browse_files.rb | 83 +++++++++++++++++++++- 13 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 app/assets/javascripts/blob/blob_file_dropzone.js.coffee create mode 100644 app/views/projects/blob/_replace.html.haml create mode 100644 app/views/projects/blob/_upload.html.haml diff --git a/CHANGELOG b/CHANGELOG index b41237f8679..2b1edf54c42 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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) diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee new file mode 100644 index 00000000000..090af9bb376 --- /dev/null +++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee @@ -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 = $("
").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 diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 71ca37c0cd7..df7fab07a57 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -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; +} diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 100d3d3b317..8776721d243 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -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, diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 91d715b2d63..ffbb5993279 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -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 diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 13f8271b979..5b61846fe6d 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -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 diff --git a/app/views/projects/blob/_replace.html.haml b/app/views/projects/blob/_replace.html.haml new file mode 100644 index 00000000000..84abf0303d0 --- /dev/null +++ b/app/views/projects/blob/_replace.html.haml @@ -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') diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml new file mode 100644 index 00000000000..5a6a3358a17 --- /dev/null +++ b/app/views/projects/blob/_upload.html.haml @@ -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') diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 7c2a4fece94..6fb46ea2040 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -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 diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index bd2fc43633c..19e876ec34c 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -10,3 +10,4 @@ - if allowed_tree_edit? = render 'projects/blob/remove' + = render 'projects/blob/replace' diff --git a/config/routes.rb b/config/routes.rb index 011af4825fa..ab1d8f7ce92 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index d3a77466a35..b5b6abe6aff 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -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 diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 5cb085db207..7a0ee4df45e 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -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.$('').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 -- cgit v1.2.3