diff options
author | Sean McGivern <sean@mcgivern.me.uk> | 2017-03-13 19:56:33 +0300 |
---|---|---|
committer | Sean McGivern <sean@mcgivern.me.uk> | 2017-03-13 19:56:33 +0300 |
commit | 1585608bdcf932b58d301a7943c01ea824ea524e (patch) | |
tree | 86dc4290cf711aa6f19ab7dc64d432c849cd004b /app | |
parent | 1d4b11f3388ddd7cf0076f95ac26196f6949dc0b (diff) | |
parent | e0fe2834ebe6d9678444c8a10b79ca093f944232 (diff) |
Merge branch '24501-new-file-existing-branch' into 'master'
New file from interface on existing branch
Closes #24501
See merge request !8427
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/blob/blob_file_dropzone.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/blob/create_branch_dropdown.js | 88 | ||||
-rw-r--r-- | app/assets/javascripts/blob/target_branch_dropdown.js | 152 | ||||
-rw-r--r-- | app/assets/javascripts/dispatcher.js | 26 | ||||
-rw-r--r-- | app/assets/javascripts/main.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/new_commit_form.js | 12 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/projects.scss | 3 | ||||
-rw-r--r-- | app/controllers/projects/blob_controller.rb | 7 | ||||
-rw-r--r-- | app/controllers/projects/branches_controller.rb | 13 | ||||
-rw-r--r-- | app/views/projects/commit/_change.html.haml | 2 | ||||
-rw-r--r-- | app/views/shared/_branch_switcher.html.haml | 8 | ||||
-rw-r--r-- | app/views/shared/_new_commit_form.html.haml | 2 | ||||
-rw-r--r-- | app/views/shared/projects/blob/_branch_page_create.html.haml | 8 | ||||
-rw-r--r-- | app/views/shared/projects/blob/_branch_page_default.html.haml | 10 |
14 files changed, 318 insertions, 17 deletions
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index 5f14ff40eee..8f6bf162d6e 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -36,7 +36,7 @@ this.removeFile(file); }); return this.on('sending', function(file, xhr, formData) { - formData.append('target_branch', form.find('.js-target-branch').val()); + formData.append('target_branch', form.find('input[name="target_branch"]').val()); formData.append('create_merge_request', form.find('.js-create-merge-request').val()); formData.append('commit_message', form.find('.js-commit-message').val()); }); diff --git a/app/assets/javascripts/blob/create_branch_dropdown.js b/app/assets/javascripts/blob/create_branch_dropdown.js new file mode 100644 index 00000000000..95517f51b1c --- /dev/null +++ b/app/assets/javascripts/blob/create_branch_dropdown.js @@ -0,0 +1,88 @@ +class CreateBranchDropdown { + constructor(el, targetBranchDropdown) { + this.targetBranchDropdown = targetBranchDropdown; + this.el = el; + this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back'); + this.cancelButton = this.el.querySelector('.js-cancel-branch-btn'); + this.newBranchField = this.el.querySelector('#new_branch_name'); + this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn'); + + this.newBranchCreateButton.setAttribute('disabled', ''); + + this.addBindings(); + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('beforeunload', this.cleanupWrapper); + } + + cleanup() { + this.cleanBindings(); + document.removeEventListener('beforeunload', this.cleanupWrapper); + } + + cleanBindings() { + this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper); + this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper); + this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper); + this.dropdownBack.removeEventListener('click', this.resetFormWrapper); + this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper); + this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper); + } + + addBindings() { + this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this); + this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this); + this.resetFormWrapper = this.resetForm.bind(this); + this.handleCancelClickWrapper = this.handleCancelClick.bind(this); + this.createBranchWrapper = this.createBranch.bind(this); + + this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper); + this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper); + this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper); + this.dropdownBack.addEventListener('click', this.resetFormWrapper); + this.cancelButton.addEventListener('click', this.handleCancelClickWrapper); + this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper); + } + + handleCancelClick(e) { + e.preventDefault(); + e.stopPropagation(); + + this.resetForm(); + this.dropdownBack.click(); + } + + handleNewBranchKeydown(e) { + const keyCode = e.which; + const ENTER_KEYCODE = 13; + if (keyCode === ENTER_KEYCODE) { + this.createBranch(e); + } + } + + enableBranchCreateButton() { + if (this.newBranchField.value !== '') { + this.newBranchCreateButton.removeAttribute('disabled'); + } else { + this.newBranchCreateButton.setAttribute('disabled', ''); + } + } + + resetForm() { + this.newBranchField.value = ''; + this.enableBranchCreateButtonWrapper(); + } + + createBranch(e) { + e.preventDefault(); + + if (this.newBranchCreateButton.getAttribute('disabled') === '') { + return; + } + const newBranchName = this.newBranchField.value; + this.targetBranchDropdown.setNewBranch(newBranchName); + this.resetForm(); + } +} + +window.gl = window.gl || {}; +gl.CreateBranchDropdown = CreateBranchDropdown; diff --git a/app/assets/javascripts/blob/target_branch_dropdown.js b/app/assets/javascripts/blob/target_branch_dropdown.js new file mode 100644 index 00000000000..216f069ef71 --- /dev/null +++ b/app/assets/javascripts/blob/target_branch_dropdown.js @@ -0,0 +1,152 @@ +/* eslint-disable class-methods-use-this */ +const SELECT_ITEM_MSG = 'Select'; + +class TargetBranchDropDown { + constructor(dropdown) { + this.dropdown = dropdown; + this.$dropdown = $(dropdown); + this.fieldName = this.dropdown.getAttribute('data-field-name'); + this.form = this.dropdown.closest('form'); + this.createDropdown(); + } + + static bootstrap() { + const dropdowns = document.querySelectorAll('.js-project-branches-dropdown'); + [].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown)); + } + + createDropdown() { + const self = this; + this.$dropdown.glDropdown({ + selectable: true, + filterable: true, + search: { + fields: ['title'], + }, + data: (term, callback) => $.ajax({ + url: self.dropdown.getAttribute('data-refs-url'), + data: { + ref: self.dropdown.getAttribute('data-ref'), + show_all: true, + }, + dataType: 'json', + }).done(refs => callback(self.dropdownData(refs))), + toggleLabel(item, el) { + if (el.is('.is-active')) { + return item.text; + } + return SELECT_ITEM_MSG; + }, + clicked(item, el, e) { + e.preventDefault(); + self.onClick.call(self); + }, + fieldName: self.fieldName, + }); + return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this); + } + + onClick() { + this.enableSubmit(); + this.$dropdown.trigger('change.branch'); + } + + enableSubmit() { + const submitBtn = this.form.querySelector('[type="submit"]'); + if (this.branchInput && this.branchInput.value) { + submitBtn.removeAttribute('disabled'); + } else { + submitBtn.setAttribute('disabled', ''); + } + } + + dropdownData(refs) { + const branchList = this.dropdownItems(refs); + this.cachedRefs = refs; + this.addDefaultBranch(branchList); + this.addNewBranch(branchList); + return { Branches: branchList }; + } + + dropdownItems(refs) { + return refs.map(this.dropdownItem); + } + + dropdownItem(ref) { + return { id: ref, text: ref, title: ref }; + } + + addDefaultBranch(branchList) { + // when no branch is selected do nothing + if (!this.branchInput) { + return; + } + + const branchInputVal = this.branchInput.value; + const currentBranchIndex = this.searchBranch(branchList, branchInputVal); + + if (currentBranchIndex === -1) { + this.unshiftBranch(branchList, this.dropdownItem(branchInputVal)); + } + } + + addNewBranch(branchList) { + if (this.newBranch) { + this.unshiftBranch(branchList, this.newBranch); + } + } + + searchBranch(branchList, branchName) { + return _.findIndex(branchList, el => branchName === el.id); + } + + unshiftBranch(branchList, branch) { + const branchIndex = this.searchBranch(branchList, branch.id); + + if (branchIndex === -1) { + branchList.unshift(branch); + } + } + + setNewBranch(newBranchName) { + this.newBranch = this.dropdownItem(newBranchName); + this.refreshData(); + this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName)); + } + + refreshData() { + this.glDropdown.fullData = this.dropdownData(this.cachedRefs); + this.clearFilter(); + } + + clearFilter() { + // apply an empty filter in order to refresh the data + this.glDropdown.filter.filter(''); + this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = ''; + } + + selectBranch(index) { + const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index]; + + if (!branch.classList.contains('is-active')) { + branch.click(); + } else { + this.closeDropdown(); + } + } + + closeDropdown() { + this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click(); + } + + get branchInput() { + return this.form.querySelector(`input[name="${this.fieldName}"]`); + } + + get glDropdown() { + return this.$dropdown.data('glDropdown'); + } +} + +window.gl = window.gl || {}; +gl.TargetBranchDropDown = TargetBranchDropDown; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 7b9b9123c31..5739a28699f 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -59,7 +59,7 @@ const UserCallout = require('./user_callout'); } Dispatcher.prototype.initPageScripts = function() { - var page, path, shortcut_handler; + var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl; page = $('body').attr('data-page'); if (!page) { return false; @@ -245,16 +245,36 @@ const UserCallout = require('./user_callout'); case 'projects:tree:show': shortcut_handler = new ShortcutsNavigation(); new TreeView(); + gl.TargetBranchDropDown.bootstrap(); break; case 'projects:find_file:show': shortcut_handler = true; break; + case 'projects:blob:new': + gl.TargetBranchDropDown.bootstrap(); + break; + case 'projects:blob:create': + gl.TargetBranchDropDown.bootstrap(); + break; case 'projects:blob:show': + gl.TargetBranchDropDown.bootstrap(); + new LineHighlighter(); + shortcut_handler = new ShortcutsNavigation(); + fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); + fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); + new ShortcutsBlob({ + skipResetBindings: true, + fileBlobPermalinkUrl, + }); + break; + case 'projects:blob:edit': + gl.TargetBranchDropDown.bootstrap(); + break; case 'projects:blame:show': new LineHighlighter(); shortcut_handler = new ShortcutsNavigation(); - const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); - const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); + fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); + fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); new ShortcutsBlob({ skipResetBindings: true, fileBlobPermalinkUrl, diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 689a6c3a93a..604ed91627a 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -66,6 +66,8 @@ import './blob/blob_gitignore_selectors'; import './blob/blob_license_selector'; import './blob/blob_license_selectors'; import './blob/template_selector'; +import './blob/create_branch_dropdown'; +import './blob/target_branch_dropdown'; // templates import './templates/issuable_template_selector'; diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index 747f693726e..ad36f08840d 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -3,19 +3,23 @@ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.NewCommitForm = (function() { - function NewCommitForm(form) { + function NewCommitForm(form, targetBranchName = 'target_branch') { + this.form = form; + this.targetBranchName = targetBranchName; this.renderDestination = bind(this.renderDestination, this); - this.newBranch = form.find('.js-target-branch'); + this.targetBranchDropdown = form.find('button.js-target-branch'); this.originalBranch = form.find('.js-original-branch'); this.createMergeRequest = form.find('.js-create-merge-request'); this.createMergeRequestContainer = form.find('.js-create-merge-request-container'); + this.targetBranchDropdown.on('change.branch', this.renderDestination); this.renderDestination(); - this.newBranch.keyup(this.renderDestination); } NewCommitForm.prototype.renderDestination = function() { var different; - different = this.newBranch.val() !== this.originalBranch.val(); + var targetBranch = this.form.find(`input[name="${this.targetBranchName}"]`); + + different = targetBranch.val() !== this.originalBranch.val(); if (different) { this.createMergeRequestContainer.show(); if (!this.wasDifferent) { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 4914933430f..efa47be9a73 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -795,7 +795,8 @@ pre.light-well { } .project-refs-form .dropdown-menu, -.dropdown-menu-projects { +.dropdown-menu-projects, +.dropdown-menu-branches { width: 300px; @media (min-width: $screen-sm-min) { diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 21ed0660762..52fc67d162c 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -23,6 +23,8 @@ class Projects::BlobController < Projects::ApplicationController end def create + update_ref + create_commit(Files::CreateService, success_notice: "The file has been successfully created.", success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }, failure_view: :new, @@ -87,6 +89,11 @@ class Projects::BlobController < Projects::ApplicationController private + def update_ref + branch_exists = @repository.find_branch(@target_branch) + @ref = @target_branch if branch_exists + end + def blob @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path)) diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index c40f9b7f75f..22714d9c5a4 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -10,15 +10,16 @@ class Projects::BranchesController < Projects::ApplicationController def index @sort = params[:sort].presence || sort_value_name @branches = BranchesFinder.new(@repository, params).execute - @branches = Kaminari.paginate_array(@branches).page(params[:page]) - @max_commits = @branches.reduce(0) do |memo, branch| - diverging_commit_counts = repository.diverging_commit_counts(branch) - [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max - end + @branches = Kaminari.paginate_array(@branches).page(params[:page]) unless params[:show_all].present? respond_to do |format| - format.html + format.html do + @max_commits = @branches.reduce(0) do |memo, branch| + diverging_commit_counts = repository.diverging_commit_counts(branch) + [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max + end + end format.json do render json: @branches.map(&:name) end diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index 2ebd4f9069a..b5f67cae341 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -37,4 +37,4 @@ = commit_in_fork_help :javascript - new NewCommitForm($('.js-#{type}-form')) + new NewCommitForm($('.js-#{type}-form'), 'start_branch') diff --git a/app/views/shared/_branch_switcher.html.haml b/app/views/shared/_branch_switcher.html.haml new file mode 100644 index 00000000000..7799aff6b5b --- /dev/null +++ b/app/views/shared/_branch_switcher.html.haml @@ -0,0 +1,8 @@ +- dropdown_toggle_text = @target_branch || tree_edit_branch += hidden_field_tag 'target_branch', dropdown_toggle_text + +.dropdown + = dropdown_toggle dropdown_toggle_text, { toggle: 'dropdown', selected: dropdown_toggle_text, field_name: 'target_branch', form_id: '.js-edit-blob-form', refs_url: namespace_project_branches_path(@project.namespace, @project) }, { toggle_class: 'js-project-branches-dropdown js-target-branch' } + .dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging.dropdown-menu-branches + = render partial: 'shared/projects/blob/branch_page_default' + = render partial: 'shared/projects/blob/branch_page_create' diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml index 0c8ac48bb58..3ac5e15d1c4 100644 --- a/app/views/shared/_new_commit_form.html.haml +++ b/app/views/shared/_new_commit_form.html.haml @@ -7,7 +7,7 @@ .form-group.branch = label_tag 'target_branch', 'Target branch', class: 'control-label' .col-sm-10 - = text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch" + = render 'shared/branch_switcher' .js-create-merge-request-container .checkbox diff --git a/app/views/shared/projects/blob/_branch_page_create.html.haml b/app/views/shared/projects/blob/_branch_page_create.html.haml new file mode 100644 index 00000000000..c279a0d8846 --- /dev/null +++ b/app/views/shared/projects/blob/_branch_page_create.html.haml @@ -0,0 +1,8 @@ +.dropdown-page-two.dropdown-new-branch + = dropdown_title('Create new branch', back: true) + = dropdown_content do + %input#new_branch_name.default-dropdown-input.append-bottom-10{ type: "text", placeholder: "Name new branch" } + %button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" } + Create + %button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" } + Cancel diff --git a/app/views/shared/projects/blob/_branch_page_default.html.haml b/app/views/shared/projects/blob/_branch_page_default.html.haml new file mode 100644 index 00000000000..9bf78d10878 --- /dev/null +++ b/app/views/shared/projects/blob/_branch_page_default.html.haml @@ -0,0 +1,10 @@ +.dropdown-page-one + = dropdown_title "Select branch" + = dropdown_filter "Search branches" + = dropdown_content + = dropdown_loading + = dropdown_footer do + %ul.dropdown-footer-list + %li + %a.create-new-branch.dropdown-toggle-page{ href: "#" } + Create new branch |