From f5e43f0788438758e4b1404da19fb4255245aead Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 30 Sep 2016 11:14:42 +0200 Subject: Prevent rendering the link when the author has no access --- lib/banzai/filter/user_reference_filter.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index e1ca7f4d24b..b3c8e565c27 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -106,13 +106,17 @@ module Banzai project = context[:project] author = context[:author] - url = urls.namespace_project_url(project.namespace, project, - only_path: context[:only_path]) + if author && !project.team.member?(author) + '@all' + else + url = urls.namespace_project_url(project.namespace, project, + only_path: context[:only_path]) - data = data_attribute(project: project.id, author: author.try(:id)) - text = link_text || User.reference_prefix + 'all' + data = data_attribute(project: project.id, author: author.try(:id)) + text = link_text || User.reference_prefix + 'all' - link_tag(url, data, text, 'All Project and Group Members') + link_tag(url, data, text, 'All Project and Group Members') + end end def link_to_namespace(namespace, link_text: nil) -- cgit v1.2.1 From bf3b8d1c1d3d854311a638ebeaf87991367e4052 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 30 Sep 2016 14:53:37 +0200 Subject: Fix test, add author attribute to all tests --- CHANGELOG | 1 + lib/banzai/filter/user_reference_filter.rb | 4 ++-- spec/lib/banzai/filter/user_reference_filter_spec.rb | 9 +++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a52ac53bae7..b55ac5eb511 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,7 @@ v 8.13.0 (unreleased) - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) + - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index b3c8e565c27..c6302b586d3 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -106,8 +106,8 @@ module Banzai project = context[:project] author = context[:author] - if author && !project.team.member?(author) - '@all' + if author && !project.team.member?(author) + link_text else url = urls.namespace_project_url(project.namespace, project, only_path: context[:only_path]) diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index fdbdb21eac1..4b1ea6403cc 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -31,13 +31,16 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end it 'supports a special @all mention' do + project.team << [user, :developer] doc = reference_filter("Hey #{reference}", author: user) + expect(doc.css('a').length).to eq 1 expect(doc.css('a').first.attr('href')) .to eq urls.namespace_project_url(project.namespace, project) end it 'includes a data-author attribute when there is an author' do + project.team << [user, :developer] doc = reference_filter(reference, author: user) expect(doc.css('a').first.attr('data-author')).to eq(user.id.to_s) @@ -48,6 +51,12 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do expect(doc.css('a').first.has_attribute?('data-author')).to eq(false) end + + it 'ignores reference to all when user is not a project member' do + doc = reference_filter("Hey #{reference}", author: user) + + expect(doc.css('a').length).to eq 0 + end end context 'mentioning a user' do -- cgit v1.2.1 From fd8c30d1d7da92b45732e532890453b277428588 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Sat, 1 Oct 2016 11:17:56 +0200 Subject: Imrove grammar --- spec/lib/banzai/filter/user_reference_filter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 4b1ea6403cc..729e77fd43f 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -52,7 +52,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do expect(doc.css('a').first.has_attribute?('data-author')).to eq(false) end - it 'ignores reference to all when user is not a project member' do + it 'ignores reference to all when the user is not a project member' do doc = reference_filter("Hey #{reference}", author: user) expect(doc.css('a').length).to eq 0 -- cgit v1.2.1 From 8cb9b3764a3e790399c6ee7923f5b5da1fa72171 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 29 Sep 2016 11:31:47 +0100 Subject: Fixes invalid html - table was inside an ul and had 2 tbody tags Adds hidden-xs class to the all column instead of the div inside it in order to remove extra whitespace when the actions are hidden Fixes columns width in pipeplines table Changes table parent element to not be an ul and reduces column width for the Build column Adds entry to CHANGELOG Removes added width for build table. Adds the CHANGELOG entry in the middle to avoid more conflicts with master --- CHANGELOG | 1 + app/assets/stylesheets/pages/pipelines.scss | 6 +- app/views/projects/builds/_table.html.haml | 2 +- app/views/projects/builds/index.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 65 +++++++++++----------- .../projects/ci/pipelines/_pipeline.html.haml | 55 +++++++++--------- app/views/projects/pipelines/index.html.haml | 16 +++--- 7 files changed, 75 insertions(+), 72 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6ce15ef28bf..ed25218236d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Fix broken repository 500 errors in project list + - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) v 8.12.4 (unreleased) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index b035bfc9f3c..6484e5c02a9 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -39,6 +39,10 @@ overflow: auto; } +.pipelines .table.builds .branch-commit { + width: 33%; +} + .table.builds { min-width: 900px; @@ -77,7 +81,7 @@ } .branch-commit { - + .branch-name { font-weight: bold; max-width: 150px; diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index c2bcfb773a6..f3747ba2a21 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -1,7 +1,7 @@ - admin = local_assigns.fetch(:admin, false) - if builds.blank? - %li + %div .nothing-here-block No builds to show - else .table-holder diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 5c60b7a7364..06070f12bbd 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -19,5 +19,5 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %ul.content-list.builds-content-list + %div.content-list.builds-content-list = render "table", builds: @builds, project: @project diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 75192c48188..9248adfde80 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -13,45 +13,44 @@ - else = ci_status_with_icon(build.status) - %td - .branch-commit - - if can?(current_user, :read_build, build) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span.build-link ##{build.id} - - else + %td.branch-commit + - if can?(current_user, :read_build, build) + = link_to namespace_project_build_url(build.project.namespace, build.project, build) do %span.build-link ##{build.id} + - else + %span.build-link ##{build.id} - - if ref - - if build.ref - .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - - else - .light none + - if ref + - if build.ref .icon-container - = custom_icon("icon_commit") + = build.tag? ? icon('tag') : icon('code-fork') + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" + - else + .light none + .icon-container + = custom_icon("icon_commit") - - if commit_sha - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" + - if commit_sha + = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - - if build.stuck? - = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if retried - = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') + - if build.stuck? + = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') + - if retried + = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %span.label.label-info triggered - - if build.try(:allow_failure) - %span.label.label-danger allowed to fail - - if retried - %span.label.label-warning retried - - if build.manual? - %span.label.label-info manual + .label-container + - if build.tags.any? + - build.tags.each do |tag| + %span.label.label-primary + = tag + - if build.try(:trigger_request) + %span.label.label-info triggered + - if build.try(:allow_failure) + %span.label.label-danger allowed to fail + - if retried + %span.label.label-warning retried + - if build.manual? + %span.label.label-info manual - if admin %td diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 04e48a4dc17..b87c7a485df 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -9,33 +9,32 @@ = ci_icon_for_status(status) - else = ci_status_with_icon(status) - %td - .branch-commit - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do - %span ##{pipeline.id} - - if pipeline.ref && show_branch - .icon-container - = pipeline.tag? ? icon('tag') : icon('code-fork') - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" - - if show_commit - .icon-container - = custom_icon("icon_commit") - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" - - if pipeline.latest? - %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest - - if pipeline.triggered? - %span.label.label-primary triggered - - if pipeline.yaml_errors.present? - %span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid - - if pipeline.builds.any?(&:stuck?) - %span.label.label-warning stuck + %td.branch-commit + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do + %span ##{pipeline.id} + - if pipeline.ref && show_branch + .icon-container + = pipeline.tag? ? icon('tag') : icon('code-fork') + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" + - if show_commit + .icon-container + = custom_icon("icon_commit") + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" + - if pipeline.latest? + %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest + - if pipeline.triggered? + %span.label.label-primary triggered + - if pipeline.yaml_errors.present? + %span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid + - if pipeline.builds.any?(&:stuck?) + %span.label.label-warning stuck - %p.commit-title - - if commit = pipeline.commit - = author_avatar(commit, size: 20) - = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message" - - else - Cant find HEAD commit for this branch + %p.commit-title + - if commit = pipeline.commit + = author_avatar(commit, size: 20) + = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message" + - else + Cant find HEAD commit for this branch - stages_status = pipeline.statuses.relevant.latest.stages_status @@ -58,8 +57,8 @@ = icon("calendar") #{time_ago_with_tooltip(pipeline.finished_at, short_format: false, skip_js: true)} - %td.pipeline-actions - .controls.hidden-xs.pull-right + %td.pipeline-actions.hidden-xs + .controls.pull-right - artifacts = pipeline.builds.latest.with_artifacts_not_expired - actions = pipeline.manual_actions - if artifacts.present? || actions.any? diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 50c7e5044b2..e1c983e1679 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -36,20 +36,20 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %ul.content-list.pipelines + %div.content-list.pipelines - stages = @pipelines.stages - if @pipelines.blank? - %li + %div .nothing-here-block No pipelines to show - else .table-holder %table.table.builds - %tbody - %th Status - %th Pipeline - %th Stages - %th - %th + %thead + %th.col-xs-1.col-sm-1 Status + %th.col-xs-2.col-sm-4 Pipeline + %th.col-xs-2.col-sm-2 Stages + %th.col-xs-2.col-sm-2.commit-message + %th.hidden-xs.col-sm-3 = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = paginate @pipelines, theme: 'gitlab' -- cgit v1.2.1 From 706737a004b67303f15ccbd1f8630b0d80f481e9 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 4 Oct 2016 13:21:26 +0200 Subject: Exclude system notes from Project.trending Having many system notes isn't really an indication of a project being trending. Including these notes would lead to projects with lots of commit cross references (for example) showing up in the trending projects list. --- app/models/project.rb | 1 + spec/models/project_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 507228606df..ecd742a17d5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -380,6 +380,7 @@ class Project < ActiveRecord::Base SELECT project_id, COUNT(*) AS amount FROM notes WHERE created_at >= #{sanitize(since)} + AND system IS FALSE GROUP BY project_id ) join_note_counts ON projects.id = join_note_counts.project_id" diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ef854a25321..1a316176f63 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -826,6 +826,14 @@ describe Project, models: true do expect(subject).to eq([project2, project1]) end end + + it 'does not take system notes into account' do + 10.times do + create(:note_on_commit, project: project2, system: true) + end + + expect(described_class.trending.to_a).to eq([project1, project2]) + end end describe '.visible_to_user' do -- cgit v1.2.1 From 2a0c6e8a86823544c08183cb5ba0ced7f2510935 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 22:50:59 +0100 Subject: Improves CSS for `branch-commit` class --- app/assets/stylesheets/pages/pipelines.scss | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6484e5c02a9..b2662b812b7 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -22,6 +22,11 @@ .table.builds { min-width: 1200px; + + .branch-commit { + width: 33%; + } + } } @@ -39,10 +44,6 @@ overflow: auto; } -.pipelines .table.builds .branch-commit { - width: 33%; -} - .table.builds { min-width: 900px; -- cgit v1.2.1 From 27536459e53d5fd78f525465464743164a8c5820 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Tue, 4 Oct 2016 18:18:18 -0700 Subject: 19499 Update project dropdowns --- app/assets/stylesheets/pages/projects.scss | 3 ++- app/views/projects/buttons/_download.html.haml | 4 ++-- app/views/projects/buttons/_dropdown.html.haml | 3 ++- app/views/projects/show.html.haml | 5 ++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 78bc4b79e86..87548dcb590 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -146,7 +146,8 @@ } .project-repo-btn-group, - .notification-dropdown { + .notification-dropdown, + .project-dropdown { margin-left: 10px; } diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 24de020917a..9089586a89d 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,9 +1,9 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'} + %span{class: 'hidden-xs hidden-sm'} .dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') - %span.caret + = icon("caret-down") %span.sr-only Select Archive Format %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index ca907077c2b..6cd9b98a706 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,7 +1,8 @@ - if current_user - .btn-group + .dropdown.inline.project-dropdown %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('plus') + = icon("caret-down") %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown - can_create_issue = can?(current_user, :create_issue, @project) - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 9adce776c1c..ea4deb6cb28 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -71,9 +71,8 @@ = render 'shared/members/access_request_buttons', source: @project = render "projects/buttons/koding" - .btn-group.project-repo-btn-group - = render 'projects/buttons/download', project: @project, ref: @ref - = render 'projects/buttons/dropdown' + = render 'projects/buttons/download', project: @project, ref: @ref + = render 'projects/buttons/dropdown' = render 'shared/notifications/button', notification_setting: @notification_setting - if @repository.commit -- cgit v1.2.1 From 8e3e1ea0a4111fbbf8f91946f67519116fb633f3 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 10:15:05 +0100 Subject: Removes class --- app/views/projects/pipelines/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index e1c983e1679..2d1df095bfa 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -48,7 +48,7 @@ %th.col-xs-1.col-sm-1 Status %th.col-xs-2.col-sm-4 Pipeline %th.col-xs-2.col-sm-2 Stages - %th.col-xs-2.col-sm-2.commit-message + %th.col-xs-2.col-sm-2 %th.hidden-xs.col-sm-3 = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages -- cgit v1.2.1 From 2dc40f3c8061951624c1e922661311af650f2ef2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 12:20:55 +0200 Subject: Refactor BlobCiYamlSelector and BlobCiYamlSelectors to ES6. (Removes opts destructuring and inheritance boilerplate) --- app/assets/javascripts/blob/blob_ci_yaml.js | 46 ------------------------- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 32 +++++++++++++++++ 2 files changed, 32 insertions(+), 46 deletions(-) delete mode 100644 app/assets/javascripts/blob/blob_ci_yaml.js create mode 100644 app/assets/javascripts/blob/blob_ci_yaml.js.es6 diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js b/app/assets/javascripts/blob/blob_ci_yaml.js deleted file mode 100644 index 68758574967..00000000000 --- a/app/assets/javascripts/blob/blob_ci_yaml.js +++ /dev/null @@ -1,46 +0,0 @@ - -/*= require blob/template_selector */ - -(function() { - var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; - - this.BlobCiYamlSelector = (function(superClass) { - extend(BlobCiYamlSelector, superClass); - - function BlobCiYamlSelector() { - return BlobCiYamlSelector.__super__.constructor.apply(this, arguments); - } - - BlobCiYamlSelector.prototype.requestFile = function(query) { - return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); - }; - - return BlobCiYamlSelector; - - })(TemplateSelector); - - this.BlobCiYamlSelectors = (function() { - function BlobCiYamlSelectors(opts) { - var ref; - this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitlab-ci-yml-selector'), this.editor = opts.editor; - this.$dropdowns.each((function(_this) { - return function(i, dropdown) { - var $dropdown; - $dropdown = $(dropdown); - return new BlobCiYamlSelector({ - pattern: /(.gitlab-ci.yml)/, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), - dropdown: $dropdown, - editor: _this.editor - }); - }; - })(this)); - } - - return BlobCiYamlSelectors; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 new file mode 100644 index 00000000000..9f219cecc23 --- /dev/null +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -0,0 +1,32 @@ +/*= require blob/template_selector */ + +class BlobCiYamlSelector extends TemplateSelector { + constructor(...args) { + super(...args); + } + + requestFile(query) { + return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); + }; +}; + +class BlobCiYamlSelectors { + constructor(opts) { + this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); + this.editor = opts.editor; + this.initSelectors(); + } + + initSelectors() { + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + return new BlobCiYamlSelector({ + pattern: /(.gitlab-ci.yml)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), + dropdown: $dropdown, + editor: this.editor + }); + }); + } +} -- cgit v1.2.1 From cfb03b3d06a3dbd16c6e193a128bb737a0a327fd Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 14:26:43 +0200 Subject: Refactor UserTabs to ES6. --- app/assets/javascripts/user.js.es6 | 2 +- app/assets/javascripts/user_tabs.js.es6 | 160 ++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/user_tabs.js.es6 diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index 6889d3a7491..c5893745d74 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -14,7 +14,7 @@ } initTabs() { - return new UserTabs({ + return new global.UserTabs({ parentEl: '.user-profile', action: this.opts.action }); diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 new file mode 100644 index 00000000000..b787700070e --- /dev/null +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -0,0 +1,160 @@ +/* +UserTabs + +Handles persisting and restoring the current tab selection and lazily-loading +content on the Users#show page. + +### Example Markup + + + +
+
+ Activity Content +
+
+ Groups Content +
+
+ Contributed projects content +
+
+ Projects content +
+
+ Snippets content +
+
+ +
+
+ Loading Animation +
+
+*/ +(global => { + class UserTabs { + constructor (opts) { + this.loaded = {}; + this.defaultAction = opts.defaultAction || 'activity'; + this.action = opts.action || 'activity'; + this.$parentEl = $(opts.parentEl) || $(document); + this._location = window.location; + this.$parentEl.find('.nav-links a') + .each((i, navLink) => { + this.loaded[$(navLink).attr('data-action')] = false; + }); + this.actions = Object.keys(this.loaded); + this.bindEvents(); + + if (this.action === 'show') { + this.action = this.defaultAction; + } + + this.activateTab(this.action); + } + + bindEvents() { + return this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') + .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', (event) => this.tabShown(event)); + } + + tabShown(event) { + const $target = $(event.target); + const action = $target.data('action'); + const source = $target.attr('href'); + this.setTab(source, action); + return this.setCurrentAction(action); + } + + activateTab(action) { + return this.$parentEl.find(".nav-links .js-" + action + "-tab a") + .tab('show'); + } + + setTab(source, action) { + if (this.loaded[action]) { + return; + } + if (action === 'activity') { + this.loadActivities(source); + } + if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') { + return this.loadTab(source, action); + } + } + + loadTab(source, action) { + return $.ajax({ + beforeSend: () => this.toggleLoading(true), + complete: () => this.toggleLoading(false), + dataType: 'json', + type: 'GET', + url: source + ".json", + success: (data) => { + const tabSelector = 'div#' + action; + this.$parentEl.find(tabSelector).html(data.html); + this.loaded[action] = true; + return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); + } + }); + } + + loadActivities(source) { + if (this.loaded['activity']) { + return; + } + const $calendarWrap = this.$parentEl.find('.user-calendar'); + $calendarWrap.load($calendarWrap.data('href')); + new Activities(); + return this.loaded['activity'] = true; + } + + toggleLoading(status) { + return this.$parentEl.find('.loading-status .loading') + .toggle(status); + } + + setCurrentAction(action) { + const regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$'); + let new_state = this._location.pathname; + new_state = new_state.replace(/\/+$/, ""); + new_state = new_state.replace(regExp, ''); + if (action !== this.defaultAction) { + new_state += "/" + action; + } + new_state += this._location.search + this._location.hash; + history.replaceState({ + turbolinks: true, + url: new_state + }, document.title, new_state); + return new_state; + }; + } + global.UserTabs = UserTabs; +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 45ae34f883b777b83ce4ae319c23cb5396403b5e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 14:30:32 +0200 Subject: Properly scope BlobCiYamlSelector and BlobCiYamlSelectors. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 55 ++++++++++++++----------- app/assets/javascripts/blob_edit/edit_blob.js | 2 +- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index 9f219cecc23..5ae6f1a5940 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -1,32 +1,39 @@ /*= require blob/template_selector */ +((global) => { -class BlobCiYamlSelector extends TemplateSelector { - constructor(...args) { - super(...args); - } + class BlobCiYamlSelector extends TemplateSelector { + constructor(...args) { + super(...args); + } - requestFile(query) { - return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); + requestFile(query) { + return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); + }; }; -}; -class BlobCiYamlSelectors { - constructor(opts) { - this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); - this.editor = opts.editor; - this.initSelectors(); - } + global.BlobCiYamlSelector = BlobCiYamlSelector; + + class BlobCiYamlSelectors { + constructor(opts) { + this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); + this.editor = opts.editor; + this.initSelectors(); + } - initSelectors() { - this.$dropdowns.each((i, dropdown) => { - const $dropdown = $(dropdown); - return new BlobCiYamlSelector({ - pattern: /(.gitlab-ci.yml)/, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), - dropdown: $dropdown, - editor: this.editor + initSelectors() { + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + return new BlobCiYamlSelector({ + pattern: /(.gitlab-ci.yml)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), + dropdown: $dropdown, + editor: this.editor + }); }); - }); + } } -} + + global.BlobCiYamlSelectors = BlobCiYamlSelectors; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index de6cdd851be..0be4b6392bf 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -29,7 +29,7 @@ new BlobGitignoreSelectors({ editor: this.editor }); - new BlobCiYamlSelectors({ + new gl.BlobCiYamlSelectors({ editor: this.editor }); } -- cgit v1.2.1 From 8377c2fbc3f6b43b7cd3d965eca2a87e318ae227 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 15:13:15 +0200 Subject: Refactor Todos to ES6. --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/todos.js | 176 ----------------------------------- app/assets/javascripts/todos.js.es6 | 161 ++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 177 deletions(-) delete mode 100644 app/assets/javascripts/todos.js create mode 100644 app/assets/javascripts/todos.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ddf11ecf34c..fb2b6c19c46 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -40,7 +40,7 @@ new Milestone(); break; case 'dashboard:todos:index': - new Todos(); + new gl.Todos(); break; case 'projects:milestones:new': case 'projects:milestones:edit': diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js deleted file mode 100644 index 93421649ac7..00000000000 --- a/app/assets/javascripts/todos.js +++ /dev/null @@ -1,176 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Todos = (function() { - function Todos(opts) { - var ref; - if (opts == null) { - opts = {}; - } - this.allDoneClicked = bind(this.allDoneClicked, this); - this.doneClicked = bind(this.doneClicked, this); - this.el = (ref = opts.el) != null ? ref : $('.js-todos-options'); - this.perPage = this.el.data('perPage'); - this.clearListeners(); - this.initBtnListeners(); - this.initFilters(); - } - - Todos.prototype.clearListeners = function() { - $('.done-todo').off('click'); - $('.js-todos-mark-all').off('click'); - return $('.todo').off('click'); - }; - - Todos.prototype.initBtnListeners = function() { - $('.done-todo').on('click', this.doneClicked); - $('.js-todos-mark-all').on('click', this.allDoneClicked); - return $('.todo').on('click', this.goToTodoUrl); - }; - - Todos.prototype.initFilters = function() { - new UsersSelect(); - this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); - this.initFilterDropdown($('.js-type-search'), 'type'); - this.initFilterDropdown($('.js-action-search'), 'action_id'); - - $('form.filter-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '&' + $(this).serialize()); - }); - }; - - Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) { - $dropdown.glDropdown({ - selectable: true, - filterable: searchFields ? true : false, - fieldName: fieldName, - search: { fields: searchFields }, - data: $dropdown.data('data'), - clicked: function() { - return $dropdown.closest('form.filter-form').submit(); - } - }) - }; - - Todos.prototype.doneClicked = function(e) { - var $this; - e.preventDefault(); - e.stopImmediatePropagation(); - $this = $(e.currentTarget); - $this.disable(); - return $.ajax({ - type: 'POST', - url: $this.attr('href'), - dataType: 'json', - data: { - '_method': 'delete' - }, - success: (function(_this) { - return function(data) { - _this.redirectIfNeeded(data.count); - _this.clearDone($this.closest('li')); - return _this.updateBadges(data); - }; - })(this) - }); - }; - - Todos.prototype.allDoneClicked = function(e) { - var $this; - e.preventDefault(); - e.stopImmediatePropagation(); - $this = $(e.currentTarget); - $this.disable(); - return $.ajax({ - type: 'POST', - url: $this.attr('href'), - dataType: 'json', - data: { - '_method': 'delete' - }, - success: (function(_this) { - return function(data) { - $this.remove(); - $('.prepend-top-default').html('
You\'re all done!
'); - return _this.updateBadges(data); - }; - })(this) - }); - }; - - Todos.prototype.clearDone = function($row) { - var $ul; - $ul = $row.closest('ul'); - $row.remove(); - if (!$ul.find('li').length) { - return $ul.parents('.panel').remove(); - } - }; - - Todos.prototype.updateBadges = function(data) { - $('.todos-pending .badge, .todos-pending-count').text(data.count); - return $('.todos-done .badge').text(data.done_count); - }; - - Todos.prototype.getTotalPages = function() { - return this.el.data('totalPages'); - }; - - Todos.prototype.getCurrentPage = function() { - return this.el.data('currentPage'); - }; - - Todos.prototype.getTodosPerPage = function() { - return this.el.data('perPage'); - }; - - Todos.prototype.redirectIfNeeded = function(total) { - var currPage, currPages, newPages, pageParams, url; - currPages = this.getTotalPages(); - currPage = this.getCurrentPage(); - // Refresh if no remaining Todos - if (!total) { - location.reload(); - return; - } - // Do nothing if no pagination - if (!currPages) { - return; - } - newPages = Math.ceil(total / this.getTodosPerPage()); - // Includes query strings - url = location.href; - // If new total of pages is different than we have now - if (newPages !== currPages) { - // Redirect to previous page if there's one available - if (currPages > 1 && currPage === currPages) { - pageParams = { - page: currPages - 1 - }; - url = gl.utils.mergeUrlParams(pageParams, url); - } - return Turbolinks.visit(url); - } - }; - - Todos.prototype.goToTodoUrl = function(e) { - var todoLink; - todoLink = $(this).data('url'); - if (!todoLink) { - return; - } - // Allow Meta-Click or Mouse3-click to open in a new tab - if (e.metaKey || e.which === 2) { - e.preventDefault(); - return window.open(todoLink, '_blank'); - } else { - return Turbolinks.visit(todoLink); - } - }; - - return Todos; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 new file mode 100644 index 00000000000..63e4911400b --- /dev/null +++ b/app/assets/javascripts/todos.js.es6 @@ -0,0 +1,161 @@ +(global => { + + class Todos { + constructor(opts = {}) { + this.allDoneClicked = this.allDoneClicked.bind(this); + this.doneClicked = this.doneClicked.bind(this); + this.el = opts.el || $('.js-todos-options'); + this.perPage = this.el.data('perPage'); + this.clearListeners(); + this.initBtnListeners(); + this.initFilters(); + } + + clearListeners() { + $('.done-todo').off('click'); + $('.js-todos-mark-all').off('click'); + return $('.todo').off('click'); + } + + initBtnListeners() { + $('.done-todo').on('click', this.doneClicked); + $('.js-todos-mark-all').on('click', this.allDoneClicked); + return $('.todo').on('click', this.goToTodoUrl); + } + + initFilters() { + new UsersSelect(); + this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); + this.initFilterDropdown($('.js-type-search'), 'type'); + this.initFilterDropdown($('.js-action-search'), 'action_id'); + + $('form.filter-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '&' + $(this).serialize()); + }); + } + + initFilterDropdown($dropdown, fieldName, searchFields) { + $dropdown.glDropdown({ + fieldName, + selectable: true, + filterable: searchFields ? true : false, + search: { fields: searchFields }, + data: $dropdown.data('data'), + clicked: function() { + return $dropdown.closest('form.filter-form').submit(); + } + }) + } + + doneClicked(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + const $target = $(e.currentTarget); + $target.disable(); + return $.ajax({ + type: 'POST', + url: $target.attr('href'), + dataType: 'json', + data: { + '_method': 'delete' + }, + success: data => { + this.redirectIfNeeded(data.count); + this.clearDone($target.closest('li')); + return this.updateBadges(data); + } + }); + } + + allDoneClicked(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + $target = $(e.currentTarget); + $target.disable(); + return $.ajax({ + type: 'POST', + url: $target.attr('href'), + dataType: 'json', + data: { + '_method': 'delete' + }, + success: data => { + $target.remove(); + $('.prepend-top-default').html('
You\'re all done!
'); + return this.updateBadges(data); + } + }); + } + + clearDone($row) { + const $ul = $row.closest('ul'); + $row.remove(); + if (!$ul.find('li').length) { + return $ul.parents('.panel').remove(); + } + } + + updateBadges(data) { + $('.todos-pending .badge, .todos-pending-count').text(data.count); + return $('.todos-done .badge').text(data.done_count); + } + + getTotalPages() { + return this.el.data('totalPages'); + } + + getCurrentPage() { + return this.el.data('currentPage'); + } + + getTodosPerPage() { + return this.el.data('perPage'); + } + + redirectIfNeeded(total) { + let currPages = this.getTotalPages(); + currPage = this.getCurrentPage(); + + // Refresh if no remaining Todos + if (!total) { + window.location.reload(); + return; + } + // Do nothing if no pagination + if (!currPages) { + return; + } + + const newPages = Math.ceil(total / this.getTodosPerPage()); + let url = location.href; + + if (newPages !== currPages) { + // Redirect to previous page if there's one available + if (currPages > 1 && currPage === currPages) { + const pageParams = { + page: currPages - 1 + }; + url = gl.utils.mergeUrlParams(pageParams, url); + } + return Turbolinks.visit(url); + } + } + + goToTodoUrl(e) { + const todoLink = $(this).data('url'); + if (!todoLink) { + return; + } + // Allow Meta-Click or Mouse3-click to open in a new tab + if (e.metaKey || e.which === 2) { + e.preventDefault(); + return window.open(todoLink, '_blank'); + } else { + return Turbolinks.visit(todoLink); + } + } + } + + global.Todos = Todos; +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 01fdb521e2a4232ea857e9d85610be91b70f50fe Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 15:54:48 +0200 Subject: Fixup refs to currPage and currPages. --- app/assets/javascripts/todos.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index 63e4911400b..a0386dcc018 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -114,8 +114,8 @@ } redirectIfNeeded(total) { - let currPages = this.getTotalPages(); - currPage = this.getCurrentPage(); + const currPages = this.getTotalPages(); + const currPage = this.getCurrentPage(); // Refresh if no remaining Todos if (!total) { -- cgit v1.2.1 From b93ca73e223385c3689f28c1db1d0ea9e9e34153 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 15:59:33 +0200 Subject: Refactor SearchAutocomplete to ES6 class syntax. --- app/assets/javascripts/dispatcher.js | 3 +- app/assets/javascripts/search_autocomplete.js | 429 ---------------------- app/assets/javascripts/search_autocomplete.js.es6 | 424 +++++++++++++++++++++ 3 files changed, 426 insertions(+), 430 deletions(-) delete mode 100644 app/assets/javascripts/search_autocomplete.js create mode 100644 app/assets/javascripts/search_autocomplete.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index fb2b6c19c46..71af9a84710 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -279,7 +279,8 @@ Dispatcher.prototype.initSearch = function() { // Only when search form is present if ($('.search').length) { - return new SearchAutocomplete(); + debugger; + return new gl.SearchAutocomplete(); } }; diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js deleted file mode 100644 index 678d836f56f..00000000000 --- a/app/assets/javascripts/search_autocomplete.js +++ /dev/null @@ -1,429 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.SearchAutocomplete = (function() { - var KEYCODE; - - KEYCODE = { - ESCAPE: 27, - BACKSPACE: 8, - ENTER: 13, - UP: 38, - DOWN: 40 - }; - - function SearchAutocomplete(opts) { - var ref, ref1, ref2, ref3, ref4; - if (opts == null) { - opts = {}; - } - this.onSearchInputBlur = bind(this.onSearchInputBlur, this); - this.onClearInputClick = bind(this.onClearInputClick, this); - this.onSearchInputFocus = bind(this.onSearchInputFocus, this); - this.onSearchInputClick = bind(this.onSearchInputClick, this); - this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this); - this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this); - this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || ''; - // Dropdown Element - this.dropdown = this.wrap.find('.dropdown'); - this.dropdownContent = this.dropdown.find('.dropdown-content'); - this.locationBadgeEl = this.getElement('.location-badge'); - this.scopeInputEl = this.getElement('#scope'); - this.searchInput = this.getElement('.search-input'); - this.projectInputEl = this.getElement('#search_project_id'); - this.groupInputEl = this.getElement('#group_id'); - this.searchCodeInputEl = this.getElement('#search_code'); - this.repositoryInputEl = this.getElement('#repository_ref'); - this.clearInput = this.getElement('.js-clear-input'); - this.saveOriginalState(); - // Only when user is logged in - if (gon.current_user_id) { - this.createAutocomplete(); - } - this.searchInput.addClass('disabled'); - this.saveTextLength(); - this.bindEvents(); - } - - // Finds an element inside wrapper element - SearchAutocomplete.prototype.getElement = function(selector) { - return this.wrap.find(selector); - }; - - SearchAutocomplete.prototype.saveOriginalState = function() { - return this.originalState = this.serializeState(); - }; - - SearchAutocomplete.prototype.saveTextLength = function() { - return this.lastTextLength = this.searchInput.val().length; - }; - - SearchAutocomplete.prototype.createAutocomplete = function() { - return this.searchInput.glDropdown({ - filterInputBlur: false, - filterable: true, - filterRemote: true, - highlight: true, - enterCallback: false, - filterInput: 'input#search', - search: { - fields: ['text'] - }, - data: this.getData.bind(this), - selectable: true, - clicked: this.onClick.bind(this) - }); - }; - - SearchAutocomplete.prototype.getData = function(term, callback) { - var _this, contents, jqXHR; - _this = this; - if (!term) { - if (contents = this.getCategoryContents()) { - this.searchInput.data('glDropdown').filter.options.callback(contents); - this.enableAutocomplete(); - } - return; - } - // Prevent multiple ajax calls - if (this.loadingSuggestions) { - return; - } - this.loadingSuggestions = true; - return jqXHR = $.get(this.autocompletePath, { - project_id: this.projectId, - project_ref: this.projectRef, - term: term - }, function(response) { - var data, firstCategory, i, lastCategory, len, suggestion; - // Hide dropdown menu if no suggestions returns - if (!response.length) { - _this.disableAutocomplete(); - return; - } - data = []; - // List results - firstCategory = true; - for (i = 0, len = response.length; i < len; i++) { - suggestion = response[i]; - // Add group header before list each group - if (lastCategory !== suggestion.category) { - if (!firstCategory) { - data.push('separator'); - } - if (firstCategory) { - firstCategory = false; - } - data.push({ - header: suggestion.category - }); - lastCategory = suggestion.category; - } - data.push({ - id: (suggestion.category.toLowerCase()) + "-" + suggestion.id, - category: suggestion.category, - text: suggestion.label, - url: suggestion.url - }); - } - // Add option to proceed with the search - if (data.length) { - data.push('separator'); - data.push({ - text: "Result name contains \"" + term + "\"", - url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val()) - }); - } - return callback(data); - }).always(function() { - return _this.loadingSuggestions = false; - }); - }; - - SearchAutocomplete.prototype.getCategoryContents = function() { - var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils; - userId = gon.current_user_id; - utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; - if (utils.isInGroupsPage() && groupOptions) { - options = groupOptions[utils.getGroupSlug()]; - } else if (utils.isInProjectPage() && projectOptions) { - options = projectOptions[utils.getProjectSlug()]; - } else if (dashboardOptions) { - options = dashboardOptions; - } - issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name; - items = [ - { - header: "" + name - }, { - text: 'Issues assigned to me', - url: issuesPath + "/?assignee_id=" + userId - }, { - text: "Issues I've created", - url: issuesPath + "/?author_id=" + userId - }, 'separator', { - text: 'Merge requests assigned to me', - url: mrPath + "/?assignee_id=" + userId - }, { - text: "Merge requests I've created", - url: mrPath + "/?author_id=" + userId - } - ]; - if (!name) { - items.splice(0, 1); - } - return items; - }; - - SearchAutocomplete.prototype.serializeState = function() { - return { - // Search Criteria - search_project_id: this.projectInputEl.val(), - group_id: this.groupInputEl.val(), - search_code: this.searchCodeInputEl.val(), - repository_ref: this.repositoryInputEl.val(), - scope: this.scopeInputEl.val(), - // Location badge - _location: this.locationBadgeEl.text() - }; - }; - - SearchAutocomplete.prototype.bindEvents = function() { - this.searchInput.on('keydown', this.onSearchInputKeyDown); - this.searchInput.on('keyup', this.onSearchInputKeyUp); - this.searchInput.on('click', this.onSearchInputClick); - this.searchInput.on('focus', this.onSearchInputFocus); - this.searchInput.on('blur', this.onSearchInputBlur); - this.clearInput.on('click', this.onClearInputClick); - return this.locationBadgeEl.on('click', (function(_this) { - return function() { - return _this.searchInput.focus(); - }; - })(this)); - }; - - SearchAutocomplete.prototype.enableAutocomplete = function() { - var _this; - // No need to enable anything if user is not logged in - if (!gon.current_user_id) { - return; - } - if (!this.dropdown.hasClass('open')) { - _this = this; - this.loadingSuggestions = false; - this.dropdown.addClass('open').trigger('shown.bs.dropdown'); - return this.searchInput.removeClass('disabled'); - } - }; - - SearchAutocomplete.prototype.onSearchInputKeyDown = function() { - // Saves last length of the entered text - return this.saveTextLength(); - }; - - SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) { - switch (e.keyCode) { - case KEYCODE.BACKSPACE: - // when trying to remove the location badge - if (this.lastTextLength === 0 && this.badgePresent()) { - this.removeLocationBadge(); - } - // When removing the last character and no badge is present - if (this.lastTextLength === 1) { - this.disableAutocomplete(); - } - // When removing any character from existin value - if (this.lastTextLength > 1) { - this.enableAutocomplete(); - } - break; - case KEYCODE.ESCAPE: - this.restoreOriginalState(); - break; - case KEYCODE.ENTER: - this.disableAutocomplete(); - break; - case KEYCODE.UP: - case KEYCODE.DOWN: - return; - default: - // Handle the case when deleting the input value other than backspace - // e.g. Pressing ctrl + backspace or ctrl + x - if (this.searchInput.val() === '') { - this.disableAutocomplete(); - } else { - // We should display the menu only when input is not empty - if (e.keyCode !== KEYCODE.ENTER) { - this.enableAutocomplete(); - } - } - } - this.wrap.toggleClass('has-value', !!e.target.value); - }; - - // Avoid falsy value to be returned - SearchAutocomplete.prototype.onSearchInputClick = function(e) { - // Prevents closing the dropdown menu - return e.stopImmediatePropagation(); - }; - - SearchAutocomplete.prototype.onSearchInputFocus = function() { - this.isFocused = true; - this.wrap.addClass('search-active'); - if (this.getValue() === '') { - return this.getData(); - } - }; - - SearchAutocomplete.prototype.getValue = function() { - return this.searchInput.val(); - }; - - SearchAutocomplete.prototype.onClearInputClick = function(e) { - e.preventDefault(); - return this.searchInput.val('').focus(); - }; - - SearchAutocomplete.prototype.onSearchInputBlur = function(e) { - this.isFocused = false; - this.wrap.removeClass('search-active'); - // If input is blank then restore state - if (this.searchInput.val() === '') { - return this.restoreOriginalState(); - } - }; - - SearchAutocomplete.prototype.addLocationBadge = function(item) { - var badgeText, category, value; - category = item.category != null ? item.category + ": " : ''; - value = item.value != null ? item.value : ''; - badgeText = "" + category + value; - this.locationBadgeEl.text(badgeText).show(); - return this.wrap.addClass('has-location-badge'); - }; - - SearchAutocomplete.prototype.hasLocationBadge = function() { - return this.wrap.is('.has-location-badge'); - }; - - SearchAutocomplete.prototype.restoreOriginalState = function() { - var i, input, inputs, len; - inputs = Object.keys(this.originalState); - for (i = 0, len = inputs.length; i < len; i++) { - input = inputs[i]; - this.getElement("#" + input).val(this.originalState[input]); - } - if (this.originalState._location === '') { - return this.locationBadgeEl.hide(); - } else { - return this.addLocationBadge({ - value: this.originalState._location - }); - } - }; - - SearchAutocomplete.prototype.badgePresent = function() { - return this.locationBadgeEl.length; - }; - - SearchAutocomplete.prototype.resetSearchState = function() { - var i, input, inputs, len, results; - inputs = Object.keys(this.originalState); - results = []; - for (i = 0, len = inputs.length; i < len; i++) { - input = inputs[i]; - // _location isnt a input - if (input === '_location') { - break; - } - results.push(this.getElement("#" + input).val('')); - } - return results; - }; - - SearchAutocomplete.prototype.removeLocationBadge = function() { - this.locationBadgeEl.hide(); - this.resetSearchState(); - this.wrap.removeClass('has-location-badge'); - return this.disableAutocomplete(); - }; - - SearchAutocomplete.prototype.disableAutocomplete = function() { - if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) { - this.searchInput.addClass('disabled'); - this.dropdown.removeClass('open').trigger('hidden.bs.dropdown'); - this.restoreMenu(); - } - }; - - SearchAutocomplete.prototype.restoreMenu = function() { - var html; - html = ""; - return this.dropdownContent.html(html); - }; - - SearchAutocomplete.prototype.onClick = function(item, $el, e) { - if (location.pathname.indexOf(item.url) !== -1) { - e.preventDefault(); - if (!this.badgePresent) { - if (item.category === 'Projects') { - this.projectInputEl.val(item.id); - this.addLocationBadge({ - value: 'This project' - }); - } - if (item.category === 'Groups') { - this.groupInputEl.val(item.id); - this.addLocationBadge({ - value: 'This group' - }); - } - } - $el.removeClass('is-active'); - this.disableAutocomplete(); - return this.searchInput.val('').focus(); - } - }; - - return SearchAutocomplete; - - })(); - - $(function() { - var $projectOptionsDataEl = $('.js-search-project-options'); - var $groupOptionsDataEl = $('.js-search-group-options'); - var $dashboardOptionsDataEl = $('.js-search-dashboard-options'); - - if ($projectOptionsDataEl.length) { - gl.projectOptions = gl.projectOptions || {}; - - var projectPath = $projectOptionsDataEl.data('project-path'); - - gl.projectOptions[projectPath] = { - name: $projectOptionsDataEl.data('name'), - issuesPath: $projectOptionsDataEl.data('issues-path'), - mrPath: $projectOptionsDataEl.data('mr-path') - }; - } - - if ($groupOptionsDataEl.length) { - gl.groupOptions = gl.groupOptions || {}; - - var groupPath = $groupOptionsDataEl.data('group-path'); - - gl.groupOptions[groupPath] = { - name: $groupOptionsDataEl.data('name'), - issuesPath: $groupOptionsDataEl.data('issues-path'), - mrPath: $groupOptionsDataEl.data('mr-path') - }; - } - - if ($dashboardOptionsDataEl.length) { - gl.dashboardOptions = { - issuesPath: $dashboardOptionsDataEl.data('issues-path'), - mrPath: $dashboardOptionsDataEl.data('mr-path') - }; - } - }); - -}).call(this); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 new file mode 100644 index 00000000000..781e0cd782c --- /dev/null +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -0,0 +1,424 @@ +(global => { + + const KEYCODE = { + ESCAPE: 27, + BACKSPACE: 8, + ENTER: 13, + UP: 38, + DOWN: 40 + }; + + class SearchAutocomplete { + constructor(opts = {}) { + this.onSearchInputBlur = this.onSearchInputBlur.bind(this); + this.onClearInputClick = this.onClearInputClick.bind(this); + this.onSearchInputFocus = this.onSearchInputFocus.bind(this); + this.onSearchInputClick = this.onSearchInputClick.bind(this); + this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); + this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); + this.wrap = opts.wrap || $('.search'); + this.optsEl = opts.optsEl || this.wrap.find('.search-autocomplete-opts'); + this.autocompletePath = opts.autocompletePath || this.optsEl.data('autocomplete-path') + this.projectId = opts.projectId || this.optsEl.data('autocomplete-project-id') || ''; + this.projectRef = opts.projectRef || this.optsEl.data('autocomplete-project-ref') || ''; + this.dropdown = this.wrap.find('.dropdown'); + this.dropdownContent = this.dropdown.find('.dropdown-content'); + this.locationBadgeEl = this.getElement('.location-badge'); + this.scopeInputEl = this.getElement('#scope'); + this.searchInput = this.getElement('.search-input'); + this.projectInputEl = this.getElement('#search_project_id'); + this.groupInputEl = this.getElement('#group_id'); + this.searchCodeInputEl = this.getElement('#search_code'); + this.repositoryInputEl = this.getElement('#repository_ref'); + this.clearInput = this.getElement('.js-clear-input'); + this.saveOriginalState(); + // Only when user is logged in + if (gon.current_user_id) { + this.createAutocomplete(); + } + this.searchInput.addClass('disabled'); + this.saveTextLength(); + this.bindEvents(); + } + + // Finds an element inside wrapper element + getElement(selector) { + return this.wrap.find(selector); + } + + saveOriginalState() { + return this.originalState = this.serializeState(); + } + + saveTextLength() { + return this.lastTextLength = this.searchInput.val().length; + } + + createAutocomplete() { + return this.searchInput.glDropdown({ + filterInputBlur: false, + filterable: true, + filterRemote: true, + highlight: true, + enterCallback: false, + filterInput: 'input#search', + search: { + fields: ['text'] + }, + data: this.getData.bind(this), + selectable: true, + clicked: this.onClick.bind(this) + }); + } + + getData(term, callback) { + var _this, contents, jqXHR; + _this = this; + if (!term) { + if (contents = this.getCategoryContents()) { + this.searchInput.data('glDropdown').filter.options.callback(contents); + this.enableAutocomplete(); + } + return; + } + // Prevent multiple ajax calls + if (this.loadingSuggestions) { + return; + } + this.loadingSuggestions = true; + return jqXHR = $.get(this.autocompletePath, { + project_id: this.projectId, + project_ref: this.projectRef, + term: term + }, function(response) { + var data, firstCategory, i, lastCategory, len, suggestion; + // Hide dropdown menu if no suggestions returns + if (!response.length) { + _this.disableAutocomplete(); + return; + } + data = []; + // List results + firstCategory = true; + for (i = 0, len = response.length; i < len; i++) { + suggestion = response[i]; + // Add group header before list each group + if (lastCategory !== suggestion.category) { + if (!firstCategory) { + data.push('separator'); + } + if (firstCategory) { + firstCategory = false; + } + data.push({ + header: suggestion.category + }); + lastCategory = suggestion.category; + } + data.push({ + id: (suggestion.category.toLowerCase()) + "-" + suggestion.id, + category: suggestion.category, + text: suggestion.label, + url: suggestion.url + }); + } + // Add option to proceed with the search + if (data.length) { + data.push('separator'); + data.push({ + text: "Result name contains \"" + term + "\"", + url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val()) + }); + } + return callback(data); + }).always(function() { + return _this.loadingSuggestions = false; + }); + } + + getCategoryContents() { + var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils; + userId = gon.current_user_id; + utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; + if (utils.isInGroupsPage() && groupOptions) { + options = groupOptions[utils.getGroupSlug()]; + } else if (utils.isInProjectPage() && projectOptions) { + options = projectOptions[utils.getProjectSlug()]; + } else if (dashboardOptions) { + options = dashboardOptions; + } + issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name; + items = [ + { + header: "" + name + }, { + text: 'Issues assigned to me', + url: issuesPath + "/?assignee_id=" + userId + }, { + text: "Issues I've created", + url: issuesPath + "/?author_id=" + userId + }, 'separator', { + text: 'Merge requests assigned to me', + url: mrPath + "/?assignee_id=" + userId + }, { + text: "Merge requests I've created", + url: mrPath + "/?author_id=" + userId + } + ]; + if (!name) { + items.splice(0, 1); + } + return items; + } + + serializeState() { + return { + // Search Criteria + search_project_id: this.projectInputEl.val(), + group_id: this.groupInputEl.val(), + search_code: this.searchCodeInputEl.val(), + repository_ref: this.repositoryInputEl.val(), + scope: this.scopeInputEl.val(), + // Location badge + _location: this.locationBadgeEl.text() + }; + } + + bindEvents() { + this.searchInput.on('keydown', this.onSearchInputKeyDown); + this.searchInput.on('keyup', this.onSearchInputKeyUp); + this.searchInput.on('click', this.onSearchInputClick); + this.searchInput.on('focus', this.onSearchInputFocus); + this.searchInput.on('blur', this.onSearchInputBlur); + this.clearInput.on('click', this.onClearInputClick); + return this.locationBadgeEl.on('click', (function(_this) { + return function() { + return _this.searchInput.focus(); + }; + })(this)); + } + + enableAutocomplete() { + var _this; + // No need to enable anything if user is not logged in + if (!gon.current_user_id) { + return; + } + if (!this.dropdown.hasClass('open')) { + _this = this; + this.loadingSuggestions = false; + this.dropdown.addClass('open').trigger('shown.bs.dropdown'); + return this.searchInput.removeClass('disabled'); + } + }; + + // Saves last length of the entered text + onSearchInputKeyDown() { + return this.saveTextLength(); + } + + onSearchInputKeyUp(e) { + switch (e.keyCode) { + case KEYCODE.BACKSPACE: + // when trying to remove the location badge + if (this.lastTextLength === 0 && this.badgePresent()) { + this.removeLocationBadge(); + } + // When removing the last character and no badge is present + if (this.lastTextLength === 1) { + this.disableAutocomplete(); + } + // When removing any character from existin value + if (this.lastTextLength > 1) { + this.enableAutocomplete(); + } + break; + case KEYCODE.ESCAPE: + this.restoreOriginalState(); + break; + case KEYCODE.ENTER: + this.disableAutocomplete(); + break; + case KEYCODE.UP: + case KEYCODE.DOWN: + return; + default: + // Handle the case when deleting the input value other than backspace + // e.g. Pressing ctrl + backspace or ctrl + x + if (this.searchInput.val() === '') { + this.disableAutocomplete(); + } else { + // We should display the menu only when input is not empty + if (e.keyCode !== KEYCODE.ENTER) { + this.enableAutocomplete(); + } + } + } + this.wrap.toggleClass('has-value', !!e.target.value); + } + + // Avoid falsy value to be returned + onSearchInputClick(e) { + return e.stopImmediatePropagation(); + } + + onSearchInputFocus() { + this.isFocused = true; + this.wrap.addClass('search-active'); + if (this.getValue() === '') { + return this.getData(); + } + } + + getValue() { + return this.searchInput.val(); + } + + onClearInputClick(e) { + e.preventDefault(); + return this.searchInput.val('').focus(); + } + + onSearchInputBlur(e) { + this.isFocused = false; + this.wrap.removeClass('search-active'); + // If input is blank then restore state + if (this.searchInput.val() === '') { + return this.restoreOriginalState(); + } + } + + addLocationBadge(item) { + var badgeText, category, value; + category = item.category != null ? item.category + ": " : ''; + value = item.value != null ? item.value : ''; + badgeText = "" + category + value; + this.locationBadgeEl.text(badgeText).show(); + return this.wrap.addClass('has-location-badge'); + } + + hasLocationBadge() { + return this.wrap.is('.has-location-badge'); + }; + + restoreOriginalState() { + var i, input, inputs, len; + inputs = Object.keys(this.originalState); + for (i = 0, len = inputs.length; i < len; i++) { + input = inputs[i]; + this.getElement("#" + input).val(this.originalState[input]); + } + if (this.originalState._location === '') { + return this.locationBadgeEl.hide(); + } else { + return this.addLocationBadge({ + value: this.originalState._location + }); + } + } + + badgePresent() { + return this.locationBadgeEl.length; + } + + resetSearchState() { + var i, input, inputs, len, results; + inputs = Object.keys(this.originalState); + results = []; + for (i = 0, len = inputs.length; i < len; i++) { + input = inputs[i]; + // _location isnt a input + if (input === '_location') { + break; + } + results.push(this.getElement("#" + input).val('')); + } + return results; + } + + removeLocationBadge() { + this.locationBadgeEl.hide(); + this.resetSearchState(); + this.wrap.removeClass('has-location-badge'); + return this.disableAutocomplete(); + } + + disableAutocomplete() { + if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) { + this.searchInput.addClass('disabled'); + this.dropdown.removeClass('open').trigger('hidden.bs.dropdown'); + this.restoreMenu(); + } + } + + restoreMenu() { + var html; + html = ""; + return this.dropdownContent.html(html); + }; + + onClick(item, $el, e) { + if (location.pathname.indexOf(item.url) !== -1) { + e.preventDefault(); + if (!this.badgePresent) { + if (item.category === 'Projects') { + this.projectInputEl.val(item.id); + this.addLocationBadge({ + value: 'This project' + }); + } + if (item.category === 'Groups') { + this.groupInputEl.val(item.id); + this.addLocationBadge({ + value: 'This group' + }); + } + } + $el.removeClass('is-active'); + this.disableAutocomplete(); + return this.searchInput.val('').focus(); + } + }; + + } + + global.SearchAutocomplete = SearchAutocomplete; + + $(function() { + var $projectOptionsDataEl = $('.js-search-project-options'); + var $groupOptionsDataEl = $('.js-search-group-options'); + var $dashboardOptionsDataEl = $('.js-search-dashboard-options'); + + if ($projectOptionsDataEl.length) { + gl.projectOptions = gl.projectOptions || {}; + + var projectPath = $projectOptionsDataEl.data('project-path'); + + gl.projectOptions[projectPath] = { + name: $projectOptionsDataEl.data('name'), + issuesPath: $projectOptionsDataEl.data('issues-path'), + mrPath: $projectOptionsDataEl.data('mr-path') + }; + } + + if ($groupOptionsDataEl.length) { + gl.groupOptions = gl.groupOptions || {}; + + var groupPath = $groupOptionsDataEl.data('group-path'); + + gl.groupOptions[groupPath] = { + name: $groupOptionsDataEl.data('name'), + issuesPath: $groupOptionsDataEl.data('issues-path'), + mrPath: $groupOptionsDataEl.data('mr-path') + }; + } + + if ($dashboardOptionsDataEl.length) { + gl.dashboardOptions = { + issuesPath: $dashboardOptionsDataEl.data('issues-path'), + mrPath: $dashboardOptionsDataEl.data('mr-path') + }; + } + }); + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 37b3cac42c272ebda3924817e406e5e4ac669811 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 16:17:36 +0200 Subject: Fixup debugger ref left in Dispatcher. --- app/assets/javascripts/dispatcher.js | 1 - app/assets/javascripts/profile/profile.js | 106 -------------------------- app/assets/javascripts/profile/profile.js.es6 | 106 ++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 107 deletions(-) delete mode 100644 app/assets/javascripts/profile/profile.js create mode 100644 app/assets/javascripts/profile/profile.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 71af9a84710..3073f7a3d3a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -279,7 +279,6 @@ Dispatcher.prototype.initSearch = function() { // Only when search form is present if ($('.search').length) { - debugger; return new gl.SearchAutocomplete(); } }; diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js deleted file mode 100644 index 60f9fba5777..00000000000 --- a/app/assets/javascripts/profile/profile.js +++ /dev/null @@ -1,106 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Profile = (function() { - function Profile(opts) { - var cropOpts, ref; - if (opts == null) { - opts = {}; - } - this.onSubmitForm = bind(this.onSubmitForm, this); - this.form = (ref = opts.form) != null ? ref : $('.edit-user'); - $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() { - return $(this).parents('form').submit(); - // Automatically submit the Preferences form when any of its radio buttons change - }); - $('#user_notification_email').on('change', function() { - return $(this).parents('form').submit(); - // Automatically submit email form when it changes - }); - $('.update-username').on('ajax:before', function() { - $('.loading-username').show(); - $(this).find('.update-success').hide(); - return $(this).find('.update-failed').hide(); - }); - $('.update-username').on('ajax:complete', function() { - $('.loading-username').hide(); - $(this).find('.btn-save').enable(); - return $(this).find('.loading-gif').hide(); - }); - $('.update-notifications').on('ajax:success', function(e, data) { - if (data.saved) { - return new Flash("Notification settings saved", "notice"); - } else { - return new Flash("Failed to save new settings", "alert"); - } - }); - this.bindEvents(); - cropOpts = { - filename: '.js-avatar-filename', - previewImage: '.avatar-image .avatar', - modalCrop: '.modal-profile-crop', - pickImageEl: '.js-choose-user-avatar-button', - uploadImageBtn: '.js-upload-user-avatar', - modalCropImg: '.modal-profile-crop-image' - }; - this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); - } - - Profile.prototype.bindEvents = function() { - return this.form.on('submit', this.onSubmitForm); - }; - - Profile.prototype.onSubmitForm = function(e) { - e.preventDefault(); - return this.saveForm(); - }; - - Profile.prototype.saveForm = function() { - var avatarBlob, formData, self; - self = this; - formData = new FormData(this.form[0]); - avatarBlob = this.avatarGlCrop.getBlob(); - if (avatarBlob != null) { - formData.append('user[avatar]', avatarBlob, 'avatar.png'); - } - return $.ajax({ - url: this.form.attr('action'), - type: this.form.attr('method'), - data: formData, - dataType: "json", - processData: false, - contentType: false, - success: function(response) { - return new Flash(response.message, 'notice'); - }, - error: function(jqXHR) { - return new Flash(jqXHR.responseJSON.message, 'alert'); - }, - complete: function() { - window.scrollTo(0, 0); - // Enable submit button after requests ends - return self.form.find(':input[disabled]').enable(); - } - }); - }; - - return Profile; - - })(); - - $(function() { - $(document).on('focusout.ssh_key', '#key_key', function() { - var $title, comment; - $title = $('#key_title'); - comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); - if (comment && comment.length > 1 && $title.val() === '') { - return $title.val(comment[1]).change(); - } - // Extract the SSH Key title from its comment - }); - if (gl.utils.getPagePath() === 'profiles') { - return new Profile(); - } - }); - -}).call(this); diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 new file mode 100644 index 00000000000..60f9fba5777 --- /dev/null +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -0,0 +1,106 @@ +(function() { + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + this.Profile = (function() { + function Profile(opts) { + var cropOpts, ref; + if (opts == null) { + opts = {}; + } + this.onSubmitForm = bind(this.onSubmitForm, this); + this.form = (ref = opts.form) != null ? ref : $('.edit-user'); + $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() { + return $(this).parents('form').submit(); + // Automatically submit the Preferences form when any of its radio buttons change + }); + $('#user_notification_email').on('change', function() { + return $(this).parents('form').submit(); + // Automatically submit email form when it changes + }); + $('.update-username').on('ajax:before', function() { + $('.loading-username').show(); + $(this).find('.update-success').hide(); + return $(this).find('.update-failed').hide(); + }); + $('.update-username').on('ajax:complete', function() { + $('.loading-username').hide(); + $(this).find('.btn-save').enable(); + return $(this).find('.loading-gif').hide(); + }); + $('.update-notifications').on('ajax:success', function(e, data) { + if (data.saved) { + return new Flash("Notification settings saved", "notice"); + } else { + return new Flash("Failed to save new settings", "alert"); + } + }); + this.bindEvents(); + cropOpts = { + filename: '.js-avatar-filename', + previewImage: '.avatar-image .avatar', + modalCrop: '.modal-profile-crop', + pickImageEl: '.js-choose-user-avatar-button', + uploadImageBtn: '.js-upload-user-avatar', + modalCropImg: '.modal-profile-crop-image' + }; + this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); + } + + Profile.prototype.bindEvents = function() { + return this.form.on('submit', this.onSubmitForm); + }; + + Profile.prototype.onSubmitForm = function(e) { + e.preventDefault(); + return this.saveForm(); + }; + + Profile.prototype.saveForm = function() { + var avatarBlob, formData, self; + self = this; + formData = new FormData(this.form[0]); + avatarBlob = this.avatarGlCrop.getBlob(); + if (avatarBlob != null) { + formData.append('user[avatar]', avatarBlob, 'avatar.png'); + } + return $.ajax({ + url: this.form.attr('action'), + type: this.form.attr('method'), + data: formData, + dataType: "json", + processData: false, + contentType: false, + success: function(response) { + return new Flash(response.message, 'notice'); + }, + error: function(jqXHR) { + return new Flash(jqXHR.responseJSON.message, 'alert'); + }, + complete: function() { + window.scrollTo(0, 0); + // Enable submit button after requests ends + return self.form.find(':input[disabled]').enable(); + } + }); + }; + + return Profile; + + })(); + + $(function() { + $(document).on('focusout.ssh_key', '#key_key', function() { + var $title, comment; + $title = $('#key_title'); + comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); + if (comment && comment.length > 1 && $title.val() === '') { + return $title.val(comment[1]).change(); + } + // Extract the SSH Key title from its comment + }); + if (gl.utils.getPagePath() === 'profiles') { + return new Profile(); + } + }); + +}).call(this); -- cgit v1.2.1 From 4ae2a3026914a70eb34929a768570043a4e73022 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 17:18:35 +0200 Subject: Refactor Profile to use ES6. --- app/assets/javascripts/profile/profile.js.es6 | 117 +++++++++++++------------- 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 60f9fba5777..c7498c68e7c 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,41 +1,14 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Profile = (function() { - function Profile(opts) { - var cropOpts, ref; - if (opts == null) { - opts = {}; - } - this.onSubmitForm = bind(this.onSubmitForm, this); - this.form = (ref = opts.form) != null ? ref : $('.edit-user'); - $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() { - return $(this).parents('form').submit(); - // Automatically submit the Preferences form when any of its radio buttons change - }); - $('#user_notification_email').on('change', function() { - return $(this).parents('form').submit(); - // Automatically submit email form when it changes - }); - $('.update-username').on('ajax:before', function() { - $('.loading-username').show(); - $(this).find('.update-success').hide(); - return $(this).find('.update-failed').hide(); - }); - $('.update-username').on('ajax:complete', function() { - $('.loading-username').hide(); - $(this).find('.btn-save').enable(); - return $(this).find('.loading-gif').hide(); - }); - $('.update-notifications').on('ajax:success', function(e, data) { - if (data.saved) { - return new Flash("Notification settings saved", "notice"); - } else { - return new Flash("Failed to save new settings", "alert"); - } - }); +(global => { + class Profile { + constructor(opts = {}) { + this.onSubmitForm = this.onSubmitForm.bind(this); + this.form = opts.form || $('.edit-user'); this.bindEvents(); - cropOpts = { + this.initAvatarGlCrop(); + } + + initAvatarGlCrop() { + const cropOpts = { filename: '.js-avatar-filename', previewImage: '.avatar-image .avatar', modalCrop: '.modal-profile-crop', @@ -46,23 +19,51 @@ this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); } - Profile.prototype.bindEvents = function() { - return this.form.on('submit', this.onSubmitForm); - }; + bindEvents() { + $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); + $('#user_notification_email').on('change', this.submitForm); + $('.update-username').on('ajax:before', this.beforeUpdateUsername); + $('.update-username').on('ajax:complete', this.afterUpdateUsername); + $('.update-notifications').on('ajax:success', this.onUpdateNotifs); + this.form.on('submit', this.onSubmitForm); + } + + submitForm() { + return $(this).parents('form').submit(); + } - Profile.prototype.onSubmitForm = function(e) { + onSubmitForm(e) { e.preventDefault(); return this.saveForm(); - }; + } + + beforeUpdateUsername() { + $('.loading-username').show(); + $(this).find('.update-success').hide(); + return $(this).find('.update-failed').hide(); + } + + afterUpdateUsername() { + $('.loading-username').hide(); + $(this).find('.btn-save').enable(); + return $(this).find('.loading-gif').hide(); + } + + onUpdateNotifs(e, data) { + return data.saved ? + new Flash("Notification settings saved", "notice") : + new Flash("Failed to save new settings", "alert"); + } + + saveForm() { + const self = this; + const formData = new FormData(this.form[0]); + const avatarBlob = this.avatarGlCrop.getBlob(); - Profile.prototype.saveForm = function() { - var avatarBlob, formData, self; - self = this; - formData = new FormData(this.form[0]); - avatarBlob = this.avatarGlCrop.getBlob(); if (avatarBlob != null) { formData.append('user[avatar]', avatarBlob, 'avatar.png'); } + return $.ajax({ url: this.form.attr('action'), type: this.form.attr('method'), @@ -70,37 +71,33 @@ dataType: "json", processData: false, contentType: false, - success: function(response) { + success: (response) => { return new Flash(response.message, 'notice'); }, - error: function(jqXHR) { + error: (jqXHR) => { return new Flash(jqXHR.responseJSON.message, 'alert'); }, - complete: function() { + complete: () => { window.scrollTo(0, 0); // Enable submit button after requests ends return self.form.find(':input[disabled]').enable(); } }); - }; - - return Profile; - - })(); + } + } $(function() { $(document).on('focusout.ssh_key', '#key_key', function() { - var $title, comment; - $title = $('#key_title'); - comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); + const $title = $('#key_title'); + const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); if (comment && comment.length > 1 && $title.val() === '') { return $title.val(comment[1]).change(); } // Extract the SSH Key title from its comment }); - if (gl.utils.getPagePath() === 'profiles') { + if (global.utils.getPagePath() === 'profiles') { return new Profile(); } }); -}).call(this); +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From b690c19dbf9a74e356b75e1da63f7dcf237a8c81 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 17:22:14 +0200 Subject: Use parentheses in IFFE's as per AirBnb styleguide. --- app/assets/javascripts/profile/profile.js.es6 | 3 ++- app/assets/javascripts/search_autocomplete.js.es6 | 2 +- app/assets/javascripts/todos.js.es6 | 2 +- app/assets/javascripts/user.js.es6 | 2 +- app/assets/javascripts/user_tabs.js.es6 | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index c7498c68e7c..e62e0a89867 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,4 +1,5 @@ -(global => { +((global) => { + class Profile { constructor(opts = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 781e0cd782c..d1e8c79336a 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -1,4 +1,4 @@ -(global => { +((global) => { const KEYCODE = { ESCAPE: 27, diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index a0386dcc018..fd85b7506ce 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,4 +1,4 @@ -(global => { +((global) => { class Todos { constructor(opts = {}) { diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index c5893745d74..6930d14094c 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -1,4 +1,4 @@ -(global => { +((global) => { global.User = class { constructor(opts) { this.opts = opts; diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index b787700070e..4a69a79118e 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -57,7 +57,7 @@ content on the Users#show page. */ -(global => { +((global) => { class UserTabs { constructor (opts) { this.loaded = {}; -- cgit v1.2.1 From 13182a9c5c97b9e104e9efcda203d8b566b72f28 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 16:57:13 +0200 Subject: Make use of destructuring options, clean up based on feedback. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 12 ++++---- app/assets/javascripts/profile/profile.js.es6 | 12 +++----- app/assets/javascripts/search_autocomplete.js.es6 | 35 ++++++++++++++--------- app/assets/javascripts/todos.js.es6 | 10 +++---- app/assets/javascripts/user.js.es6 | 6 ++-- app/assets/javascripts/user_tabs.js.es6 | 26 +++++++++-------- 6 files changed, 54 insertions(+), 47 deletions(-) diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index 5ae6f1a5940..46496153d7c 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -8,15 +8,15 @@ requestFile(query) { return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); - }; + } }; global.BlobCiYamlSelector = BlobCiYamlSelector; class BlobCiYamlSelectors { - constructor(opts) { - this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); - this.editor = opts.editor; + constructor({ editor, $dropdowns = $('.js-gitlab-ci-yml-selector') }) { + this.editor = editor; + this.$dropdowns = $dropdowns; this.initSelectors(); } @@ -24,11 +24,11 @@ this.$dropdowns.each((i, dropdown) => { const $dropdown = $(dropdown); return new BlobCiYamlSelector({ + editor, pattern: /(.gitlab-ci.yml)/, data: $dropdown.data('data'), wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), - dropdown: $dropdown, - editor: this.editor + dropdown: $dropdown }); }); } diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index e62e0a89867..5b1a5920c95 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,9 +1,9 @@ ((global) => { class Profile { - constructor(opts = {}) { + constructor({ form = $('.edit-user') }) { this.onSubmitForm = this.onSubmitForm.bind(this); - this.form = opts.form || $('.edit-user'); + this.form = form; this.bindEvents(); this.initAvatarGlCrop(); } @@ -72,12 +72,8 @@ dataType: "json", processData: false, contentType: false, - success: (response) => { - return new Flash(response.message, 'notice'); - }, - error: (jqXHR) => { - return new Flash(jqXHR.responseJSON.message, 'alert'); - }, + success: response => new Flash(response.message, 'notice'), + error: jqXHR => new Flash(jqXHR.responseJSON.message, 'alert'), complete: () => { window.scrollTo(0, 0); // Enable submit button after requests ends diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index d1e8c79336a..abd0748f6a3 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -9,19 +9,20 @@ }; class SearchAutocomplete { - constructor(opts = {}) { - this.onSearchInputBlur = this.onSearchInputBlur.bind(this); - this.onClearInputClick = this.onClearInputClick.bind(this); - this.onSearchInputFocus = this.onSearchInputFocus.bind(this); - this.onSearchInputClick = this.onSearchInputClick.bind(this); - this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); - this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); - this.wrap = opts.wrap || $('.search'); - this.optsEl = opts.optsEl || this.wrap.find('.search-autocomplete-opts'); - this.autocompletePath = opts.autocompletePath || this.optsEl.data('autocomplete-path') - this.projectId = opts.projectId || this.optsEl.data('autocomplete-project-id') || ''; - this.projectRef = opts.projectRef || this.optsEl.data('autocomplete-project-ref') || ''; - this.dropdown = this.wrap.find('.dropdown'); + constructor({ + wrap = $('.search'), + optsEl = wrap.find('.search-autocomplete-opts'), + autocompletePath = optsEl.data('autocomplete-path'), + projectId = (optsEl.data('autocomplete-project-id') || ''), + projectRef = (optsEl.data('autocomplete-project-ref') || '') + }) { + this.bindEventContext(); + this.wrap = wrap; + this.optsEl = optsEl; + this.autocompletePath = autocompletePath; + this.projectId = projectId; + this.projectRef = projectRef; + this.dropdown = wrap.find('.dropdown'); this.dropdownContent = this.dropdown.find('.dropdown-content'); this.locationBadgeEl = this.getElement('.location-badge'); this.scopeInputEl = this.getElement('#scope'); @@ -42,6 +43,14 @@ } // Finds an element inside wrapper element + bindEventContext() { + this.onSearchInputBlur = this.onSearchInputBlur.bind(this); + this.onClearInputClick = this.onClearInputClick.bind(this); + this.onSearchInputFocus = this.onSearchInputFocus.bind(this); + this.onSearchInputClick = this.onSearchInputClick.bind(this); + this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); + this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); + } getElement(selector) { return this.wrap.find(selector); } diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index fd85b7506ce..d8dca490e3e 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,11 +1,11 @@ ((global) => { class Todos { - constructor(opts = {}) { + constructor({ el = $('.js-todos-options') }) { this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); - this.el = opts.el || $('.js-todos-options'); - this.perPage = this.el.data('perPage'); + this.el = el; + this.perPage = el.data('perPage'); this.clearListeners(); this.initBtnListeners(); this.initFilters(); @@ -60,7 +60,7 @@ data: { '_method': 'delete' }, - success: data => { + success: (data) => { this.redirectIfNeeded(data.count); this.clearDone($target.closest('li')); return this.updateBadges(data); @@ -80,7 +80,7 @@ data: { '_method': 'delete' }, - success: data => { + success: (data) => { $target.remove(); $('.prepend-top-default').html('
You\'re all done!
'); return this.updateBadges(data); diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index 6930d14094c..0f97924d94e 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -1,7 +1,7 @@ ((global) => { global.User = class { - constructor(opts) { - this.opts = opts; + constructor({ action }) { + this.action = action; this.placeProfileAvatarsToTop(); this.initTabs(); this.hideProjectLimitMessage(); @@ -16,7 +16,7 @@ initTabs() { return new global.UserTabs({ parentEl: '.user-profile', - action: this.opts.action + action: this.action }); } diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 4a69a79118e..1ce0b31c01f 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -59,11 +59,11 @@ content on the Users#show page. */ ((global) => { class UserTabs { - constructor (opts) { + constructor ({ defaultAction = 'activity', action = defaultAction, parentEl }) { this.loaded = {}; - this.defaultAction = opts.defaultAction || 'activity'; - this.action = opts.action || 'activity'; - this.$parentEl = $(opts.parentEl) || $(document); + this.defaultAction = defaultAction; + this.action = action; + this.$parentEl = $(parentEl) || $(document); this._location = window.location; this.$parentEl.find('.nav-links a') .each((i, navLink) => { @@ -81,7 +81,7 @@ content on the Users#show page. bindEvents() { return this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') - .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', (event) => this.tabShown(event)); + .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); } tabShown(event) { @@ -93,7 +93,7 @@ content on the Users#show page. } activateTab(action) { - return this.$parentEl.find(".nav-links .js-" + action + "-tab a") + return this.$parentEl.find(`.nav-links .js-${action}-tab a`) .tab('show'); } @@ -104,7 +104,9 @@ content on the Users#show page. if (action === 'activity') { this.loadActivities(source); } - if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') { + + const loadableActions = [ 'groups', 'contributed', 'projects', 'snippets' ]; + if (loadableActions.indexOf(action) > -1) { return this.loadTab(source, action); } } @@ -115,9 +117,9 @@ content on the Users#show page. complete: () => this.toggleLoading(false), dataType: 'json', type: 'GET', - url: source + ".json", + url: `${source}.json`, success: (data) => { - const tabSelector = 'div#' + action; + const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); this.loaded[action] = true; return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); @@ -141,12 +143,12 @@ content on the Users#show page. } setCurrentAction(action) { - const regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$'); + const regExp = new RegExp(`\/(${this.actions.join('|')})(\.html)?\/?$`); let new_state = this._location.pathname; - new_state = new_state.replace(/\/+$/, ""); + new_state = new_state.replace(/\/+$/, ''); new_state = new_state.replace(regExp, ''); if (action !== this.defaultAction) { - new_state += "/" + action; + new_state += `/${action}`; } new_state += this._location.search + this._location.hash; history.replaceState({ -- cgit v1.2.1 From 5bc3b7be601a234dfbf16a32b2e58b95d3aa677b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 17:49:13 +0200 Subject: Refactor GitLabCrop to ES6. --- app/assets/javascripts/profile/gl_crop.js | 177 -------------------------- app/assets/javascripts/profile/gl_crop.js.es6 | 172 +++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 177 deletions(-) delete mode 100644 app/assets/javascripts/profile/gl_crop.js create mode 100644 app/assets/javascripts/profile/gl_crop.js.es6 diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js deleted file mode 100644 index 30cd6f6e470..00000000000 --- a/app/assets/javascripts/profile/gl_crop.js +++ /dev/null @@ -1,177 +0,0 @@ -(function() { - var GitLabCrop, - bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - GitLabCrop = (function() { - var FILENAMEREGEX; - - // Matches everything but the file name - FILENAMEREGEX = /^.*[\\\/]/; - - function GitLabCrop(input, opts) { - var ref, ref1, ref2, ref3, ref4; - if (opts == null) { - opts = {}; - } - this.onUploadImageBtnClick = bind(this.onUploadImageBtnClick, this); - this.onModalHide = bind(this.onModalHide, this); - this.onModalShow = bind(this.onModalShow, this); - this.onPickImageClick = bind(this.onPickImageClick, this); - this.fileInput = $(input); - // We should rename to avoid spec to fail - // Form will submit the proper input filed with a file using FormData - this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger"); - // Set defaults - this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg; - // Required params - // Ensure needed elements are jquery objects - // If selector is provided we will convert them to a jQuery Object - this.filename = this.getElement(this.filename); - this.previewImage = this.getElement(this.previewImage); - this.pickImageEl = this.getElement(this.pickImageEl); - // Modal elements usually are outside the @form element - this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop; - this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn; - this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; - this.cropActionsBtn = this.modalCrop.find('[data-method]'); - this.bindEvents(); - } - - GitLabCrop.prototype.getElement = function(selector) { - return $(selector, this.form); - }; - - GitLabCrop.prototype.bindEvents = function() { - var _this; - _this = this; - this.fileInput.on('change', function(e) { - return _this.onFileInputChange(e, this); - }); - this.pickImageEl.on('click', this.onPickImageClick); - this.modalCrop.on('shown.bs.modal', this.onModalShow); - this.modalCrop.on('hidden.bs.modal', this.onModalHide); - this.uploadImageBtn.on('click', this.onUploadImageBtnClick); - this.cropActionsBtn.on('click', function(e) { - var btn; - btn = this; - return _this.onActionBtnClick(btn); - }); - return this.croppedImageBlob = null; - }; - - GitLabCrop.prototype.onPickImageClick = function() { - return this.fileInput.trigger('click'); - }; - - GitLabCrop.prototype.onModalShow = function() { - var _this; - _this = this; - return this.modalCropImg.cropper({ - viewMode: 1, - center: false, - aspectRatio: 1, - modal: true, - scalable: false, - rotatable: false, - zoomable: true, - dragMode: 'move', - guides: false, - zoomOnTouch: false, - zoomOnWheel: false, - cropBoxMovable: false, - cropBoxResizable: false, - toggleDragModeOnDblclick: false, - built: function() { - var $image, container, cropBoxHeight, cropBoxWidth; - $image = $(this); - container = $image.cropper('getContainerData'); - cropBoxWidth = _this.cropBoxWidth; - cropBoxHeight = _this.cropBoxHeight; - return $image.cropper('setCropBoxData', { - width: cropBoxWidth, - height: cropBoxHeight, - left: (container.width - cropBoxWidth) / 2, - top: (container.height - cropBoxHeight) / 2 - }); - } - }); - }; - - GitLabCrop.prototype.onModalHide = function() { - return this.modalCropImg.attr('src', '').cropper('destroy'); - }; - - GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image - e.preventDefault(); // Destroy cropper instance - this.setBlob(); - this.setPreview(); - this.modalCrop.modal('hide'); - return this.fileInput.val(''); - }; - - GitLabCrop.prototype.onActionBtnClick = function(btn) { - var data, result; - data = $(btn).data(); - if (this.modalCropImg.data('cropper') && data.method) { - return result = this.modalCropImg.cropper(data.method, data.option); - } - }; - - GitLabCrop.prototype.onFileInputChange = function(e, input) { - return this.readFile(input); - }; - - GitLabCrop.prototype.readFile = function(input) { - var _this, reader; - _this = this; - reader = new FileReader; - reader.onload = function() { - _this.modalCropImg.attr('src', reader.result); - return _this.modalCrop.modal('show'); - }; - return reader.readAsDataURL(input.files[0]); - }; - - GitLabCrop.prototype.dataURLtoBlob = function(dataURL) { - var array, binary, i, k, len, v; - binary = atob(dataURL.split(',')[1]); - array = []; - for (k = i = 0, len = binary.length; i < len; k = ++i) { - v = binary[k]; - array.push(binary.charCodeAt(k)); - } - return new Blob([new Uint8Array(array)], { - type: 'image/png' - }); - }; - - GitLabCrop.prototype.setPreview = function() { - var filename; - this.previewImage.attr('src', this.dataURL); - filename = this.fileInput.val().replace(FILENAMEREGEX, ''); - return this.filename.text(filename); - }; - - GitLabCrop.prototype.setBlob = function() { - this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', { - width: 200, - height: 200 - }).toDataURL('image/png'); - return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL); - }; - - GitLabCrop.prototype.getBlob = function() { - return this.croppedImageBlob; - }; - - return GitLabCrop; - - })(); - - $.fn.glCrop = function(opts) { - return this.each(function() { - return $(this).data('glcrop', new GitLabCrop(this, opts)); - }); - }; - -}).call(this); diff --git a/app/assets/javascripts/profile/gl_crop.js.es6 b/app/assets/javascripts/profile/gl_crop.js.es6 new file mode 100644 index 00000000000..a1b0126e857 --- /dev/null +++ b/app/assets/javascripts/profile/gl_crop.js.es6 @@ -0,0 +1,172 @@ +((global) => { + + // Matches everything but the file name + const FILENAMEREGEX = /^.*[\\\/]/; + + class GitLabCrop { + constructor(input, { filename, previewImage, modalCrop, pickImageEl, uploadImageBtn, modalCropImg, + exportWidth = 200, exportHeight = 200, cropBoxWidth = 200, cropBoxHeight = 200 } = {}) { + + this.onUploadImageBtnClick = this.onUploadImageBtnClick.bind(this); + this.onModalHide = this.onModalHide.bind(this); + this.onModalShow = this.onModalShow.bind(this); + this.onPickImageClick = this.onPickImageClick.bind(this); + this.fileInput = $(input); + this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; + this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `this.fileInput.attr('id')-trigger`); + this.exportWidth = exportWidth; + this.exportHeight = exportHeight; + this.cropBoxWidth = cropBoxWidth; + this.cropBoxHeight = cropBoxHeight; + this.form = this.fileInput.parents('form'); + this.filename = filename; + this.previewImage = previewImage; + this.modalCrop = modalCrop; + this.pickImageEl = pickImageEl; + this.uploadImageBtn = uploadImageBtn; + this.modalCropImg = modalCropImg; + this.filename = this.getElement(filename); + this.previewImage = this.getElement(previewImage); + this.pickImageEl = this.getElement(pickImageEl); + this.modalCrop = _.isString(modalCrop) ? $(modalCrop) : modalCrop; + this.uploadImageBtn = _.isString(uploadImageBtn) ? $(uploadImageBtn) : uploadImageBtn; + this.modalCropImg = _.isString(modalCropImg) ? $(modalCropImg) : modalCropImg; + this.cropActionsBtn = this.modalCrop.find('[data-method]'); + this.bindEvents(); + } + + getElement(selector) { + return $(selector, this.form); + } + + bindEvents() { + var _this; + _this = this; + this.fileInput.on('change', function(e) { + return _this.onFileInputChange(e, this); + }); + this.pickImageEl.on('click', this.onPickImageClick); + this.modalCrop.on('shown.bs.modal', this.onModalShow); + this.modalCrop.on('hidden.bs.modal', this.onModalHide); + this.uploadImageBtn.on('click', this.onUploadImageBtnClick); + this.cropActionsBtn.on('click', function(e) { + var btn; + btn = this; + return _this.onActionBtnClick(btn); + }); + return this.croppedImageBlob = null; + } + + onPickImageClick() { + return this.fileInput.trigger('click'); + } + + onModalShow() { + var _this; + _this = this; + return this.modalCropImg.cropper({ + viewMode: 1, + center: false, + aspectRatio: 1, + modal: true, + scalable: false, + rotatable: false, + zoomable: true, + dragMode: 'move', + guides: false, + zoomOnTouch: false, + zoomOnWheel: false, + cropBoxMovable: false, + cropBoxResizable: false, + toggleDragModeOnDblclick: false, + built: function() { + var $image, container, cropBoxHeight, cropBoxWidth; + $image = $(this); + container = $image.cropper('getContainerData'); + cropBoxWidth = _this.cropBoxWidth; + cropBoxHeight = _this.cropBoxHeight; + return $image.cropper('setCropBoxData', { + width: cropBoxWidth, + height: cropBoxHeight, + left: (container.width - cropBoxWidth) / 2, + top: (container.height - cropBoxHeight) / 2 + }); + } + }); + } + + onModalHide() { + return this.modalCropImg.attr('src', '').cropper('destroy'); + } + + onUploadImageBtnClick(e) { + e.preventDefault(); + this.setBlob(); + this.setPreview(); + this.modalCrop.modal('hide'); + return this.fileInput.val(''); + } + + onActionBtnClick(btn) { + var data, result; + data = $(btn).data(); + if (this.modalCropImg.data('cropper') && data.method) { + return result = this.modalCropImg.cropper(data.method, data.option); + } + } + + onFileInputChange(e, input) { + return this.readFile(input); + } + + readFile(input) { + var _this, reader; + _this = this; + reader = new FileReader; + reader.onload = () => { + _this.modalCropImg.attr('src', reader.result); + return _this.modalCrop.modal('show'); + }; + return reader.readAsDataURL(input.files[0]); + } + + dataURLtoBlob(dataURL) { + var array, binary, i, k, len, v; + binary = atob(dataURL.split(',')[1]); + array = []; + for (k = i = 0, len = binary.length; i < len; k = ++i) { + v = binary[k]; + array.push(binary.charCodeAt(k)); + } + return new Blob([new Uint8Array(array)], { + type: 'image/png' + }); + } + + setPreview() { + var filename; + this.previewImage.attr('src', this.dataURL); + filename = this.fileInput.val().replace(FILENAMEREGEX, ''); + return this.filename.text(filename); + } + + setBlob() { + this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', { + width: 200, + height: 200 + }).toDataURL('image/png'); + return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL); + } + + getBlob() { + return this.croppedImageBlob; + } + } + + $.fn.glCrop = function(opts) { + return this.each(function() { + return $(this).data('glcrop', new GitLabCrop(this, opts)); + }); + } + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 7beb9f3483cf59a9717c2ccc6bd102cd9a97f906 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 17:49:48 +0200 Subject: Pass default opts. --- app/assets/javascripts/profile/profile.js.es6 | 2 +- app/assets/javascripts/search_autocomplete.js.es6 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 5b1a5920c95..5f674c36de5 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,7 +1,7 @@ ((global) => { class Profile { - constructor({ form = $('.edit-user') }) { + constructor({ form = $('.edit-user') } = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); this.form = form; this.bindEvents(); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index abd0748f6a3..3d710d17ef3 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -15,7 +15,8 @@ autocompletePath = optsEl.data('autocomplete-path'), projectId = (optsEl.data('autocomplete-project-id') || ''), projectRef = (optsEl.data('autocomplete-project-ref') || '') - }) { + } = {}) { + this.bindEventContext(); this.wrap = wrap; this.optsEl = optsEl; -- cgit v1.2.1 From b3917d4868120c5a62e4e5525bd3321cb152e2fd Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 18:31:26 +0200 Subject: Set defaults in constructor, in case opts are undefined. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 4 +- app/assets/javascripts/issues-bulk-assignment.js | 167 --------------------- .../javascripts/issues-bulk-assignment.js.es6 | 167 +++++++++++++++++++++ app/assets/javascripts/profile/profile.js.es6 | 4 +- app/assets/javascripts/search_autocomplete.js.es6 | 19 +-- app/assets/javascripts/todos.js.es6 | 4 +- app/assets/javascripts/user_tabs.js.es6 | 6 +- 7 files changed, 182 insertions(+), 189 deletions(-) delete mode 100644 app/assets/javascripts/issues-bulk-assignment.js create mode 100644 app/assets/javascripts/issues-bulk-assignment.js.es6 diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index 46496153d7c..bb9d0444a8c 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -14,9 +14,9 @@ global.BlobCiYamlSelector = BlobCiYamlSelector; class BlobCiYamlSelectors { - constructor({ editor, $dropdowns = $('.js-gitlab-ci-yml-selector') }) { + constructor({ editor, $dropdowns }) { this.editor = editor; - this.$dropdowns = $dropdowns; + this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector'); this.initSelectors(); } diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js deleted file mode 100644 index 62a7fc9a06c..00000000000 --- a/app/assets/javascripts/issues-bulk-assignment.js +++ /dev/null @@ -1,167 +0,0 @@ -(function() { - this.IssuableBulkActions = (function() { - function IssuableBulkActions(opts) { - // Set defaults - var ref, ref1, ref2; - if (opts == null) { - opts = {}; - } - this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); - // Save instance - this.form.data('bulkActions', this); - this.willUpdateLabels = false; - this.bindEvents(); - // Fixes bulk-assign not working when navigating through pages - Issuable.initChecks(); - } - - IssuableBulkActions.prototype.getElement = function(selector) { - return this.container.find(selector); - }; - - IssuableBulkActions.prototype.bindEvents = function() { - return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); - }; - - IssuableBulkActions.prototype.onFormSubmit = function(e) { - e.preventDefault(); - return this.submit(); - }; - - IssuableBulkActions.prototype.submit = function() { - var _this, xhr; - _this = this; - xhr = $.ajax({ - url: this.form.attr('action'), - method: this.form.attr('method'), - dataType: 'JSON', - data: this.getFormDataAsObject() - }); - xhr.done(function(response, status, xhr) { - return location.reload(); - }); - xhr.fail(function() { - return new Flash("Issue update failed"); - }); - return xhr.always(this.onFormSubmitAlways.bind(this)); - }; - - IssuableBulkActions.prototype.onFormSubmitAlways = function() { - return this.form.find('[type="submit"]').enable(); - }; - - IssuableBulkActions.prototype.getSelectedIssues = function() { - return this.issues.has('.selected_issue:checked'); - }; - - IssuableBulkActions.prototype.getLabelsFromSelection = function() { - var labels; - labels = []; - this.getSelectedIssues().map(function() { - var _labels; - _labels = $(this).data('labels'); - if (_labels) { - return _labels.map(function(labelId) { - if (labels.indexOf(labelId) === -1) { - return labels.push(labelId); - } - }); - } - }); - return labels; - }; - - - /** - * Will return only labels that were marked previously and the user has unmarked - * @return {Array} Label IDs - */ - - IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() { - var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result; - result = []; - labelsToKeep = []; - ref = this.getElement('.labels-filter .is-indeterminate'); - for (i = 0, len = ref.length; i < len; i++) { - el = ref[i]; - labelsToKeep.push($(el).data('labelId')); - } - ref1 = this.getLabelsFromSelection(); - for (j = 0, len1 = ref1.length; j < len1; j++) { - id = ref1[j]; - // Only the ones that we are not going to keep - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - } - return result; - }; - - - /** - * Simple form serialization, it will return just what we need - * Returns key/value pairs from form data - */ - - IssuableBulkActions.prototype.getFormDataAsObject = function() { - var formData; - formData = { - update: { - state_event: this.form.find('input[name="update[state_event]"]').val(), - assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), - milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), - issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), - subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), - add_label_ids: [], - remove_label_ids: [] - } - }; - if (this.willUpdateLabels) { - this.getLabelsToApply().map(function(id) { - return formData.update.add_label_ids.push(id); - }); - this.getLabelsToRemove().map(function(id) { - return formData.update.remove_label_ids.push(id); - }); - } - return formData; - }; - - IssuableBulkActions.prototype.getLabelsToApply = function() { - var $labels, labelIds; - labelIds = []; - $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); - $labels.each(function(k, label) { - if (label) { - return labelIds.push(parseInt($(label).val())); - } - }); - return labelIds; - }; - - - /** - * Returns Label IDs that will be removed from issue selection - * @return {Array} Array of labels IDs - */ - - IssuableBulkActions.prototype.getLabelsToRemove = function() { - var indeterminatedLabels, labelsToApply, result; - result = []; - indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); - labelsToApply = this.getLabelsToApply(); - indeterminatedLabels.map(function(id) { - // We need to exclude label IDs that will be applied - // By not doing this will cause issues from selection to not add labels at all - if (labelsToApply.indexOf(id) === -1) { - return result.push(id); - } - }); - return result; - }; - - return IssuableBulkActions; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 new file mode 100644 index 00000000000..62a7fc9a06c --- /dev/null +++ b/app/assets/javascripts/issues-bulk-assignment.js.es6 @@ -0,0 +1,167 @@ +(function() { + this.IssuableBulkActions = (function() { + function IssuableBulkActions(opts) { + // Set defaults + var ref, ref1, ref2; + if (opts == null) { + opts = {}; + } + this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); + // Save instance + this.form.data('bulkActions', this); + this.willUpdateLabels = false; + this.bindEvents(); + // Fixes bulk-assign not working when navigating through pages + Issuable.initChecks(); + } + + IssuableBulkActions.prototype.getElement = function(selector) { + return this.container.find(selector); + }; + + IssuableBulkActions.prototype.bindEvents = function() { + return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); + }; + + IssuableBulkActions.prototype.onFormSubmit = function(e) { + e.preventDefault(); + return this.submit(); + }; + + IssuableBulkActions.prototype.submit = function() { + var _this, xhr; + _this = this; + xhr = $.ajax({ + url: this.form.attr('action'), + method: this.form.attr('method'), + dataType: 'JSON', + data: this.getFormDataAsObject() + }); + xhr.done(function(response, status, xhr) { + return location.reload(); + }); + xhr.fail(function() { + return new Flash("Issue update failed"); + }); + return xhr.always(this.onFormSubmitAlways.bind(this)); + }; + + IssuableBulkActions.prototype.onFormSubmitAlways = function() { + return this.form.find('[type="submit"]').enable(); + }; + + IssuableBulkActions.prototype.getSelectedIssues = function() { + return this.issues.has('.selected_issue:checked'); + }; + + IssuableBulkActions.prototype.getLabelsFromSelection = function() { + var labels; + labels = []; + this.getSelectedIssues().map(function() { + var _labels; + _labels = $(this).data('labels'); + if (_labels) { + return _labels.map(function(labelId) { + if (labels.indexOf(labelId) === -1) { + return labels.push(labelId); + } + }); + } + }); + return labels; + }; + + + /** + * Will return only labels that were marked previously and the user has unmarked + * @return {Array} Label IDs + */ + + IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() { + var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result; + result = []; + labelsToKeep = []; + ref = this.getElement('.labels-filter .is-indeterminate'); + for (i = 0, len = ref.length; i < len; i++) { + el = ref[i]; + labelsToKeep.push($(el).data('labelId')); + } + ref1 = this.getLabelsFromSelection(); + for (j = 0, len1 = ref1.length; j < len1; j++) { + id = ref1[j]; + // Only the ones that we are not going to keep + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + } + return result; + }; + + + /** + * Simple form serialization, it will return just what we need + * Returns key/value pairs from form data + */ + + IssuableBulkActions.prototype.getFormDataAsObject = function() { + var formData; + formData = { + update: { + state_event: this.form.find('input[name="update[state_event]"]').val(), + assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), + milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), + issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), + subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), + add_label_ids: [], + remove_label_ids: [] + } + }; + if (this.willUpdateLabels) { + this.getLabelsToApply().map(function(id) { + return formData.update.add_label_ids.push(id); + }); + this.getLabelsToRemove().map(function(id) { + return formData.update.remove_label_ids.push(id); + }); + } + return formData; + }; + + IssuableBulkActions.prototype.getLabelsToApply = function() { + var $labels, labelIds; + labelIds = []; + $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); + $labels.each(function(k, label) { + if (label) { + return labelIds.push(parseInt($(label).val())); + } + }); + return labelIds; + }; + + + /** + * Returns Label IDs that will be removed from issue selection + * @return {Array} Array of labels IDs + */ + + IssuableBulkActions.prototype.getLabelsToRemove = function() { + var indeterminatedLabels, labelsToApply, result; + result = []; + indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); + labelsToApply = this.getLabelsToApply(); + indeterminatedLabels.map(function(id) { + // We need to exclude label IDs that will be applied + // By not doing this will cause issues from selection to not add labels at all + if (labelsToApply.indexOf(id) === -1) { + return result.push(id); + } + }); + return result; + }; + + return IssuableBulkActions; + + })(); + +}).call(this); diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 5f674c36de5..c16eb93a2dd 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,9 +1,9 @@ ((global) => { class Profile { - constructor({ form = $('.edit-user') } = {}) { + constructor({ form }) { this.onSubmitForm = this.onSubmitForm.bind(this); - this.form = form; + this.form = form || $('.edit-user'); this.bindEvents(); this.initAvatarGlCrop(); } diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 3d710d17ef3..b32199b9721 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -9,20 +9,13 @@ }; class SearchAutocomplete { - constructor({ - wrap = $('.search'), - optsEl = wrap.find('.search-autocomplete-opts'), - autocompletePath = optsEl.data('autocomplete-path'), - projectId = (optsEl.data('autocomplete-project-id') || ''), - projectRef = (optsEl.data('autocomplete-project-ref') || '') - } = {}) { - + constructor({ wrap, optsEl, autocompletePath, projectId, projectRef }) { this.bindEventContext(); - this.wrap = wrap; - this.optsEl = optsEl; - this.autocompletePath = autocompletePath; - this.projectId = projectId; - this.projectRef = projectRef; + this.wrap = wrap || $('.search'); + this.optsEl = optsEl || wrap.find('.search-autocomplete-opts'); + this.autocompletePath = autocompletePath || optsEl.data('autocomplete-path'); + this.projectId = projectId || (optsEl.data('autocomplete-project-id') || ''); + this.projectRef = projectRef || (optsEl.data('autocomplete-project-ref') || ''); this.dropdown = wrap.find('.dropdown'); this.dropdownContent = this.dropdown.find('.dropdown-content'); this.locationBadgeEl = this.getElement('.location-badge'); diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index d8dca490e3e..7e606196435 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,10 +1,10 @@ ((global) => { class Todos { - constructor({ el = $('.js-todos-options') }) { + constructor({ el }) { this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); - this.el = el; + this.el = el || $('.js-todos-options'); this.perPage = el.data('perPage'); this.clearListeners(); this.initBtnListeners(); diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 1ce0b31c01f..7cb38b8ec07 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -59,10 +59,10 @@ content on the Users#show page. */ ((global) => { class UserTabs { - constructor ({ defaultAction = 'activity', action = defaultAction, parentEl }) { + constructor ({ defaultAction, action, parentEl }) { this.loaded = {}; - this.defaultAction = defaultAction; - this.action = action; + this.defaultAction = defaultAction || 'activity'; + this.action = action || this.defaultAction; this.$parentEl = $(parentEl) || $(document); this._location = window.location; this.$parentEl.find('.nav-links a') -- cgit v1.2.1 From 603d60a2fcdca90ddbac1f1c53213848fb535aef Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 18:57:59 +0200 Subject: Refactor IssuesBulkAssignment to ES6. --- app/assets/javascripts/dispatcher.js | 2 +- .../javascripts/issues-bulk-assignment.js.es6 | 132 +++++++++------------ app/assets/javascripts/search_autocomplete.js.es6 | 12 +- 3 files changed, 63 insertions(+), 83 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 3073f7a3d3a..0685540f935 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -26,7 +26,7 @@ case 'projects:merge_requests:index': case 'projects:issues:index': Issuable.init(); - new IssuableBulkActions(); + new gl.IssuableBulkActions(); shortcut_handler = new ShortcutsNavigation(); break; case 'projects:issues:show': diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 index 62a7fc9a06c..012002a293a 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js.es6 +++ b/app/assets/javascripts/issues-bulk-assignment.js.es6 @@ -1,13 +1,10 @@ -(function() { - this.IssuableBulkActions = (function() { - function IssuableBulkActions(opts) { - // Set defaults - var ref, ref1, ref2; - if (opts == null) { - opts = {}; - } - this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); - // Save instance +((global) => { + + class IssuableBulkActions { + constructor({ container, form, issues } = {}) { + this.container = container || $('.content'), + this.form = form || this.getElement('.bulk-update'); + this.issues = issues || this.getElement('.issues-list .issue'); this.form.data('bulkActions', this); this.willUpdateLabels = false; this.bindEvents(); @@ -15,53 +12,46 @@ Issuable.initChecks(); } - IssuableBulkActions.prototype.getElement = function(selector) { + getElement(selector) { return this.container.find(selector); - }; + } - IssuableBulkActions.prototype.bindEvents = function() { + bindEvents() { return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); - }; + } - IssuableBulkActions.prototype.onFormSubmit = function(e) { + onFormSubmit(e) { e.preventDefault(); return this.submit(); - }; + } - IssuableBulkActions.prototype.submit = function() { - var _this, xhr; - _this = this; - xhr = $.ajax({ + submit() { + const _this = this; + const xhr = $.ajax({ url: this.form.attr('action'), method: this.form.attr('method'), dataType: 'JSON', data: this.getFormDataAsObject() }); - xhr.done(function(response, status, xhr) { - return location.reload(); - }); - xhr.fail(function() { - return new Flash("Issue update failed"); - }); + xhr.done(() => window.location.reload()); + xhr.fail(() => new Flash("Issue update failed")); return xhr.always(this.onFormSubmitAlways.bind(this)); - }; + } - IssuableBulkActions.prototype.onFormSubmitAlways = function() { + onFormSubmitAlways() { return this.form.find('[type="submit"]').enable(); - }; + } - IssuableBulkActions.prototype.getSelectedIssues = function() { + getSelectedIssues() { return this.issues.has('.selected_issue:checked'); - }; + } - IssuableBulkActions.prototype.getLabelsFromSelection = function() { - var labels; - labels = []; + getLabelsFromSelection() { + const labels = []; this.getSelectedIssues().map(function() { - var _labels; - _labels = $(this).data('labels'); - if (_labels) { - return _labels.map(function(labelId) { + const labelsData = $(this).data('labels'); + if (labelsData) { + return labelsData.map(function(labelId) { if (labels.indexOf(labelId) === -1) { return labels.push(labelId); } @@ -69,7 +59,7 @@ } }); return labels; - }; + } /** @@ -77,25 +67,19 @@ * @return {Array} Label IDs */ - IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() { - var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result; - result = []; - labelsToKeep = []; - ref = this.getElement('.labels-filter .is-indeterminate'); - for (i = 0, len = ref.length; i < len; i++) { - el = ref[i]; - labelsToKeep.push($(el).data('labelId')); - } - ref1 = this.getLabelsFromSelection(); - for (j = 0, len1 = ref1.length; j < len1; j++) { - id = ref1[j]; - // Only the ones that we are not going to keep - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - } + getUnmarkedIndeterminedLabels() { + const result = []; + const elements = this.getElement('.labels-filter .is-indeterminate'); + const labelsToKeep = elements.map((el) => labelsToKeep.push($(el).data('labelId'))); + const selectedLabels = this.getLabelsFromSelection() + .forEach(() => { + const id = selectedLabels[j]; + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + }); return result; - }; + } /** @@ -103,9 +87,8 @@ * Returns key/value pairs from form data */ - IssuableBulkActions.prototype.getFormDataAsObject = function() { - var formData; - formData = { + getFormDataAsObject() { + const formData = { update: { state_event: this.form.find('input[name="update[state_event]"]').val(), assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), @@ -125,19 +108,18 @@ }); } return formData; - }; + } - IssuableBulkActions.prototype.getLabelsToApply = function() { - var $labels, labelIds; - labelIds = []; - $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); + getLabelsToApply() { + const labelIds = []; + const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); $labels.each(function(k, label) { if (label) { return labelIds.push(parseInt($(label).val())); } }); return labelIds; - }; + } /** @@ -145,11 +127,10 @@ * @return {Array} Array of labels IDs */ - IssuableBulkActions.prototype.getLabelsToRemove = function() { - var indeterminatedLabels, labelsToApply, result; - result = []; - indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); - labelsToApply = this.getLabelsToApply(); + getLabelsToRemove() { + const result = []; + const indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); + const labelsToApply = this.getLabelsToApply(); indeterminatedLabels.map(function(id) { // We need to exclude label IDs that will be applied // By not doing this will cause issues from selection to not add labels at all @@ -158,10 +139,9 @@ } }); return result; - }; - - return IssuableBulkActions; + } + } - })(); + global.IssuableBulkActions = IssuableBulkActions; -}).call(this); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index b32199b9721..b4c6226dc68 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -9,14 +9,14 @@ }; class SearchAutocomplete { - constructor({ wrap, optsEl, autocompletePath, projectId, projectRef }) { + constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) { this.bindEventContext(); this.wrap = wrap || $('.search'); - this.optsEl = optsEl || wrap.find('.search-autocomplete-opts'); - this.autocompletePath = autocompletePath || optsEl.data('autocomplete-path'); - this.projectId = projectId || (optsEl.data('autocomplete-project-id') || ''); - this.projectRef = projectRef || (optsEl.data('autocomplete-project-ref') || ''); - this.dropdown = wrap.find('.dropdown'); + this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts'); + this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path'); + this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || ''); + this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || ''); + this.dropdown = this.wrap.find('.dropdown'); this.dropdownContent = this.dropdown.find('.dropdown-content'); this.locationBadgeEl = this.getElement('.location-badge'); this.scopeInputEl = this.getElement('#scope'); -- cgit v1.2.1 From 53541e269119d57016441b6493fab86f60eacf88 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 12 Sep 2016 19:05:47 +0200 Subject: Update LabelManager to ES6. --- app/assets/javascripts/LabelManager.js.es6 | 104 +++++++++++++++++++++++++++++ app/assets/javascripts/dispatcher.js | 3 +- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/LabelManager.js.es6 diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 new file mode 100644 index 00000000000..b655dfa7113 --- /dev/null +++ b/app/assets/javascripts/LabelManager.js.es6 @@ -0,0 +1,104 @@ +((global) => { + + class LabelManager { + constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { + this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); + this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); + this.otherLabels = otherLabels || $('.js-other-labels'); + this.errorMessage = 'Unable to update label prioritization at this time'; + this.prioritizedLabels.sortable({ + items: 'li', + placeholder: 'list-placeholder', + axis: 'y', + update: this.onPrioritySortUpdate.bind(this) + }); + this.bindEvents(); + } + + bindEvents() { + // TODO: Check if this is being bound correctly + return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); + } + + onTogglePriorityClick(e) { + e.preventDefault(); + const _this = e.data; + const $btn = $(e.currentTarget); + const $label = $(`#${$btn.data('domId')}`); + const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; + const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); + $tooltip.tooltip('destroy'); + return _this.toggleLabelPriority($label, action); + } + + toggleLabelPriority($label, action, persistState) { + if (persistState == null) { + persistState = true; + } + let xhr; + const _this = this; + const url = $label.find('.js-toggle-priority').data('url'); + let $target = this.prioritizedLabels; + let $from = this.otherLabels; + if (action === 'remove') { + $target = this.otherLabels; + $from = this.prioritizedLabels; + } + if ($from.find('li').length === 1) { + $from.find('.empty-message').removeClass('hidden'); + } + if (!$target.find('li').length) { + $target.find('.empty-message').addClass('hidden'); + } + $label.detach().appendTo($target); + if (!persistState) { + return; + } + if (action === 'remove') { + xhr = $.ajax({ + url, + type: 'DELETE' + }); + if (!$from.find('li').length) { + $from.find('.empty-message').removeClass('hidden'); + } + } else { + xhr = this.savePrioritySort($label, action); + } + return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); + } + + onPrioritySortUpdate() { + const xhr = this.savePrioritySort(); + return xhr.fail(function() { + return new Flash(this.errorMessage, 'alert'); + }); + } + + savePrioritySort() { + return $.post({ + url: this.prioritizedLabels.data('url'), + data: { + label_ids: this.getSortedLabelsIds() + } + }); + } + + rollbackLabelPosition($label, originalAction) { + const action = originalAction === 'remove' ? 'add' : 'remove'; + this.toggleLabelPriority($label, action, false); + return new Flash(this.errorMessage, 'alert'); + } + + getSortedLabelsIds() { + // TODO: Check that this works how you expect + return this.prioritizedLabels.find('li').map(function(item) { + return $(item).data('id'); + }); + } + } + + gl.LabelManager = LabelManager; + +})(window.gl || (window.gl = {})); + diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 0685540f935..ecb5812c015 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -164,8 +164,9 @@ new Labels(); break; case 'projects:labels:index': + debugger; if ($('.prioritized-labels').length) { - new LabelManager(); + new gl.LabelManager(); } break; case 'projects:network:show': -- cgit v1.2.1 From 2c5bd97dc4414b703d6eaadc0af586699c228004 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 12 Sep 2016 19:19:02 +0200 Subject: Convert BlobLicenseSelctors to ES6. --- .../javascripts/blob/blob_license_selectors.js | 25 ---------------------- .../javascripts/blob/blob_license_selectors.js.es6 | 21 ++++++++++++++++++ 2 files changed, 21 insertions(+), 25 deletions(-) delete mode 100644 app/assets/javascripts/blob/blob_license_selectors.js create mode 100644 app/assets/javascripts/blob/blob_license_selectors.js.es6 diff --git a/app/assets/javascripts/blob/blob_license_selectors.js b/app/assets/javascripts/blob/blob_license_selectors.js deleted file mode 100644 index 39237705e8d..00000000000 --- a/app/assets/javascripts/blob/blob_license_selectors.js +++ /dev/null @@ -1,25 +0,0 @@ -(function() { - this.BlobLicenseSelectors = (function() { - function BlobLicenseSelectors(opts) { - var ref; - this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-license-selector'), this.editor = opts.editor; - this.$dropdowns.each((function(_this) { - return function(i, dropdown) { - var $dropdown; - $dropdown = $(dropdown); - return new BlobLicenseSelector({ - pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-license-selector-wrap'), - dropdown: $dropdown, - editor: _this.editor - }); - }; - })(this)); - } - - return BlobLicenseSelectors; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 new file mode 100644 index 00000000000..f8391919068 --- /dev/null +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -0,0 +1,21 @@ +((global) => { + class BlobLicenseSelectors() { + constructor({ $dropdowns, editor }) { + this.$dropdowns = $('.js-license-selector'); + this.editor = editor; + this.$dropdowns.each((dropdown) => { + const $dropdown = $(dropdown); + return new BlobLicenseSelector({ + editor, + pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-license-selector-wrap'), + dropdown: $dropdown, + }); + }); + } + } + + global.BlobLicenseSelectors = BlobLicenseSelectors; + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 6fc9118cddf102e18b00156c2892450960ee2d17 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 19 Sep 2016 16:38:31 +0200 Subject: Remove super passthrough. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 4 ---- app/assets/javascripts/blob/blob_license_selectors.js.es6 | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index bb9d0444a8c..dee06a7196f 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -2,10 +2,6 @@ ((global) => { class BlobCiYamlSelector extends TemplateSelector { - constructor(...args) { - super(...args); - } - requestFile(query) { return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); } diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 index f8391919068..ebc1618816f 100644 --- a/app/assets/javascripts/blob/blob_license_selectors.js.es6 +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -1,5 +1,5 @@ ((global) => { - class BlobLicenseSelectors() { + class BlobLicenseSelectors { constructor({ $dropdowns, editor }) { this.$dropdowns = $('.js-license-selector'); this.editor = editor; -- cgit v1.2.1 From 8fa3e576a6afa4c424b0e476d6213890491c97c1 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 19 Sep 2016 16:48:20 +0200 Subject: Remove unneeded semicolon. --- app/assets/javascripts/user_tabs.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 7cb38b8ec07..63bce0a6f6f 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -156,7 +156,7 @@ content on the Users#show page. url: new_state }, document.title, new_state); return new_state; - }; + } } global.UserTabs = UserTabs; })(window.gl || (window.gl = {})); -- cgit v1.2.1 From c756f1cf7545ac50393829a418d61dd98248406a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 11:35:07 +0200 Subject: Update Changelog. --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 51515ac5ade..bcedb3fcec2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -83,6 +83,10 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.1 (unreleased) + - Refactor remnants of CoffeeScript destructured opts and super !6261 + +v 8.12.0 (released) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region -- cgit v1.2.1 From 47217666194d5767cab4e5c7ba3e0aaaae10f943 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 12:47:58 +0200 Subject: Fix test for SearchAutocomplete. --- spec/javascripts/search_autocomplete_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 00d9fc1302a..4470fbcb099 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -112,7 +112,7 @@ fixture.preload('search_autocomplete.html'); beforeEach(function() { fixture.load('search_autocomplete.html'); - return widget = new SearchAutocomplete; + return widget = new gl.SearchAutocomplete; }); it('should show Dashboard specific dropdown menu', function() { var list; -- cgit v1.2.1 From 87036a6acc6f857e026fe9e31eb4d1605fccacf5 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 12:57:49 +0200 Subject: Fix tests for Blob Selectors. --- app/assets/javascripts/blob_edit/edit_blob.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 0be4b6392bf..e248b557c96 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -23,10 +23,10 @@ })(this)); this.initModePanesAndLinks(); this.initSoftWrap(); - new BlobLicenseSelectors({ + new gl.BlobLicenseSelectors({ editor: this.editor }); - new BlobGitignoreSelectors({ + new gl.BlobGitignoreSelectors({ editor: this.editor }); new gl.BlobCiYamlSelectors({ -- cgit v1.2.1 From 014bef80bc6cded43ae695ba54376a532d9809e5 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:20:27 +0200 Subject: Fix up for issues bulk assignment test. --- app/assets/javascripts/issues-bulk-assignment.js.es6 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 index 012002a293a..0808f538f01 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js.es6 +++ b/app/assets/javascripts/issues-bulk-assignment.js.es6 @@ -69,15 +69,17 @@ getUnmarkedIndeterminedLabels() { const result = []; - const elements = this.getElement('.labels-filter .is-indeterminate'); - const labelsToKeep = elements.map((el) => labelsToKeep.push($(el).data('labelId'))); - const selectedLabels = this.getLabelsFromSelection() - .forEach(() => { - const id = selectedLabels[j]; - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - }); + const labelsToKeep = []; + + this.getElement('.labels-filter .is-indeterminate') + .each((i, el) => labelsToKeep.push($(el).data('labelId'))); + + this.getLabelsFromSelection().forEach((id) => { + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + }); + return result; } -- cgit v1.2.1 From 07bf262ffeb319e162fe81529af61b781a774bc2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:30:37 +0200 Subject: Remove old LabelManager. --- app/assets/javascripts/LabelManager.js | 115 --------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 app/assets/javascripts/LabelManager.js diff --git a/app/assets/javascripts/LabelManager.js b/app/assets/javascripts/LabelManager.js deleted file mode 100644 index d4a4c7abaa1..00000000000 --- a/app/assets/javascripts/LabelManager.js +++ /dev/null @@ -1,115 +0,0 @@ -(function() { - this.LabelManager = (function() { - LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time'; - - function LabelManager(opts) { - // Defaults - var ref, ref1, ref2; - if (opts == null) { - opts = {}; - } - this.togglePriorityButton = (ref = opts.togglePriorityButton) != null ? ref : $('.js-toggle-priority'), this.prioritizedLabels = (ref1 = opts.prioritizedLabels) != null ? ref1 : $('.js-prioritized-labels'), this.otherLabels = (ref2 = opts.otherLabels) != null ? ref2 : $('.js-other-labels'); - this.prioritizedLabels.sortable({ - items: 'li', - placeholder: 'list-placeholder', - axis: 'y', - update: this.onPrioritySortUpdate.bind(this) - }); - this.bindEvents(); - } - - LabelManager.prototype.bindEvents = function() { - return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); - }; - - LabelManager.prototype.onTogglePriorityClick = function(e) { - var $btn, $label, $tooltip, _this, action; - e.preventDefault(); - _this = e.data; - $btn = $(e.currentTarget); - $label = $("#" + ($btn.data('domId'))); - action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; - // Make sure tooltip will hide - $tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby'))); - $tooltip.tooltip('destroy'); - return _this.toggleLabelPriority($label, action); - }; - - LabelManager.prototype.toggleLabelPriority = function($label, action, persistState) { - var $from, $target, _this, url, xhr; - if (persistState == null) { - persistState = true; - } - _this = this; - url = $label.find('.js-toggle-priority').data('url'); - $target = this.prioritizedLabels; - $from = this.otherLabels; - // Optimistic update - if (action === 'remove') { - $target = this.otherLabels; - $from = this.prioritizedLabels; - } - if ($from.find('li').length === 1) { - $from.find('.empty-message').removeClass('hidden'); - } - if (!$target.find('li').length) { - $target.find('.empty-message').addClass('hidden'); - } - $label.detach().appendTo($target); - // Return if we are not persisting state - if (!persistState) { - return; - } - if (action === 'remove') { - xhr = $.ajax({ - url: url, - type: 'DELETE' - }); - // Restore empty message - if (!$from.find('li').length) { - $from.find('.empty-message').removeClass('hidden'); - } - } else { - xhr = this.savePrioritySort($label, action); - } - return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); - }; - - LabelManager.prototype.onPrioritySortUpdate = function() { - var xhr; - xhr = this.savePrioritySort(); - return xhr.fail(function() { - return new Flash(this.errorMessage, 'alert'); - }); - }; - - LabelManager.prototype.savePrioritySort = function() { - return $.post({ - url: this.prioritizedLabels.data('url'), - data: { - label_ids: this.getSortedLabelsIds() - } - }); - }; - - LabelManager.prototype.rollbackLabelPosition = function($label, originalAction) { - var action; - action = originalAction === 'remove' ? 'add' : 'remove'; - this.toggleLabelPriority($label, action, false); - return new Flash(this.errorMessage, 'alert'); - }; - - LabelManager.prototype.getSortedLabelsIds = function() { - var sortedIds; - sortedIds = []; - this.prioritizedLabels.find('li').each(function() { - return sortedIds.push($(this).data('id')); - }); - return sortedIds; - }; - - return LabelManager; - - })(); - -}).call(this); -- cgit v1.2.1 From ed8f27e636b4ba5cb9f02c97de4d3a34a732b79b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:37:26 +0200 Subject: Fix profile test failure. --- app/assets/javascripts/profile/profile.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index c16eb93a2dd..b2307be73ad 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,7 +1,7 @@ ((global) => { class Profile { - constructor({ form }) { + constructor({ form } = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); this.form = form || $('.edit-user'); this.bindEvents(); -- cgit v1.2.1 From e0d66819e99c678f1400caec1dd137d2c63790c0 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:40:31 +0200 Subject: Fix todos specs. --- app/assets/javascripts/todos.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index 7e606196435..945000c2f43 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,7 +1,7 @@ ((global) => { class Todos { - constructor({ el }) { + constructor({ el } = {}) { this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); this.el = el || $('.js-todos-options'); -- cgit v1.2.1 From f80b8bd26c785aa07e0f0b0523d44af2808eb110 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 15:01:20 +0200 Subject: Remove CHANGELOG duplicate. --- CHANGELOG | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bcedb3fcec2..eefab58eccc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,15 +77,12 @@ v 8.12.2 - Only update issuable labels if they have been changed - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path + - Refactor remnants of CoffeeScript destructured opts and super !6261 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix issue with search filter labels not displaying -v 8.12.0 -v 8.12.1 (unreleased) - - Refactor remnants of CoffeeScript destructured opts and super !6261 - v 8.12.0 (released) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable -- cgit v1.2.1 From 5ef3e8669db802e2355fd8dea1bef68ea7a48726 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 16:58:36 +0200 Subject: Make fixes to get tests passing. --- app/assets/javascripts/LabelManager.js.es6 | 7 ++++--- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 2 +- app/assets/javascripts/blob/blob_license_selectors.js.es6 | 2 +- app/assets/javascripts/blob_edit/edit_blob.js | 2 +- app/assets/javascripts/todos.js.es6 | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 index b655dfa7113..a66767825e3 100644 --- a/app/assets/javascripts/LabelManager.js.es6 +++ b/app/assets/javascripts/LabelManager.js.es6 @@ -91,10 +91,11 @@ } getSortedLabelsIds() { - // TODO: Check that this works how you expect - return this.prioritizedLabels.find('li').map(function(item) { - return $(item).data('id'); + const sortedIds = []; + this.prioritizedLabels.find('li').each(function() { + sortedIds.push($(this).data('id')); }); + return sortedIds; } } diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index dee06a7196f..a0fc9519aaa 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -10,7 +10,7 @@ global.BlobCiYamlSelector = BlobCiYamlSelector; class BlobCiYamlSelectors { - constructor({ editor, $dropdowns }) { + constructor({ editor, $dropdowns } = {}) { this.editor = editor; this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector'); this.initSelectors(); diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 index ebc1618816f..153ed457559 100644 --- a/app/assets/javascripts/blob/blob_license_selectors.js.es6 +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -3,7 +3,7 @@ constructor({ $dropdowns, editor }) { this.$dropdowns = $('.js-license-selector'); this.editor = editor; - this.$dropdowns.each((dropdown) => { + this.$dropdowns.each((i, dropdown) => { const $dropdown = $(dropdown); return new BlobLicenseSelector({ editor, diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index e248b557c96..8db4f6a3b28 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -26,7 +26,7 @@ new gl.BlobLicenseSelectors({ editor: this.editor }); - new gl.BlobGitignoreSelectors({ + new BlobGitignoreSelectors({ editor: this.editor }); new gl.BlobCiYamlSelectors({ diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index 945000c2f43..055228c5df8 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -5,7 +5,7 @@ this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); this.el = el || $('.js-todos-options'); - this.perPage = el.data('perPage'); + this.perPage = this.el.data('perPage'); this.clearListeners(); this.initBtnListeners(); this.initFilters(); -- cgit v1.2.1 From 14d681ebc8cb777cc03b0099d603c3e3dd2e7579 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 29 Sep 2016 19:58:09 +0200 Subject: Refactor TemplateSelector and fix for tests. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 9 +- .../javascripts/blob/blob_gitignore_selector.js | 2 +- .../javascripts/blob/blob_license_selector.js | 2 +- .../javascripts/blob/template_selector.js.es6 | 99 ++++++++++++++++++++++ .../templates/issuable_template_selector.js.es6 | 2 +- 5 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/blob/template_selector.js.es6 diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index a0fc9519aaa..d6ea4f84f57 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -1,11 +1,15 @@ /*= require blob/template_selector */ ((global) => { - class BlobCiYamlSelector extends TemplateSelector { + class BlobCiYamlSelector extends gl.TemplateSelector { requestFile(query) { return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); } - }; + + requestFileSuccess(file) { + return super.requestFileSuccess(file); + } + } global.BlobCiYamlSelector = BlobCiYamlSelector; @@ -17,6 +21,7 @@ } initSelectors() { + const editor = this.editor; this.$dropdowns.each((i, dropdown) => { const $dropdown = $(dropdown); return new BlobCiYamlSelector({ diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js index 54a09e919f8..cd746b05cf6 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selector.js +++ b/app/assets/javascripts/blob/blob_gitignore_selector.js @@ -18,6 +18,6 @@ return BlobGitignoreSelector; - })(TemplateSelector); + })(gl.TemplateSelector); }).call(this); diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js index 9a8ef08f4e5..2701df3e6de 100644 --- a/app/assets/javascripts/blob/blob_license_selector.js +++ b/app/assets/javascripts/blob/blob_license_selector.js @@ -23,6 +23,6 @@ return BlobLicenseSelector; - })(TemplateSelector); + })(gl.TemplateSelector); }).call(this); diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 new file mode 100644 index 00000000000..a86a2a7c2fc --- /dev/null +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -0,0 +1,99 @@ +((global) => { + class TemplateSelector { + constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) { + this.onClick = this.onClick.bind(this); + this.dropdown = dropdown; + this.data = data; + this.pattern = pattern; + this.wrapper = wrapper; + this.editor = editor; + this.fileEndpoint = fileEndpoint; + this.$input = $input || $('#file_name'); + this.dropdownIcon = $('.fa-chevron-down', this.dropdown); + this.buildDropdown(); + this.bindEvents(); + this.onFilenameUpdate(); + + this.autosizeUpdateEvent = document.createEvent('Event'); + this.autosizeUpdateEvent.initEvent('autosize:update', true, false); + } + + buildDropdown() { + return this.dropdown.glDropdown({ + data: this.data, + filterable: true, + selectable: true, + toggleLabel: this.toggleLabel, + search: { + fields: ['name'] + }, + clicked: this.onClick, + text: function(item) { + return item.name; + } + }); + } + + bindEvents() { + return this.$input.on('keyup blur', (function(_this) { + return function(e) { + return _this.onFilenameUpdate(); + }; + })(this)); + } + + toggleLabel(item) { + return item.name; + } + + onFilenameUpdate() { + var filenameMatches; + if (!this.$input.length) { + return; + } + filenameMatches = this.pattern.test(this.$input.val().trim()); + if (!filenameMatches) { + this.wrapper.addClass('hidden'); + return; + } + return this.wrapper.removeClass('hidden'); + } + + onClick(item, el, e) { + e.preventDefault(); + return this.requestFile(item); + } + + requestFile(item) { + // This `requestFile` method is an abstract method that should + // be added by all subclasses. + } + + // To be implemented on the extending class + // e.g. + // Api.gitignoreText item.name, @requestFileSuccess.bind(@) + requestFileSuccess(file, skipFocus) { + this.editor.setValue(file.content, 1); + if (!skipFocus) this.editor.focus(); + + if (this.editor instanceof jQuery) { + this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); + } + } + + startLoadingSpinner() { + this.dropdownIcon + .addClass('fa-spinner fa-spin') + .removeClass('fa-chevron-down'); + } + + stopLoadingSpinner() { + this.dropdownIcon + .addClass('fa-chevron-down') + .removeClass('fa-spinner fa-spin'); + } + } + + global.TemplateSelector = TemplateSelector; + })(window.gl || ( window.gl = {})); + diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index 017008c8438..f2a55f9caa0 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -1,7 +1,7 @@ /*= require ../blob/template_selector */ ((global) => { - class IssuableTemplateSelector extends TemplateSelector { + class IssuableTemplateSelector extends gl.TemplateSelector { constructor(...args) { super(...args); this.projectPath = this.dropdown.data('project-path'); -- cgit v1.2.1 From 3ca521d63366beede3df2d02aa4291344d1db43f Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:24:17 +0200 Subject: Remove 'released' from CHANGELOG. --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index eefab58eccc..5badb2f66ee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -83,7 +83,7 @@ v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix issue with search filter labels not displaying -v 8.12.0 (released) +v 8.12.0 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region -- cgit v1.2.1 From 6880fe840090c24ce60b6d388714b5c6087e977e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:35:59 +0200 Subject: Fix leftover or inadvertantly removed comments. --- app/assets/javascripts/LabelManager.js.es6 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 index a66767825e3..bc68e53504f 100644 --- a/app/assets/javascripts/LabelManager.js.es6 +++ b/app/assets/javascripts/LabelManager.js.es6 @@ -16,7 +16,6 @@ } bindEvents() { - // TODO: Check if this is being bound correctly return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); } @@ -51,6 +50,7 @@ $target.find('.empty-message').addClass('hidden'); } $label.detach().appendTo($target); + // Return if we are not persisting state if (!persistState) { return; } @@ -59,6 +59,7 @@ url, type: 'DELETE' }); + // Restore empty message if (!$from.find('li').length) { $from.find('.empty-message').removeClass('hidden'); } -- cgit v1.2.1 From 6c37adcab93c2162ded987f72276d2eaa0e632c2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 3 Oct 2016 14:22:16 +0200 Subject: Remove debug flag. --- app/assets/javascripts/dispatcher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ecb5812c015..93183d0720a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -164,7 +164,6 @@ new Labels(); break; case 'projects:labels:index': - debugger; if ($('.prioritized-labels').length) { new gl.LabelManager(); } -- cgit v1.2.1 From 16097bbedab5133dfc984ece1da64ca8cd50de03 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 4 Oct 2016 18:21:45 +0200 Subject: Delete leftover usertabs and template_selector es5 files. --- app/assets/javascripts/blob/template_selector.js | 108 ------------- app/assets/javascripts/user_tabs.js | 188 ----------------------- 2 files changed, 296 deletions(-) delete mode 100644 app/assets/javascripts/blob/template_selector.js delete mode 100644 app/assets/javascripts/user_tabs.js diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js deleted file mode 100644 index 6d41442cdfc..00000000000 --- a/app/assets/javascripts/blob/template_selector.js +++ /dev/null @@ -1,108 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.TemplateSelector = (function() { - function TemplateSelector(opts) { - var ref; - if (opts == null) { - opts = {}; - } - this.onClick = bind(this.onClick, this); - this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name'); - this.dropdownIcon = $('.fa-chevron-down', this.dropdown); - this.buildDropdown(); - this.bindEvents(); - this.onFilenameUpdate(); - - this.autosizeUpdateEvent = document.createEvent('Event'); - this.autosizeUpdateEvent.initEvent('autosize:update', true, false); - } - - TemplateSelector.prototype.buildDropdown = function() { - return this.dropdown.glDropdown({ - data: this.data, - filterable: true, - selectable: true, - toggleLabel: this.toggleLabel, - search: { - fields: ['name'] - }, - clicked: this.onClick, - text: function(item) { - return item.name; - } - }); - }; - - TemplateSelector.prototype.bindEvents = function() { - return this.$input.on('keyup blur', (function(_this) { - return function(e) { - return _this.onFilenameUpdate(); - }; - })(this)); - }; - - TemplateSelector.prototype.toggleLabel = function(item) { - return item.name; - }; - - TemplateSelector.prototype.onFilenameUpdate = function() { - var filenameMatches; - if (!this.$input.length) { - return; - } - filenameMatches = this.pattern.test(this.$input.val().trim()); - if (!filenameMatches) { - this.wrapper.addClass('hidden'); - return; - } - return this.wrapper.removeClass('hidden'); - }; - - TemplateSelector.prototype.onClick = function(item, el, e) { - e.preventDefault(); - return this.requestFile(item); - }; - - TemplateSelector.prototype.requestFile = function(item) { - // This `requestFile` method is an abstract method that should - // be added by all subclasses. - }; - - // To be implemented on the extending class - // e.g. - // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - TemplateSelector.prototype.requestFileSuccess = function(file, opts) { - var oldValue = this.editor.getValue(); - var newValue = file.content; - if (opts == null) { - opts = {}; - } - if (opts.append && oldValue.length && oldValue !== newValue) { - newValue = oldValue + '\n\n' + newValue; - } - this.editor.setValue(newValue, 1); - if (!opts.skipFocus) this.editor.focus(); - - if (this.editor instanceof jQuery) { - this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); - } - }; - - TemplateSelector.prototype.startLoadingSpinner = function() { - this.dropdownIcon - .addClass('fa-spinner fa-spin') - .removeClass('fa-chevron-down'); - }; - - TemplateSelector.prototype.stopLoadingSpinner = function() { - this.dropdownIcon - .addClass('fa-chevron-down') - .removeClass('fa-spinner fa-spin'); - }; - - return TemplateSelector; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js deleted file mode 100644 index 8a657780eb6..00000000000 --- a/app/assets/javascripts/user_tabs.js +++ /dev/null @@ -1,188 +0,0 @@ -// UserTabs -// -// Handles persisting and restoring the current tab selection and lazily-loading -// content on the Users#show page. -// -// ### Example Markup -// -// -// -//
-//
-// Activity Content -//
-//
-// Groups Content -//
-//
-// Contributed projects content -//
-//
-// Projects content -//
-//
-// Snippets content -//
-//
-// -//
-//
-// Loading Animation -//
-//
-// -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.UserTabs = (function() { - function UserTabs(opts) { - this.tabShown = bind(this.tabShown, this); - var i, item, len, ref, ref1, ref2, ref3; - this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document); - // Make jQuery object if selector is provided - if (typeof this.parentEl === 'string') { - this.parentEl = $(this.parentEl); - } - // Store the `location` object, allowing for easier stubbing in tests - this._location = location; - // Set tab states - this.loaded = {}; - ref3 = this.parentEl.find('.nav-links a'); - for (i = 0, len = ref3.length; i < len; i++) { - item = ref3[i]; - this.loaded[$(item).attr('data-action')] = false; - } - // Actions - this.actions = Object.keys(this.loaded); - this.bindEvents(); - // Set active tab - if (this.action === 'show') { - this.action = this.defaultAction; - } - this.activateTab(this.action); - } - - UserTabs.prototype.bindEvents = function() { - // Toggle event listeners - return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown); - }; - - UserTabs.prototype.tabShown = function(event) { - var $target, action, source; - $target = $(event.target); - action = $target.data('action'); - source = $target.attr('href'); - this.setTab(source, action); - return this.setCurrentAction(action); - }; - - UserTabs.prototype.activateTab = function(action) { - return this.parentEl.find(".nav-links .js-" + action + "-tab a").tab('show'); - }; - - UserTabs.prototype.setTab = function(source, action) { - if (this.loaded[action] === true) { - return; - } - if (action === 'activity') { - this.loadActivities(source); - } - if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') { - return this.loadTab(source, action); - } - }; - - UserTabs.prototype.loadTab = function(source, action) { - return $.ajax({ - beforeSend: (function(_this) { - return function() { - return _this.toggleLoading(true); - }; - })(this), - complete: (function(_this) { - return function() { - return _this.toggleLoading(false); - }; - })(this), - dataType: 'json', - type: 'GET', - url: source + ".json", - success: (function(_this) { - return function(data) { - var tabSelector; - tabSelector = 'div#' + action; - _this.parentEl.find(tabSelector).html(data.html); - _this.loaded[action] = true; - // Fix tooltips - return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); - }; - })(this) - }); - }; - - UserTabs.prototype.loadActivities = function(source) { - var $calendarWrap; - if (this.loaded['activity'] === true) { - return; - } - $calendarWrap = this.parentEl.find('.user-calendar'); - $calendarWrap.load($calendarWrap.data('href')); - new Activities(); - return this.loaded['activity'] = true; - }; - - UserTabs.prototype.toggleLoading = function(status) { - return this.parentEl.find('.loading-status .loading').toggle(status); - }; - - UserTabs.prototype.setCurrentAction = function(action) { - var new_state, regExp; - // Remove possible actions from URL - regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$'); - new_state = this._location.pathname; - // remove trailing slashes - new_state = new_state.replace(/\/+$/, ""); - new_state = new_state.replace(regExp, ''); - // Append the new action if we're on a tab other than 'activity' - if (action !== this.defaultAction) { - new_state += "/" + action; - } - // Ensure parameters and hash come along for the ride - new_state += this._location.search + this._location.hash; - history.replaceState({ - turbolinks: true, - url: new_state - }, document.title, new_state); - return new_state; - }; - - return UserTabs; - - })(); - -}).call(this); -- cgit v1.2.1 From b6d52a6f10d5f3d65459a953b53ba1c380fd39af Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 4 Oct 2016 18:33:19 +0200 Subject: Simplify TemplateSelector keyup/blur handler. --- app/assets/javascripts/blob/template_selector.js.es6 | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index a86a2a7c2fc..ba60b3b2b98 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -35,11 +35,7 @@ } bindEvents() { - return this.$input.on('keyup blur', (function(_this) { - return function(e) { - return _this.onFilenameUpdate(); - }; - })(this)); + return this.$input.on('keyup blur', (e) => this.onFilenameUpdate()); } toggleLabel(item) { -- cgit v1.2.1 From 2d41c3f5a04ae9879749bb8f0d903c28212f4298 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 12:48:18 +0200 Subject: Restore inexplicably removed code from requestFileSuccess. (also clean up a few global refs) --- app/assets/javascripts/blob/template_selector.js.es6 | 11 +++++++++-- app/assets/javascripts/dispatcher.js | 4 ++-- .../javascripts/templates/issuable_template_selector.js.es6 | 2 +- .../javascripts/templates/issuable_template_selectors.js.es6 | 12 ++++++------ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index ba60b3b2b98..4e309e480b0 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -68,8 +68,15 @@ // To be implemented on the extending class // e.g. // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - requestFileSuccess(file, skipFocus) { - this.editor.setValue(file.content, 1); + requestFileSuccess(file, { skipFocus, append } = {}) { + const oldValue = this.editor.getValue(); + let newValue = file.content; + + if (append && oldValue.length && oldValue !== newValue) { + newValue = oldValue + '\n\n' + newValue; + } + + this.editor.setValue(newValue, 1); if (!skipFocus) this.editor.focus(); if (this.editor instanceof jQuery) { diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 93183d0720a..ae910dbdcf0 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -59,7 +59,7 @@ shortcut_handler = new ShortcutsNavigation(); new GLForm($('.issue-form')); new IssuableForm($('.issue-form')); - new IssuableTemplateSelectors(); + new gl.IssuableTemplateSelectors(); break; case 'projects:merge_requests:new': case 'projects:merge_requests:edit': @@ -67,7 +67,7 @@ shortcut_handler = new ShortcutsNavigation(); new GLForm($('.merge-request-form')); new IssuableForm($('.merge-request-form')); - new IssuableTemplateSelectors(); + new gl.IssuableTemplateSelectors(); break; case 'projects:tags:new': new ZenMode(); diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index f2a55f9caa0..2ecf3b18975 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -50,4 +50,4 @@ } global.IssuableTemplateSelector = IssuableTemplateSelector; -})(window); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 index bd8cdde033e..4e8247b89e1 100644 --- a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 @@ -1,12 +1,12 @@ ((global) => { class IssuableTemplateSelectors { - constructor(opts = {}) { - this.$dropdowns = opts.$dropdowns || $('.js-issuable-selector'); - this.editor = opts.editor || this.initEditor(); + constructor({ $dropdowns, editor } = {}) { + this.$dropdowns = $dropdowns || $('.js-issuable-selector'); + this.editor = editor || this.initEditor(); this.$dropdowns.each((i, dropdown) => { - let $dropdown = $(dropdown); - new IssuableTemplateSelector({ + const $dropdown = $(dropdown); + new gl.IssuableTemplateSelector({ pattern: /(\.md)/, data: $dropdown.data('data'), wrapper: $dropdown.closest('.js-issuable-selector-wrap'), @@ -26,4 +26,4 @@ } global.IssuableTemplateSelectors = IssuableTemplateSelectors; -})(window); +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From f223a41660f1a629614d3d559468b1a5488b96d8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Oct 2016 15:21:27 +0300 Subject: Split routes on multiple files Signed-off-by: Dmitriy Zaporozhets --- config/routes.rb | 819 +-------------------------------------------- config/routes/admin.rb | 102 ++++++ config/routes/dashboard.rb | 27 ++ config/routes/explore.rb | 16 + config/routes/group.rb | 18 + config/routes/import.rb | 42 +++ config/routes/profile.rb | 43 +++ config/routes/project.rb | 464 +++++++++++++++++++++++++ config/routes/uploads.rb | 21 ++ config/routes/user.rb | 26 ++ 10 files changed, 775 insertions(+), 803 deletions(-) create mode 100644 config/routes/admin.rb create mode 100644 config/routes/dashboard.rb create mode 100644 config/routes/explore.rb create mode 100644 config/routes/group.rb create mode 100644 config/routes/import.rb create mode 100644 config/routes/profile.rb create mode 100644 config/routes/project.rb create mode 100644 config/routes/uploads.rb create mode 100644 config/routes/user.rb diff --git a/config/routes.rb b/config/routes.rb index ba3864b92be..c364a963fb7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,12 @@ require 'sidekiq/web' require 'sidekiq/cron/web' require 'api/api' +class ActionDispatch::Routing::Mapper + def draw(routes_name) + instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb"))) + end +end + Rails.application.routes.draw do if Gitlab::Sherlock.enabled? namespace :sherlock do @@ -94,14 +100,10 @@ Rails.application.routes.draw do get 'help/ui' => 'help#ui' get 'help/*path' => 'help#show', as: :help_page - # # Koding route - # get 'koding' => 'koding#index' - # # Global snippets - # resources :snippets, concerns: :awardable do member do get 'raw' @@ -111,9 +113,7 @@ Rails.application.routes.draw do get '/s/:username', to: redirect('/u/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+(? 'explore/projects#index' - get 'public/projects' => 'explore/projects#index' - - # - # Admin Area - # - namespace :admin do - resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do - resources :keys, only: [:show, :destroy] - resources :identities, except: [:show] - - member do - get :projects - get :keys - get :groups - put :block - put :unblock - put :unlock - put :confirm - post :impersonate - patch :disable_two_factor - delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' - end - end - - resource :impersonation, only: :destroy - - resources :abuse_reports, only: [:index, :destroy] - resources :spam_logs, only: [:index, :destroy] do - member do - post :mark_as_ham - end - end - - resources :applications - - resources :groups, constraints: { id: /[^\/]+/ } do - member do - put :members_update - end - end - - resources :deploy_keys, only: [:index, :new, :create, :destroy] - - resources :hooks, only: [:index, :create, :destroy] do - get :test - end - - resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do - post :preview, on: :collection - end - - resource :logs, only: [:show] - resource :health_check, controller: 'health_check', only: [:show] - resource :background_jobs, controller: 'background_jobs', only: [:show] - resource :system_info, controller: 'system_info', only: [:show] - resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } - - resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - root to: 'projects#index', as: :projects - - resources(:projects, - path: '/', - constraints: { id: /[a-zA-Z.0-9_\-]+/ }, - only: [:index, :show]) do - root to: 'projects#show' - - member do - put :transfer - post :repository_check - end - - resources :runner_projects, only: [:create, :destroy] - end - end - - resource :appearances, only: [:show, :create, :update], path: 'appearance' do - member do - get :preview - delete :logo - delete :header_logos - end - end - - resource :application_settings, only: [:show, :update] do - resources :services, only: [:index, :edit, :update] - put :reset_runners_token - put :reset_health_check_token - put :clear_repository_check_states - end - - resources :labels - - resources :runners, only: [:index, :show, :update, :destroy] do - member do - get :resume - get :pause - end - end - - resources :builds, only: :index do - collection do - post :cancel_all - end - end - - root to: 'dashboard#index' - end - - # - # Profile Area - # - resource :profile, only: [:show, :update] do - member do - get :audit_log - get :applications, to: 'oauth/applications#index' - - put :reset_private_token - put :update_username - end - - scope module: :profiles do - resource :account, only: [:show] do - member do - delete :unlink - end - end - resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create, :edit, :update] do - member do - put :reset - end - end - resource :preferences, only: [:show, :update] - resources :keys, only: [:index, :show, :new, :create, :destroy] - resources :emails, only: [:index, :create, :destroy] - resource :avatar, only: [:destroy] - - resources :personal_access_tokens, only: [:index, :create] do - member do - put :revoke - end - end - - resource :two_factor_auth, only: [:show, :create, :destroy] do - member do - post :create_u2f - post :codes - patch :skip - end - end - - resources :u2f_registrations, only: [:destroy] - end - end - - scope(path: 'u/:username', - as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error - get '/users/almost_there' => 'confirmations#almost_there' - end + get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } root to: "root#index" - - # - # Project Area - # - resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template - - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - 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 - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - - scope do - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) - end - - scope do - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) - end - - scope do - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, - as: :commits - ) - end - - resource :avatar, only: [:show, :destroy] - resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do - member do - get :branches - get :builds - get :pipelines - post :cancel_builds - post :retry_builds - post :revert - post :cherry_pick - get :diff_for_path - end - end - - resources :compare, only: [:index, :create] do - collection do - get :diff_for_path - end - end - - get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - - # Don't use format parameter as file extension (old 3.0.x behavior) - # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments - scope format: false do - resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do - member do - get :commits - get :ci - get :languages - end - end - end - - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get 'raw' - end - end - - WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID - - scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' - - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' - end - - resource :repository, only: [:create] do - member do - get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do - member do - get :test - end - end - - resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do - member do - put :enable - put :disable - end - end - - resources :forks, only: [:index, :new, :create] - resource :import, only: [:new, :create, :show] - - resources :refs, only: [] do - collection do - get 'switch' - end - - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } - end - end - - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get :commits - get :diffs - get :conflicts - get :builds - get :pipelines - get :merge_check - post :merge - post :cancel_merge_when_build_succeeds - get :ci_status - post :toggle_subscription - post :remove_wip - get :diff_for_path - post :resolve_conflicts - end - - collection do - get :branch_from - get :branch_to - get :update_branches - get :diff_for_path - post :bulk_update - end - - resources :discussions, only: [], constraints: { id: /\h{40}/ } do - member do - post :resolve - delete :resolve, action: :unresolve - end - end - end - - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release, only: [:edit, :update] - end - - resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :variables, only: [:index, :show, :update, :create, :destroy] - resources :triggers, only: [:index, :create, :destroy] - - resources :pipelines, only: [:index, :new, :create, :show] do - collection do - resource :pipelines_settings, path: 'settings', only: [:show, :update] - end - - member do - post :cancel - post :retry - end - end - - resources :environments - - resource :cycle_analytics, only: [:show] - - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do - collection do - post :cancel_all - - resources :artifacts, only: [] do - collection do - get :latest_succeeded, - path: '*ref_name_and_path', - format: false - end - end - end - - member do - get :status - post :cancel - post :retry - post :play - post :erase - get :trace - get :raw - end - - resource :artifacts, only: [] do - get :download - get :browse, path: 'browse(/*path)', format: false - get :file, path: 'file/*path', format: false - post :keep - end - end - - resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do - member do - get :test - end - end - - resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } - - resources :milestones, constraints: { id: /\d+/ } do - member do - put :sort_issues - put :sort_merge_requests - end - end - - resources :labels, except: [:show], constraints: { id: /\d+/ } do - collection do - post :generate - post :set_priorities - end - - member do - post :toggle_subscription - delete :remove_priority - end - end - - resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do - member do - post :toggle_subscription - post :mark_as_spam - get :referenced_merge_requests - get :related_branches - get :can_create_branch - end - collection do - post :bulk_update - end - end - - resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do - collection do - delete :leave - - # Used for import team - # from another project - get :import - post :apply_import - end - - member do - post :resend_invite - end - end - - resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } - - resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do - member do - delete :delete_attachment - post :resolve - delete :resolve, action: :unresolve - end - end - - resource :board, only: [:show] do - scope module: :boards do - resources :issues, only: [:update] - - resources :lists, only: [:index, :create, :update, :destroy] do - collection do - post :generate - end - - resources :issues, only: [:index] - end - end - end - - resources :todos, only: [:create] - - resources :uploads, only: [:create] do - collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } - end - end - - resources :runners, only: [:index, :edit, :update, :destroy, :show] do - member do - get :resume - get :pause - end - - collection do - post :toggle_shared_runners - end - end - - resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [:index] do - collection do - scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do - constraints format: /svg/ do - get :build - get :coverage - end - end - end - end - end - end - end - - # Get all keys of user - get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } end diff --git a/config/routes/admin.rb b/config/routes/admin.rb new file mode 100644 index 00000000000..5ae985da561 --- /dev/null +++ b/config/routes/admin.rb @@ -0,0 +1,102 @@ +namespace :admin do + resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do + resources :keys, only: [:show, :destroy] + resources :identities, except: [:show] + + member do + get :projects + get :keys + get :groups + put :block + put :unblock + put :unlock + put :confirm + post :impersonate + patch :disable_two_factor + delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' + end + end + + resource :impersonation, only: :destroy + + resources :abuse_reports, only: [:index, :destroy] + resources :spam_logs, only: [:index, :destroy] do + member do + post :mark_as_ham + end + end + + resources :applications + + resources :groups, constraints: { id: /[^\/]+/ } do + member do + put :members_update + end + end + + resources :deploy_keys, only: [:index, :new, :create, :destroy] + + resources :hooks, only: [:index, :create, :destroy] do + get :test + end + + resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do + post :preview, on: :collection + end + + resource :logs, only: [:show] + resource :health_check, controller: 'health_check', only: [:show] + resource :background_jobs, controller: 'background_jobs', only: [:show] + resource :system_info, controller: 'system_info', only: [:show] + resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } + + resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + root to: 'projects#index', as: :projects + + resources(:projects, + path: '/', + constraints: { id: /[a-zA-Z.0-9_\-]+/ }, + only: [:index, :show]) do + root to: 'projects#show' + + member do + put :transfer + post :repository_check + end + + resources :runner_projects, only: [:create, :destroy] + end + end + + resource :appearances, only: [:show, :create, :update], path: 'appearance' do + member do + get :preview + delete :logo + delete :header_logos + end + end + + resource :application_settings, only: [:show, :update] do + resources :services, only: [:index, :edit, :update] + put :reset_runners_token + put :reset_health_check_token + put :clear_repository_check_states + end + + resources :labels + + resources :runners, only: [:index, :show, :update, :destroy] do + member do + get :resume + get :pause + end + end + + resources :builds, only: :index do + collection do + post :cancel_all + end + end + + root to: 'dashboard#index' +end diff --git a/config/routes/dashboard.rb b/config/routes/dashboard.rb new file mode 100644 index 00000000000..fb20c63bc63 --- /dev/null +++ b/config/routes/dashboard.rb @@ -0,0 +1,27 @@ +resource :dashboard, controller: 'dashboard', only: [] do + get :issues + get :merge_requests + get :activity + + scope module: :dashboard do + resources :milestones, only: [:index, :show] + resources :labels, only: [:index] + + resources :groups, only: [:index] + resources :snippets, only: [:index] + + resources :todos, only: [:index, :destroy] do + collection do + delete :destroy_all + end + end + + resources :projects, only: [:index] do + collection do + get :starred + end + end + end + + root to: "dashboard/projects#index" +end diff --git a/config/routes/explore.rb b/config/routes/explore.rb new file mode 100644 index 00000000000..42ec5e8abec --- /dev/null +++ b/config/routes/explore.rb @@ -0,0 +1,16 @@ +namespace :explore do + resources :projects, only: [:index] do + collection do + get :trending + get :starred + end + end + + resources :groups, only: [:index] + resources :snippets, only: [:index] + root to: 'projects#trending' +end + +# Compatibility with old routing +get 'public' => 'explore/projects#index' +get 'public/projects' => 'explore/projects#index' diff --git a/config/routes/group.rb b/config/routes/group.rb new file mode 100644 index 00000000000..5b3e25d5e3d --- /dev/null +++ b/config/routes/group.rb @@ -0,0 +1,18 @@ +resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template + + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + 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 + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + + scope do + get( + '/find_file/*id', + to: 'find_file#show', + constraints: { id: /.+/, format: /html/ }, + as: :find_file + ) + end + + scope do + get( + '/files/*id', + to: 'find_file#list', + constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, + as: :files + ) + end + + scope do + post( + '/create_dir/*id', + to: 'tree#create_dir', + constraints: { id: /.+/ }, + as: 'create_dir' + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/commits/*id', + to: 'commits#show', + constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + as: :commits + ) + end + + resource :avatar, only: [:show, :destroy] + resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do + member do + get :branches + get :builds + get :pipelines + post :cancel_builds + post :retry_builds + post :revert + post :cherry_pick + get :diff_for_path + end + end + + resources :compare, only: [:index, :create] do + collection do + get :diff_for_path + end + end + + get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } + + # Don't use format parameter as file extension (old 3.0.x behavior) + # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments + scope format: false do + resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } + + resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do + member do + get :commits + get :ci + get :languages + end + end + end + + resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get 'raw' + end + end + + WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID + + scope do + # Order matters to give priority to these matches + get '/wikis/git_access', to: 'wikis#git_access' + get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' + post '/wikis', to: 'wikis#create' + + get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID + get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID + + get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID + delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID + put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID + post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' + end + + resource :repository, only: [:create] do + member do + get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } + end + end + + resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do + member do + get :test + end + end + + resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do + member do + put :enable + put :disable + end + end + + resources :forks, only: [:index, :new, :create] + resource :import, only: [:new, :create, :show] + + resources :refs, only: [] do + collection do + get 'switch' + end + + member do + # tree viewer logs + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + # Directories with leading dots erroneously get rejected if git + # ref regex used in constraints. Regex verification now done in controller. + get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { + id: /.*/, + path: /.*/ + } + end + end + + resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get :commits + get :diffs + get :conflicts + get :builds + get :pipelines + get :merge_check + post :merge + post :cancel_merge_when_build_succeeds + get :ci_status + post :toggle_subscription + post :remove_wip + get :diff_for_path + post :resolve_conflicts + end + + collection do + get :branch_from + get :branch_to + get :update_branches + get :diff_for_path + post :bulk_update + end + + resources :discussions, only: [], constraints: { id: /\h{40}/ } do + member do + post :resolve + delete :resolve, action: :unresolve + end + end + end + + resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do + resource :release, only: [:edit, :update] + end + + resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :variables, only: [:index, :show, :update, :create, :destroy] + resources :triggers, only: [:index, :create, :destroy] + + resources :pipelines, only: [:index, :new, :create, :show] do + collection do + resource :pipelines_settings, path: 'settings', only: [:show, :update] + end + + member do + post :cancel + post :retry + end + end + + resources :environments + + resource :cycle_analytics, only: [:show] + + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do + collection do + post :cancel_all + + resources :artifacts, only: [] do + collection do + get :latest_succeeded, + path: '*ref_name_and_path', + format: false + end + end + end + + member do + get :status + post :cancel + post :retry + post :play + post :erase + get :trace + get :raw + end + + resource :artifacts, only: [] do + get :download + get :browse, path: 'browse(/*path)', format: false + get :file, path: 'file/*path', format: false + post :keep + end + end + + resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do + member do + get :test + end + end + + resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } + + resources :milestones, constraints: { id: /\d+/ } do + member do + put :sort_issues + put :sort_merge_requests + end + end + + resources :labels, except: [:show], constraints: { id: /\d+/ } do + collection do + post :generate + post :set_priorities + end + + member do + post :toggle_subscription + delete :remove_priority + end + end + + resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do + member do + post :toggle_subscription + post :mark_as_spam + get :referenced_merge_requests + get :related_branches + get :can_create_branch + end + collection do + post :bulk_update + end + end + + resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do + collection do + delete :leave + + # Used for import team + # from another project + get :import + post :apply_import + end + + member do + post :resend_invite + end + end + + resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + + resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do + member do + delete :delete_attachment + post :resolve + delete :resolve, action: :unresolve + end + end + + resource :board, only: [:show] do + scope module: :boards do + resources :issues, only: [:update] + + resources :lists, only: [:index, :create, :update, :destroy] do + collection do + post :generate + end + + resources :issues, only: [:index] + end + end + end + + resources :todos, only: [:create] + + resources :uploads, only: [:create] do + collection do + get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } + end + end + + resources :runners, only: [:index, :edit, :update, :destroy, :show] do + member do + get :resume + get :pause + end + + collection do + post :toggle_shared_runners + end + end + + resources :runner_projects, only: [:create, :destroy] + resources :badges, only: [:index] do + collection do + scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do + constraints format: /svg/ do + get :build + get :coverage + end + end + end + end + end + end +end diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb new file mode 100644 index 00000000000..2b22148a134 --- /dev/null +++ b/config/routes/uploads.rb @@ -0,0 +1,21 @@ +scope path: :uploads do + # Note attachments and User/Group/Project avatars + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + + # Appearance + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } + + # Project markdown uploads + get ":namespace_id/:project_id/:secret/:filename", + to: "projects/uploads#show", + constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } +end + +# Redirect old note attachments path to new uploads path. +get "files/note/:id/:filename", + to: redirect("uploads/note/attachment/%{id}/%{filename}"), + constraints: { filename: /[^\/]+/ } diff --git a/config/routes/user.rb b/config/routes/user.rb new file mode 100644 index 00000000000..b6a6a07e3c4 --- /dev/null +++ b/config/routes/user.rb @@ -0,0 +1,26 @@ +scope(path: 'u/:username', + as: :user, + constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error + get '/users/almost_there' => 'confirmations#almost_there' +end + +# Get all keys of user +get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From b3151828e74346f54b20711593829191b1a7a829 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Oct 2016 16:05:26 +0300 Subject: Put user keys routing back below project routing Signed-off-by: Dmitriy Zaporozhets --- config/routes.rb | 3 +++ config/routes/user.rb | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index c364a963fb7..75bc551feb5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -145,5 +145,8 @@ Rails.application.routes.draw do get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } + # Get all keys of user + get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } + root to: "root#index" end diff --git a/config/routes/user.rb b/config/routes/user.rb index b6a6a07e3c4..bbb30cedd4d 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -21,6 +21,3 @@ devise_scope :user do get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error get '/users/almost_there' => 'confirmations#almost_there' end - -# Get all keys of user -get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From a7352c35fcefeba7246801c6e179e389945d3c2a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Oct 2016 16:40:11 +0300 Subject: Put namespaces#show route below user ssh keys route Signed-off-by: Dmitriy Zaporozhets --- config/routes.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 75bc551feb5..525953449cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -143,10 +143,10 @@ Rails.application.routes.draw do draw :user draw :project - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } - # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } + get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } + root to: "root#index" end -- cgit v1.2.1 From 69bb6976f271d8f40baeaef904d5869112f778d5 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 5 Oct 2016 16:25:55 +0200 Subject: added 245px max height and overflow scroll-y to .grouped-pipeline-dropdown --- app/assets/stylesheets/pages/pipelines.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index b2662b812b7..68fc6da6c1b 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -82,7 +82,7 @@ } .branch-commit { - + .branch-name { font-weight: bold; max-width: 150px; @@ -390,6 +390,8 @@ left: auto; right: -214px; top: -9px; + max-height: 245px; + overflow-y: scroll; a:hover { .ci-status-text { -- cgit v1.2.1 From e234fe96109b753574f16c69f20cc93efd19b981 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 5 Oct 2016 16:32:28 +0200 Subject: Grouped pipeline dropdown is a scrollable container added to CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c3ed6011216..81875cd194e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -54,6 +54,7 @@ v 8.13.0 (unreleased) - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Grouped pipeline dropdown is a scrollable container v 8.12.4 (unreleased) - Fix type mismatch bug when closing Jira issue -- cgit v1.2.1 From 154253cab55491d54dfe264fa946acb9c399398a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 4 Oct 2016 14:09:54 +0200 Subject: Refactor TrendingProjectsFinder to support caching == Public Projects This finder class now _only_ returns public projects. Previously this finder would also return private and internal projects. Including these projects makes caching data much harder and less efficient. Meanwhile including this data isn't very useful as very few users would be interested in seeing projects they have access to as trending. That is, the feature is more useful when you want to see what _other_ popular projects there are. == Caching The data returned by TrendingProjectsFinder is now cached for a day based on the number of months the data should be restricted to. The cache is not flushed explicitly, instead it's rebuilt whenever it expires. == Timings To measure the impact I changed the finder code to use the last 24 months instead of the last month. I then executed and measured 10 requests to the explore page. On the current "master" branch (commit 88fa5916ffa0aea491cd339272ed7437c3f52dc7) this would take an average of 2.43 seconds. Using the changes of this commit this was reduced to around 1.7 seconds. Fixes gitlab-org/gitlab-ce#22164 --- CHANGELOG | 1 + app/controllers/explore/projects_controller.rb | 2 +- app/finders/trending_projects_finder.rb | 13 +++++-- spec/finders/trending_projects_finder_spec.rb | 53 +++++++++++++++----------- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a47ec511452..11141b38a0e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.13.0 (unreleased) - Take filters in account in issuable counters. !6496 - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - Append issue template to existing description !6149 (Joseph Frazier) + - Trending projects now only show public projects and the list of projects is cached for a day - Revoke button in Applications Settings underlines on hover. - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Fix Long commit messages overflow viewport in file tree diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 88a0c18180b..38e5943eb76 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -21,7 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = TrendingProjectsFinder.new.execute(current_user) + @projects = TrendingProjectsFinder.new.execute @projects = filter_projects(@projects) @projects = @projects.page(params[:page]) diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb index 81a12403801..c1e434d9926 100644 --- a/app/finders/trending_projects_finder.rb +++ b/app/finders/trending_projects_finder.rb @@ -1,11 +1,16 @@ +# Finder for retrieving public trending projects in a given time range. class TrendingProjectsFinder - def execute(current_user, start_date = 1.month.ago) - projects_for(current_user).trending(start_date) + # current_user - The currently logged in User, if any. + # last_months - The number of months to limit the trending data to. + def execute(months_limit = 1) + Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do + Project.public_only.trending(months_limit.months.ago) + end end private - def projects_for(current_user) - ProjectsFinder.new.execute(current_user) + def cache_key_for(months) + "trending_projects/#{months}" end end diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb index a49cbfd5160..cfe15b9defa 100644 --- a/spec/finders/trending_projects_finder_spec.rb +++ b/spec/finders/trending_projects_finder_spec.rb @@ -1,39 +1,48 @@ require 'spec_helper' describe TrendingProjectsFinder do - let(:user) { build(:user) } + let(:user) { create(:user) } + let(:public_project1) { create(:empty_project, :public) } + let(:public_project2) { create(:empty_project, :public) } + let(:private_project) { create(:empty_project, :private) } + let(:internal_project) { create(:empty_project, :internal) } + + before do + 3.times do + create(:note_on_commit, project: public_project1) + end - describe '#execute' do - describe 'without an explicit start date' do - subject { described_class.new } + 2.times do + create(:note_on_commit, project: public_project2, created_at: 5.weeks.ago) + end - it 'returns the trending projects' do - relation = double(:ar_relation) + create(:note_on_commit, project: private_project) + create(:note_on_commit, project: internal_project) + end - allow(subject).to receive(:projects_for) - .with(user) - .and_return(relation) + describe '#execute', caching: true do + context 'without an explicit time range' do + it 'returns public trending projects' do + projects = described_class.new.execute - allow(relation).to receive(:trending) - .with(an_instance_of(ActiveSupport::TimeWithZone)) + expect(projects).to eq([public_project1]) end end - describe 'with an explicit start date' do - let(:date) { 2.months.ago } + context 'with an explicit time range' do + it 'returns public trending projects' do + projects = described_class.new.execute(2) - subject { described_class.new } + expect(projects).to eq([public_project1, public_project2]) + end + end - it 'returns the trending projects' do - relation = double(:ar_relation) + it 'caches the list of projects' do + projects = described_class.new - allow(subject).to receive(:projects_for) - .with(user) - .and_return(relation) + expect(Project).to receive(:trending).once - allow(relation).to receive(:trending) - .with(date) - end + 2.times { projects.execute } end end end -- cgit v1.2.1