diff options
-rw-r--r-- | Gemfile | 1 | ||||
-rw-r--r-- | Gemfile.lock | 4 | ||||
-rw-r--r-- | app/assets/javascripts/gl_dropdown.js | 18 | ||||
-rw-r--r-- | app/models/blob.rb | 7 | ||||
-rw-r--r-- | app/models/blob_viewer/base.rb | 14 | ||||
-rw-r--r-- | app/views/projects/blob/_viewer.html.haml | 14 | ||||
-rw-r--r-- | changelogs/unreleased/dm-blob-binaryness-change.yml | 5 | ||||
-rw-r--r-- | changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml | 4 | ||||
-rw-r--r-- | config/boot.rb | 12 | ||||
-rw-r--r-- | lib/gitlab/git/blob.rb | 1 | ||||
-rw-r--r-- | spec/features/projects/blobs/blob_show_spec.rb | 37 | ||||
-rw-r--r-- | spec/javascripts/gl_dropdown_spec.js | 4 | ||||
-rw-r--r-- | spec/models/blob_viewer/base_spec.rb | 4 |
13 files changed, 103 insertions, 22 deletions
@@ -2,6 +2,7 @@ source 'https://rubygems.org' gem 'rails', '4.2.8' gem 'rails-deprecated_sanitizer', '~> 1.0.3' +gem 'bootsnap', '~> 1.0.0' # Responders respond_to and respond_with gem 'responders', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index b5f9c3beca7..d34b84df5e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,6 +82,8 @@ GEM bindata (2.3.5) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) + bootsnap (1.0.0) + msgpack (~> 1.0) bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) @@ -459,6 +461,7 @@ GEM minitest (5.7.0) mmap2 (2.2.6) mousetrap-rails (1.4.6) + msgpack (1.1.0) multi_json (1.12.1) multi_xml (0.6.0) multipart-post (2.0.0) @@ -888,6 +891,7 @@ DEPENDENCIES benchmark-ips (~> 2.3.0) better_errors (~> 2.1.0) binding_of_caller (~> 0.7.2) + bootsnap (~> 1.0.0) bootstrap-sass (~> 3.3.0) brakeman (~> 3.6.0) browser (~> 2.2) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d34561e5512..3babe273100 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -248,7 +248,7 @@ GitLabDropdown = (function() { return function(data) { _this.fullData = data; _this.parseData(_this.fullData); - _this.focusTextInput(); + _this.focusTextInput(true); if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') { return _this.filter.input.trigger('input'); } @@ -728,8 +728,20 @@ GitLabDropdown = (function() { return [selectedObject, isMarking]; }; - GitLabDropdown.prototype.focusTextInput = function() { - if (this.options.filterable) { this.filterInput.focus(); } + GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) { + if (this.options.filterable) { + $(':focus').blur(); + + this.dropdown.one('transitionend', () => { + this.filterInput.focus(); + }); + + if (triggerFocus) { + // This triggers after a ajax request + // in case of slow requests, the dropdown transition could already be finished + this.dropdown.trigger('transitionend'); + } + } }; GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { diff --git a/app/models/blob.rb b/app/models/blob.rb index 3869e79ba75..954d4e4d779 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -191,9 +191,12 @@ class Blob < SimpleDelegator rendered_as_text? && rich_viewer end + def expanded? + !!@expanded + end + def expand! - simple_viewer&.expanded = true - rich_viewer&.expanded = true + @expanded = true end private diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb index d2aa33d994e..35965d01692 100644 --- a/app/models/blob_viewer/base.rb +++ b/app/models/blob_viewer/base.rb @@ -6,15 +6,15 @@ module BlobViewer self.loading_partial_name = 'loading' - delegate :partial_path, :loading_partial_path, :rich?, :simple?, :text?, :binary?, to: :class + delegate :partial_path, :loading_partial_path, :rich?, :simple?, :load_async?, :text?, :binary?, to: :class attr_reader :blob - attr_accessor :expanded delegate :project, to: :blob def initialize(blob) @blob = blob + @initially_binary = blob.binary? end def self.partial_path @@ -57,14 +57,10 @@ module BlobViewer false end - def load_async? - self.class.load_async? && render_error.nil? - end - def collapsed? return @collapsed if defined?(@collapsed) - @collapsed = !expanded && collapse_limit && blob.raw_size > collapse_limit + @collapsed = !blob.expanded? && collapse_limit && blob.raw_size > collapse_limit end def too_large? @@ -73,6 +69,10 @@ module BlobViewer @too_large = size_limit && blob.raw_size > size_limit end + def binary_detected_after_load? + !@initially_binary && blob.binary? + end + # This method is used on the server side to check whether we can attempt to # render the blob at all. Human-readable error messages are found in the # `BlobHelper#blob_render_error_reason` helper. diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml index 4252f27d007..013f1c267c8 100644 --- a/app/views/projects/blob/_viewer.html.haml +++ b/app/views/projects/blob/_viewer.html.haml @@ -1,13 +1,19 @@ - hidden = local_assigns.fetch(:hidden, false) - render_error = viewer.render_error -- load_async = local_assigns.fetch(:load_async, viewer.load_async?) +- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?) - viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async .blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) } - - if load_async - = render viewer.loading_partial_path, viewer: viewer - - elsif render_error + - if render_error = render 'projects/blob/render_error', viewer: viewer + - elsif load_async + = render viewer.loading_partial_path, viewer: viewer - else - viewer.prepare! + + -# In the rare case where the first kilobyte of the file looks like text, + -# but the file turns out to actually be binary after loading all data, + -# we fall back on the binary Download viewer. + - viewer = BlobViewer::Download.new(viewer.blob) if viewer.binary_detected_after_load? + = render viewer.partial_path, viewer: viewer diff --git a/changelogs/unreleased/dm-blob-binaryness-change.yml b/changelogs/unreleased/dm-blob-binaryness-change.yml new file mode 100644 index 00000000000..f3e3af26f12 --- /dev/null +++ b/changelogs/unreleased/dm-blob-binaryness-change.yml @@ -0,0 +1,5 @@ +--- +title: Detect if file that appears to be text in the first 1024 bytes is actually + binary afer loading all data +merge_request: +author: diff --git a/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml b/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml new file mode 100644 index 00000000000..05d52fcad0f --- /dev/null +++ b/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml @@ -0,0 +1,4 @@ +--- +title: Fixed dropdown filter input not focusing after transition +merge_request: +author: diff --git a/config/boot.rb b/config/boot.rb index f2830ae3166..17a71148370 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -4,3 +4,15 @@ require 'rubygems' ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +# Default Bootsnap configuration from https://github.com/Shopify/bootsnap#usage +require 'bootsnap' +Bootsnap.setup( + cache_dir: 'tmp/cache', + development_mode: ENV['RAILS_ENV'] == 'development', + load_path_cache: true, + autoload_paths_cache: true, + disable_trace: false, + compile_cache_iseq: true, + compile_cache_yaml: true +) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index d60e607b02b..33a7624e303 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -123,6 +123,7 @@ module Gitlab @loaded_all_data = true @data = repository.lookup(id).content @loaded_size = @data.bytesize + @binary = nil end def name diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 82cfbfda157..45fdb36e506 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' feature 'File blob', :js, feature: true do let(:project) { create(:project, :public) } - def visit_blob(path, fragment = nil) - visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment) + def visit_blob(path, anchor: nil, ref: 'master') + visit namespace_project_blob_path(project.namespace, project, File.join(ref, path), anchor: anchor) wait_for_requests end @@ -101,7 +101,7 @@ feature 'File blob', :js, feature: true do context 'visiting with a line number anchor' do before do - visit_blob('files/markdown/ruby-style-guide.md', 'L1') + visit_blob('files/markdown/ruby-style-guide.md', anchor: 'L1') end it 'displays the blob using the simple viewer' do @@ -352,6 +352,37 @@ feature 'File blob', :js, feature: true do end end + context 'binary file that appears to be text in the first 1024 bytes' do + before do + visit_blob('encoding/binary-1.bin', ref: 'binary-encoding') + end + + it 'displays the blob' do + aggregate_failures do + # shows a download link + expect(page).to have_link('Download (23.8 KB)') + + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') + + # The specs below verify an arguably incorrect result, but since we only + # learn that the file is not actually text once the text viewer content + # is loaded asynchronously, there is no straightforward way to get these + # synchronously loaded elements to display correctly. + # + # Clicking the copy button will result in nothing being copied. + # Clicking the raw button will result in the binary file being downloaded, + # as expected. + + # shows an enabled copy button, incorrectly + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + + # shows a raw button, incorrectly + expect(page).to have_link('Open raw') + end + end + end + context '.gitlab-ci.yml' do before do project.add_master(project.creator) diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 3292590b9ed..10fcc590c89 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -185,7 +185,7 @@ import '~/lib/utils/url_utility'; expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); }); - it('should focus on input when opening for the second time', () => { + it('should focus on input when opening for the second time after transition', () => { remoteCallback(); this.dropdownContainerElement.trigger({ type: 'keyup', @@ -193,6 +193,7 @@ import '~/lib/utils/url_utility'; keyCode: ARROW_KEYS.ESC }); this.dropdownButtonElement.click(); + this.dropdownContainerElement.trigger('transitionend'); expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); }); }); @@ -201,6 +202,7 @@ import '~/lib/utils/url_utility'; it('should focus input when passing array data to drop down', () => { initDropDown.call(this, false, true); this.dropdownButtonElement.click(); + this.dropdownContainerElement.trigger('transitionend'); expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); }); }); diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb index d56379eb59d..574438838d8 100644 --- a/spec/models/blob_viewer/base_spec.rb +++ b/spec/models/blob_viewer/base_spec.rb @@ -106,9 +106,9 @@ describe BlobViewer::Base, model: true do end describe '#render_error' do - context 'when expanded' do + context 'when the blob is expanded' do before do - viewer.expanded = true + blob.expand! end context 'when the blob size is larger than the size limit' do |