Merge remote-tracking branch 'upstream/master' into 30634-protected-pipeline
* upstream/master: (638 commits) Simplify background migrations stealing code Expire cached user IDs that can see the performance after 5 minutes Promote visibility level helpers from Group to Namespace Fix off-by-one error in background migration retries Recover from all exceptions when stealing bg migration Fix label creation from new list for subgroup projects move click handler to button. when on the icon it wasn't triggered in firefox Fix incorrect AWS ELB metrics. Fix wrong link to docs in docs styleguide Update issue-related docs Refactor groups docs Add subgroups limitations to Pages docs Update Google launcher details Split docs on IP whitelist for monitoring access Update health check docs Bump fog-core to 1.44.3 and fog providers' plugins to latest Introduce have_gitlab_http_status Remove Repository#search_files Update Pipeline's badge count in Merge Request and Commits view to match real-time content Fixes the user order being overriden in the autocomplete controller ...
This commit is contained in:
commit
65e722ee97
|
@ -3,3 +3,4 @@ lib/gitlab/sanitizers/svg/whitelist.rb
|
|||
lib/gitlab/diff/position_tracer.rb
|
||||
app/policies/project_policy.rb
|
||||
app/models/concerns/relative_positioning.rb
|
||||
lib/gitlab/redis/*.rb
|
||||
|
|
|
@ -31,6 +31,9 @@ eslint-report.html
|
|||
/config/initializers/smtp_settings.rb
|
||||
/config/initializers/relative_url.rb
|
||||
/config/resque.yml
|
||||
/config/redis.cache.yml
|
||||
/config/redis.queues.yml
|
||||
/config/redis.shared_state.yml
|
||||
/config/unicorn.rb
|
||||
/config/secrets.yml
|
||||
/config/sidekiq.yml
|
||||
|
@ -60,3 +63,4 @@ eslint-report.html
|
|||
/.gitlab_workhorse_secret
|
||||
/webpack-report/
|
||||
/locale/**/LC_MESSAGES
|
||||
/.rspec
|
||||
|
|
122
.gitlab-ci.yml
122
.gitlab-ci.yml
|
@ -203,69 +203,69 @@ setup-test-env:
|
|||
- public/assets
|
||||
- tmp/tests
|
||||
|
||||
rspec-pg 0 20: *rspec-knapsack-pg
|
||||
rspec-pg 1 20: *rspec-knapsack-pg
|
||||
rspec-pg 2 20: *rspec-knapsack-pg
|
||||
rspec-pg 3 20: *rspec-knapsack-pg
|
||||
rspec-pg 4 20: *rspec-knapsack-pg
|
||||
rspec-pg 5 20: *rspec-knapsack-pg
|
||||
rspec-pg 6 20: *rspec-knapsack-pg
|
||||
rspec-pg 7 20: *rspec-knapsack-pg
|
||||
rspec-pg 8 20: *rspec-knapsack-pg
|
||||
rspec-pg 9 20: *rspec-knapsack-pg
|
||||
rspec-pg 10 20: *rspec-knapsack-pg
|
||||
rspec-pg 11 20: *rspec-knapsack-pg
|
||||
rspec-pg 12 20: *rspec-knapsack-pg
|
||||
rspec-pg 13 20: *rspec-knapsack-pg
|
||||
rspec-pg 14 20: *rspec-knapsack-pg
|
||||
rspec-pg 15 20: *rspec-knapsack-pg
|
||||
rspec-pg 16 20: *rspec-knapsack-pg
|
||||
rspec-pg 17 20: *rspec-knapsack-pg
|
||||
rspec-pg 18 20: *rspec-knapsack-pg
|
||||
rspec-pg 19 20: *rspec-knapsack-pg
|
||||
rspec-pg 0 25: *rspec-knapsack-pg
|
||||
rspec-pg 1 25: *rspec-knapsack-pg
|
||||
rspec-pg 2 25: *rspec-knapsack-pg
|
||||
rspec-pg 3 25: *rspec-knapsack-pg
|
||||
rspec-pg 4 25: *rspec-knapsack-pg
|
||||
rspec-pg 5 25: *rspec-knapsack-pg
|
||||
rspec-pg 6 25: *rspec-knapsack-pg
|
||||
rspec-pg 7 25: *rspec-knapsack-pg
|
||||
rspec-pg 8 25: *rspec-knapsack-pg
|
||||
rspec-pg 9 25: *rspec-knapsack-pg
|
||||
rspec-pg 10 25: *rspec-knapsack-pg
|
||||
rspec-pg 11 25: *rspec-knapsack-pg
|
||||
rspec-pg 12 25: *rspec-knapsack-pg
|
||||
rspec-pg 13 25: *rspec-knapsack-pg
|
||||
rspec-pg 14 25: *rspec-knapsack-pg
|
||||
rspec-pg 15 25: *rspec-knapsack-pg
|
||||
rspec-pg 16 25: *rspec-knapsack-pg
|
||||
rspec-pg 17 25: *rspec-knapsack-pg
|
||||
rspec-pg 18 25: *rspec-knapsack-pg
|
||||
rspec-pg 19 25: *rspec-knapsack-pg
|
||||
rspec-pg 20 25: *rspec-knapsack-pg
|
||||
rspec-pg 21 25: *rspec-knapsack-pg
|
||||
rspec-pg 22 25: *rspec-knapsack-pg
|
||||
rspec-pg 23 25: *rspec-knapsack-pg
|
||||
rspec-pg 24 25: *rspec-knapsack-pg
|
||||
|
||||
rspec-mysql 0 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 1 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 2 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 3 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 4 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 5 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 6 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 7 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 8 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 9 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 10 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 11 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 12 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 13 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 14 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 15 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 16 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 17 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 18 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 19 20: *rspec-knapsack-mysql
|
||||
rspec-mysql 0 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 1 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 2 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 3 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 4 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 5 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 6 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 7 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 8 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 9 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 10 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 11 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 12 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 13 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 14 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 15 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 16 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 17 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 18 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 19 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 20 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 21 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 22 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 23 25: *rspec-knapsack-mysql
|
||||
rspec-mysql 24 25: *rspec-knapsack-mysql
|
||||
|
||||
spinach-pg 0 10: *spinach-knapsack-pg
|
||||
spinach-pg 1 10: *spinach-knapsack-pg
|
||||
spinach-pg 2 10: *spinach-knapsack-pg
|
||||
spinach-pg 3 10: *spinach-knapsack-pg
|
||||
spinach-pg 4 10: *spinach-knapsack-pg
|
||||
spinach-pg 5 10: *spinach-knapsack-pg
|
||||
spinach-pg 6 10: *spinach-knapsack-pg
|
||||
spinach-pg 7 10: *spinach-knapsack-pg
|
||||
spinach-pg 8 10: *spinach-knapsack-pg
|
||||
spinach-pg 9 10: *spinach-knapsack-pg
|
||||
spinach-pg 0 5: *spinach-knapsack-pg
|
||||
spinach-pg 1 5: *spinach-knapsack-pg
|
||||
spinach-pg 2 5: *spinach-knapsack-pg
|
||||
spinach-pg 3 5: *spinach-knapsack-pg
|
||||
spinach-pg 4 5: *spinach-knapsack-pg
|
||||
|
||||
spinach-mysql 0 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 1 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 2 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 3 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 4 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 5 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 6 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 7 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 8 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 9 10: *spinach-knapsack-mysql
|
||||
spinach-mysql 0 5: *spinach-knapsack-mysql
|
||||
spinach-mysql 1 5: *spinach-knapsack-mysql
|
||||
spinach-mysql 2 5: *spinach-knapsack-mysql
|
||||
spinach-mysql 3 5: *spinach-knapsack-mysql
|
||||
spinach-mysql 4 5: *spinach-knapsack-mysql
|
||||
|
||||
# Static analysis jobs
|
||||
.ruby-static-analysis: &ruby-static-analysis
|
||||
|
@ -362,6 +362,7 @@ db:migrate:reset-mysql:
|
|||
- git fetch origin v8.14.10
|
||||
- git checkout -f FETCH_HEAD
|
||||
- bundle install $BUNDLE_INSTALL_FLAGS
|
||||
- cp config/gitlab.yml.example config/gitlab.yml
|
||||
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
|
||||
- git checkout $CI_COMMIT_SHA
|
||||
- bundle install $BUNDLE_INSTALL_FLAGS
|
||||
|
@ -556,3 +557,4 @@ gitlab_git_test:
|
|||
SETUP_DB: "false"
|
||||
script:
|
||||
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
|
||||
<<: *except-docs
|
||||
|
|
10
.rubocop.yml
10
.rubocop.yml
|
@ -965,6 +965,10 @@ RSpec/AnyInstance:
|
|||
RSpec/BeEql:
|
||||
Enabled: true
|
||||
|
||||
# We don't enforce this as we use this technique in a few places.
|
||||
RSpec/BeforeAfterAll:
|
||||
Enabled: false
|
||||
|
||||
# Check that the first argument to the top level describe is the tested class or
|
||||
# module.
|
||||
RSpec/DescribeClass:
|
||||
|
@ -1024,6 +1028,12 @@ RSpec/FilePath:
|
|||
RSpec/Focus:
|
||||
Enabled: true
|
||||
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: is_expected, should
|
||||
RSpec/ImplicitExpect:
|
||||
Enabled: true
|
||||
EnforcedStyle: is_expected
|
||||
|
||||
# Checks for the usage of instance variables.
|
||||
RSpec/InstanceVariable:
|
||||
Enabled: false
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 54
|
||||
RSpec/BeforeAfterAll:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 233
|
||||
RSpec/EmptyLineAfterFinalLet:
|
||||
Enabled: false
|
||||
|
@ -24,12 +20,6 @@ RSpec/EmptyLineAfterSubject:
|
|||
RSpec/HookArgument:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 12
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: is_expected, should
|
||||
RSpec/ImplicitExpect:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 11
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: it_behaves_like, it_should_behave_like
|
||||
|
|
|
@ -10,7 +10,7 @@ linters:
|
|||
# Reports when you use improper spacing around ! (the "bang") in !default,
|
||||
# !global, !important, and !optional flags.
|
||||
BangFormat:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# Whether or not to prefer `border: 0` over `border: none`.
|
||||
BorderZero:
|
||||
|
@ -43,10 +43,11 @@ linters:
|
|||
# Rule sets should be ordered as follows:
|
||||
# - @extend declarations
|
||||
# - @include declarations without inner @content
|
||||
# - properties, @include declarations with inner @content
|
||||
# - properties
|
||||
# - @include declarations with inner @content
|
||||
# - nested rule sets.
|
||||
DeclarationOrder:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# `scss-lint:disable` control comments should be preceded by a comment
|
||||
# explaining why these linters are being disabled for this file.
|
||||
|
@ -93,7 +94,7 @@ linters:
|
|||
# The basenames of @imported SCSS partials should not begin with an
|
||||
# underscore and should not include the filename extension.
|
||||
ImportPath:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# Avoid using !important in properties. It is usually indicative of a
|
||||
# misunderstanding of CSS specificity and can lead to brittle code.
|
||||
|
@ -133,7 +134,7 @@ linters:
|
|||
# Reports when you use an unknown or disabled CSS property
|
||||
# (ignoring vendor-prefixed properties).
|
||||
PropertySpelling:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# Configure which units are allowed for property values.
|
||||
PropertyUnits:
|
||||
|
@ -176,6 +177,10 @@ linters:
|
|||
|
||||
# Commas in lists should be followed by a space.
|
||||
SpaceAfterComma:
|
||||
enabled: true
|
||||
|
||||
# Comment literals should be followed by a space.
|
||||
SpaceAfterComment:
|
||||
enabled: false
|
||||
|
||||
# Properties should be formatted with a single space separating the colon
|
||||
|
@ -240,7 +245,7 @@ linters:
|
|||
# Do not use parent selector references (&) when they would otherwise
|
||||
# be unnecessary.
|
||||
UnnecessaryParentReference:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# URLs should be valid and not contain protocols or domain names.
|
||||
UrlFormat:
|
||||
|
|
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -2,9 +2,24 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 9.3.6 (2017-07-12)
|
||||
|
||||
- Fix API Scoping. !12300
|
||||
- Username and password are no longer stripped from import url on mirror update. !12725
|
||||
- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
|
||||
- Fixed GFM references not being included when updating issues inline.
|
||||
|
||||
## 9.3.5 (2017-07-05)
|
||||
|
||||
- Remove "Remove from board" button from backlog and closed list. !12430
|
||||
- Do not delete protected branches when deleting all merged branches. !12624
|
||||
- Set default for Remove source branch to false.
|
||||
- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion.
|
||||
- Expires full_path cache after a repository is renamed/transferred.
|
||||
|
||||
## 9.3.4 (2017-07-03)
|
||||
|
||||
- No changes.
|
||||
- Update gitlab-shell to 5.1.1 !12615
|
||||
|
||||
## 9.3.3 (2017-06-30)
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.14.0
|
||||
0.17.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.4.3
|
||||
0.5.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
5.1.1
|
||||
5.3.1
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.2.0
|
||||
3.0.0
|
||||
|
|
12
Gemfile
12
Gemfile
|
@ -2,7 +2,6 @@ source 'https://rubygems.org'
|
|||
|
||||
gem 'rails', '4.2.8'
|
||||
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
||||
gem 'bootsnap', '~> 1.1'
|
||||
|
||||
# Responders respond_to and respond_with
|
||||
gem 'responders', '~> 2.0'
|
||||
|
@ -13,7 +12,7 @@ gem 'sprockets', '~> 3.7.0'
|
|||
gem 'default_value_for', '~> 3.0.0'
|
||||
|
||||
# Supported DBs
|
||||
gem 'mysql2', '~> 0.3.16', group: :mysql
|
||||
gem 'mysql2', '~> 0.4.5', group: :mysql
|
||||
gem 'pg', '~> 0.18.2', group: :postgres
|
||||
|
||||
gem 'rugged', '~> 0.25.1.1'
|
||||
|
@ -92,7 +91,7 @@ gem 'carrierwave', '~> 1.1'
|
|||
gem 'dropzonejs-rails', '~> 0.7.1'
|
||||
|
||||
# for backups
|
||||
gem 'fog-aws', '~> 0.9'
|
||||
gem 'fog-aws', '~> 1.4'
|
||||
gem 'fog-core', '~> 1.44'
|
||||
gem 'fog-google', '~> 0.5'
|
||||
gem 'fog-local', '~> 0.3'
|
||||
|
@ -251,11 +250,10 @@ gem 'jquery-rails', '~> 4.1.0'
|
|||
gem 'request_store', '~> 1.3'
|
||||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
gem 'base32', '~> 0.3.0'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven', '~> 2.4.0'
|
||||
gem 'sentry-raven', '~> 2.5.3'
|
||||
|
||||
gem 'premailer-rails', '~> 1.9.7'
|
||||
|
||||
|
@ -336,7 +334,7 @@ group :development, :test do
|
|||
|
||||
gem 'rubocop', '~> 0.47.1', require: false
|
||||
gem 'rubocop-rspec', '~> 1.15.0', require: false
|
||||
gem 'scss_lint', '~> 0.47.0', require: false
|
||||
gem 'scss_lint', '~> 0.54.0', require: false
|
||||
gem 'haml_lint', '~> 0.21.0', require: false
|
||||
gem 'simplecov', '~> 0.14.0', require: false
|
||||
gem 'flay', '~> 2.8.0', require: false
|
||||
|
@ -386,7 +384,7 @@ gem 'vmstat', '~> 2.3.0'
|
|||
gem 'sys-filesystem', '~> 1.1.6'
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly', '~> 0.9.0'
|
||||
gem 'gitaly', '~> 0.14.0'
|
||||
|
||||
gem 'toml-rb', '~> 0.3.15', require: false
|
||||
|
||||
|
|
44
Gemfile.lock
44
Gemfile.lock
|
@ -83,8 +83,6 @@ GEM
|
|||
bindata (2.3.5)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.1.1)
|
||||
msgpack (~> 1.0)
|
||||
bootstrap-sass (3.3.6)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sass (>= 3.3.4)
|
||||
|
@ -188,7 +186,7 @@ GEM
|
|||
et-orbi (1.0.3)
|
||||
tzinfo
|
||||
eventmachine (1.0.8)
|
||||
excon (0.55.0)
|
||||
excon (0.57.1)
|
||||
execjs (2.6.0)
|
||||
expression_parser (0.9.0)
|
||||
extlib (0.9.16)
|
||||
|
@ -224,26 +222,26 @@ GEM
|
|||
fog-json (~> 1.0)
|
||||
ipaddress (~> 0.8)
|
||||
xml-simple (~> 1.1)
|
||||
fog-aws (0.13.0)
|
||||
fog-aws (1.4.0)
|
||||
fog-core (~> 1.38)
|
||||
fog-json (~> 1.0)
|
||||
fog-xml (~> 0.1)
|
||||
ipaddress (~> 0.8)
|
||||
fog-core (1.44.1)
|
||||
fog-core (1.44.3)
|
||||
builder
|
||||
excon (~> 0.49)
|
||||
formatador (~> 0.2)
|
||||
fog-google (0.5.0)
|
||||
fog-google (0.5.3)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-xml
|
||||
fog-json (1.0.2)
|
||||
fog-core (~> 1.0)
|
||||
multi_json (~> 1.10)
|
||||
fog-local (0.3.0)
|
||||
fog-local (0.3.1)
|
||||
fog-core (~> 1.27)
|
||||
fog-openstack (0.1.6)
|
||||
fog-core (>= 1.39)
|
||||
fog-openstack (0.1.21)
|
||||
fog-core (>= 1.40)
|
||||
fog-json (>= 1.0)
|
||||
ipaddress (>= 0.8)
|
||||
fog-rackspace (0.1.1)
|
||||
|
@ -278,7 +276,7 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly (0.9.0)
|
||||
gitaly (0.14.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
github-linguist (4.7.6)
|
||||
|
@ -465,7 +463,6 @@ GEM
|
|||
minitest (5.7.0)
|
||||
mmap2 (2.2.7)
|
||||
mousetrap-rails (1.4.6)
|
||||
msgpack (1.1.0)
|
||||
multi_json (1.12.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
|
@ -473,9 +470,8 @@ GEM
|
|||
tool (~> 0.2)
|
||||
mustermann-grape (0.4.0)
|
||||
mustermann (= 0.4.0)
|
||||
mysql2 (0.3.20)
|
||||
mysql2 (0.4.5)
|
||||
net-ldap (0.12.1)
|
||||
net-ssh (3.0.1)
|
||||
netrc (0.11.0)
|
||||
nokogiri (1.6.8.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
|
@ -766,16 +762,16 @@ GEM
|
|||
sawyer (0.8.1)
|
||||
addressable (>= 2.3.5, < 2.6)
|
||||
faraday (~> 0.8, < 1.0)
|
||||
scss_lint (0.47.1)
|
||||
rake (>= 0.9, < 11)
|
||||
sass (~> 3.4.15)
|
||||
scss_lint (0.54.0)
|
||||
rake (>= 0.9, < 13)
|
||||
sass (~> 3.4.20)
|
||||
securecompare (1.0.0)
|
||||
seed-fu (2.3.6)
|
||||
activerecord (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
select2-rails (3.5.9.3)
|
||||
thor (~> 0.14)
|
||||
sentry-raven (2.4.0)
|
||||
sentry-raven (2.5.3)
|
||||
faraday (>= 0.7.6, < 1.0)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.9.0)
|
||||
|
@ -783,7 +779,7 @@ GEM
|
|||
rack
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (5.0.0)
|
||||
sidekiq (5.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
|
@ -930,7 +926,6 @@ DEPENDENCIES
|
|||
benchmark-ips (~> 2.3.0)
|
||||
better_errors (~> 2.1.0)
|
||||
binding_of_caller (~> 0.7.2)
|
||||
bootsnap (~> 1.1)
|
||||
bootstrap-sass (~> 3.3.0)
|
||||
bootstrap_form (~> 2.7.0)
|
||||
brakeman (~> 3.6.0)
|
||||
|
@ -966,7 +961,7 @@ DEPENDENCIES
|
|||
flipper (~> 0.10.2)
|
||||
flipper-active_record (~> 0.10.2)
|
||||
fog-aliyun (~> 0.1.0)
|
||||
fog-aws (~> 0.9)
|
||||
fog-aws (~> 1.4)
|
||||
fog-core (~> 1.44)
|
||||
fog-google (~> 0.5)
|
||||
fog-local (~> 0.3)
|
||||
|
@ -980,7 +975,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.2.0)
|
||||
gitaly (~> 0.9.0)
|
||||
gitaly (~> 0.14.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.5.1)
|
||||
|
@ -1016,8 +1011,7 @@ DEPENDENCIES
|
|||
method_source (~> 0.8)
|
||||
minitest (~> 5.7.0)
|
||||
mousetrap-rails (~> 1.4.6)
|
||||
mysql2 (~> 0.3.16)
|
||||
net-ssh (~> 3.0.1)
|
||||
mysql2 (~> 0.4.5)
|
||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||
oauth2 (~> 1.4)
|
||||
octokit (~> 4.6.2)
|
||||
|
@ -1087,10 +1081,10 @@ DEPENDENCIES
|
|||
rugged (~> 0.25.1.1)
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 5.0.6)
|
||||
scss_lint (~> 0.47.0)
|
||||
scss_lint (~> 0.54.0)
|
||||
seed-fu (~> 2.3.5)
|
||||
select2-rails (~> 3.5.9)
|
||||
sentry-raven (~> 2.4.0)
|
||||
sentry-raven (~> 2.5.3)
|
||||
settingslogic (~> 2.0.9)
|
||||
sham_rack (~> 1.3.6)
|
||||
shoulda-matchers (~> 2.8.0)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
## Test coverage
|
||||
|
||||
- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby
|
||||
- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
|
||||
- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
|
||||
|
||||
## Canonical source
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 14 KiB |
|
@ -51,8 +51,9 @@ export default () => {
|
|||
methods: {
|
||||
loadFile() {
|
||||
this.$http.get(el.dataset.endpoint)
|
||||
.then(response => response.json())
|
||||
.then((res) => {
|
||||
this.json = res.json();
|
||||
this.json = res;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
|
|
|
@ -81,8 +81,9 @@ $(() => {
|
|||
mounted () {
|
||||
Store.disabled = this.disabled;
|
||||
gl.boardService.all()
|
||||
.then(response => response.json())
|
||||
.then((resp) => {
|
||||
resp.json().forEach((board) => {
|
||||
resp.forEach((board) => {
|
||||
const list = Store.addList(board, this.defaultAvatar);
|
||||
|
||||
if (list.type === 'closed') {
|
||||
|
@ -97,7 +98,8 @@ $(() => {
|
|||
|
||||
Store.addBlankState();
|
||||
this.loading = false;
|
||||
}).catch(() => new Flash('An error occurred. Please try again.'));
|
||||
})
|
||||
.catch(() => new Flash('An error occurred. Please try again.'));
|
||||
},
|
||||
methods: {
|
||||
updateTokens() {
|
||||
|
|
|
@ -64,8 +64,9 @@ export default {
|
|||
|
||||
// Save the labels
|
||||
gl.boardService.generateDefaultLists()
|
||||
.then((resp) => {
|
||||
resp.json().forEach((listObj) => {
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
data.forEach((listObj) => {
|
||||
const list = Store.findList('title', listObj.title);
|
||||
|
||||
list.id = listObj.id;
|
||||
|
|
|
@ -88,9 +88,9 @@ gl.issueBoards.IssuesModal = Vue.extend({
|
|||
return gl.boardService.getBacklog(queryData(this.filter.path, {
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
})).then((res) => {
|
||||
const data = res.json();
|
||||
|
||||
}))
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
if (clearIssues) {
|
||||
this.issues = [];
|
||||
}
|
||||
|
|
|
@ -40,9 +40,8 @@ class List {
|
|||
|
||||
save () {
|
||||
return gl.boardService.createList(this.label.id)
|
||||
.then((resp) => {
|
||||
const data = resp.json();
|
||||
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
this.id = data.id;
|
||||
this.type = data.list_type;
|
||||
this.position = data.position;
|
||||
|
@ -91,8 +90,8 @@ class List {
|
|||
}
|
||||
|
||||
return gl.boardService.getIssuesForList(this.id, data)
|
||||
.then((resp) => {
|
||||
const data = resp.json();
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
this.loading = false;
|
||||
this.issuesSize = data.size;
|
||||
|
||||
|
@ -109,8 +108,8 @@ class List {
|
|||
this.issuesSize += 1;
|
||||
|
||||
return gl.boardService.newIssue(this.id, issue)
|
||||
.then((resp) => {
|
||||
const data = resp.json();
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
issue.id = data.iid;
|
||||
|
||||
if (this.issuesSize > 1) {
|
||||
|
|
|
@ -23,11 +23,6 @@ class BoardService {
|
|||
url: bulkUpdatePath,
|
||||
},
|
||||
});
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
all () {
|
||||
|
|
|
@ -13,25 +13,21 @@ window.Build = (function () {
|
|||
this.options = options || $('.js-build-options').data();
|
||||
|
||||
this.pageUrl = this.options.pageUrl;
|
||||
this.buildUrl = this.options.buildUrl;
|
||||
this.buildStatus = this.options.buildStatus;
|
||||
this.state = this.options.logState;
|
||||
this.buildStage = this.options.buildStage;
|
||||
this.$document = $(document);
|
||||
this.logBytes = 0;
|
||||
this.scrollOffsetPadding = 30;
|
||||
this.hasBeenScrolled = false;
|
||||
|
||||
this.updateDropdown = this.updateDropdown.bind(this);
|
||||
this.getBuildTrace = this.getBuildTrace.bind(this);
|
||||
this.scrollToBottom = this.scrollToBottom.bind(this);
|
||||
|
||||
this.$body = $('body');
|
||||
this.$buildTrace = $('#build-trace');
|
||||
this.$buildRefreshAnimation = $('.js-build-refresh');
|
||||
this.$truncatedInfo = $('.js-truncated-info');
|
||||
this.$buildTraceOutput = $('.js-build-output');
|
||||
this.$scrollContainer = $('.js-scroll-container');
|
||||
this.$topBar = $('.js-top-bar');
|
||||
|
||||
// Scroll controllers
|
||||
this.$scrollTopBtn = $('.js-scroll-up');
|
||||
|
@ -63,13 +59,22 @@ window.Build = (function () {
|
|||
.off('click')
|
||||
.on('click', this.scrollToBottom.bind(this));
|
||||
|
||||
const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
|
||||
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
|
||||
|
||||
this.$scrollContainer
|
||||
$(window)
|
||||
.off('scroll')
|
||||
.on('scroll', () => {
|
||||
this.hasBeenScrolled = true;
|
||||
scrollThrottled();
|
||||
const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
|
||||
if (contentHeight > this.windowSize) {
|
||||
// means the user did not scroll, the content was updated.
|
||||
this.windowSize = contentHeight;
|
||||
} else {
|
||||
// User scrolled
|
||||
this.hasBeenScrolled = true;
|
||||
this.toggleScrollAnimation(false);
|
||||
}
|
||||
|
||||
this.scrollThrottled();
|
||||
});
|
||||
|
||||
$(window)
|
||||
|
@ -77,59 +82,73 @@ window.Build = (function () {
|
|||
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
|
||||
|
||||
this.updateArtifactRemoveDate();
|
||||
this.initAffixTopArea();
|
||||
|
||||
// eslint-disable-next-line
|
||||
this.getBuildTrace()
|
||||
.then(() => this.toggleScroll())
|
||||
.then(() => {
|
||||
if (!this.hasBeenScrolled) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
})
|
||||
.then(() => this.verifyTopPosition());
|
||||
this.getBuildTrace();
|
||||
}
|
||||
|
||||
Build.prototype.canScroll = function () {
|
||||
return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
|
||||
Build.prototype.initAffixTopArea = function () {
|
||||
/**
|
||||
If the browser does not support position sticky, it returns the position as static.
|
||||
If the browser does support sticky, then we allow the browser to handle it, if not
|
||||
then we default back to Bootstraps affix
|
||||
**/
|
||||
if (this.$topBar.css('position') !== 'static') return;
|
||||
|
||||
const offsetTop = this.$buildTrace.offset().top;
|
||||
|
||||
this.$topBar.affix({
|
||||
offset: {
|
||||
top: offsetTop,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.canScroll = function () {
|
||||
return document.body.scrollHeight > window.innerHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* | | Up | Down |
|
||||
* |--------------------------|----------|----------|
|
||||
* | on scroll bottom | active | disabled |
|
||||
* | on scroll top | disabled | active |
|
||||
* | no scroll | disabled | disabled |
|
||||
* | on.('scroll') is on top | disabled | active |
|
||||
* | on('scroll) is on bottom | active | disabled |
|
||||
*
|
||||
*/
|
||||
Build.prototype.toggleScroll = function () {
|
||||
const currentPosition = this.$scrollContainer.scrollTop();
|
||||
const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
|
||||
const currentPosition = document.body.scrollTop;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
if (this.canScroll()) {
|
||||
if (currentPosition === 0) {
|
||||
if (currentPosition > 0 &&
|
||||
(document.body.scrollHeight - currentPosition !== windowHeight)) {
|
||||
// User is in the middle of the log
|
||||
|
||||
this.toggleDisableButton(this.$scrollTopBtn, false);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, false);
|
||||
} else if (currentPosition === 0) {
|
||||
// User is at Top of Build Log
|
||||
|
||||
this.toggleDisableButton(this.$scrollTopBtn, true);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, false);
|
||||
} else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
|
||||
} else if (document.body.scrollHeight - currentPosition === windowHeight) {
|
||||
// User is at the bottom of the build log.
|
||||
|
||||
this.toggleDisableButton(this.$scrollTopBtn, false);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, true);
|
||||
} else {
|
||||
this.toggleDisableButton(this.$scrollTopBtn, false);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, false);
|
||||
}
|
||||
} else {
|
||||
this.toggleDisableButton(this.$scrollTopBtn, true);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, true);
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.scrollToTop = function () {
|
||||
this.hasBeenScrolled = true;
|
||||
this.$scrollContainer.scrollTop(0);
|
||||
this.toggleScroll();
|
||||
Build.prototype.scrollDown = function () {
|
||||
document.body.scrollTop = document.body.scrollHeight;
|
||||
};
|
||||
|
||||
Build.prototype.scrollToBottom = function () {
|
||||
this.scrollDown();
|
||||
this.hasBeenScrolled = true;
|
||||
this.toggleScroll();
|
||||
};
|
||||
|
||||
Build.prototype.scrollToTop = function () {
|
||||
document.body.scrollTop = 0;
|
||||
this.hasBeenScrolled = true;
|
||||
this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
|
||||
this.toggleScroll();
|
||||
};
|
||||
|
||||
|
@ -142,47 +161,6 @@ window.Build = (function () {
|
|||
this.$scrollBottomBtn.toggleClass('animate', toggle);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build trace top position depends on the space ocupied by the elments rendered before
|
||||
*/
|
||||
Build.prototype.verifyTopPosition = function () {
|
||||
const $buildPage = $('.build-page');
|
||||
|
||||
const $flashError = $('.alert-wrapper');
|
||||
const $header = $('.build-header', $buildPage);
|
||||
const $runnersStuck = $('.js-build-stuck', $buildPage);
|
||||
const $startsEnvironment = $('.js-environment-container', $buildPage);
|
||||
const $erased = $('.js-build-erased', $buildPage);
|
||||
const prependTopDefault = 20;
|
||||
|
||||
// header + navigation + margin
|
||||
let topPostion = 168;
|
||||
|
||||
if ($header.length) {
|
||||
topPostion += $header.outerHeight();
|
||||
}
|
||||
|
||||
if ($runnersStuck.length) {
|
||||
topPostion += $runnersStuck.outerHeight();
|
||||
}
|
||||
|
||||
if ($startsEnvironment.length) {
|
||||
topPostion += $startsEnvironment.outerHeight() + prependTopDefault;
|
||||
}
|
||||
|
||||
if ($erased.length) {
|
||||
topPostion += $erased.outerHeight() + prependTopDefault;
|
||||
}
|
||||
|
||||
if ($flashError.length) {
|
||||
topPostion += $flashError.outerHeight() + prependTopDefault;
|
||||
}
|
||||
|
||||
this.$buildTrace.css({
|
||||
top: topPostion,
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.initSidebar = function () {
|
||||
this.$sidebar = $('.js-build-sidebar');
|
||||
this.$sidebar.niceScroll();
|
||||
|
@ -200,6 +178,8 @@ window.Build = (function () {
|
|||
this.state = log.state;
|
||||
}
|
||||
|
||||
this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
|
||||
|
||||
if (log.append) {
|
||||
this.$buildTraceOutput.append(log.html);
|
||||
this.logBytes += log.size;
|
||||
|
@ -227,14 +207,7 @@ window.Build = (function () {
|
|||
}
|
||||
|
||||
Build.timeout = setTimeout(() => {
|
||||
//eslint-disable-next-line
|
||||
this.getBuildTrace()
|
||||
.then(() => {
|
||||
if (!this.hasBeenScrolled) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
})
|
||||
.then(() => this.verifyTopPosition());
|
||||
this.getBuildTrace();
|
||||
}, 4000);
|
||||
} else {
|
||||
this.$buildRefreshAnimation.remove();
|
||||
|
@ -247,7 +220,13 @@ window.Build = (function () {
|
|||
})
|
||||
.fail(() => {
|
||||
this.$buildRefreshAnimation.remove();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
if (!this.hasBeenScrolled) {
|
||||
this.scrollDown();
|
||||
}
|
||||
})
|
||||
.then(() => this.toggleScroll());
|
||||
};
|
||||
|
||||
Build.prototype.shouldHideSidebarForViewport = function () {
|
||||
|
@ -259,14 +238,11 @@ window.Build = (function () {
|
|||
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
|
||||
const $toggleButton = $('.js-sidebar-build-toggle-header');
|
||||
|
||||
this.$buildTrace
|
||||
.toggleClass('sidebar-expanded', shouldShow)
|
||||
.toggleClass('sidebar-collapsed', shouldHide);
|
||||
this.$sidebar
|
||||
.toggleClass('right-sidebar-expanded', shouldShow)
|
||||
.toggleClass('right-sidebar-collapsed', shouldHide);
|
||||
|
||||
$('.js-build-page')
|
||||
this.$topBar
|
||||
.toggleClass('sidebar-expanded', shouldShow)
|
||||
.toggleClass('sidebar-collapsed', shouldHide);
|
||||
|
||||
|
@ -279,17 +255,10 @@ window.Build = (function () {
|
|||
|
||||
Build.prototype.sidebarOnResize = function () {
|
||||
this.toggleSidebar(this.shouldHideSidebarForViewport());
|
||||
|
||||
this.verifyTopPosition();
|
||||
|
||||
if (this.canScroll()) {
|
||||
this.toggleScroll();
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.sidebarOnClick = function () {
|
||||
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
|
||||
this.verifyTopPosition();
|
||||
};
|
||||
|
||||
Build.prototype.updateArtifactRemoveDate = function () {
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import DropLab from './droplab/drop_lab';
|
||||
import ISetter from './droplab/plugins/input_setter';
|
||||
|
||||
// Todo: Remove this when fixing issue in input_setter plugin
|
||||
const InputSetter = Object.assign({}, ISetter);
|
||||
|
||||
class CloseReopenReportToggle {
|
||||
constructor(opts = {}) {
|
||||
this.dropdownTrigger = opts.dropdownTrigger;
|
||||
this.dropdownList = opts.dropdownList;
|
||||
this.button = opts.button;
|
||||
}
|
||||
|
||||
initDroplab() {
|
||||
this.reopenItem = this.dropdownList.querySelector('.reopen-item');
|
||||
this.closeItem = this.dropdownList.querySelector('.close-item');
|
||||
|
||||
this.droplab = new DropLab();
|
||||
|
||||
const config = this.setConfig();
|
||||
|
||||
this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
|
||||
}
|
||||
|
||||
updateButton(isClosed) {
|
||||
this.toggleButtonType(isClosed);
|
||||
|
||||
this.button.blur();
|
||||
}
|
||||
|
||||
toggleButtonType(isClosed) {
|
||||
const [showItem, hideItem] = this.getButtonTypes(isClosed);
|
||||
|
||||
showItem.classList.remove('hidden');
|
||||
showItem.classList.add('droplab-item-selected');
|
||||
|
||||
hideItem.classList.add('hidden');
|
||||
hideItem.classList.remove('droplab-item-selected');
|
||||
|
||||
showItem.click();
|
||||
}
|
||||
|
||||
getButtonTypes(isClosed) {
|
||||
return isClosed ? [this.reopenItem, this.closeItem] : [this.closeItem, this.reopenItem];
|
||||
}
|
||||
|
||||
setDisable(shouldDisable = true) {
|
||||
if (shouldDisable) {
|
||||
this.button.setAttribute('disabled', 'true');
|
||||
this.dropdownTrigger.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
this.button.removeAttribute('disabled');
|
||||
this.dropdownTrigger.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
setConfig() {
|
||||
const config = {
|
||||
InputSetter: [
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-text',
|
||||
inputAttribute: 'data-value',
|
||||
},
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-text',
|
||||
inputAttribute: 'title',
|
||||
},
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-button-class',
|
||||
inputAttribute: 'class',
|
||||
},
|
||||
{
|
||||
input: this.dropdownTrigger,
|
||||
valueAttribute: 'data-toggle-class',
|
||||
inputAttribute: 'class',
|
||||
},
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-url',
|
||||
inputAttribute: 'href',
|
||||
},
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-method',
|
||||
inputAttribute: 'data-method',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
export default CloseReopenReportToggle;
|
|
@ -1,5 +1,8 @@
|
|||
import DropLab from './droplab/drop_lab';
|
||||
import InputSetter from './droplab/plugins/input_setter';
|
||||
import ISetter from './droplab/plugins/input_setter';
|
||||
|
||||
// Todo: Remove this when fixing issue in input_setter plugin
|
||||
const InputSetter = Object.assign({}, ISetter);
|
||||
|
||||
class CommentTypeToggle {
|
||||
constructor(opts = {}) {
|
||||
|
|
|
@ -18,13 +18,26 @@ window.gl.CommitPipelinesTable = CommitPipelinesTable;
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
|
||||
|
||||
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
|
||||
const table = new CommitPipelinesTable({
|
||||
propsData: {
|
||||
endpoint: pipelineTableViewEl.dataset.endpoint,
|
||||
helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
|
||||
},
|
||||
}).$mount();
|
||||
pipelineTableViewEl.appendChild(table.$el);
|
||||
if (pipelineTableViewEl) {
|
||||
// Update MR and Commits tabs
|
||||
pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
|
||||
if (event.detail.pipelines &&
|
||||
event.detail.pipelines.count &&
|
||||
event.detail.pipelines.count.all) {
|
||||
const badge = document.querySelector('.js-pipelines-mr-count');
|
||||
|
||||
badge.textContent = event.detail.pipelines.count.all;
|
||||
}
|
||||
});
|
||||
|
||||
if (pipelineTableViewEl.dataset.disableInitialization === undefined) {
|
||||
const table = new CommitPipelinesTable({
|
||||
propsData: {
|
||||
endpoint: pipelineTableViewEl.dataset.endpoint,
|
||||
helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
|
||||
},
|
||||
}).$mount();
|
||||
pipelineTableViewEl.appendChild(table.$el);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -51,11 +51,22 @@
|
|||
},
|
||||
methods: {
|
||||
successCallback(resp) {
|
||||
const response = resp.json();
|
||||
return resp.json().then((response) => {
|
||||
// depending of the endpoint the response can either bring a `pipelines` key or not.
|
||||
const pipelines = response.pipelines || response;
|
||||
this.setCommonData(pipelines);
|
||||
|
||||
// depending of the endpoint the response can either bring a `pipelines` key or not.
|
||||
const pipelines = response.pipelines || response;
|
||||
this.setCommonData(pipelines);
|
||||
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
|
||||
detail: {
|
||||
pipelines: response,
|
||||
},
|
||||
});
|
||||
|
||||
// notifiy to update the count in tabs
|
||||
if (this.$el.parentElement) {
|
||||
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import './lib/utils/url_utility';
|
||||
import FilesCommentButton from './files_comment_button';
|
||||
import SingleFileDiff from './single_file_diff';
|
||||
|
||||
const UNFOLD_COUNT = 20;
|
||||
let isBound = false;
|
||||
|
@ -10,7 +11,11 @@ class Diff {
|
|||
constructor() {
|
||||
const $diffFile = $('.files .diff-file');
|
||||
|
||||
$diffFile.singleFileDiff();
|
||||
$diffFile.each((index, file) => {
|
||||
if (!$.data(file, 'singleFileDiff')) {
|
||||
$.data(file, 'singleFileDiff', new SingleFileDiff(file));
|
||||
}
|
||||
});
|
||||
|
||||
FilesCommentButton.init($diffFile);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */
|
||||
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */
|
||||
/* global CommentsStore */
|
||||
/* global ResolveService */
|
||||
/* global Flash */
|
||||
|
@ -64,8 +64,6 @@ const ResolveBtn = Vue.extend({
|
|||
});
|
||||
},
|
||||
resolve: function () {
|
||||
const errorFlashMsg = 'An error occurred when trying to resolve a comment. Please try again.';
|
||||
|
||||
if (!this.canResolve) return;
|
||||
|
||||
let promise;
|
||||
|
@ -79,24 +77,20 @@ const ResolveBtn = Vue.extend({
|
|||
.resolve(this.noteId);
|
||||
}
|
||||
|
||||
promise.then((response) => {
|
||||
this.loading = false;
|
||||
promise
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
this.loading = false;
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.json();
|
||||
const resolved_by = data ? data.resolved_by : null;
|
||||
|
||||
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
|
||||
this.discussion.updateHeadline(data);
|
||||
gl.mrWidget.checkStatus();
|
||||
} else {
|
||||
new Flash(errorFlashMsg);
|
||||
}
|
||||
|
||||
this.updateTooltip();
|
||||
}).catch(() => {
|
||||
new Flash(errorFlashMsg);
|
||||
});
|
||||
this.updateTooltip();
|
||||
})
|
||||
.catch(() => new Flash('An error occurred when trying to resolve a comment. Please try again.'));
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
|
||||
/* global Flash */
|
||||
/* global CommentsStore */
|
||||
|
||||
|
@ -32,27 +31,22 @@ class ResolveServiceClass {
|
|||
promise = this.resolveAll(mergeRequestId, discussionId);
|
||||
}
|
||||
|
||||
promise.then((response) => {
|
||||
discussion.loading = false;
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.json();
|
||||
const resolved_by = data ? data.resolved_by : null;
|
||||
promise
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
discussion.loading = false;
|
||||
const resolvedBy = data ? data.resolved_by : null;
|
||||
|
||||
if (isResolved) {
|
||||
discussion.unResolveAllNotes();
|
||||
} else {
|
||||
discussion.resolveAllNotes(resolved_by);
|
||||
discussion.resolveAllNotes(resolvedBy);
|
||||
}
|
||||
|
||||
gl.mrWidget.checkStatus();
|
||||
discussion.updateHeadline(data);
|
||||
} else {
|
||||
throw new Error('An error occurred when trying to resolve discussion.');
|
||||
}
|
||||
}).catch(() => {
|
||||
new Flash('An error occurred when trying to resolve a discussion. Please try again.');
|
||||
});
|
||||
})
|
||||
.catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
|
||||
}
|
||||
|
||||
resolveAll(mergeRequestId, discussionId) {
|
||||
|
@ -62,7 +56,7 @@ class ResolveServiceClass {
|
|||
|
||||
return this.discussionResource.save({
|
||||
mergeRequestId,
|
||||
discussionId
|
||||
discussionId,
|
||||
}, {});
|
||||
}
|
||||
|
||||
|
@ -73,7 +67,7 @@ class ResolveServiceClass {
|
|||
|
||||
return this.discussionResource.delete({
|
||||
mergeRequestId,
|
||||
discussionId
|
||||
discussionId,
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
|
||||
/* global UsernameValidator */
|
||||
/* global ActiveTabMemoizer */
|
||||
/* global ShortcutsNavigation */
|
||||
/* global IssuableIndex */
|
||||
/* global ShortcutsIssuable */
|
||||
/* global ZenMode */
|
||||
/* global Milestone */
|
||||
/* global IssuableForm */
|
||||
/* global LabelsSelect */
|
||||
/* global MilestoneSelect */
|
||||
/* global Commit */
|
||||
/* global NotificationsForm */
|
||||
/* global TreeView */
|
||||
/* global NotificationsDropdown */
|
||||
/* global GroupAvatar */
|
||||
/* global LineHighlighter */
|
||||
|
@ -25,7 +21,6 @@
|
|||
/* global ProjectAvatar */
|
||||
/* global CompareAutocomplete */
|
||||
/* global ProjectNew */
|
||||
/* global Star */
|
||||
/* global ProjectShow */
|
||||
/* global Labels */
|
||||
/* global Shortcuts */
|
||||
|
@ -54,8 +49,19 @@ import UsersSelect from './users_select';
|
|||
import RefSelectDropdown from './ref_select_dropdown';
|
||||
import GfmAutoComplete from './gfm_auto_complete';
|
||||
import ShortcutsBlob from './shortcuts_blob';
|
||||
import SigninTabsMemoizer from './signin_tabs_memoizer';
|
||||
import Star from './star';
|
||||
import Todos from './todos';
|
||||
import TreeView from './tree';
|
||||
import UsagePing from './usage_ping';
|
||||
import UsernameValidator from './username_validator';
|
||||
import VersionCheckImage from './version_check_image';
|
||||
import Wikis from './wikis';
|
||||
import ZenMode from './zen_mode';
|
||||
import initSettingsPanels from './settings_panels';
|
||||
import initExperimentalFlags from './experimental_flags';
|
||||
import OAuthRememberMe from './oauth_remember_me';
|
||||
import PerformanceBar from './performance_bar';
|
||||
|
||||
(function() {
|
||||
var Dispatcher;
|
||||
|
@ -126,7 +132,8 @@ import initExperimentalFlags from './experimental_flags';
|
|||
break;
|
||||
case 'sessions:new':
|
||||
new UsernameValidator();
|
||||
new ActiveTabMemoizer();
|
||||
new SigninTabsMemoizer();
|
||||
new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
|
||||
break;
|
||||
case 'projects:boards:show':
|
||||
case 'projects:boards:index':
|
||||
|
@ -161,7 +168,7 @@ import initExperimentalFlags from './experimental_flags';
|
|||
new UsersSelect();
|
||||
break;
|
||||
case 'dashboard:todos:index':
|
||||
new gl.Todos();
|
||||
new Todos();
|
||||
break;
|
||||
case 'dashboard:projects:index':
|
||||
case 'dashboard:projects:starred':
|
||||
|
@ -315,7 +322,7 @@ import initExperimentalFlags from './experimental_flags';
|
|||
new gl.Members();
|
||||
new UsersSelect();
|
||||
break;
|
||||
case 'projects:settings:members:show':
|
||||
case 'projects:project_members:index':
|
||||
new gl.MemberExpirationDate('.js-access-expiration-date-groups');
|
||||
new GroupsSelect();
|
||||
new gl.MemberExpirationDate();
|
||||
|
@ -377,7 +384,7 @@ import initExperimentalFlags from './experimental_flags';
|
|||
new BlobViewer();
|
||||
break;
|
||||
case 'help:index':
|
||||
gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
|
||||
VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
|
||||
break;
|
||||
case 'search:show':
|
||||
new Search();
|
||||
|
@ -393,6 +400,7 @@ import initExperimentalFlags from './experimental_flags';
|
|||
initSettingsPanels();
|
||||
break;
|
||||
case 'projects:settings:ci_cd:show':
|
||||
case 'groups:settings:ci_cd:show':
|
||||
new gl.ProjectVariables();
|
||||
break;
|
||||
case 'ci:lints:create':
|
||||
|
@ -429,7 +437,7 @@ import initExperimentalFlags from './experimental_flags';
|
|||
new Admin();
|
||||
switch (path[1]) {
|
||||
case 'cohorts':
|
||||
new gl.UsagePing();
|
||||
new UsagePing();
|
||||
break;
|
||||
case 'groups':
|
||||
new UsersSelect();
|
||||
|
@ -481,7 +489,7 @@ import initExperimentalFlags from './experimental_flags';
|
|||
new NotificationsDropdown();
|
||||
break;
|
||||
case 'wikis':
|
||||
new gl.Wikis();
|
||||
new Wikis();
|
||||
shortcut_handler = new ShortcutsWiki();
|
||||
new ZenMode();
|
||||
new gl.GLForm($('.wiki-form'), true);
|
||||
|
@ -513,6 +521,10 @@ import initExperimentalFlags from './experimental_flags';
|
|||
if (!shortcut_handler) {
|
||||
new Shortcuts();
|
||||
}
|
||||
|
||||
if (document.querySelector('#peek')) {
|
||||
new PerformanceBar({ container: '#peek' });
|
||||
}
|
||||
};
|
||||
|
||||
Dispatcher.prototype.initSearch = function() {
|
||||
|
|
|
@ -32,7 +32,6 @@ export default {
|
|||
state: store.state,
|
||||
visibility: 'available',
|
||||
isLoading: false,
|
||||
isLoadingFolderContent: false,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
endpoint: environmentsData.environmentsDataEndpoint,
|
||||
canCreateDeployment: environmentsData.canCreateDeployment,
|
||||
|
@ -86,9 +85,6 @@ export default {
|
|||
errorCallback: this.errorCallback,
|
||||
notificationCallback: (isMakingRequest) => {
|
||||
this.isMakingRequest = isMakingRequest;
|
||||
|
||||
// We need to verify if any folder is open to also fecth it
|
||||
this.openFolders = this.store.getOpenFolders();
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -119,7 +115,7 @@ export default {
|
|||
this.store.toggleFolder(folder);
|
||||
|
||||
if (!folder.isOpen) {
|
||||
this.fetchChildEnvironments(folder, folderUrl);
|
||||
this.fetchChildEnvironments(folder, folderUrl, true);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -147,19 +143,17 @@ export default {
|
|||
.catch(this.errorCallback);
|
||||
},
|
||||
|
||||
fetchChildEnvironments(folder, folderUrl) {
|
||||
this.isLoadingFolderContent = true;
|
||||
fetchChildEnvironments(folder, folderUrl, showLoader = false) {
|
||||
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
|
||||
|
||||
this.service.getFolderContent(folderUrl)
|
||||
.then(resp => resp.json())
|
||||
.then((response) => {
|
||||
this.store.setfolderContent(folder, response.environments);
|
||||
this.isLoadingFolderContent = false;
|
||||
})
|
||||
.then(response => this.store.setfolderContent(folder, response.environments))
|
||||
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
|
||||
.catch(() => {
|
||||
this.isLoadingFolderContent = false;
|
||||
// eslint-disable-next-line no-new
|
||||
new Flash('An error occurred while fetching the environments.');
|
||||
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -176,13 +170,13 @@ export default {
|
|||
successCallback(resp) {
|
||||
this.saveData(resp);
|
||||
|
||||
// If folders are open while polling we need to open them again
|
||||
if (this.openFolders.length) {
|
||||
this.openFolders.map((folder) => {
|
||||
// We need to verify if any folder is open to also update it
|
||||
const openFolders = this.store.getOpenFolders();
|
||||
if (openFolders.length) {
|
||||
openFolders.forEach((folder) => {
|
||||
// TODO - Move this to the backend
|
||||
const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
|
||||
|
||||
this.store.updateFolder(folder, 'isOpen', true);
|
||||
return this.fetchChildEnvironments(folder, folderUrl);
|
||||
});
|
||||
}
|
||||
|
@ -267,7 +261,7 @@ export default {
|
|||
:environments="state.environments"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:is-loading-folder-content="isLoadingFolderContent" />
|
||||
/>
|
||||
</div>
|
||||
|
||||
<table-pagination
|
||||
|
|
|
@ -498,9 +498,9 @@ export default {
|
|||
<div class="table-section section-15 hidden-xs hidden-sm" role="gridcell">
|
||||
<a
|
||||
v-if="shouldRenderBuildName"
|
||||
class="build-link"
|
||||
class="build-link flex-truncate-parent"
|
||||
:href="buildPath">
|
||||
{{buildName}}
|
||||
<span class="flex-truncate-child">{{buildName}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -29,12 +29,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
isLoadingFolderContent: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -74,7 +68,7 @@ export default {
|
|||
/>
|
||||
|
||||
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
|
||||
<div v-if="isLoadingFolderContent">
|
||||
<div v-if="model.isLoadingFolderContent">
|
||||
<loading-icon size="2" />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
export default {
|
||||
methods: {
|
||||
saveData(resp) {
|
||||
const response = {
|
||||
headers: resp.headers,
|
||||
body: resp.json(),
|
||||
};
|
||||
const headers = resp.headers;
|
||||
return resp.json().then((response) => {
|
||||
this.isLoading = false;
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
this.store.storeAvailableCount(response.body.available_count);
|
||||
this.store.storeStoppedCount(response.body.stopped_count);
|
||||
this.store.storeEnvironments(response.body.environments);
|
||||
this.store.setPagination(response.headers);
|
||||
this.store.storeAvailableCount(response.available_count);
|
||||
this.store.storeStoppedCount(response.stopped_count);
|
||||
this.store.storeEnvironments(response.environments);
|
||||
this.store.setPagination(headers);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -35,14 +35,18 @@ export default class EnvironmentsStore {
|
|||
*/
|
||||
storeEnvironments(environments = []) {
|
||||
const filteredEnvironments = environments.map((env) => {
|
||||
const oldEnvironmentState = this.state.environments
|
||||
.find(element => element.id === env.latest.id) || {};
|
||||
|
||||
let filtered = {};
|
||||
|
||||
if (env.size > 1) {
|
||||
filtered = Object.assign({}, env, {
|
||||
isFolder: true,
|
||||
isLoadingFolderContent: oldEnvironmentState.isLoading || false,
|
||||
folderName: env.name,
|
||||
isOpen: false,
|
||||
children: [],
|
||||
isOpen: oldEnvironmentState.isOpen || false,
|
||||
children: oldEnvironmentState.children || [],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -98,7 +102,7 @@ export default class EnvironmentsStore {
|
|||
* @return {Array}
|
||||
*/
|
||||
toggleFolder(folder) {
|
||||
return this.updateFolder(folder, 'isOpen', !folder.isOpen);
|
||||
return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,23 +129,23 @@ export default class EnvironmentsStore {
|
|||
return updated;
|
||||
});
|
||||
|
||||
return this.updateFolder(folder, 'children', updatedEnvironments);
|
||||
return this.updateEnvironmentProp(folder, 'children', updatedEnvironments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a folder a prop and a new value updates the correct folder.
|
||||
* Given a environment, a prop and a new value updates the correct environment.
|
||||
*
|
||||
* @param {Object} folder
|
||||
* @param {Object} environment
|
||||
* @param {String} prop
|
||||
* @param {String|Boolean|Object|Array} newValue
|
||||
* @return {Array}
|
||||
*/
|
||||
updateFolder(folder, prop, newValue) {
|
||||
updateEnvironmentProp(environment, prop, newValue) {
|
||||
const environments = this.state.environments;
|
||||
|
||||
const updatedEnvironments = environments.map((env) => {
|
||||
const updateEnv = Object.assign({}, env);
|
||||
if (env.isFolder && env.id === folder.id) {
|
||||
if (env.id === environment.id) {
|
||||
updateEnv[prop] = newValue;
|
||||
}
|
||||
|
||||
|
@ -149,8 +153,6 @@ export default class EnvironmentsStore {
|
|||
});
|
||||
|
||||
this.state.environments = updatedEnvironments;
|
||||
|
||||
return updatedEnvironments;
|
||||
}
|
||||
|
||||
getOpenFolders() {
|
||||
|
|
|
@ -7,5 +7,8 @@ export default () => {
|
|||
Cookies.set(el.name, el.value, {
|
||||
expires: 365 * 10,
|
||||
});
|
||||
|
||||
document.body.scrollTop = 0;
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ class GfmAutoComplete {
|
|||
this.input.each((i, input) => {
|
||||
const $input = $(input);
|
||||
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
|
||||
$input.on('change.atwho', () => input.dispatchEvent(new Event('input')));
|
||||
// This triggers at.js again
|
||||
// Needed for quick actions with suffixes (ex: /label ~)
|
||||
$input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
|
||||
|
|
|
@ -99,8 +99,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
page: currentPath,
|
||||
}, document.title, currentPath);
|
||||
|
||||
this.updateGroups(response.json());
|
||||
this.updatePagination(response.headers);
|
||||
return response.json().then((data) => {
|
||||
this.updateGroups(data);
|
||||
this.updatePagination(response.headers);
|
||||
});
|
||||
})
|
||||
.catch(this.handleErrorResponse);
|
||||
},
|
||||
|
@ -114,18 +116,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
},
|
||||
leaveGroup(group, collection) {
|
||||
this.service.leaveGroup(group.leavePath)
|
||||
.then(resp => resp.json())
|
||||
.then((response) => {
|
||||
$.scrollTo(0);
|
||||
|
||||
this.store.removeGroup(group, collection);
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Flash(response.json().notice, 'notice');
|
||||
new Flash(response.notice, 'notice');
|
||||
})
|
||||
.catch((response) => {
|
||||
.catch((error) => {
|
||||
let message = 'An error occurred. Please try again.';
|
||||
|
||||
if (response.status === 403) {
|
||||
if (error.status === 403) {
|
||||
message = 'Failed to leave the group. Please make sure you are not the only owner';
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import CloseReopenReportToggle from '../close_reopen_report_toggle';
|
||||
|
||||
function initCloseReopenReport() {
|
||||
const container = document.querySelector('.js-issuable-close-dropdown');
|
||||
|
||||
if (!container) return undefined;
|
||||
|
||||
const dropdownTrigger = container.querySelector('.js-issuable-close-toggle');
|
||||
const dropdownList = container.querySelector('.js-issuable-close-menu');
|
||||
const button = container.querySelector('.js-issuable-close-button');
|
||||
|
||||
const closeReopenReportToggle = new CloseReopenReportToggle({
|
||||
dropdownTrigger,
|
||||
dropdownList,
|
||||
button,
|
||||
});
|
||||
|
||||
closeReopenReportToggle.initDroplab();
|
||||
|
||||
return closeReopenReportToggle;
|
||||
}
|
||||
|
||||
const IssuablesHelper = {
|
||||
initCloseReopenReport,
|
||||
};
|
||||
|
||||
export default IssuablesHelper;
|
|
@ -1,12 +1,12 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
|
||||
/* global GitLab */
|
||||
/* global ZenMode */
|
||||
/* global Autosave */
|
||||
/* global dateFormat */
|
||||
/* global Pikaday */
|
||||
|
||||
import UsersSelect from './users_select';
|
||||
import GfmAutoComplete from './gfm_auto_complete';
|
||||
import ZenMode from './zen_mode';
|
||||
|
||||
(function() {
|
||||
this.IssuableForm = (function() {
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
import 'vendor/jquery.waitforimages';
|
||||
import '~/lib/utils/text_utility';
|
||||
import './flash';
|
||||
import './task_list';
|
||||
import TaskList from './task_list';
|
||||
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
|
||||
import IssuablesHelper from './helpers/issuables_helper';
|
||||
|
||||
class Issue {
|
||||
constructor() {
|
||||
if ($('a.btn-close').length) {
|
||||
this.taskList = new gl.TaskList({
|
||||
this.taskList = new TaskList({
|
||||
dataType: 'issue',
|
||||
fieldName: 'description',
|
||||
selector: '.detail-page-description',
|
||||
|
@ -28,6 +29,11 @@ class Issue {
|
|||
Issue.initMergeRequests();
|
||||
Issue.initRelatedBranches();
|
||||
|
||||
this.closeButtons = $('a.btn-close');
|
||||
this.reopenButtons = $('a.btn-reopen');
|
||||
|
||||
this.initCloseReopenReport();
|
||||
|
||||
if (Issue.createMrDropdownWrap) {
|
||||
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
|
||||
}
|
||||
|
@ -35,13 +41,8 @@ class Issue {
|
|||
|
||||
initIssueBtnEventListeners() {
|
||||
const issueFailMessage = 'Unable to update this issue at this time.';
|
||||
const closeButtons = $('a.btn-close');
|
||||
const isClosedBadge = $('div.status-box-closed');
|
||||
const isOpenBadge = $('div.status-box-open');
|
||||
const projectIssuesCounter = $('.issue_counter');
|
||||
const reopenButtons = $('a.btn-reopen');
|
||||
|
||||
return closeButtons.add(reopenButtons).on('click', (e) => {
|
||||
return $(document).on('click', 'a.btn-close, a.btn-reopen', (e) => {
|
||||
var $button, shouldSubmit, url;
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
@ -50,7 +51,9 @@ class Issue {
|
|||
if (shouldSubmit) {
|
||||
Issue.submitNoteForm($button.closest('form'));
|
||||
}
|
||||
$button.prop('disabled', true);
|
||||
|
||||
this.disableCloseReopenButton($button);
|
||||
|
||||
url = $button.attr('href');
|
||||
return $.ajax({
|
||||
type: 'PUT',
|
||||
|
@ -58,15 +61,19 @@ class Issue {
|
|||
})
|
||||
.fail(() => new Flash(issueFailMessage))
|
||||
.done((data) => {
|
||||
const isClosedBadge = $('div.status-box-closed');
|
||||
const isOpenBadge = $('div.status-box-open');
|
||||
const projectIssuesCounter = $('.issue_counter');
|
||||
|
||||
if ('id' in data) {
|
||||
$(document).trigger('issuable:change');
|
||||
|
||||
const isClosed = $button.hasClass('btn-close');
|
||||
closeButtons.toggleClass('hidden', isClosed);
|
||||
reopenButtons.toggleClass('hidden', !isClosed);
|
||||
isClosedBadge.toggleClass('hidden', !isClosed);
|
||||
isOpenBadge.toggleClass('hidden', isClosed);
|
||||
|
||||
this.toggleCloseReopenButton(isClosed);
|
||||
|
||||
let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
|
||||
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
|
||||
projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
|
||||
|
@ -83,12 +90,34 @@ class Issue {
|
|||
} else {
|
||||
new Flash(issueFailMessage);
|
||||
}
|
||||
|
||||
$button.prop('disabled', false);
|
||||
})
|
||||
.then(() => {
|
||||
this.disableCloseReopenButton($button, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initCloseReopenReport() {
|
||||
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
|
||||
|
||||
if (this.closeButtons) this.closeButtons = this.closeButtons.not('.issuable-close-button');
|
||||
if (this.reopenButtons) this.reopenButtons = this.reopenButtons.not('.issuable-close-button');
|
||||
}
|
||||
|
||||
disableCloseReopenButton($button, shouldDisable) {
|
||||
if (this.closeReopenReportToggle) {
|
||||
this.closeReopenReportToggle.setDisable(shouldDisable);
|
||||
} else {
|
||||
$button.prop('disabled', shouldDisable);
|
||||
}
|
||||
}
|
||||
|
||||
toggleCloseReopenButton(isClosed) {
|
||||
if (this.closeReopenReportToggle) this.closeReopenReportToggle.updateButton(isClosed);
|
||||
this.closeButtons.toggleClass('hidden', isClosed);
|
||||
this.reopenButtons.toggleClass('hidden', !isClosed);
|
||||
}
|
||||
|
||||
static submitNoteForm(form) {
|
||||
var noteText;
|
||||
noteText = form.find("textarea.js-note-text").val();
|
||||
|
|
|
@ -202,10 +202,7 @@ export default {
|
|||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'getData',
|
||||
successCallback: (res) => {
|
||||
const data = res.json();
|
||||
this.store.updateState(data);
|
||||
},
|
||||
successCallback: res => res.json().then(data => this.store.updateState(data)),
|
||||
errorCallback(err) {
|
||||
throw new Error(err);
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import animateMixin from '../mixins/animate';
|
||||
import TaskList from '../../task_list';
|
||||
|
||||
export default {
|
||||
mixins: [animateMixin],
|
||||
|
@ -46,7 +47,7 @@
|
|||
|
||||
if (this.canUpdate) {
|
||||
// eslint-disable-next-line no-new
|
||||
new gl.TaskList({
|
||||
new TaskList({
|
||||
dataType: 'issue',
|
||||
fieldName: 'description',
|
||||
selector: '.detail-page-description',
|
||||
|
|
|
@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
mounted() {
|
||||
this.mediator.initBuildClass();
|
||||
},
|
||||
updated() {
|
||||
// Wait for flash message to be appended
|
||||
Vue.nextTick(() => {
|
||||
if (this.mediator.build) {
|
||||
this.mediator.build.verifyTopPosition();
|
||||
}
|
||||
});
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('job-header', {
|
||||
props: {
|
||||
|
|
|
@ -54,9 +54,8 @@ export default class JobMediator {
|
|||
}
|
||||
|
||||
successCallback(response) {
|
||||
const data = response.json();
|
||||
this.state.isLoading = false;
|
||||
this.store.storeJob(data);
|
||||
return response.json().then(data => this.store.storeJob(data));
|
||||
}
|
||||
|
||||
errorCallback() {
|
||||
|
|
|
@ -143,26 +143,12 @@ import './render_math';
|
|||
import './right_sidebar';
|
||||
import './search';
|
||||
import './search_autocomplete';
|
||||
import './signin_tabs_memoizer';
|
||||
import './single_file_diff';
|
||||
import './smart_interval';
|
||||
import './snippets_list';
|
||||
import './star';
|
||||
import './subscription';
|
||||
import './subscription_select';
|
||||
import './syntax_highlight';
|
||||
import './task_list';
|
||||
import './todos';
|
||||
import './tree';
|
||||
import './usage_ping';
|
||||
import './user';
|
||||
import './user_tabs';
|
||||
import './username_validator';
|
||||
import './users_select';
|
||||
import './version_check_image';
|
||||
import './visibility_select';
|
||||
import './wikis';
|
||||
import './zen_mode';
|
||||
|
||||
// eslint-disable-next-line global-require, import/no-commonjs
|
||||
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
/* global MergeRequestTabs */
|
||||
|
||||
import 'vendor/jquery.waitforimages';
|
||||
import './task_list';
|
||||
import TaskList from './task_list';
|
||||
import './merge_request_tabs';
|
||||
import IssuablesHelper from './helpers/issuables_helper';
|
||||
|
||||
(function() {
|
||||
this.MergeRequest = (function() {
|
||||
|
@ -21,11 +22,14 @@ import './merge_request_tabs';
|
|||
return _this.showAllCommits();
|
||||
};
|
||||
})(this));
|
||||
|
||||
this.initTabs();
|
||||
this.initMRBtnListeners();
|
||||
this.initCommitMessageListeners();
|
||||
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
|
||||
|
||||
if ($("a.btn-close").length) {
|
||||
this.taskList = new gl.TaskList({
|
||||
this.taskList = new TaskList({
|
||||
dataType: 'merge_request',
|
||||
fieldName: 'description',
|
||||
selector: '.detail-page-description',
|
||||
|
@ -64,11 +68,15 @@ import './merge_request_tabs';
|
|||
if (shouldSubmit && $this.data('submitted')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
|
||||
|
||||
if (shouldSubmit) {
|
||||
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return _this.submitNoteForm($this.closest('form'), $this);
|
||||
|
||||
_this.submitNoteForm($this.closest('form'), $this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
graphHeight: 500,
|
||||
graphHeight: 450,
|
||||
graphWidth: 600,
|
||||
graphHeightOffset: 120,
|
||||
xScale: {},
|
||||
|
@ -88,7 +88,9 @@
|
|||
},
|
||||
|
||||
paddingBottomRootSvg() {
|
||||
return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0;
|
||||
return {
|
||||
paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -103,9 +105,9 @@
|
|||
this.measurements = measurements.small;
|
||||
}
|
||||
this.data = query.result[0].values;
|
||||
this.unitOfDisplay = query.unit || 'N/A';
|
||||
this.unitOfDisplay = query.unit || '';
|
||||
this.yAxisLabel = this.columnData.y_label || 'Values';
|
||||
this.legendTitle = query.legend || 'Average';
|
||||
this.legendTitle = query.label || 'Average';
|
||||
this.graphWidth = this.$refs.baseSvg.clientWidth -
|
||||
this.margin.left - this.margin.right;
|
||||
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
|
||||
|
@ -157,12 +159,12 @@
|
|||
|
||||
const xAxis = d3.svg.axis()
|
||||
.scale(axisXScale)
|
||||
.ticks(measurements.ticks)
|
||||
.ticks(measurements.xTicks)
|
||||
.orient('bottom');
|
||||
|
||||
const yAxis = d3.svg.axis()
|
||||
.scale(this.yScale)
|
||||
.ticks(measurements.ticks)
|
||||
.ticks(measurements.yTicks)
|
||||
.orient('left');
|
||||
|
||||
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
|
||||
|
@ -170,8 +172,12 @@
|
|||
const width = this.graphWidth;
|
||||
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
|
||||
.selectAll('.tick')
|
||||
.each(function createTickLines() {
|
||||
d3.select(this).select('line').attr('x2', width);
|
||||
.each(function createTickLines(d, i) {
|
||||
if (i > 0) {
|
||||
d3.select(this).select('line')
|
||||
.attr('x2', width)
|
||||
.attr('class', 'axis-tick');
|
||||
} // Avoid adding the class to the first tick, to prevent coloring
|
||||
}); // This will select all of the ticks once they're rendered
|
||||
|
||||
this.xScale = d3.time.scale()
|
||||
|
@ -198,7 +204,7 @@
|
|||
watch: {
|
||||
updateAspectRatio() {
|
||||
if (this.updateAspectRatio) {
|
||||
this.graphHeight = 500;
|
||||
this.graphHeight = 450;
|
||||
this.graphWidth = 600;
|
||||
this.measurements = measurements.large;
|
||||
this.draw();
|
||||
|
@ -213,17 +219,17 @@
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
<div
|
||||
:class="classType">
|
||||
<h5
|
||||
class="text-center">
|
||||
<h5
|
||||
class="text-center graph-title">
|
||||
{{columnData.title}}
|
||||
</h5>
|
||||
<div
|
||||
class="prometheus-svg-container">
|
||||
<svg
|
||||
<div
|
||||
class="prometheus-svg-container"
|
||||
:style="paddingBottomRootSvg">
|
||||
<svg
|
||||
:viewBox="outterViewBox"
|
||||
:style="{ 'padding-bottom': paddingBottomRootSvg }"
|
||||
ref="baseSvg">
|
||||
<g
|
||||
class="x-axis"
|
||||
|
@ -233,7 +239,7 @@
|
|||
class="y-axis"
|
||||
transform="translate(70, 20)">
|
||||
</g>
|
||||
<monitoring-legends
|
||||
<monitoring-legends
|
||||
:graph-width="graphWidth"
|
||||
:graph-height="graphHeight"
|
||||
:margin="margin"
|
||||
|
@ -243,7 +249,7 @@
|
|||
:y-axis-label="yAxisLabel"
|
||||
:metric-usage="metricUsage"
|
||||
/>
|
||||
<svg
|
||||
<svg
|
||||
class="graph-data"
|
||||
:viewBox="innerViewBox"
|
||||
ref="graphData">
|
||||
|
@ -261,7 +267,7 @@
|
|||
stroke-width="2"
|
||||
transform="translate(-5, 20)">
|
||||
</path>
|
||||
<rect
|
||||
<rect
|
||||
class="prometheus-graph-overlay"
|
||||
:width="(graphWidth - 70)"
|
||||
:height="(graphHeight - 100)"
|
||||
|
@ -275,7 +281,7 @@
|
|||
:graph-height="graphHeight"
|
||||
:graph-height-offset="graphHeightOffset"
|
||||
/>
|
||||
<monitoring-flag
|
||||
<monitoring-flag
|
||||
v-if="showFlag"
|
||||
:current-x-coordinate="currentXCoordinate"
|
||||
:current-y-coordinate="currentYCoordinate"
|
||||
|
|
|
@ -87,14 +87,14 @@
|
|||
</rect>
|
||||
<text
|
||||
class="text-metric text-metric-bold"
|
||||
x="8"
|
||||
x="16"
|
||||
y="35"
|
||||
transform="translate(-5, 20)">
|
||||
{{formatTime}}
|
||||
</text>
|
||||
<text
|
||||
class="text-metric-date"
|
||||
x="8"
|
||||
class="text-metric"
|
||||
x="16"
|
||||
y="15"
|
||||
transform="translate(-5, 20)">
|
||||
{{formatDate}}
|
||||
|
|
|
@ -109,13 +109,13 @@
|
|||
</text>
|
||||
<rect
|
||||
class="rect-axis-text"
|
||||
:x="xPosition + 50"
|
||||
:x="xPosition + 60"
|
||||
:y="graphHeight - 80"
|
||||
width="50"
|
||||
width="35"
|
||||
height="50">
|
||||
</rect>
|
||||
<text
|
||||
class="label-axis-text"
|
||||
class="label-axis-text x-label-text"
|
||||
:x="xPosition + 60"
|
||||
:y="yPosition"
|
||||
dy=".35em">
|
||||
|
@ -131,13 +131,13 @@
|
|||
<text
|
||||
class="text-metric-title"
|
||||
x="50"
|
||||
:y="graphHeight - 40">
|
||||
:y="graphHeight - 25">
|
||||
{{legendTitle}}
|
||||
</text>
|
||||
<text
|
||||
class="text-metric-usage"
|
||||
x="50"
|
||||
:y="graphHeight - 25">
|
||||
:y="graphHeight - 10">
|
||||
{{metricUsage}}
|
||||
</text>
|
||||
</g>
|
||||
|
|
|
@ -8,14 +8,14 @@ export default {
|
|||
},
|
||||
legends: {
|
||||
width: 15,
|
||||
height: 30,
|
||||
height: 25,
|
||||
},
|
||||
backgroundLegend: {
|
||||
width: 30,
|
||||
height: 50,
|
||||
},
|
||||
axisLabelLineOffset: -20,
|
||||
legendOffset: 52,
|
||||
legendOffset: 35,
|
||||
},
|
||||
large: { // This covers both md and lg screen sizes
|
||||
margin: {
|
||||
|
@ -26,14 +26,15 @@ export default {
|
|||
},
|
||||
legends: {
|
||||
width: 20,
|
||||
height: 35,
|
||||
height: 30,
|
||||
},
|
||||
backgroundLegend: {
|
||||
width: 30,
|
||||
height: 150,
|
||||
},
|
||||
axisLabelLineOffset: 20,
|
||||
legendOffset: 55,
|
||||
legendOffset: 38,
|
||||
},
|
||||
ticks: 3,
|
||||
xTicks: 8,
|
||||
yTicks: 3,
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ import CommentTypeToggle from './comment_type_toggle';
|
|||
import loadAwardsHandler from './awards_handler';
|
||||
import './autosave';
|
||||
import './dropzone_input';
|
||||
import './task_list';
|
||||
import TaskList from './task_list';
|
||||
|
||||
window.autosize = autosize;
|
||||
window.Dropzone = Dropzone;
|
||||
|
@ -71,7 +71,7 @@ export default class Notes {
|
|||
this.addBinding();
|
||||
this.setPollingInterval();
|
||||
this.setupMainTargetNoteForm();
|
||||
this.taskList = new gl.TaskList({
|
||||
this.taskList = new TaskList({
|
||||
dataType: 'note',
|
||||
fieldName: 'note',
|
||||
selector: '.notes'
|
||||
|
@ -1270,7 +1270,7 @@ export default class Notes {
|
|||
<div class="timeline-entry-inner">
|
||||
<div class="timeline-icon">
|
||||
<a href="/${currentUsername}">
|
||||
<img class="avatar s40" src="${currentUserAvatar}">
|
||||
<img class="avatar s40" src="${currentUserAvatar}" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="timeline-content ${discussionClass}">
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* OAuth-based login buttons have a separate "remember me" checkbox.
|
||||
*
|
||||
* Toggling this checkbox adds/removes a `remember_me` parameter to the
|
||||
* login buttons' href, which is passed on to the omniauth callback.
|
||||
**/
|
||||
|
||||
export default class OAuthRememberMe {
|
||||
constructor(opts = {}) {
|
||||
this.container = opts.container || '';
|
||||
this.loginLinkSelector = '.oauth-login';
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
$('#remember_me', this.container).on('click', this.toggleRememberMe);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
toggleRememberMe(event) {
|
||||
const rememberMe = $(event.target).is(':checked');
|
||||
|
||||
$('.oauth-login', this.container).each((i, element) => {
|
||||
const href = $(element).attr('href');
|
||||
|
||||
if (rememberMe) {
|
||||
$(element).attr('href', `${href}?remember_me=1`);
|
||||
} else {
|
||||
$(element).attr('href', href.replace('?remember_me=1', ''));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import 'vendor/peek';
|
||||
import 'vendor/peek.performance_bar';
|
||||
|
||||
$(document).on('click', '#peek-show-queries', (e) => {
|
||||
e.preventDefault();
|
||||
$('.peek-rblineprof-modal').hide();
|
||||
const $modal = $('#modal-peek-pg-queries');
|
||||
if ($modal.length) {
|
||||
$modal.modal('toggle');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.js-lineprof-file', (e) => {
|
||||
e.preventDefault();
|
||||
$(e.target).parents('.peek-rblineprof-file').find('.data').toggle();
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
import 'vendor/peek';
|
||||
import 'vendor/peek.performance_bar';
|
||||
|
||||
export default class PerformanceBar {
|
||||
constructor(opts) {
|
||||
if (!PerformanceBar.singleton) {
|
||||
this.init(opts);
|
||||
PerformanceBar.singleton = this;
|
||||
}
|
||||
return PerformanceBar.singleton;
|
||||
}
|
||||
|
||||
init(opts) {
|
||||
const $container = $(opts.container);
|
||||
this.$sqlProfileLink = $container.find('.js-toggle-modal-peek-sql');
|
||||
this.$sqlProfileModal = $container.find('#modal-peek-pg-queries');
|
||||
this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
|
||||
this.$lineProfileModal = $('#modal-peek-line-profile');
|
||||
this.initEventListeners();
|
||||
this.showModalOnLoad();
|
||||
}
|
||||
|
||||
initEventListeners() {
|
||||
this.$sqlProfileLink.on('click', () => this.handleSQLProfileLink());
|
||||
this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
|
||||
$(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
|
||||
}
|
||||
|
||||
showModalOnLoad() {
|
||||
// When a lineprofiler query-string param is present, we show the line
|
||||
// profiler modal upon page load
|
||||
if (/lineprofiler/.test(window.location.search)) {
|
||||
PerformanceBar.toggleModal(this.$lineProfileModal);
|
||||
}
|
||||
}
|
||||
|
||||
handleSQLProfileLink() {
|
||||
PerformanceBar.toggleModal(this.$sqlProfileModal);
|
||||
}
|
||||
|
||||
handleLineProfileLink(e) {
|
||||
const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler');
|
||||
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
|
||||
const shouldToggleModal = lineProfilerParameter.length > 0 &&
|
||||
lineProfilerParameterRegex.test(e.currentTarget.href);
|
||||
|
||||
if (shouldToggleModal) {
|
||||
e.preventDefault();
|
||||
PerformanceBar.toggleModal(this.$lineProfileModal);
|
||||
}
|
||||
}
|
||||
|
||||
static toggleModal($modal) {
|
||||
if ($modal.length) {
|
||||
$modal.modal('toggle');
|
||||
}
|
||||
}
|
||||
|
||||
static toggleLineProfileFile(e) {
|
||||
$(e.currentTarget).parents('.peek-rblineprof-file').find('.data').toggle();
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import Translate from '../vue_shared/translate';
|
|||
import intervalPatternInput from './components/interval_pattern_input.vue';
|
||||
import TimezoneDropdown from './components/timezone_dropdown';
|
||||
import TargetBranchDropdown from './components/target_branch_dropdown';
|
||||
import { setupPipelineVariableList } from './setup_pipeline_variable_list';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
|
@ -39,4 +40,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
gl.timezoneDropdown = new TimezoneDropdown();
|
||||
gl.targetBranchDropdown = new TargetBranchDropdown();
|
||||
gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement);
|
||||
|
||||
setupPipelineVariableList($('.js-pipeline-variable-list'));
|
||||
});
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
function insertRow($row) {
|
||||
const $rowClone = $row.clone();
|
||||
$rowClone.removeAttr('data-is-persisted');
|
||||
$rowClone.find('input, textarea').val('');
|
||||
$row.after($rowClone);
|
||||
}
|
||||
|
||||
function removeRow($row) {
|
||||
const isPersisted = gl.utils.convertPermissionToBoolean($row.attr('data-is-persisted'));
|
||||
|
||||
if (isPersisted) {
|
||||
$row.hide();
|
||||
$row
|
||||
.find('.js-destroy-input')
|
||||
.val(1);
|
||||
} else {
|
||||
$row.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfRowTouched($row) {
|
||||
return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
|
||||
}
|
||||
|
||||
function setupPipelineVariableList(parent = document) {
|
||||
const $parent = $(parent);
|
||||
|
||||
$parent.on('click', '.js-row-remove-button', (e) => {
|
||||
const $row = $(e.currentTarget).closest('.js-row');
|
||||
removeRow($row);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// Remove any empty rows except the last r
|
||||
$parent.on('blur', '.js-user-input', (e) => {
|
||||
const $row = $(e.currentTarget).closest('.js-row');
|
||||
|
||||
const isTouched = checkIfRowTouched($row);
|
||||
if ($row.is(':not(:last-child)') && !isTouched) {
|
||||
removeRow($row);
|
||||
}
|
||||
});
|
||||
|
||||
// Always make sure there is an empty last row
|
||||
$parent.on('input', '.js-user-input', () => {
|
||||
const $lastRow = $parent.find('.js-row').last();
|
||||
|
||||
const isTouched = checkIfRowTouched($lastRow);
|
||||
if (isTouched) {
|
||||
insertRow($lastRow);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear out the empty last row so it
|
||||
// doesn't get submitted and throw validation errors
|
||||
$parent.closest('form').on('submit', () => {
|
||||
const $lastRow = $parent.find('.js-row').last();
|
||||
|
||||
const isTouched = checkIfRowTouched($lastRow);
|
||||
if (!isTouched) {
|
||||
$lastRow.find('input, textarea').attr('name', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
setupPipelineVariableList,
|
||||
insertRow,
|
||||
removeRow,
|
||||
};
|
|
@ -129,14 +129,11 @@
|
|||
},
|
||||
|
||||
successCallback(resp) {
|
||||
const response = {
|
||||
headers: resp.headers,
|
||||
body: resp.json(),
|
||||
};
|
||||
|
||||
this.store.storeCount(response.body.count);
|
||||
this.store.storePagination(response.headers);
|
||||
this.setCommonData(response.body.pipelines);
|
||||
return resp.json().then((response) => {
|
||||
this.store.storeCount(response.count);
|
||||
this.store.storePagination(resp.headers);
|
||||
this.setCommonData(response.pipelines);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -73,8 +73,9 @@ export default {
|
|||
|
||||
fetchJobs() {
|
||||
this.$http.get(this.stage.dropdown_path)
|
||||
.then((response) => {
|
||||
this.dropdownContent = response.json().html;
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
this.dropdownContent = data.html;
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
|
@ -40,10 +40,10 @@ export default class pipelinesMediator {
|
|||
}
|
||||
|
||||
successCallback(response) {
|
||||
const data = response.json();
|
||||
|
||||
this.state.isLoading = false;
|
||||
this.store.storePipeline(data);
|
||||
return response.json().then((data) => {
|
||||
this.state.isLoading = false;
|
||||
this.store.storePipeline(data);
|
||||
});
|
||||
}
|
||||
|
||||
errorCallback() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */
|
||||
|
||||
import VisibilitySelect from './visibility_select';
|
||||
|
||||
function highlightChanges($elm) {
|
||||
$elm.addClass('highlight-changes');
|
||||
setTimeout(() => $elm.removeClass('highlight-changes'), 10);
|
||||
|
@ -30,7 +32,7 @@ function highlightChanges($elm) {
|
|||
ProjectNew.prototype.initVisibilitySelect = function() {
|
||||
const visibilityContainer = document.querySelector('.js-visibility-select');
|
||||
if (!visibilityContainer) return;
|
||||
const visibilitySelect = new gl.VisibilitySelect(visibilityContainer);
|
||||
const visibilitySelect = new VisibilitySelect(visibilityContainer);
|
||||
visibilitySelect.init();
|
||||
|
||||
const $visibilitySelect = $(visibilityContainer).find('select');
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
|
||||
/* global Mousetrap */
|
||||
/* global findFileURL */
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import findAndFollowLink from './shortcuts_dashboard_navigation';
|
||||
|
@ -20,6 +19,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
|
|||
|
||||
const $globalDropdownMenu = $('.global-dropdown-menu');
|
||||
const $globalDropdownToggle = $('.global-dropdown-toggle');
|
||||
const findFileURL = document.body.dataset.findFile;
|
||||
|
||||
$('.global-dropdown').on('hide.bs.dropdown', () => {
|
||||
$globalDropdownMenu.removeClass('shortcuts');
|
||||
|
@ -62,7 +62,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
|
|||
if (Cookies.get(performanceBarCookieName) === 'true') {
|
||||
Cookies.remove(performanceBarCookieName, { path: '/' });
|
||||
} else {
|
||||
Cookies.set(performanceBarCookieName, true, { path: '/' });
|
||||
Cookies.set(performanceBarCookieName, 'true', { path: '/' });
|
||||
}
|
||||
gl.utils.refreshCurrentPage();
|
||||
};
|
||||
|
|
|
@ -28,8 +28,8 @@ export default class SidebarMediator {
|
|||
|
||||
fetch() {
|
||||
this.service.get()
|
||||
.then((response) => {
|
||||
const data = response.json();
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
this.store.setAssigneeData(data);
|
||||
this.store.setTimeTrackingData(data);
|
||||
})
|
||||
|
|
|
@ -2,56 +2,52 @@
|
|||
/* eslint no-new: "off" */
|
||||
import AccessorUtilities from './lib/utils/accessor';
|
||||
|
||||
((global) => {
|
||||
/**
|
||||
* Memorize the last selected tab after reloading a page.
|
||||
* Does that setting the current selected tab in the localStorage
|
||||
*/
|
||||
class ActiveTabMemoizer {
|
||||
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
|
||||
this.currentTabKey = currentTabKey;
|
||||
this.tabSelector = tabSelector;
|
||||
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
|
||||
/**
|
||||
* Memorize the last selected tab after reloading a page.
|
||||
* Does that setting the current selected tab in the localStorage
|
||||
*/
|
||||
export default class SigninTabsMemoizer {
|
||||
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
|
||||
this.currentTabKey = currentTabKey;
|
||||
this.tabSelector = tabSelector;
|
||||
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
|
||||
|
||||
this.bootstrap();
|
||||
}
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
const tabs = document.querySelectorAll(this.tabSelector);
|
||||
if (tabs.length > 0) {
|
||||
tabs[0].addEventListener('click', (e) => {
|
||||
if (e.target && e.target.nodeName === 'A') {
|
||||
const anchorName = e.target.getAttribute('href');
|
||||
this.saveData(anchorName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.showTab();
|
||||
}
|
||||
|
||||
showTab() {
|
||||
const anchorName = this.readData();
|
||||
if (anchorName) {
|
||||
const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
|
||||
if (tab) {
|
||||
tab.click();
|
||||
bootstrap() {
|
||||
const tabs = document.querySelectorAll(this.tabSelector);
|
||||
if (tabs.length > 0) {
|
||||
tabs[0].addEventListener('click', (e) => {
|
||||
if (e.target && e.target.nodeName === 'A') {
|
||||
const anchorName = e.target.getAttribute('href');
|
||||
this.saveData(anchorName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.showTab();
|
||||
}
|
||||
|
||||
showTab() {
|
||||
const anchorName = this.readData();
|
||||
if (anchorName) {
|
||||
const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
|
||||
if (tab) {
|
||||
tab.click();
|
||||
}
|
||||
}
|
||||
|
||||
saveData(val) {
|
||||
if (!this.isLocalStorageAvailable) return undefined;
|
||||
|
||||
return window.localStorage.setItem(this.currentTabKey, val);
|
||||
}
|
||||
|
||||
readData() {
|
||||
if (!this.isLocalStorageAvailable) return null;
|
||||
|
||||
return window.localStorage.getItem(this.currentTabKey);
|
||||
}
|
||||
}
|
||||
|
||||
global.ActiveTabMemoizer = ActiveTabMemoizer;
|
||||
})(window);
|
||||
saveData(val) {
|
||||
if (!this.isLocalStorageAvailable) return undefined;
|
||||
|
||||
return window.localStorage.setItem(this.currentTabKey, val);
|
||||
}
|
||||
|
||||
readData() {
|
||||
if (!this.isLocalStorageAvailable) return null;
|
||||
|
||||
return window.localStorage.getItem(this.currentTabKey);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,99 +2,82 @@
|
|||
|
||||
import FilesCommentButton from './files_comment_button';
|
||||
|
||||
(function() {
|
||||
window.SingleFileDiff = (function() {
|
||||
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
|
||||
const WRAPPER = '<div class="diff-content"></div>';
|
||||
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
|
||||
const ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
|
||||
const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
|
||||
|
||||
WRAPPER = '<div class="diff-content"></div>';
|
||||
|
||||
LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
|
||||
|
||||
ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
|
||||
|
||||
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
|
||||
|
||||
function SingleFileDiff(file) {
|
||||
this.file = file;
|
||||
this.toggleDiff = this.toggleDiff.bind(this);
|
||||
this.content = $('.diff-content', this.file);
|
||||
this.$toggleIcon = $('.diff-toggle-caret', this.file);
|
||||
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
|
||||
this.isOpen = !this.diffForPath;
|
||||
if (this.diffForPath) {
|
||||
this.collapsedContent = this.content;
|
||||
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
|
||||
this.content = null;
|
||||
this.collapsedContent.after(this.loadingContent);
|
||||
this.$toggleIcon.addClass('fa-caret-right');
|
||||
} else {
|
||||
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
|
||||
this.content.after(this.collapsedContent);
|
||||
this.$toggleIcon.addClass('fa-caret-down');
|
||||
}
|
||||
|
||||
$('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
|
||||
this.toggleDiff($(e.target));
|
||||
}).bind(this));
|
||||
export default class SingleFileDiff {
|
||||
constructor(file) {
|
||||
this.file = file;
|
||||
this.toggleDiff = this.toggleDiff.bind(this);
|
||||
this.content = $('.diff-content', this.file);
|
||||
this.$toggleIcon = $('.diff-toggle-caret', this.file);
|
||||
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
|
||||
this.isOpen = !this.diffForPath;
|
||||
if (this.diffForPath) {
|
||||
this.collapsedContent = this.content;
|
||||
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
|
||||
this.content = null;
|
||||
this.collapsedContent.after(this.loadingContent);
|
||||
this.$toggleIcon.addClass('fa-caret-right');
|
||||
} else {
|
||||
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
|
||||
this.content.after(this.collapsedContent);
|
||||
this.$toggleIcon.addClass('fa-caret-down');
|
||||
}
|
||||
|
||||
SingleFileDiff.prototype.toggleDiff = function($target, cb) {
|
||||
if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
|
||||
this.isOpen = !this.isOpen;
|
||||
if (!this.isOpen && !this.hasError) {
|
||||
this.content.hide();
|
||||
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
|
||||
this.collapsedContent.show();
|
||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||
gl.diffNotesCompileComponents();
|
||||
}
|
||||
} else if (this.content) {
|
||||
this.collapsedContent.hide();
|
||||
this.content.show();
|
||||
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
|
||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||
gl.diffNotesCompileComponents();
|
||||
}
|
||||
} else {
|
||||
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
|
||||
return this.getContentHTML(cb);
|
||||
}
|
||||
};
|
||||
$('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
|
||||
this.toggleDiff($(e.target));
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
SingleFileDiff.prototype.getContentHTML = function(cb) {
|
||||
toggleDiff($target, cb) {
|
||||
if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
|
||||
this.isOpen = !this.isOpen;
|
||||
if (!this.isOpen && !this.hasError) {
|
||||
this.content.hide();
|
||||
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
|
||||
this.collapsedContent.show();
|
||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||
gl.diffNotesCompileComponents();
|
||||
}
|
||||
} else if (this.content) {
|
||||
this.collapsedContent.hide();
|
||||
this.loadingContent.show();
|
||||
$.get(this.diffForPath, (function(_this) {
|
||||
return function(data) {
|
||||
_this.loadingContent.hide();
|
||||
if (data.html) {
|
||||
_this.content = $(data.html);
|
||||
_this.content.syntaxHighlight();
|
||||
} else {
|
||||
_this.hasError = true;
|
||||
_this.content = $(ERROR_HTML);
|
||||
}
|
||||
_this.collapsedContent.after(_this.content);
|
||||
|
||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||
gl.diffNotesCompileComponents();
|
||||
}
|
||||
|
||||
FilesCommentButton.init($(_this.file));
|
||||
|
||||
if (cb) cb();
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
return SingleFileDiff;
|
||||
})();
|
||||
|
||||
$.fn.singleFileDiff = function() {
|
||||
return this.each(function() {
|
||||
if (!$.data(this, 'singleFileDiff')) {
|
||||
return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
|
||||
this.content.show();
|
||||
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
|
||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||
gl.diffNotesCompileComponents();
|
||||
}
|
||||
});
|
||||
};
|
||||
}).call(window);
|
||||
} else {
|
||||
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
|
||||
return this.getContentHTML(cb);
|
||||
}
|
||||
}
|
||||
|
||||
getContentHTML(cb) {
|
||||
this.collapsedContent.hide();
|
||||
this.loadingContent.show();
|
||||
$.get(this.diffForPath, (function(_this) {
|
||||
return function(data) {
|
||||
_this.loadingContent.hide();
|
||||
if (data.html) {
|
||||
_this.content = $(data.html);
|
||||
_this.content.syntaxHighlight();
|
||||
} else {
|
||||
_this.hasError = true;
|
||||
_this.content = $(ERROR_HTML);
|
||||
}
|
||||
_this.collapsedContent.after(_this.content);
|
||||
|
||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||
gl.diffNotesCompileComponents();
|
||||
}
|
||||
|
||||
FilesCommentButton.init($(_this.file));
|
||||
|
||||
if (cb) cb();
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,158 +1,157 @@
|
|||
/*
|
||||
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
|
||||
* and controllable by a public API.
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
|
||||
* and controllable by a public API.
|
||||
*/
|
||||
|
||||
(() => {
|
||||
class SmartInterval {
|
||||
/**
|
||||
* @param { function } opts.callback Function to be called on each iteration (required)
|
||||
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
|
||||
* @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
|
||||
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
|
||||
* when the page is hidden
|
||||
* @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
|
||||
* @param { boolean } opts.lazyStart Configure if timer is initialized on
|
||||
* instantiation or lazily
|
||||
* @param { boolean } opts.immediateExecution Configure if callback should
|
||||
* be executed before the first interval.
|
||||
*/
|
||||
constructor(opts = {}) {
|
||||
this.cfg = {
|
||||
callback: opts.callback,
|
||||
startingInterval: opts.startingInterval,
|
||||
maxInterval: opts.maxInterval,
|
||||
hiddenInterval: opts.hiddenInterval,
|
||||
incrementByFactorOf: opts.incrementByFactorOf,
|
||||
lazyStart: opts.lazyStart,
|
||||
immediateExecution: opts.immediateExecution,
|
||||
};
|
||||
class SmartInterval {
|
||||
/**
|
||||
* @param { function } opts.callback Function to be called on each iteration (required)
|
||||
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
|
||||
* @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
|
||||
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
|
||||
* when the page is hidden
|
||||
* @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
|
||||
* @param { boolean } opts.lazyStart Configure if timer is initialized on
|
||||
* instantiation or lazily
|
||||
* @param { boolean } opts.immediateExecution Configure if callback should
|
||||
* be executed before the first interval.
|
||||
*/
|
||||
constructor(opts = {}) {
|
||||
this.cfg = {
|
||||
callback: opts.callback,
|
||||
startingInterval: opts.startingInterval,
|
||||
maxInterval: opts.maxInterval,
|
||||
hiddenInterval: opts.hiddenInterval,
|
||||
incrementByFactorOf: opts.incrementByFactorOf,
|
||||
lazyStart: opts.lazyStart,
|
||||
immediateExecution: opts.immediateExecution,
|
||||
};
|
||||
|
||||
this.state = {
|
||||
intervalId: null,
|
||||
currentInterval: this.cfg.startingInterval,
|
||||
pageVisibility: 'visible',
|
||||
};
|
||||
this.state = {
|
||||
intervalId: null,
|
||||
currentInterval: this.cfg.startingInterval,
|
||||
pageVisibility: 'visible',
|
||||
};
|
||||
|
||||
this.initInterval();
|
||||
this.initInterval();
|
||||
}
|
||||
|
||||
/* public */
|
||||
|
||||
start() {
|
||||
const cfg = this.cfg;
|
||||
const state = this.state;
|
||||
|
||||
if (cfg.immediateExecution) {
|
||||
cfg.immediateExecution = false;
|
||||
cfg.callback();
|
||||
}
|
||||
/* public */
|
||||
|
||||
start() {
|
||||
const cfg = this.cfg;
|
||||
const state = this.state;
|
||||
state.intervalId = window.setInterval(() => {
|
||||
cfg.callback();
|
||||
|
||||
if (cfg.immediateExecution) {
|
||||
cfg.immediateExecution = false;
|
||||
cfg.callback();
|
||||
if (this.getCurrentInterval() === cfg.maxInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.intervalId = window.setInterval(() => {
|
||||
cfg.callback();
|
||||
this.incrementInterval();
|
||||
this.resume();
|
||||
}, this.getCurrentInterval());
|
||||
}
|
||||
|
||||
if (this.getCurrentInterval() === cfg.maxInterval) {
|
||||
return;
|
||||
}
|
||||
// cancel the existing timer, setting the currentInterval back to startingInterval
|
||||
cancel() {
|
||||
this.setCurrentInterval(this.cfg.startingInterval);
|
||||
this.stopTimer();
|
||||
}
|
||||
|
||||
this.incrementInterval();
|
||||
this.resume();
|
||||
}, this.getCurrentInterval());
|
||||
}
|
||||
|
||||
// cancel the existing timer, setting the currentInterval back to startingInterval
|
||||
cancel() {
|
||||
this.setCurrentInterval(this.cfg.startingInterval);
|
||||
this.stopTimer();
|
||||
}
|
||||
|
||||
onVisibilityHidden() {
|
||||
if (this.cfg.hiddenInterval) {
|
||||
this.setCurrentInterval(this.cfg.hiddenInterval);
|
||||
this.resume();
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// start a timer, using the existing interval
|
||||
resume() {
|
||||
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
|
||||
this.start();
|
||||
}
|
||||
|
||||
onVisibilityVisible() {
|
||||
onVisibilityHidden() {
|
||||
if (this.cfg.hiddenInterval) {
|
||||
this.setCurrentInterval(this.cfg.hiddenInterval);
|
||||
this.resume();
|
||||
} else {
|
||||
this.cancel();
|
||||
this.start();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.cancel();
|
||||
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
|
||||
$(document).off('visibilitychange').off('beforeunload');
|
||||
}
|
||||
|
||||
/* private */
|
||||
|
||||
initInterval() {
|
||||
const cfg = this.cfg;
|
||||
|
||||
if (!cfg.lazyStart) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
this.initVisibilityChangeHandling();
|
||||
this.initPageUnloadHandling();
|
||||
}
|
||||
|
||||
initVisibilityChangeHandling() {
|
||||
// cancel interval when tab no longer shown (prevents cached pages from polling)
|
||||
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
||||
}
|
||||
|
||||
initPageUnloadHandling() {
|
||||
// TODO: Consider refactoring in light of turbolinks removal.
|
||||
// prevent interval continuing after page change, when kept in cache by Turbolinks
|
||||
$(document).on('beforeunload', () => this.cancel());
|
||||
}
|
||||
|
||||
handleVisibilityChange(e) {
|
||||
this.state.pageVisibility = e.target.visibilityState;
|
||||
const intervalAction = this.isPageVisible() ?
|
||||
this.onVisibilityVisible :
|
||||
this.onVisibilityHidden;
|
||||
|
||||
intervalAction.apply(this);
|
||||
}
|
||||
|
||||
getCurrentInterval() {
|
||||
return this.state.currentInterval;
|
||||
}
|
||||
|
||||
setCurrentInterval(newInterval) {
|
||||
this.state.currentInterval = newInterval;
|
||||
}
|
||||
|
||||
incrementInterval() {
|
||||
const cfg = this.cfg;
|
||||
const currentInterval = this.getCurrentInterval();
|
||||
if (cfg.hiddenInterval && !this.isPageVisible()) return;
|
||||
let nextInterval = currentInterval * cfg.incrementByFactorOf;
|
||||
|
||||
if (nextInterval > cfg.maxInterval) {
|
||||
nextInterval = cfg.maxInterval;
|
||||
}
|
||||
|
||||
this.setCurrentInterval(nextInterval);
|
||||
}
|
||||
|
||||
isPageVisible() { return this.state.pageVisibility === 'visible'; }
|
||||
|
||||
stopTimer() {
|
||||
const state = this.state;
|
||||
|
||||
state.intervalId = window.clearInterval(state.intervalId);
|
||||
}
|
||||
}
|
||||
gl.SmartInterval = SmartInterval;
|
||||
})(window.gl || (window.gl = {}));
|
||||
|
||||
// start a timer, using the existing interval
|
||||
resume() {
|
||||
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
|
||||
this.start();
|
||||
}
|
||||
|
||||
onVisibilityVisible() {
|
||||
this.cancel();
|
||||
this.start();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.cancel();
|
||||
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
|
||||
$(document).off('visibilitychange').off('beforeunload');
|
||||
}
|
||||
|
||||
/* private */
|
||||
|
||||
initInterval() {
|
||||
const cfg = this.cfg;
|
||||
|
||||
if (!cfg.lazyStart) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
this.initVisibilityChangeHandling();
|
||||
this.initPageUnloadHandling();
|
||||
}
|
||||
|
||||
initVisibilityChangeHandling() {
|
||||
// cancel interval when tab no longer shown (prevents cached pages from polling)
|
||||
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
||||
}
|
||||
|
||||
initPageUnloadHandling() {
|
||||
// TODO: Consider refactoring in light of turbolinks removal.
|
||||
// prevent interval continuing after page change, when kept in cache by Turbolinks
|
||||
$(document).on('beforeunload', () => this.cancel());
|
||||
}
|
||||
|
||||
handleVisibilityChange(e) {
|
||||
this.state.pageVisibility = e.target.visibilityState;
|
||||
const intervalAction = this.isPageVisible() ?
|
||||
this.onVisibilityVisible :
|
||||
this.onVisibilityHidden;
|
||||
|
||||
intervalAction.apply(this);
|
||||
}
|
||||
|
||||
getCurrentInterval() {
|
||||
return this.state.currentInterval;
|
||||
}
|
||||
|
||||
setCurrentInterval(newInterval) {
|
||||
this.state.currentInterval = newInterval;
|
||||
}
|
||||
|
||||
incrementInterval() {
|
||||
const cfg = this.cfg;
|
||||
const currentInterval = this.getCurrentInterval();
|
||||
if (cfg.hiddenInterval && !this.isPageVisible()) return;
|
||||
let nextInterval = currentInterval * cfg.incrementByFactorOf;
|
||||
|
||||
if (nextInterval > cfg.maxInterval) {
|
||||
nextInterval = cfg.maxInterval;
|
||||
}
|
||||
|
||||
this.setCurrentInterval(nextInterval);
|
||||
}
|
||||
|
||||
isPageVisible() { return this.state.pageVisibility === 'visible'; }
|
||||
|
||||
stopTimer() {
|
||||
const state = this.state;
|
||||
|
||||
state.intervalId = window.clearInterval(state.intervalId);
|
||||
}
|
||||
}
|
||||
|
||||
window.gl.SmartInterval = SmartInterval;
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
|
||||
function SnippetsList() {
|
||||
const $holder = $('.snippets-list-holder');
|
||||
|
||||
(global => {
|
||||
global.gl = global.gl || {};
|
||||
$holder.find('.pagination').on('ajax:success', (e, data) => {
|
||||
$holder.replaceWith(data.html);
|
||||
});
|
||||
}
|
||||
|
||||
gl.SnippetsList = function() {
|
||||
var $holder = $('.snippets-list-holder');
|
||||
|
||||
$holder.find('.pagination').on('ajax:success', (e, data) => {
|
||||
$holder.replaceWith(data.html);
|
||||
});
|
||||
};
|
||||
})(window);
|
||||
window.gl.SnippetsList = SnippetsList;
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
|
||||
/* global Flash */
|
||||
|
||||
(function() {
|
||||
this.Star = (function() {
|
||||
function Star() {
|
||||
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
|
||||
var $starIcon, $starSpan, $this, toggleStar;
|
||||
$this = $(this);
|
||||
$starSpan = $this.find('span');
|
||||
$starIcon = $this.find('i');
|
||||
toggleStar = function(isStarred) {
|
||||
$this.parent().find('.star-count').text(data.star_count);
|
||||
if (isStarred) {
|
||||
$starSpan.removeClass('starred').text('Star');
|
||||
$starIcon.removeClass('fa-star').addClass('fa-star-o');
|
||||
} else {
|
||||
$starSpan.addClass('starred').text('Unstar');
|
||||
$starIcon.removeClass('fa-star-o').addClass('fa-star');
|
||||
}
|
||||
};
|
||||
toggleStar($starSpan.hasClass('starred'));
|
||||
}).on('ajax:error', function(e, xhr, status, error) {
|
||||
new Flash('Star toggle failed. Try again later.', 'alert');
|
||||
});
|
||||
}
|
||||
|
||||
return Star;
|
||||
})();
|
||||
}).call(window);
|
||||
export default class Star {
|
||||
constructor() {
|
||||
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
|
||||
var $starIcon, $starSpan, $this, toggleStar;
|
||||
$this = $(this);
|
||||
$starSpan = $this.find('span');
|
||||
$starIcon = $this.find('i');
|
||||
toggleStar = function(isStarred) {
|
||||
$this.parent().find('.star-count').text(data.star_count);
|
||||
if (isStarred) {
|
||||
$starSpan.removeClass('starred').text('Star');
|
||||
$starIcon.removeClass('fa-star').addClass('fa-star-o');
|
||||
} else {
|
||||
$starSpan.addClass('starred').text('Unstar');
|
||||
$starIcon.removeClass('fa-star-o').addClass('fa-star');
|
||||
}
|
||||
};
|
||||
toggleStar($starSpan.hasClass('starred'));
|
||||
}).on('ajax:error', function(e, xhr, status, error) {
|
||||
new Flash('Star toggle failed. Try again later.', 'alert');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,47 +1,45 @@
|
|||
(() => {
|
||||
class Subscription {
|
||||
constructor(containerElm) {
|
||||
this.containerElm = containerElm;
|
||||
class Subscription {
|
||||
constructor(containerElm) {
|
||||
this.containerElm = containerElm;
|
||||
|
||||
const subscribeButton = containerElm.querySelector('.js-subscribe-button');
|
||||
if (subscribeButton) {
|
||||
// remove class so we don't bind twice
|
||||
subscribeButton.classList.remove('js-subscribe-button');
|
||||
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
toggleSubscription(event) {
|
||||
const button = event.currentTarget;
|
||||
const buttonSpan = button.querySelector('span');
|
||||
if (!buttonSpan || button.classList.contains('disabled')) {
|
||||
return;
|
||||
}
|
||||
button.classList.add('disabled');
|
||||
|
||||
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
|
||||
const toggleActionUrl = this.containerElm.dataset.url;
|
||||
|
||||
$.post(toggleActionUrl, () => {
|
||||
button.classList.remove('disabled');
|
||||
|
||||
// hack to allow this to work with the issue boards Vue object
|
||||
if (document.querySelector('html').classList.contains('issue-boards-page')) {
|
||||
gl.issueBoards.boardStoreIssueSet(
|
||||
'subscribed',
|
||||
!gl.issueBoards.BoardsStore.detail.issue.subscribed,
|
||||
);
|
||||
} else {
|
||||
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static bindAll(selector) {
|
||||
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
|
||||
const subscribeButton = containerElm.querySelector('.js-subscribe-button');
|
||||
if (subscribeButton) {
|
||||
// remove class so we don't bind twice
|
||||
subscribeButton.classList.remove('js-subscribe-button');
|
||||
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.Subscription = Subscription;
|
||||
})();
|
||||
toggleSubscription(event) {
|
||||
const button = event.currentTarget;
|
||||
const buttonSpan = button.querySelector('span');
|
||||
if (!buttonSpan || button.classList.contains('disabled')) {
|
||||
return;
|
||||
}
|
||||
button.classList.add('disabled');
|
||||
|
||||
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
|
||||
const toggleActionUrl = this.containerElm.dataset.url;
|
||||
|
||||
$.post(toggleActionUrl, () => {
|
||||
button.classList.remove('disabled');
|
||||
|
||||
// hack to allow this to work with the issue boards Vue object
|
||||
if (document.querySelector('html').classList.contains('issue-boards-page')) {
|
||||
gl.issueBoards.boardStoreIssueSet(
|
||||
'subscribed',
|
||||
!gl.issueBoards.BoardsStore.detail.issue.subscribed,
|
||||
);
|
||||
} else {
|
||||
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static bindAll(selector) {
|
||||
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.Subscription = Subscription;
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
|
||||
(function() {
|
||||
this.SubscriptionSelect = (function() {
|
||||
function SubscriptionSelect() {
|
||||
$('.js-subscription-event').each(function(i, el) {
|
||||
var fieldName;
|
||||
fieldName = $(el).data("field-name");
|
||||
return $(el).glDropdown({
|
||||
selectable: true,
|
||||
fieldName: fieldName,
|
||||
toggleLabel: (function(_this) {
|
||||
return function(selected, el, instance) {
|
||||
var $item, label;
|
||||
label = 'Subscription';
|
||||
$item = instance.dropdown.find('.is-active');
|
||||
if ($item.length) {
|
||||
label = $item.text();
|
||||
}
|
||||
return label;
|
||||
};
|
||||
})(this),
|
||||
clicked: function(options) {
|
||||
return options.e.preventDefault();
|
||||
},
|
||||
id: function(obj, el) {
|
||||
return $(el).data("id");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return SubscriptionSelect;
|
||||
})();
|
||||
}).call(window);
|
||||
class SubscriptionSelect {
|
||||
constructor() {
|
||||
$('.js-subscription-event').each(function(i, el) {
|
||||
var fieldName;
|
||||
fieldName = $(el).data("field-name");
|
||||
return $(el).glDropdown({
|
||||
selectable: true,
|
||||
fieldName: fieldName,
|
||||
toggleLabel: (function(_this) {
|
||||
return function(selected, el, instance) {
|
||||
var $item, label;
|
||||
label = 'Subscription';
|
||||
$item = instance.dropdown.find('.is-active');
|
||||
if ($item.length) {
|
||||
label = $item.text();
|
||||
}
|
||||
return label;
|
||||
};
|
||||
})(this),
|
||||
clicked: function(options) {
|
||||
return options.e.preventDefault();
|
||||
},
|
||||
id: function(obj, el) {
|
||||
return $(el).data("id");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.SubscriptionSelect = SubscriptionSelect;
|
||||
|
|
|
@ -9,19 +9,18 @@
|
|||
//
|
||||
// <div class="js-syntax-highlight"></div>
|
||||
//
|
||||
(function() {
|
||||
$.fn.syntaxHighlight = function() {
|
||||
var $children;
|
||||
|
||||
if ($(this).hasClass('js-syntax-highlight')) {
|
||||
// Given the element itself, apply highlighting
|
||||
return $(this).addClass(gon.user_color_scheme);
|
||||
} else {
|
||||
// Given a parent element, recurse to any of its applicable children
|
||||
$children = $(this).find('.js-syntax-highlight');
|
||||
if ($children.length) {
|
||||
return $children.syntaxHighlight();
|
||||
}
|
||||
$.fn.syntaxHighlight = function() {
|
||||
var $children;
|
||||
|
||||
if ($(this).hasClass('js-syntax-highlight')) {
|
||||
// Given the element itself, apply highlighting
|
||||
return $(this).addClass(gon.user_color_scheme);
|
||||
} else {
|
||||
// Given a parent element, recurse to any of its applicable children
|
||||
$children = $(this).find('.js-syntax-highlight');
|
||||
if ($children.length) {
|
||||
return $children.syntaxHighlight();
|
||||
}
|
||||
};
|
||||
}).call(window);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import 'deckar01-task_list';
|
||||
|
||||
class TaskList {
|
||||
export default class TaskList {
|
||||
constructor(options = {}) {
|
||||
this.selector = options.selector;
|
||||
this.dataType = options.dataType;
|
||||
|
@ -48,6 +48,3 @@ class TaskList {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.TaskList = TaskList;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import UsersSelect from './users_select';
|
||||
|
||||
class Todos {
|
||||
export default class Todos {
|
||||
constructor() {
|
||||
this.initFilters();
|
||||
this.bindEvents();
|
||||
|
@ -159,6 +159,3 @@ class Todos {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.Todos = Todos;
|
||||
|
|
|
@ -1,68 +1,64 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */
|
||||
|
||||
(function() {
|
||||
this.TreeView = (function() {
|
||||
function TreeView() {
|
||||
this.initKeyNav();
|
||||
// Code browser tree slider
|
||||
// Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
|
||||
$(".tree-content-holder .tree-item").on('click', function(e) {
|
||||
var $clickedEl, path;
|
||||
$clickedEl = $(e.target);
|
||||
path = $('.tree-item-file-name a', this).attr('href');
|
||||
if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
|
||||
if (e.metaKey || e.which === 2) {
|
||||
e.preventDefault();
|
||||
return window.open(path, '_blank');
|
||||
} else {
|
||||
return gl.utils.visitUrl(path);
|
||||
}
|
||||
export default class TreeView {
|
||||
constructor() {
|
||||
this.initKeyNav();
|
||||
// Code browser tree slider
|
||||
// Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
|
||||
$(".tree-content-holder .tree-item").on('click', function(e) {
|
||||
var $clickedEl, path;
|
||||
$clickedEl = $(e.target);
|
||||
path = $('.tree-item-file-name a', this).attr('href');
|
||||
if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
|
||||
if (e.metaKey || e.which === 2) {
|
||||
e.preventDefault();
|
||||
return window.open(path, '_blank');
|
||||
} else {
|
||||
return gl.utils.visitUrl(path);
|
||||
}
|
||||
});
|
||||
// Show the "Loading commit data" for only the first element
|
||||
$('span.log_loading:first').removeClass('hide');
|
||||
}
|
||||
}
|
||||
});
|
||||
// Show the "Loading commit data" for only the first element
|
||||
$('span.log_loading:first').removeClass('hide');
|
||||
}
|
||||
|
||||
TreeView.prototype.initKeyNav = function() {
|
||||
var li, liSelected;
|
||||
li = $("tr.tree-item");
|
||||
liSelected = null;
|
||||
return $('body').keydown(function(e) {
|
||||
var next, path;
|
||||
if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
|
||||
return false;
|
||||
initKeyNav() {
|
||||
var li, liSelected;
|
||||
li = $("tr.tree-item");
|
||||
liSelected = null;
|
||||
return $('body').keydown(function(e) {
|
||||
var next, path;
|
||||
if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
|
||||
return false;
|
||||
}
|
||||
if (e.which === 40) {
|
||||
if (liSelected) {
|
||||
next = liSelected.next();
|
||||
if (next.length > 0) {
|
||||
liSelected.removeClass("selected");
|
||||
liSelected = next.addClass("selected");
|
||||
}
|
||||
} else {
|
||||
liSelected = li.eq(0).addClass("selected");
|
||||
}
|
||||
if (e.which === 40) {
|
||||
if (liSelected) {
|
||||
next = liSelected.next();
|
||||
if (next.length > 0) {
|
||||
liSelected.removeClass("selected");
|
||||
liSelected = next.addClass("selected");
|
||||
}
|
||||
} else {
|
||||
liSelected = li.eq(0).addClass("selected");
|
||||
}
|
||||
return $(liSelected).focus();
|
||||
} else if (e.which === 38) {
|
||||
if (liSelected) {
|
||||
next = liSelected.prev();
|
||||
if (next.length > 0) {
|
||||
liSelected.removeClass("selected");
|
||||
liSelected = next.addClass("selected");
|
||||
}
|
||||
} else {
|
||||
liSelected = li.last().addClass("selected");
|
||||
}
|
||||
return $(liSelected).focus();
|
||||
} else if (e.which === 13) {
|
||||
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
|
||||
if (path) {
|
||||
return gl.utils.visitUrl(path);
|
||||
return $(liSelected).focus();
|
||||
} else if (e.which === 38) {
|
||||
if (liSelected) {
|
||||
next = liSelected.prev();
|
||||
if (next.length > 0) {
|
||||
liSelected.removeClass("selected");
|
||||
liSelected = next.addClass("selected");
|
||||
}
|
||||
} else {
|
||||
liSelected = li.last().addClass("selected");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return TreeView;
|
||||
})();
|
||||
}).call(window);
|
||||
return $(liSelected).focus();
|
||||
} else if (e.which === 13) {
|
||||
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
|
||||
if (path) {
|
||||
return gl.utils.visitUrl(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
function UsagePing() {
|
||||
export default function UsagePing() {
|
||||
const usageDataUrl = $('.usage-data').data('endpoint');
|
||||
|
||||
$.ajax({
|
||||
|
@ -10,6 +10,3 @@ function UsagePing() {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.UsagePing = UsagePing;
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */
|
||||
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
((global) => {
|
||||
global.User = class {
|
||||
constructor({ action }) {
|
||||
this.action = action;
|
||||
this.placeProfileAvatarsToTop();
|
||||
this.initTabs();
|
||||
this.hideProjectLimitMessage();
|
||||
}
|
||||
|
||||
placeProfileAvatarsToTop() {
|
||||
$('.profile-groups-avatars').tooltip({
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
|
||||
initTabs() {
|
||||
return new global.UserTabs({
|
||||
parentEl: '.user-profile',
|
||||
action: this.action
|
||||
});
|
||||
}
|
||||
|
||||
hideProjectLimitMessage() {
|
||||
$('.hide-project-limit-message').on('click', e => {
|
||||
e.preventDefault();
|
||||
Cookies.set('hide_project_limit_message', 'false');
|
||||
$(this).parents('.project-limit-message').remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,175 +0,0 @@
|
|||
/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */
|
||||
|
||||
/*
|
||||
UserTabs
|
||||
|
||||
Handles persisting and restoring the current tab selection and lazily-loading
|
||||
content on the Users#show page.
|
||||
|
||||
### Example Markup
|
||||
|
||||
<ul class="nav-links">
|
||||
<li class="activity-tab active">
|
||||
<a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
|
||||
Activity
|
||||
</a>
|
||||
</li>
|
||||
<li class="groups-tab">
|
||||
<a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
|
||||
Groups
|
||||
</a>
|
||||
</li>
|
||||
<li class="contributed-tab">
|
||||
<a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
|
||||
Contributed projects
|
||||
</a>
|
||||
</li>
|
||||
<li class="projects-tab">
|
||||
<a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
|
||||
Personal projects
|
||||
</a>
|
||||
</li>
|
||||
<li class="snippets-tab">
|
||||
<a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane" id="activity">
|
||||
Activity Content
|
||||
</div>
|
||||
<div class="tab-pane" id="groups">
|
||||
Groups Content
|
||||
</div>
|
||||
<div class="tab-pane" id="contributed">
|
||||
Contributed projects content
|
||||
</div>
|
||||
<div class="tab-pane" id="projects">
|
||||
Projects content
|
||||
</div>
|
||||
<div class="tab-pane" id="snippets">
|
||||
Snippets content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading-status">
|
||||
<div class="loading">
|
||||
Loading Animation
|
||||
</div>
|
||||
</div>
|
||||
*/
|
||||
((global) => {
|
||||
class UserTabs {
|
||||
constructor ({ defaultAction, action, parentEl }) {
|
||||
this.loaded = {};
|
||||
this.defaultAction = defaultAction || 'activity';
|
||||
this.action = action || this.defaultAction;
|
||||
this.$parentEl = $(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() {
|
||||
this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
|
||||
|
||||
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));
|
||||
|
||||
this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
|
||||
}
|
||||
|
||||
changeProjectsPage(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('.tab-pane.active').empty();
|
||||
const endpoint = $(e.target).attr('href');
|
||||
this.loadTab(this.getCurrentAction(), endpoint);
|
||||
}
|
||||
|
||||
tabShown(event) {
|
||||
const $target = $(event.target);
|
||||
const action = $target.data('action');
|
||||
const source = $target.attr('href');
|
||||
const endpoint = $target.data('endpoint');
|
||||
this.setTab(action, endpoint);
|
||||
return this.setCurrentAction(source);
|
||||
}
|
||||
|
||||
activateTab(action) {
|
||||
return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
|
||||
.tab('show');
|
||||
}
|
||||
|
||||
setTab(action, endpoint) {
|
||||
if (this.loaded[action]) {
|
||||
return;
|
||||
}
|
||||
if (action === 'activity') {
|
||||
this.loadActivities();
|
||||
}
|
||||
|
||||
const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
|
||||
if (loadableActions.indexOf(action) > -1) {
|
||||
return this.loadTab(action, endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
loadTab(action, endpoint) {
|
||||
return $.ajax({
|
||||
beforeSend: () => this.toggleLoading(true),
|
||||
complete: () => this.toggleLoading(false),
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
url: endpoint,
|
||||
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() {
|
||||
if (this.loaded['activity']) {
|
||||
return;
|
||||
}
|
||||
const $calendarWrap = this.$parentEl.find('.user-calendar');
|
||||
$calendarWrap.load($calendarWrap.data('href'));
|
||||
new gl.Activities();
|
||||
return this.loaded['activity'] = true;
|
||||
}
|
||||
|
||||
toggleLoading(status) {
|
||||
return this.$parentEl.find('.loading-status .loading')
|
||||
.toggle(status);
|
||||
}
|
||||
|
||||
setCurrentAction(source) {
|
||||
let new_state = source;
|
||||
new_state = new_state.replace(/\/+$/, '');
|
||||
new_state += this._location.search + this._location.hash;
|
||||
history.replaceState({
|
||||
url: new_state
|
||||
}, document.title, new_state);
|
||||
return new_state;
|
||||
}
|
||||
|
||||
getCurrentAction() {
|
||||
return this.$parentEl.find('.nav-links .active a').data('action');
|
||||
}
|
||||
}
|
||||
global.UserTabs = UserTabs;
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,135 +1,131 @@
|
|||
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
|
||||
|
||||
((global) => {
|
||||
const debounceTimeoutDuration = 1000;
|
||||
const invalidInputClass = 'gl-field-error-outline';
|
||||
const successInputClass = 'gl-field-success-outline';
|
||||
const unavailableMessageSelector = '.username .validation-error';
|
||||
const successMessageSelector = '.username .validation-success';
|
||||
const pendingMessageSelector = '.username .validation-pending';
|
||||
const invalidMessageSelector = '.username .gl-field-error';
|
||||
const debounceTimeoutDuration = 1000;
|
||||
const invalidInputClass = 'gl-field-error-outline';
|
||||
const successInputClass = 'gl-field-success-outline';
|
||||
const unavailableMessageSelector = '.username .validation-error';
|
||||
const successMessageSelector = '.username .validation-success';
|
||||
const pendingMessageSelector = '.username .validation-pending';
|
||||
const invalidMessageSelector = '.username .gl-field-error';
|
||||
|
||||
class UsernameValidator {
|
||||
constructor() {
|
||||
this.inputElement = $('#new_user_username');
|
||||
this.inputDomElement = this.inputElement.get(0);
|
||||
this.state = {
|
||||
available: false,
|
||||
valid: false,
|
||||
pending: false,
|
||||
empty: true
|
||||
};
|
||||
export default class UsernameValidator {
|
||||
constructor() {
|
||||
this.inputElement = $('#new_user_username');
|
||||
this.inputDomElement = this.inputElement.get(0);
|
||||
this.state = {
|
||||
available: false,
|
||||
valid: false,
|
||||
pending: false,
|
||||
empty: true
|
||||
};
|
||||
|
||||
const debounceTimeout = _.debounce((username) => {
|
||||
this.validateUsername(username);
|
||||
}, debounceTimeoutDuration);
|
||||
const debounceTimeout = _.debounce((username) => {
|
||||
this.validateUsername(username);
|
||||
}, debounceTimeoutDuration);
|
||||
|
||||
this.inputElement.on('keyup.username_check', () => {
|
||||
const username = this.inputElement.val();
|
||||
this.inputElement.on('keyup.username_check', () => {
|
||||
const username = this.inputElement.val();
|
||||
|
||||
this.state.valid = this.inputDomElement.validity.valid;
|
||||
this.state.empty = !username.length;
|
||||
this.state.valid = this.inputDomElement.validity.valid;
|
||||
this.state.empty = !username.length;
|
||||
|
||||
if (this.state.valid) {
|
||||
return debounceTimeout(username);
|
||||
}
|
||||
|
||||
this.renderState();
|
||||
});
|
||||
|
||||
// Override generic field validation
|
||||
this.inputElement.on('invalid', this.interceptInvalid.bind(this));
|
||||
}
|
||||
|
||||
renderState() {
|
||||
// Clear all state
|
||||
this.clearFieldValidationState();
|
||||
|
||||
if (this.state.valid && this.state.available) {
|
||||
return this.setSuccessState();
|
||||
}
|
||||
|
||||
if (this.state.empty) {
|
||||
return this.clearFieldValidationState();
|
||||
}
|
||||
|
||||
if (this.state.pending) {
|
||||
return this.setPendingState();
|
||||
}
|
||||
|
||||
if (!this.state.available) {
|
||||
return this.setUnavailableState();
|
||||
}
|
||||
|
||||
if (!this.state.valid) {
|
||||
return this.setInvalidState();
|
||||
}
|
||||
}
|
||||
|
||||
interceptInvalid(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
validateUsername(username) {
|
||||
if (this.state.valid) {
|
||||
this.state.pending = true;
|
||||
this.state.available = false;
|
||||
this.renderState();
|
||||
return $.ajax({
|
||||
type: 'GET',
|
||||
url: `${gon.relative_url_root}/users/${username}/exists`,
|
||||
dataType: 'json',
|
||||
success: (res) => this.setAvailabilityState(res.exists)
|
||||
});
|
||||
return debounceTimeout(username);
|
||||
}
|
||||
}
|
||||
|
||||
setAvailabilityState(usernameTaken) {
|
||||
if (usernameTaken) {
|
||||
this.state.valid = false;
|
||||
this.state.available = false;
|
||||
} else {
|
||||
this.state.available = true;
|
||||
}
|
||||
this.state.pending = false;
|
||||
this.renderState();
|
||||
});
|
||||
|
||||
// Override generic field validation
|
||||
this.inputElement.on('invalid', this.interceptInvalid.bind(this));
|
||||
}
|
||||
|
||||
renderState() {
|
||||
// Clear all state
|
||||
this.clearFieldValidationState();
|
||||
|
||||
if (this.state.valid && this.state.available) {
|
||||
return this.setSuccessState();
|
||||
}
|
||||
|
||||
clearFieldValidationState() {
|
||||
this.inputElement.siblings('p').hide();
|
||||
|
||||
this.inputElement.removeClass(invalidInputClass)
|
||||
.removeClass(successInputClass);
|
||||
if (this.state.empty) {
|
||||
return this.clearFieldValidationState();
|
||||
}
|
||||
|
||||
setUnavailableState() {
|
||||
const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
|
||||
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
|
||||
$usernameUnavailableMessage.show();
|
||||
if (this.state.pending) {
|
||||
return this.setPendingState();
|
||||
}
|
||||
|
||||
setSuccessState() {
|
||||
const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
|
||||
this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
|
||||
$usernameSuccessMessage.show();
|
||||
if (!this.state.available) {
|
||||
return this.setUnavailableState();
|
||||
}
|
||||
|
||||
setPendingState() {
|
||||
const $usernamePendingMessage = $(pendingMessageSelector);
|
||||
if (this.state.pending) {
|
||||
$usernamePendingMessage.show();
|
||||
} else {
|
||||
$usernamePendingMessage.hide();
|
||||
}
|
||||
}
|
||||
|
||||
setInvalidState() {
|
||||
const $inputErrorMessage = $(invalidMessageSelector);
|
||||
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
|
||||
$inputErrorMessage.show();
|
||||
if (!this.state.valid) {
|
||||
return this.setInvalidState();
|
||||
}
|
||||
}
|
||||
|
||||
global.UsernameValidator = UsernameValidator;
|
||||
})(window);
|
||||
interceptInvalid(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
validateUsername(username) {
|
||||
if (this.state.valid) {
|
||||
this.state.pending = true;
|
||||
this.state.available = false;
|
||||
this.renderState();
|
||||
return $.ajax({
|
||||
type: 'GET',
|
||||
url: `${gon.relative_url_root}/users/${username}/exists`,
|
||||
dataType: 'json',
|
||||
success: (res) => this.setAvailabilityState(res.exists)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setAvailabilityState(usernameTaken) {
|
||||
if (usernameTaken) {
|
||||
this.state.valid = false;
|
||||
this.state.available = false;
|
||||
} else {
|
||||
this.state.available = true;
|
||||
}
|
||||
this.state.pending = false;
|
||||
this.renderState();
|
||||
}
|
||||
|
||||
clearFieldValidationState() {
|
||||
this.inputElement.siblings('p').hide();
|
||||
|
||||
this.inputElement.removeClass(invalidInputClass)
|
||||
.removeClass(successInputClass);
|
||||
}
|
||||
|
||||
setUnavailableState() {
|
||||
const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
|
||||
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
|
||||
$usernameUnavailableMessage.show();
|
||||
}
|
||||
|
||||
setSuccessState() {
|
||||
const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
|
||||
this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
|
||||
$usernameSuccessMessage.show();
|
||||
}
|
||||
|
||||
setPendingState() {
|
||||
const $usernamePendingMessage = $(pendingMessageSelector);
|
||||
if (this.state.pending) {
|
||||
$usernamePendingMessage.show();
|
||||
} else {
|
||||
$usernamePendingMessage.hide();
|
||||
}
|
||||
}
|
||||
|
||||
setInvalidState() {
|
||||
const $inputErrorMessage = $(invalidMessageSelector);
|
||||
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
|
||||
$inputErrorMessage.show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */
|
||||
|
||||
import d3 from 'd3';
|
||||
|
||||
export default class ActivityCalendar {
|
||||
constructor(timestamps, calendar_activities_path) {
|
||||
this.calendar_activities_path = calendar_activities_path;
|
||||
this.clickDay = this.clickDay.bind(this);
|
||||
this.currentSelectedDate = '';
|
||||
this.daySpace = 1;
|
||||
this.daySize = 15;
|
||||
this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
|
||||
this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
this.months = [];
|
||||
// Loop through the timestamps to create a group of objects
|
||||
// The group of objects will be grouped based on the day of the week they are
|
||||
this.timestampsTmp = [];
|
||||
var group = 0;
|
||||
|
||||
var today = new Date();
|
||||
today.setHours(0, 0, 0, 0, 0);
|
||||
|
||||
var oneYearAgo = new Date(today);
|
||||
oneYearAgo.setFullYear(today.getFullYear() - 1);
|
||||
|
||||
var days = gl.utils.getDayDifference(oneYearAgo, today);
|
||||
|
||||
for (var i = 0; i <= days; i += 1) {
|
||||
var date = new Date(oneYearAgo);
|
||||
date.setDate(date.getDate() + i);
|
||||
|
||||
var day = date.getDay();
|
||||
var count = timestamps[date.format('yyyy-mm-dd')];
|
||||
|
||||
// Create a new group array if this is the first day of the week
|
||||
// or if is first object
|
||||
if ((day === 0 && i !== 0) || i === 0) {
|
||||
this.timestampsTmp.push([]);
|
||||
group += 1;
|
||||
}
|
||||
|
||||
var innerArray = this.timestampsTmp[group - 1];
|
||||
// Push to the inner array the values that will be used to render map
|
||||
innerArray.push({
|
||||
count: count || 0,
|
||||
date: date,
|
||||
day: day
|
||||
});
|
||||
}
|
||||
|
||||
// Init color functions
|
||||
this.colorKey = this.initColorKey();
|
||||
this.color = this.initColor();
|
||||
// Init the svg element
|
||||
this.renderSvg(group);
|
||||
this.renderDays();
|
||||
this.renderMonths();
|
||||
this.renderDayTitles();
|
||||
this.renderKey();
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
// Add extra padding for the last month label if it is also the last column
|
||||
getExtraWidthPadding(group) {
|
||||
var extraWidthPadding = 0;
|
||||
var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
|
||||
var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
|
||||
|
||||
if (lastColMonth != secondLastColMonth) {
|
||||
extraWidthPadding = 3;
|
||||
}
|
||||
|
||||
return extraWidthPadding;
|
||||
}
|
||||
|
||||
renderSvg(group) {
|
||||
var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
|
||||
return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
|
||||
}
|
||||
|
||||
renderDays() {
|
||||
return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
|
||||
return function(group, i) {
|
||||
_.each(group, function(stamp, a) {
|
||||
var lastMonth, lastMonthX, month, x;
|
||||
if (a === 0 && stamp.day === 0) {
|
||||
month = stamp.date.getMonth();
|
||||
x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
|
||||
lastMonth = _.last(_this.months);
|
||||
if (lastMonth != null) {
|
||||
lastMonthX = lastMonth.x;
|
||||
}
|
||||
if (lastMonth == null) {
|
||||
return _this.months.push({
|
||||
month: month,
|
||||
x: x
|
||||
});
|
||||
} else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
|
||||
return _this.months.push({
|
||||
month: month,
|
||||
x: x
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
|
||||
};
|
||||
})(this)).selectAll('rect').data(function(stamp) {
|
||||
return stamp;
|
||||
}).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
|
||||
return function(stamp, i) {
|
||||
return _this.daySizeWithSpace * stamp.day;
|
||||
};
|
||||
})(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
|
||||
return function(stamp) {
|
||||
var contribText, date, dateText;
|
||||
date = new Date(stamp.date);
|
||||
contribText = 'No contributions';
|
||||
if (stamp.count > 0) {
|
||||
contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
|
||||
}
|
||||
dateText = date.format('mmm d, yyyy');
|
||||
return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
|
||||
};
|
||||
})(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
|
||||
return function(stamp) {
|
||||
if (stamp.count !== 0) {
|
||||
return _this.color(Math.min(stamp.count, 40));
|
||||
} else {
|
||||
return '#ededed';
|
||||
}
|
||||
};
|
||||
})(this)).attr('data-container', 'body').on('click', this.clickDay);
|
||||
}
|
||||
|
||||
renderDayTitles() {
|
||||
var days;
|
||||
days = [
|
||||
{
|
||||
text: 'M',
|
||||
y: 29 + (this.daySizeWithSpace * 1)
|
||||
}, {
|
||||
text: 'W',
|
||||
y: 29 + (this.daySizeWithSpace * 3)
|
||||
}, {
|
||||
text: 'F',
|
||||
y: 29 + (this.daySizeWithSpace * 5)
|
||||
}
|
||||
];
|
||||
return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
|
||||
return day.y;
|
||||
}).text(function(day) {
|
||||
return day.text;
|
||||
}).attr('class', 'user-contrib-text');
|
||||
}
|
||||
|
||||
renderMonths() {
|
||||
return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
|
||||
return date.x;
|
||||
}).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
|
||||
return function(date) {
|
||||
return _this.monthNames[date.month];
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
renderKey() {
|
||||
const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
|
||||
const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
|
||||
|
||||
this.svg.append('g')
|
||||
.attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
|
||||
.selectAll('rect')
|
||||
.data(keyColors)
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('width', this.daySize)
|
||||
.attr('height', this.daySize)
|
||||
.attr('x', (color, i) => this.daySizeWithSpace * i)
|
||||
.attr('y', 0)
|
||||
.attr('fill', color => color)
|
||||
.attr('class', 'js-tooltip')
|
||||
.attr('title', (color, i) => keyValues[i])
|
||||
.attr('data-container', 'body');
|
||||
}
|
||||
|
||||
initColor() {
|
||||
var colorRange;
|
||||
colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
|
||||
return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
|
||||
}
|
||||
|
||||
initColorKey() {
|
||||
return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
|
||||
}
|
||||
|
||||
clickDay(stamp) {
|
||||
var formatted_date;
|
||||
if (this.currentSelectedDate !== stamp.date) {
|
||||
this.currentSelectedDate = stamp.date;
|
||||
formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
|
||||
return $.ajax({
|
||||
url: this.calendar_activities_path,
|
||||
data: {
|
||||
date: formatted_date
|
||||
},
|
||||
cache: false,
|
||||
dataType: 'html',
|
||||
beforeSend: function() {
|
||||
return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
|
||||
},
|
||||
success: function(data) {
|
||||
return $('.user-calendar-activities').html(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.currentSelectedDate = '';
|
||||
return $('.user-calendar-activities').html('');
|
||||
}
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
return $('.js-contrib-calendar .js-tooltip').tooltip({
|
||||
html: true
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,231 +0,0 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */
|
||||
|
||||
import d3 from 'd3';
|
||||
|
||||
(function() {
|
||||
this.Calendar = (function() {
|
||||
function Calendar(timestamps, calendar_activities_path) {
|
||||
this.calendar_activities_path = calendar_activities_path;
|
||||
this.clickDay = this.clickDay.bind(this);
|
||||
this.currentSelectedDate = '';
|
||||
this.daySpace = 1;
|
||||
this.daySize = 15;
|
||||
this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
|
||||
this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
this.months = [];
|
||||
// Loop through the timestamps to create a group of objects
|
||||
// The group of objects will be grouped based on the day of the week they are
|
||||
this.timestampsTmp = [];
|
||||
var group = 0;
|
||||
|
||||
var today = new Date();
|
||||
today.setHours(0, 0, 0, 0, 0);
|
||||
|
||||
var oneYearAgo = new Date(today);
|
||||
oneYearAgo.setFullYear(today.getFullYear() - 1);
|
||||
|
||||
var days = gl.utils.getDayDifference(oneYearAgo, today);
|
||||
|
||||
for (var i = 0; i <= days; i += 1) {
|
||||
var date = new Date(oneYearAgo);
|
||||
date.setDate(date.getDate() + i);
|
||||
|
||||
var day = date.getDay();
|
||||
var count = timestamps[date.format('yyyy-mm-dd')];
|
||||
|
||||
// Create a new group array if this is the first day of the week
|
||||
// or if is first object
|
||||
if ((day === 0 && i !== 0) || i === 0) {
|
||||
this.timestampsTmp.push([]);
|
||||
group += 1;
|
||||
}
|
||||
|
||||
var innerArray = this.timestampsTmp[group - 1];
|
||||
// Push to the inner array the values that will be used to render map
|
||||
innerArray.push({
|
||||
count: count || 0,
|
||||
date: date,
|
||||
day: day
|
||||
});
|
||||
}
|
||||
|
||||
// Init color functions
|
||||
this.colorKey = this.initColorKey();
|
||||
this.color = this.initColor();
|
||||
// Init the svg element
|
||||
this.renderSvg(group);
|
||||
this.renderDays();
|
||||
this.renderMonths();
|
||||
this.renderDayTitles();
|
||||
this.renderKey();
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
// Add extra padding for the last month label if it is also the last column
|
||||
Calendar.prototype.getExtraWidthPadding = function(group) {
|
||||
var extraWidthPadding = 0;
|
||||
var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
|
||||
var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
|
||||
|
||||
if (lastColMonth != secondLastColMonth) {
|
||||
extraWidthPadding = 3;
|
||||
}
|
||||
|
||||
return extraWidthPadding;
|
||||
};
|
||||
|
||||
Calendar.prototype.renderSvg = function(group) {
|
||||
var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
|
||||
return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
|
||||
};
|
||||
|
||||
Calendar.prototype.renderDays = function() {
|
||||
return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
|
||||
return function(group, i) {
|
||||
_.each(group, function(stamp, a) {
|
||||
var lastMonth, lastMonthX, month, x;
|
||||
if (a === 0 && stamp.day === 0) {
|
||||
month = stamp.date.getMonth();
|
||||
x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
|
||||
lastMonth = _.last(_this.months);
|
||||
if (lastMonth != null) {
|
||||
lastMonthX = lastMonth.x;
|
||||
}
|
||||
if (lastMonth == null) {
|
||||
return _this.months.push({
|
||||
month: month,
|
||||
x: x
|
||||
});
|
||||
} else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
|
||||
return _this.months.push({
|
||||
month: month,
|
||||
x: x
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
|
||||
};
|
||||
})(this)).selectAll('rect').data(function(stamp) {
|
||||
return stamp;
|
||||
}).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
|
||||
return function(stamp, i) {
|
||||
return _this.daySizeWithSpace * stamp.day;
|
||||
};
|
||||
})(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
|
||||
return function(stamp) {
|
||||
var contribText, date, dateText;
|
||||
date = new Date(stamp.date);
|
||||
contribText = 'No contributions';
|
||||
if (stamp.count > 0) {
|
||||
contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
|
||||
}
|
||||
dateText = date.format('mmm d, yyyy');
|
||||
return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
|
||||
};
|
||||
})(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
|
||||
return function(stamp) {
|
||||
if (stamp.count !== 0) {
|
||||
return _this.color(Math.min(stamp.count, 40));
|
||||
} else {
|
||||
return '#ededed';
|
||||
}
|
||||
};
|
||||
})(this)).attr('data-container', 'body').on('click', this.clickDay);
|
||||
};
|
||||
|
||||
Calendar.prototype.renderDayTitles = function() {
|
||||
var days;
|
||||
days = [
|
||||
{
|
||||
text: 'M',
|
||||
y: 29 + (this.daySizeWithSpace * 1)
|
||||
}, {
|
||||
text: 'W',
|
||||
y: 29 + (this.daySizeWithSpace * 3)
|
||||
}, {
|
||||
text: 'F',
|
||||
y: 29 + (this.daySizeWithSpace * 5)
|
||||
}
|
||||
];
|
||||
return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
|
||||
return day.y;
|
||||
}).text(function(day) {
|
||||
return day.text;
|
||||
}).attr('class', 'user-contrib-text');
|
||||
};
|
||||
|
||||
Calendar.prototype.renderMonths = function() {
|
||||
return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
|
||||
return date.x;
|
||||
}).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
|
||||
return function(date) {
|
||||
return _this.monthNames[date.month];
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
Calendar.prototype.renderKey = function() {
|
||||
const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
|
||||
const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
|
||||
|
||||
this.svg.append('g')
|
||||
.attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
|
||||
.selectAll('rect')
|
||||
.data(keyColors)
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('width', this.daySize)
|
||||
.attr('height', this.daySize)
|
||||
.attr('x', (color, i) => this.daySizeWithSpace * i)
|
||||
.attr('y', 0)
|
||||
.attr('fill', color => color)
|
||||
.attr('class', 'js-tooltip')
|
||||
.attr('title', (color, i) => keyValues[i])
|
||||
.attr('data-container', 'body');
|
||||
};
|
||||
|
||||
Calendar.prototype.initColor = function() {
|
||||
var colorRange;
|
||||
colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
|
||||
return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
|
||||
};
|
||||
|
||||
Calendar.prototype.initColorKey = function() {
|
||||
return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
|
||||
};
|
||||
|
||||
Calendar.prototype.clickDay = function(stamp) {
|
||||
var formatted_date;
|
||||
if (this.currentSelectedDate !== stamp.date) {
|
||||
this.currentSelectedDate = stamp.date;
|
||||
formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
|
||||
return $.ajax({
|
||||
url: this.calendar_activities_path,
|
||||
data: {
|
||||
date: formatted_date
|
||||
},
|
||||
cache: false,
|
||||
dataType: 'html',
|
||||
beforeSend: function() {
|
||||
return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
|
||||
},
|
||||
success: function(data) {
|
||||
return $('.user-calendar-activities').html(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.currentSelectedDate = '';
|
||||
return $('.user-calendar-activities').html('');
|
||||
}
|
||||
};
|
||||
|
||||
Calendar.prototype.initTooltips = function() {
|
||||
return $('.js-contrib-calendar .js-tooltip').tooltip({
|
||||
html: true
|
||||
});
|
||||
};
|
||||
|
||||
return Calendar;
|
||||
})();
|
||||
}).call(window);
|
|
@ -0,0 +1,7 @@
|
|||
import ActivityCalendar from './activity_calendar';
|
||||
import User from './user';
|
||||
|
||||
// use legacy exports until embedded javascript is refactored
|
||||
window.Calendar = ActivityCalendar;
|
||||
window.gl = window.gl || {};
|
||||
window.gl.User = User;
|
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
import Cookies from 'js-cookie';
|
||||
import UserTabs from './user_tabs';
|
||||
|
||||
export default class User {
|
||||
constructor({ action }) {
|
||||
this.action = action;
|
||||
this.placeProfileAvatarsToTop();
|
||||
this.initTabs();
|
||||
this.hideProjectLimitMessage();
|
||||
}
|
||||
|
||||
placeProfileAvatarsToTop() {
|
||||
$('.profile-groups-avatars').tooltip({
|
||||
placement: 'top',
|
||||
});
|
||||
}
|
||||
|
||||
initTabs() {
|
||||
return new UserTabs({
|
||||
parentEl: '.user-profile',
|
||||
action: this.action,
|
||||
});
|
||||
}
|
||||
|
||||
hideProjectLimitMessage() {
|
||||
$('.hide-project-limit-message').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
Cookies.set('hide_project_limit_message', 'false');
|
||||
$(this).parents('.project-limit-message').remove();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */
|
||||
|
||||
/*
|
||||
UserTabs
|
||||
|
||||
Handles persisting and restoring the current tab selection and lazily-loading
|
||||
content on the Users#show page.
|
||||
|
||||
### Example Markup
|
||||
|
||||
<ul class="nav-links">
|
||||
<li class="activity-tab active">
|
||||
<a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
|
||||
Activity
|
||||
</a>
|
||||
</li>
|
||||
<li class="groups-tab">
|
||||
<a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
|
||||
Groups
|
||||
</a>
|
||||
</li>
|
||||
<li class="contributed-tab">
|
||||
<a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
|
||||
Contributed projects
|
||||
</a>
|
||||
</li>
|
||||
<li class="projects-tab">
|
||||
<a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
|
||||
Personal projects
|
||||
</a>
|
||||
</li>
|
||||
<li class="snippets-tab">
|
||||
<a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane" id="activity">
|
||||
Activity Content
|
||||
</div>
|
||||
<div class="tab-pane" id="groups">
|
||||
Groups Content
|
||||
</div>
|
||||
<div class="tab-pane" id="contributed">
|
||||
Contributed projects content
|
||||
</div>
|
||||
<div class="tab-pane" id="projects">
|
||||
Projects content
|
||||
</div>
|
||||
<div class="tab-pane" id="snippets">
|
||||
Snippets content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading-status">
|
||||
<div class="loading">
|
||||
Loading Animation
|
||||
</div>
|
||||
</div>
|
||||
*/
|
||||
|
||||
export default class UserTabs {
|
||||
constructor ({ defaultAction, action, parentEl }) {
|
||||
this.loaded = {};
|
||||
this.defaultAction = defaultAction || 'activity';
|
||||
this.action = action || this.defaultAction;
|
||||
this.$parentEl = $(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() {
|
||||
this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
|
||||
|
||||
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));
|
||||
|
||||
this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
|
||||
}
|
||||
|
||||
changeProjectsPage(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('.tab-pane.active').empty();
|
||||
const endpoint = $(e.target).attr('href');
|
||||
this.loadTab(this.getCurrentAction(), endpoint);
|
||||
}
|
||||
|
||||
tabShown(event) {
|
||||
const $target = $(event.target);
|
||||
const action = $target.data('action');
|
||||
const source = $target.attr('href');
|
||||
const endpoint = $target.data('endpoint');
|
||||
this.setTab(action, endpoint);
|
||||
return this.setCurrentAction(source);
|
||||
}
|
||||
|
||||
activateTab(action) {
|
||||
return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
|
||||
.tab('show');
|
||||
}
|
||||
|
||||
setTab(action, endpoint) {
|
||||
if (this.loaded[action]) {
|
||||
return;
|
||||
}
|
||||
if (action === 'activity') {
|
||||
this.loadActivities();
|
||||
}
|
||||
|
||||
const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
|
||||
if (loadableActions.indexOf(action) > -1) {
|
||||
return this.loadTab(action, endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
loadTab(action, endpoint) {
|
||||
return $.ajax({
|
||||
beforeSend: () => this.toggleLoading(true),
|
||||
complete: () => this.toggleLoading(false),
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
url: endpoint,
|
||||
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() {
|
||||
if (this.loaded['activity']) {
|
||||
return;
|
||||
}
|
||||
const $calendarWrap = this.$parentEl.find('.user-calendar');
|
||||
$calendarWrap.load($calendarWrap.data('href'));
|
||||
new gl.Activities();
|
||||
return this.loaded['activity'] = true;
|
||||
}
|
||||
|
||||
toggleLoading(status) {
|
||||
return this.$parentEl.find('.loading-status .loading')
|
||||
.toggle(status);
|
||||
}
|
||||
|
||||
setCurrentAction(source) {
|
||||
let new_state = source;
|
||||
new_state = new_state.replace(/\/+$/, '');
|
||||
new_state += this._location.search + this._location.hash;
|
||||
history.replaceState({
|
||||
url: new_state
|
||||
}, document.title, new_state);
|
||||
return new_state;
|
||||
}
|
||||
|
||||
getCurrentAction() {
|
||||
return this.$parentEl.find('.nav-links .active a').data('action');
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
import './calendar';
|
|
@ -3,6 +3,3 @@ export default class VersionCheckImage {
|
|||
imageElement.off('error').on('error', () => imageElement.hide());
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.VersionCheckImage = VersionCheckImage;
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
(() => {
|
||||
const gl = window.gl || (window.gl = {});
|
||||
export default class VisibilitySelect {
|
||||
constructor(container) {
|
||||
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
|
||||
this.container = container;
|
||||
this.helpBlock = this.container.querySelector('.help-block');
|
||||
this.select = this.container.querySelector('select');
|
||||
}
|
||||
|
||||
class VisibilitySelect {
|
||||
constructor(container) {
|
||||
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
|
||||
this.container = container;
|
||||
this.helpBlock = this.container.querySelector('.help-block');
|
||||
this.select = this.container.querySelector('select');
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.select) {
|
||||
this.updateHelpText();
|
||||
this.select.addEventListener('change', this.updateHelpText.bind(this));
|
||||
} else {
|
||||
this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
|
||||
}
|
||||
}
|
||||
|
||||
updateHelpText() {
|
||||
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
|
||||
init() {
|
||||
if (this.select) {
|
||||
this.updateHelpText();
|
||||
this.select.addEventListener('change', this.updateHelpText.bind(this));
|
||||
} else {
|
||||
this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
|
||||
}
|
||||
}
|
||||
|
||||
gl.VisibilitySelect = VisibilitySelect;
|
||||
})();
|
||||
updateHelpText() {
|
||||
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,13 +92,13 @@ export default {
|
|||
:class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}"
|
||||
:title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''"
|
||||
data-placement="bottom">
|
||||
<a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
|
||||
<a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a>
|
||||
</span>
|
||||
</strong>
|
||||
<span
|
||||
v-if="shouldShowCommitsBehindText"
|
||||
class="diverged-commits-count">
|
||||
({{mr.divergedCommitsCount}} {{commitsText}} behind)
|
||||
(<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,9 +17,6 @@ export default {
|
|||
|
||||
return hasCI && !ciStatus;
|
||||
},
|
||||
hasPipeline() {
|
||||
return Object.keys(this.mr.pipeline || {}).length > 0;
|
||||
},
|
||||
svg() {
|
||||
return statusIconEntityMap.icon_status_failed;
|
||||
},
|
||||
|
@ -33,11 +30,7 @@ export default {
|
|||
template: `
|
||||
<div class="mr-widget-heading">
|
||||
<div class="ci-widget">
|
||||
<template v-if="!hasPipeline">
|
||||
<i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
|
||||
Waiting for pipeline...
|
||||
</template>
|
||||
<template v-else-if="hasCIError">
|
||||
<template v-if="hasCIError">
|
||||
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
|
||||
<span class="js-icon-link icon-link">
|
||||
<span
|
||||
|
|
|
@ -48,6 +48,7 @@ export default class MergeRequestStore {
|
|||
this.sourceBranchLink = data.source_branch_with_namespace_link;
|
||||
this.mergeError = data.merge_error;
|
||||
this.targetBranchPath = data.target_branch_commits_path;
|
||||
this.targetBranchTreePath = data.target_branch_tree_path;
|
||||
this.conflictResolutionPath = data.conflict_resolution_path;
|
||||
this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path;
|
||||
this.removeWIPPath = data.remove_wip_path;
|
||||
|
|
|
@ -44,9 +44,8 @@
|
|||
text: this.$slots.textarea[0].elm.value,
|
||||
},
|
||||
)
|
||||
.then((res) => {
|
||||
const data = res.json();
|
||||
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
this.markdownPreviewLoading = false;
|
||||
this.markdownPreview = data.body;
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
changePage(e) {
|
||||
if (e.target.parentElement.classList.contains('disabled')) return;
|
||||
|
||||
const text = e.target.innerText;
|
||||
const { totalPages, nextPage, previousPage } = this.pageInfo;
|
||||
|
||||
|
@ -82,7 +84,9 @@ export default {
|
|||
const page = this.pageInfo.page;
|
||||
const items = [];
|
||||
|
||||
if (page > 1) items.push({ title: FIRST });
|
||||
if (page > 1) {
|
||||
items.push({ title: FIRST, first: true });
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
items.push({ title: PREV, prev: true });
|
||||
|
@ -110,7 +114,9 @@ export default {
|
|||
items.push({ title: NEXT, next: true });
|
||||
}
|
||||
|
||||
if (total - page >= 1) items.push({ title: LAST, last: true });
|
||||
if (total - page >= 1) {
|
||||
items.push({ title: LAST, last: true });
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
@ -124,13 +130,15 @@ export default {
|
|||
v-for="item in getItems"
|
||||
:class="{
|
||||
page: item.page,
|
||||
prev: item.prev,
|
||||
next: item.next,
|
||||
'js-previous-button': item.prev,
|
||||
'js-next-button': item.next,
|
||||
'js-last-button': item.last,
|
||||
'js-first-button': item.first,
|
||||
separator: item.separator,
|
||||
active: item.active,
|
||||
disabled: item.disabled
|
||||
}">
|
||||
<a @click="changePage($event)">{{item.title}}</a>
|
||||
<a @click.prevent="changePage($event)">{{item.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -14,11 +14,22 @@ Vue.http.interceptors.push((request, next) => {
|
|||
});
|
||||
});
|
||||
|
||||
// Inject CSRF token so we don't break any tests.
|
||||
// Inject CSRF token and parse headers.
|
||||
// New Vue Resource version uses Headers, we are expecting a plain object to render pagination
|
||||
// and polling.
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
if ($.rails) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
request.headers.set('X-CSRF-Token', $.rails.csrfToken());
|
||||
}
|
||||
next();
|
||||
|
||||
next((response) => {
|
||||
// Headers object has a `forEach` property that iterates through all values.
|
||||
const headers = {};
|
||||
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
response.headers = headers;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,69 +1,64 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
/* global Breakpoints */
|
||||
|
||||
import 'vendor/jquery.nicescroll';
|
||||
import './breakpoints';
|
||||
|
||||
((global) => {
|
||||
class Wikis {
|
||||
constructor() {
|
||||
this.bp = Breakpoints.get();
|
||||
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
|
||||
this.sidebarExpanded = false;
|
||||
$(this.sidebarEl).niceScroll();
|
||||
export default class Wikis {
|
||||
constructor() {
|
||||
this.bp = Breakpoints.get();
|
||||
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
|
||||
this.sidebarExpanded = false;
|
||||
$(this.sidebarEl).niceScroll();
|
||||
|
||||
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
|
||||
for (let i = 0; i < sidebarToggles.length; i += 1) {
|
||||
sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
|
||||
}
|
||||
|
||||
this.newWikiForm = document.querySelector('form.new-wiki-page');
|
||||
if (this.newWikiForm) {
|
||||
this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => this.renderSidebar());
|
||||
this.renderSidebar();
|
||||
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
|
||||
for (let i = 0; i < sidebarToggles.length; i += 1) {
|
||||
sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
|
||||
}
|
||||
|
||||
handleNewWikiSubmit(e) {
|
||||
if (!this.newWikiForm) return;
|
||||
|
||||
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
|
||||
const slug = gl.text.slugify(slugInput.value);
|
||||
|
||||
if (slug.length > 0) {
|
||||
const wikisPath = slugInput.getAttribute('data-wikis-path');
|
||||
window.location.href = `${wikisPath}/${slug}`;
|
||||
e.preventDefault();
|
||||
}
|
||||
this.newWikiForm = document.querySelector('form.new-wiki-page');
|
||||
if (this.newWikiForm) {
|
||||
this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
|
||||
}
|
||||
|
||||
handleToggleSidebar(e) {
|
||||
window.addEventListener('resize', () => this.renderSidebar());
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
handleNewWikiSubmit(e) {
|
||||
if (!this.newWikiForm) return;
|
||||
|
||||
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
|
||||
const slug = gl.text.slugify(slugInput.value);
|
||||
|
||||
if (slug.length > 0) {
|
||||
const wikisPath = slugInput.getAttribute('data-wikis-path');
|
||||
window.location.href = `${wikisPath}/${slug}`;
|
||||
e.preventDefault();
|
||||
this.sidebarExpanded = !this.sidebarExpanded;
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
sidebarCanCollapse() {
|
||||
const bootstrapBreakpoint = this.bp.getBreakpointSize();
|
||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
||||
}
|
||||
|
||||
renderSidebar() {
|
||||
if (!this.sidebarEl) return;
|
||||
const { classList } = this.sidebarEl;
|
||||
if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
|
||||
if (!classList.contains('right-sidebar-expanded')) {
|
||||
classList.remove('right-sidebar-collapsed');
|
||||
classList.add('right-sidebar-expanded');
|
||||
}
|
||||
} else if (classList.contains('right-sidebar-expanded')) {
|
||||
classList.add('right-sidebar-collapsed');
|
||||
classList.remove('right-sidebar-expanded');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.Wikis = Wikis;
|
||||
})(window.gl || (window.gl = {}));
|
||||
handleToggleSidebar(e) {
|
||||
e.preventDefault();
|
||||
this.sidebarExpanded = !this.sidebarExpanded;
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
sidebarCanCollapse() {
|
||||
const bootstrapBreakpoint = this.bp.getBreakpointSize();
|
||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
||||
}
|
||||
|
||||
renderSidebar() {
|
||||
if (!this.sidebarEl) return;
|
||||
const { classList } = this.sidebarEl;
|
||||
if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
|
||||
if (!classList.contains('right-sidebar-expanded')) {
|
||||
classList.remove('right-sidebar-collapsed');
|
||||
classList.add('right-sidebar-expanded');
|
||||
}
|
||||
} else if (classList.contains('right-sidebar-expanded')) {
|
||||
classList.add('right-sidebar-collapsed');
|
||||
classList.remove('right-sidebar-expanded');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */
|
||||
/* global Mousetrap */
|
||||
|
||||
// Zen Mode (full screen) textarea
|
||||
|
@ -34,65 +34,62 @@ window.Dropzone = Dropzone;
|
|||
// **Cancelable** No
|
||||
// **Target** a.js-zen-leave
|
||||
//
|
||||
(function() {
|
||||
this.ZenMode = (function() {
|
||||
function ZenMode() {
|
||||
this.active_backdrop = null;
|
||||
this.active_textarea = null;
|
||||
$(document).on('click', '.js-zen-enter', function(e) {
|
||||
e.preventDefault();
|
||||
return $(e.currentTarget).trigger('zen_mode:enter');
|
||||
});
|
||||
$(document).on('click', '.js-zen-leave', function(e) {
|
||||
e.preventDefault();
|
||||
return $(e.currentTarget).trigger('zen_mode:leave');
|
||||
});
|
||||
$(document).on('zen_mode:enter', (function(_this) {
|
||||
return function(e) {
|
||||
return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
|
||||
};
|
||||
})(this));
|
||||
$(document).on('zen_mode:leave', (function(_this) {
|
||||
return function(e) {
|
||||
return _this.exit();
|
||||
};
|
||||
})(this));
|
||||
$(document).on('keydown', function(e) {
|
||||
// Esc
|
||||
if (e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
return $(document).trigger('zen_mode:leave');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ZenMode.prototype.enter = function(backdrop) {
|
||||
Mousetrap.pause();
|
||||
this.active_backdrop = $(backdrop);
|
||||
this.active_backdrop.addClass('fullscreen');
|
||||
this.active_textarea = this.active_backdrop.find('textarea');
|
||||
// Prevent a user-resized textarea from persisting to fullscreen
|
||||
this.active_textarea.removeAttr('style');
|
||||
return this.active_textarea.focus();
|
||||
};
|
||||
|
||||
ZenMode.prototype.exit = function() {
|
||||
if (this.active_textarea) {
|
||||
Mousetrap.unpause();
|
||||
this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
|
||||
this.scrollTo(this.active_textarea);
|
||||
this.active_textarea = null;
|
||||
this.active_backdrop = null;
|
||||
return Dropzone.forElement('.div-dropzone').enable();
|
||||
export default class ZenMode {
|
||||
constructor() {
|
||||
this.active_backdrop = null;
|
||||
this.active_textarea = null;
|
||||
$(document).on('click', '.js-zen-enter', function(e) {
|
||||
e.preventDefault();
|
||||
return $(e.currentTarget).trigger('zen_mode:enter');
|
||||
});
|
||||
$(document).on('click', '.js-zen-leave', function(e) {
|
||||
e.preventDefault();
|
||||
return $(e.currentTarget).trigger('zen_mode:leave');
|
||||
});
|
||||
$(document).on('zen_mode:enter', (function(_this) {
|
||||
return function(e) {
|
||||
return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
|
||||
};
|
||||
})(this));
|
||||
$(document).on('zen_mode:leave', (function(_this) {
|
||||
return function(e) {
|
||||
return _this.exit();
|
||||
};
|
||||
})(this));
|
||||
$(document).on('keydown', function(e) {
|
||||
// Esc
|
||||
if (e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
return $(document).trigger('zen_mode:leave');
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
ZenMode.prototype.scrollTo = function(zen_area) {
|
||||
return $.scrollTo(zen_area, 0, {
|
||||
offset: -150
|
||||
});
|
||||
};
|
||||
enter(backdrop) {
|
||||
Mousetrap.pause();
|
||||
this.active_backdrop = $(backdrop);
|
||||
this.active_backdrop.addClass('fullscreen');
|
||||
this.active_textarea = this.active_backdrop.find('textarea');
|
||||
// Prevent a user-resized textarea from persisting to fullscreen
|
||||
this.active_textarea.removeAttr('style');
|
||||
return this.active_textarea.focus();
|
||||
}
|
||||
|
||||
return ZenMode;
|
||||
})();
|
||||
}).call(window);
|
||||
exit() {
|
||||
if (this.active_textarea) {
|
||||
Mousetrap.unpause();
|
||||
this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
|
||||
this.scrollTo(this.active_textarea);
|
||||
this.active_textarea = null;
|
||||
this.active_backdrop = null;
|
||||
return Dropzone.forElement('.div-dropzone').enable();
|
||||
}
|
||||
}
|
||||
|
||||
scrollTo(zen_area) {
|
||||
return $.scrollTo(zen_area, 0, {
|
||||
offset: -150
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,4 +42,4 @@
|
|||
/*
|
||||
* Styles for JS behaviors.
|
||||
*/
|
||||
@import "behaviors.scss";
|
||||
@import "behaviors";
|
||||
|
|
|
@ -4,49 +4,49 @@
|
|||
@import 'framework/tw_bootstrap';
|
||||
@import "framework/layout";
|
||||
|
||||
@import "framework/animations.scss";
|
||||
@import "framework/avatar.scss";
|
||||
@import "framework/asciidoctor.scss";
|
||||
@import "framework/blocks.scss";
|
||||
@import "framework/buttons.scss";
|
||||
@import "framework/badges.scss";
|
||||
@import "framework/calendar.scss";
|
||||
@import "framework/callout.scss";
|
||||
@import "framework/common.scss";
|
||||
@import "framework/dropdowns.scss";
|
||||
@import "framework/files.scss";
|
||||
@import "framework/filters.scss";
|
||||
@import "framework/flash.scss";
|
||||
@import "framework/forms.scss";
|
||||
@import "framework/gfm.scss";
|
||||
@import "framework/header.scss";
|
||||
@import "framework/highlight.scss";
|
||||
@import "framework/issue_box.scss";
|
||||
@import "framework/jquery.scss";
|
||||
@import "framework/lists.scss";
|
||||
@import "framework/logo.scss";
|
||||
@import "framework/markdown_area.scss";
|
||||
@import "framework/mobile.scss";
|
||||
@import "framework/modal.scss";
|
||||
@import "framework/nav.scss";
|
||||
@import "framework/pagination.scss";
|
||||
@import "framework/panels.scss";
|
||||
@import "framework/selects.scss";
|
||||
@import "framework/sidebar.scss";
|
||||
@import "framework/tables.scss";
|
||||
@import "framework/notes.scss";
|
||||
@import "framework/timeline.scss";
|
||||
@import "framework/typography.scss";
|
||||
@import "framework/zen.scss";
|
||||
@import "framework/animations";
|
||||
@import "framework/avatar";
|
||||
@import "framework/asciidoctor";
|
||||
@import "framework/blocks";
|
||||
@import "framework/buttons";
|
||||
@import "framework/badges";
|
||||
@import "framework/calendar";
|
||||
@import "framework/callout";
|
||||
@import "framework/common";
|
||||
@import "framework/dropdowns";
|
||||
@import "framework/files";
|
||||
@import "framework/filters";
|
||||
@import "framework/flash";
|
||||
@import "framework/forms";
|
||||
@import "framework/gfm";
|
||||
@import "framework/header";
|
||||
@import "framework/highlight";
|
||||
@import "framework/issue_box";
|
||||
@import "framework/jquery";
|
||||
@import "framework/lists";
|
||||
@import "framework/logo";
|
||||
@import "framework/markdown_area";
|
||||
@import "framework/mobile";
|
||||
@import "framework/modal";
|
||||
@import "framework/nav";
|
||||
@import "framework/pagination";
|
||||
@import "framework/panels";
|
||||
@import "framework/selects";
|
||||
@import "framework/sidebar";
|
||||
@import "framework/tables";
|
||||
@import "framework/notes";
|
||||
@import "framework/timeline";
|
||||
@import "framework/typography";
|
||||
@import "framework/zen";
|
||||
@import "framework/blank";
|
||||
@import "framework/wells.scss";
|
||||
@import "framework/page-header.scss";
|
||||
@import "framework/awards.scss";
|
||||
@import "framework/images.scss";
|
||||
@import "framework/wells";
|
||||
@import "framework/page-header";
|
||||
@import "framework/awards";
|
||||
@import "framework/images";
|
||||
@import "framework/broadcast-messages";
|
||||
@import "framework/emojis.scss";
|
||||
@import "framework/emoji-sprites.scss";
|
||||
@import "framework/icons.scss";
|
||||
@import "framework/snippets.scss";
|
||||
@import "framework/memory_graph.scss";
|
||||
@import "framework/responsive-tables.scss";
|
||||
@import "framework/emojis";
|
||||
@import "framework/emoji-sprites";
|
||||
@import "framework/icons";
|
||||
@import "framework/snippets";
|
||||
@import "framework/memory_graph";
|
||||
@import "framework/responsive-tables";
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
opacity: 0;
|
||||
transform: scale(.2);
|
||||
transform-origin: 0 -45px;
|
||||
transition: .3s cubic-bezier(.67,.06,.19,1.44);
|
||||
transition: .3s cubic-bezier(.67, .06, .19, 1.44);
|
||||
transition-property: transform, opacity;
|
||||
|
||||
&.is-aligned-right {
|
||||
|
@ -231,11 +231,11 @@
|
|||
|
||||
.award-control-icon-positive,
|
||||
.award-control-icon-super-positive {
|
||||
@include transition(opacity, transform);
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 6px;
|
||||
opacity: 0;
|
||||
@include transition(opacity, transform);
|
||||
}
|
||||
|
||||
.award-control-text {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
.blank-state-welcome {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid $border-color;
|
||||
.blank-state-parent-container {
|
||||
.section-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.blank-state-text {
|
||||
margin-bottom: 0;
|
||||
.section-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: 25px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $border-radius-default;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,41 +16,35 @@
|
|||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.blank-state-no-icon {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
&.blank-state-welcome {
|
||||
.blank-state-welcome-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.blank-state-icon {
|
||||
padding-bottom: 20px;
|
||||
color: $gray-darkest;
|
||||
font-size: 56px;
|
||||
.blank-state-text {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
path,
|
||||
polygon {
|
||||
fill: currentColor;
|
||||
.blank-state-icon {
|
||||
padding-bottom: 20px;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.blank-state-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.blank-state-text {
|
||||
max-width: $container-text-max-width;
|
||||
margin: 0 auto $gl-padding;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.blank-state-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 5px;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.blank-state-text {
|
||||
margin-top: 0;
|
||||
margin-bottom: $gl-padding;
|
||||
font-size: 14px;
|
||||
|
||||
> strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.blank-state-welcome-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue