Commit b2714763 authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into 'multiple-assignees-fe-sidebar'

# Conflicts:
#   app/views/shared/issuable/_sidebar.html.haml
parents e372a4d1 d893b691

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

{
"presets": [
["latest", { "es2015": { "modules": false } }],
"stage-2"
],
"env": {
"coverage": {
"plugins": [
["istanbul", {
"exclude": [
"app/assets/javascripts/droplab/**/*",
"spec/javascripts/**/*"
]
}],
["transform-define", {
"process.env.BABEL_ENV": "coverage"
}]
]
}
}
}
...@@ -4,3 +4,4 @@ lib/gitlab/diff/position_tracer.rb ...@@ -4,3 +4,4 @@ lib/gitlab/diff/position_tracer.rb
app/controllers/projects/approver_groups_controller.rb app/controllers/projects/approver_groups_controller.rb
app/controllers/projects/approvers_controller.rb app/controllers/projects/approvers_controller.rb
app/policies/project_policy.rb app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
*.js.es6 gitlab-language=javascript
...@@ -7,8 +7,6 @@ cache: ...@@ -7,8 +7,6 @@ cache:
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
# retry tests only in CI environment
RSPEC_RETRY_RETRY_COUNT: "3"
ELASTIC_URL: "http://elasticsearch:9200" ELASTIC_URL: "http://elasticsearch:9200"
RAILS_ENV: "test" RAILS_ENV: "test"
SIMPLECOV: "true" SIMPLECOV: "true"
...@@ -24,7 +22,7 @@ before_script: ...@@ -24,7 +22,7 @@ before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
- cp config/gitlab.yml.example config/gitlab.yml - cp config/gitlab.yml.example config/gitlab.yml
- bundle --version - bundle --version
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) $FLAGS' - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) --clean $FLAGS'
- retry gem install knapsack - retry gem install knapsack
- '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql' - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql'
- '[ "$SETUP_DB" != "true" ] || bundle exec rake geo:db:drop geo:db:create geo:db:schema:load geo:db:migrate' - '[ "$SETUP_DB" != "true" ] || bundle exec rake geo:db:drop geo:db:create geo:db:schema:load geo:db:migrate'
...@@ -65,7 +63,7 @@ stages: ...@@ -65,7 +63,7 @@ stages:
<<: *dedicated-runner <<: *dedicated-runner
<<: *use-db <<: *use-db
script: script:
- JOB_NAME=( $CI_BUILD_NAME ) - JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]} - export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]} - export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
...@@ -74,16 +72,18 @@ stages: ...@@ -74,16 +72,18 @@ stages:
- knapsack rspec "--color --format documentation" - knapsack rspec "--color --format documentation"
artifacts: artifacts:
expire_in: 31d expire_in: 31d
when: always
paths: paths:
- knapsack/
- coverage/ - coverage/
- knapsack/
- tmp/capybara/
.spinach-knapsack: &spinach-knapsack .spinach-knapsack: &spinach-knapsack
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
<<: *use-db <<: *use-db
script: script:
- JOB_NAME=( $CI_BUILD_NAME ) - JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]} - export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]} - export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
...@@ -92,9 +92,11 @@ stages: ...@@ -92,9 +92,11 @@ stages:
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts: artifacts:
expire_in: 31d expire_in: 31d
when: always
paths: paths:
- knapsack/
- coverage/ - coverage/
- knapsack/
- tmp/capybara/
# Prepare and merge knapsack tests # Prepare and merge knapsack tests
...@@ -183,7 +185,7 @@ spinach 9 10: *spinach-knapsack ...@@ -183,7 +185,7 @@ spinach 9 10: *spinach-knapsack
<<: *dedicated-runner <<: *dedicated-runner
stage: test stage: test
script: script:
- bundle exec $CI_BUILD_NAME - bundle exec $CI_JOB_NAME
rubocop: rubocop:
<<: *ruby-static-analysis <<: *ruby-static-analysis
...@@ -207,6 +209,14 @@ rake db:migrate:reset: ...@@ -207,6 +209,14 @@ rake db:migrate:reset:
script: script:
- bundle exec rake db:migrate:reset - bundle exec rake db:migrate:reset
rake db:rollback:
stage: test
<<: *use-db
<<: *dedicated-runner
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
rake db:seed_fu: rake db:seed_fu:
stage: test stage: test
<<: *use-db <<: *use-db
...@@ -252,6 +262,8 @@ rake karma: ...@@ -252,6 +262,8 @@ rake karma:
stage: test stage: test
<<: *use-db <<: *use-db
<<: *dedicated-runner <<: *dedicated-runner
variables:
BABEL_ENV: "coverage"
script: script:
- bundle exec rake karma - bundle exec rake karma
artifacts: artifacts:
...@@ -305,7 +317,8 @@ migration paths: ...@@ -305,7 +317,8 @@ migration paths:
- sed -i 's/localhost/redis/g' config/resque.yml - sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3 - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- bundle exec rake db:drop db:create db:schema:load db:seed_fu - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF - git checkout $CI_COMMIT_SHA
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- source scripts/prepare_build.sh - source scripts/prepare_build.sh
- bundle exec rake db:migrate - bundle exec rake db:migrate
...@@ -343,7 +356,7 @@ lint:javascript:report: ...@@ -343,7 +356,7 @@ lint:javascript:report:
stage: post-test stage: post-test
before_script: [] before_script: []
script: script:
- find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files - find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- yarn run eslint-report || true # ignore exit code - yarn run eslint-report || true # ignore exit code
artifacts: artifacts:
name: eslint-report name: eslint-report
...@@ -364,12 +377,12 @@ trigger_docs: ...@@ -364,12 +377,12 @@ trigger_docs:
cache: {} cache: {}
artifacts: {} artifacts: {}
script: script:
- "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ee https://gitlab.com/api/v3/projects/1794617/trigger/builds" - "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)"
- if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi
only: only:
- master@gitlab-org/gitlab-ee - master@gitlab-org/gitlab-ee
# Notify slack in the end # Notify slack in the end
notify:slack: notify:slack:
stage: post-test stage: post-test
<<: *dedicated-runner <<: *dedicated-runner
...@@ -377,7 +390,7 @@ notify:slack: ...@@ -377,7 +390,7 @@ notify:slack:
SETUP_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
script: script:
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>" - ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_SHA"/pipelines>"
when: on_failure when: on_failure
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
......
...@@ -5,3 +5,13 @@ ...@@ -5,3 +5,13 @@
### Proposal ### Proposal
### Links / references ### Links / references
### Documentation blurb
(Write the start of the documentation of this feature here, include:
1. Why should someone use it; what's the underlying problem.
2. What is the solution.
3. How does someone use this
During implementation, this can then be copied and used as a starter for the documentation.)
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 9.0.0 (2017-03-22)
- Geo: Replicate repository creation in Geo secondary node. !952
- Make approval system notes lowercase. !1125
- Issues can be exported as CSV, via email. !1126
- Try to update mirrors again after 15 minutes if the previous update failed. !1183
- Adds abitlity to render deploy boards in the frontend side. !1233
- Add filtered search to MR page. !1243
- Update project list API returns with approvals_before_merge attribute. !1245 (Geoff Webster)
- Catch Net::LDAP::DN exceptions in EE::Gitlab::LDAP::Group. !1260
- API: Use `post ":id/#{type}/:subscribable_id/subscribe"` to subscribe and `post ":id/#{type}/:subscribable_id/unsubscribe"` to unsubscribe from a resource. !1274 (Robert Schilling)
- API: Remove deprecated fields Notes#upvotes and Notes#downvotes. !1275 (Robert Schilling)
- Deploy board backend. !1278
- API: Remove the ProjectGitHook API. !1301 (Robert Schilling)
- Expose elasticsearch client params for AWS signing and HTTPS. !1305 (Matt Gresko)
- Fix LDAP DN case-mismatch bug in LDAP group sync. !1337
- Remove es6 file extension from JavaScript files. !1344 (winniehell)
- Geo: Don't load dependent models when fetching an existing GeoNode from the database. !1348
- Parallelise the gitlab:elastic:index_database Rake task. !1361
- Robustify reading attributes for elasticsearch. !1365
- Introduce one additional thread into bin/elastic_repo_indexer. !1372
- Show hook errors for fast-forward merges. !1375
- Allow all parameters of group webhooks to be set through the UI. !1376
- Fix Elasticsearch queries when a group_id is specified. !1423
- Check the right index mapping based on Rails environment for rake gitlab:elastic:add_feature_visiblity_levels_to_project. !1473
- Fix issues with another milestone that has a matching list label could not be added to a board.
- Only admins or group owners can set LDAP overrides.
- Add support for load balancing database queries.
- Only replace non-approval mr-widget-footer on getMergeStatus.
- Remove repository_storage from V4 "/application/settings" settings API.
- Added headers to protected branches access dropdowns.
- Remove support for Git Annex.
- Repositioned multiple issue boards selector.
- Added back weight in issue rows on issue list.
- Add basic support for GitLab Geo file transfers over HTTP.
- Added weight slash command.
- Set deployment status invalid when the environments does not match a k8s label.
- Combined deploy keys, push rules, protect branches and mirror repository settings options into a single one called Repository.
- Rebase - fix commiter email & name.
- Adds a EE specific dev favicon.
- Elastic security fix: Respect feature visibility level.
- Update Elasticsearch to 5.1.
- [Elasticsearch] More efficient search.
- Get Geo secondaries nodes statuses over AJAX.
## 8.17.4 (2017-03-19)
- Elastic security fix: Respect feature visibility level.
## 8.17.3 (2017-03-07) ## 8.17.3 (2017-03-07)
- No changes. - No changes.
...@@ -34,6 +83,13 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -34,6 +83,13 @@ Please view this file on the master branch, on stable branches it's out of date.
- Reduce queries needed to check if node is a primary or secondary Geo node. - Reduce queries needed to check if node is a primary or secondary Geo node.
- Allow squashing merge requests into a single commit. - Allow squashing merge requests into a single commit.
## 8.16.8 (2017-03-19)
- No changes.
- No changes.
- No changes.
- Elastic security fix: Respect feature visibility level.
## 8.16.7 (2017-02-27) ## 8.16.7 (2017-02-27)
- Fixed merge request state not updating when approvals feature is active. - Fixed merge request state not updating when approvals feature is active.
...@@ -78,6 +134,12 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -78,6 +134,12 @@ Please view this file on the master branch, on stable branches it's out of date.
- Expose issue weight in the API. !1023 (Robert Schilling) - Expose issue weight in the API. !1023 (Robert Schilling)
- Copy <some text> to clipboard. !1048 - Copy <some text> to clipboard. !1048
## 8.15.8 (2017-03-19)
- No changes.
- No changes.
- Elastic security fix: Respect feature visibility level.
## 8.15.7 (2017-02-15) ## 8.15.7 (2017-02-15)
- No changes. - No changes.
......
This diff is collapsed.
...@@ -78,6 +78,13 @@ towards getting your issue resolved. ...@@ -78,6 +78,13 @@ towards getting your issue resolved.
Issues and merge requests should be in English and contain appropriate language Issues and merge requests should be in English and contain appropriate language
for audiences of all ages. for audiences of all ages.
If a contributor is no longer actively working on a submitted merge request
we can decide that the merge request will be finished by one of our
[Merge request coaches][team] or close the merge request. We make this decision
based on how important the change is for our product vision. If a Merge request
coach is going to finish the merge request we assign the
~"coach will finish" label.
## Helping others ## Helping others
Please help other GitLab users when you can. The channels people will reach out Please help other GitLab users when you can. The channels people will reach out
...@@ -399,6 +406,12 @@ There are a few rules to get your merge request accepted: ...@@ -399,6 +406,12 @@ There are a few rules to get your merge request accepted:
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options or settings options since they complicate 1. Doesn't add configuration options or settings options since they complicate
making and testing future changes making and testing future changes
1. Changes do not adversely degrade performance.
- Avoid repeated polling of endpoints that require a significant amount of overhead
- Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- Avoid repeated access of filesystem
1. If you need polling to support real-time features, please use
[polling with ETag caching][polling-etag].
1. Changes after submitting the merge request should be in separate commits 1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is (no squashing). If necessary, you will be asked to squash when the review is
over, before merging. over, before merging.
...@@ -434,6 +447,7 @@ the feature you contribute through all of these steps. ...@@ -434,6 +447,7 @@ the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item) 1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed 1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server 1. Unit and integration tests that pass on the CI server
1. Performance/scalability implications have been considered, addressed, and tested
1. [Documented][doc-styleguide] in the /doc directory 1. [Documented][doc-styleguide] in the /doc directory
1. Changelog entry added 1. Changelog entry added
1. Reviewed and any concerns are addressed 1. Reviewed and any concerns are addressed
...@@ -465,8 +479,7 @@ merge request: ...@@ -465,8 +479,7 @@ merge request:
1. [Rails](https://github.com/bbatsov/rails-style-guide) 1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Newlines styleguide][newlines-styleguide] 1. [Newlines styleguide][newlines-styleguide]
1. [Testing](doc/development/testing.md) 1. [Testing](doc/development/testing.md)
1. [JavaScript (ES6)](https://github.com/airbnb/javascript) 1. [JavaScript styleguide][js-styleguide]
1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/es5-deprecated/es5)
1. [SCSS styleguide][scss-styleguide] 1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab 1. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security contributors to enhance security
...@@ -535,11 +548,13 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -535,11 +548,13 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming [rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[changelog]: doc/development/changelog.md "Generate a changelog entry" [changelog]: doc/development/changelog.md "Generate a changelog entry"
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide" [js-styleguide]: doc/development/fe_guide/style_guide_js.md "JavaScript styleguide"
[scss-styleguide]: doc/development/fe_guide/style_guide_scss.md "SCSS styleguide"
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide" [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/ [UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
[license-finder-doc]: doc/development/licensing.md [license-finder-doc]: doc/development/licensing.md
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues [GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[^1]: Specs other than JavaScript specs are considered backend code. Haml [^1]: Specs other than JavaScript specs are considered backend code. Haml
changes are considered backend code if they include Ruby code other than just changes are considered backend code if they include Ruby code other than just
......
...@@ -338,6 +338,7 @@ group :test do ...@@ -338,6 +338,7 @@ group :test do
gem 'test_after_commit', '~> 1.1' gem 'test_after_commit', '~> 1.1'
gem 'sham_rack', '~> 1.3.6' gem 'sham_rack', '~> 1.3.6'
gem 'timecop', '~> 0.8.0' gem 'timecop', '~> 0.8.0'
gem 'concurrent-ruby', '~> 1.0.5'
end end
gem 'octokit', '~> 4.6.2' gem 'octokit', '~> 4.6.2'
...@@ -363,4 +364,4 @@ gem 'vmstat', '~> 2.3.0' ...@@ -363,4 +364,4 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6' gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.2.1' gem 'gitaly', '~> 0.3.0'
...@@ -136,7 +136,7 @@ GEM ...@@ -136,7 +136,7 @@ GEM
execjs execjs
coffee-script-source (1.10.0) coffee-script-source (1.10.0)
colorize (0.7.7) colorize (0.7.7)
concurrent-ruby (1.0.4) concurrent-ruby (1.0.5)
connection_pool (2.2.1) connection_pool (2.2.1)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
...@@ -274,7 +274,7 @@ GEM ...@@ -274,7 +274,7 @@ GEM
json json
get_process_mem (0.2.0) get_process_mem (0.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly (0.2.1) gitaly (0.3.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -337,7 +337,7 @@ GEM ...@@ -337,7 +337,7 @@ GEM
multi_json (~> 1.10) multi_json (~> 1.10)
retriable (~> 1.4) retriable (~> 1.4)
signet (~> 0.6) signet (~> 0.6)
google-protobuf (3.2.0) google-protobuf (3.2.0.2)
googleauth (0.5.1) googleauth (0.5.1)
faraday (~> 0.9) faraday (~> 0.9)
jwt (~> 1.4) jwt (~> 1.4)
...@@ -905,6 +905,7 @@ DEPENDENCIES ...@@ -905,6 +905,7 @@ DEPENDENCIES
chronic (~> 0.10.2) chronic (~> 0.10.2)
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
concurrent-ruby (~> 1.0.5)
connection_pool (~> 2.0) connection_pool (~> 2.0)
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
...@@ -937,7 +938,7 @@ DEPENDENCIES ...@@ -937,7 +938,7 @@ DEPENDENCIES
fuubar (~> 2.0.0) fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0) gemojione (~> 3.0)
gitaly (~> 0.2.1) gitaly (~> 0.3.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-elasticsearch-git (= 1.1.1) gitlab-elasticsearch-git (= 1.1.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
......
<svg width="12" height="15" viewBox="0 0 12 15" xmlns="http://www.w3.org/2000/svg"><path d="M10.267 11.028V5.167c-.028-.728-.318-1.372-.878-1.923-.56-.55-1.194-.85-1.922-.877h-.934V.5l-2.8 2.8 2.8 2.8V4.233h.934a.976.976 0 0 1 .644.29.88.88 0 0 1 .289.644v5.861a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472zM3.733 3.3a1.86 1.86 0 0 0-1.866-1.867 1.86 1.86 0 0 0-.934 3.472v6.123a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472V4.905c.55-.317.933-.914.933-1.605z" fill-rule="nonzero"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m5 5.563v4.875c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-4.875c-1.024-.4-1.75-1.397-1.75-2.563 0-1.519 1.231-2.75 2.75-2.75 1.519 0 2.75 1.231 2.75 2.75 0 1.166-.726 2.162-1.75 2.563m-1 8.687c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25m0-10c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/><path d="m10.501 2c1.381.001 2.499 1.125 2.499 2.506v5.931c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-5.931c0-.279-.225-.506-.499-.506v.926c0 .346-.244.474-.569.271l-2.952-1.844c-.314-.196-.325-.507 0-.71l2.952-1.844c.314-.196.569-.081.569.271v.93m1.499 12.25c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/></svg>
\ No newline at end of file
/* eslint-disable no-param-reassign, class-methods-use-this */ /* eslint-disable no-param-reassign, class-methods-use-this */
/* global Pager */ /* global Pager */
/* global Cookies */
import Cookies from 'js-cookie';
class Activities { class Activities {
constructor() { constructor() {
......
/* global Cookies */ import Cookies from 'js-cookie';
import emojiMap from 'emojis/digests.json'; import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json'; import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from './behaviors/gl_emoji'; import { glEmojiTag } from './behaviors/gl_emoji';
import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const requestAnimationFrame = window.requestAnimationFrame || const requestAnimationFrame = window.requestAnimationFrame ||
...@@ -454,14 +455,21 @@ AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) ...@@ -454,14 +455,21 @@ AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji)
AwardsHandler AwardsHandler
.prototype .prototype
.addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) { .addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) {
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); if (isEmojiNameValid(emoji)) {
frequentlyUsedEmojis.push(emoji); this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }); Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
}
}; };
AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() { AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() {
const frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(','); return this.frequentlyUsedEmojis || (() => {
return _.compact(_.uniq(frequentlyUsedEmojis)); const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
inputName => isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
})();
}; };
AwardsHandler.prototype.setupSearch = function setupSearch() { AwardsHandler.prototype.setupSearch = function setupSearch() {
......
...@@ -13,9 +13,14 @@ function emojiImageTag(name, src) { ...@@ -13,9 +13,14 @@ function emojiImageTag(name, src) {
} }
function assembleFallbackImageSrc(inputName) { function assembleFallbackImageSrc(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName; emojiAliases[inputName] : inputName;
const emojiInfo = emojiMap[name]; let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`; const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`;
return fallbackImageSrc; return fallbackImageSrc;
...@@ -26,9 +31,15 @@ const glEmojiTagDefaults = { ...@@ -26,9 +31,15 @@ const glEmojiTagDefaults = {
}; };
function glEmojiTag(inputName, options) { function glEmojiTag(inputName, options) {
const opts = Object.assign({}, glEmojiTagDefaults, options); const opts = Object.assign({}, glEmojiTagDefaults, options);
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName; emojiAliases[inputName] : inputName;
const emojiInfo = emojiMap[name]; let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = assembleFallbackImageSrc(name); const fallbackImageSrc = assembleFallbackImageSrc(name);
const fallbackSpriteClass = `emoji-${name}`; const fallbackSpriteClass = `emoji-${name}`;
......
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
function isEmojiNameValid(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
return name && emojiMap[name];
}
export default isEmojiNameValid;
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form // "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
// is submitted. // is submitted.
// //
require('../extensions/jquery'); import '../commons/bootstrap';
// //
// ### Example Markup // ### Example Markup
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// When called on a form with input fields with the `required` attribute, the // When called on a form with input fields with the `required` attribute, the
// form's submit button will be disabled until all required fields have values. // form's submit button will be disabled until all required fields have values.
// //
require('../extensions/jquery'); import '../commons/bootstrap';
// //
// ### Example Markup // ### Example Markup
......
...@@ -21,8 +21,13 @@ ...@@ -21,8 +21,13 @@
// %a.js-toggle-button // %a.js-toggle-button
// %div.js-toggle-content // %div.js-toggle-content
// //
$('body').on('click', '.js-toggle-button', function() { $('body').on('click', '.js-toggle-button', function(e) {
toggleContainer($(this).closest('.js-toggle-container')); toggleContainer($(this).closest('.js-toggle-container'));
const targetTag = e.currentTarget.tagName.toLowerCase();
if (targetTag === 'a' || targetTag === 'button') {
e.preventDefault();
}
}); });
// If we're accessing a permalink, ensure it is not inside a // If we're accessing a permalink, ensure it is not inside a
......
/* eslint-disable no-param-reassign, comma-dangle */
/* global Api */
require('./template_selector');
((global) => {
class BlobCiYamlSelector extends gl.TemplateSelector {
requestFile(query) {
return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
}
requestFileSuccess(file) {
return super.requestFileSuccess(file);
}
}
global.BlobCiYamlSelector = BlobCiYamlSelector;
class BlobCiYamlSelectors {
constructor({ editor, $dropdowns } = {}) {
this.editor = editor;
this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
this.initSelectors();
}
initSelectors() {
const editor = this.editor;
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobCiYamlSelector({
editor,
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown
});
});
}
}
global.BlobCiYamlSelectors = BlobCiYamlSelectors;
})(window.gl || (window.gl = {}));
/* global Api */
require('./template_selector');
(() => {
const global = window.gl || (window.gl = {});
class BlobDockerfileSelector extends gl.TemplateSelector {
requestFile(query) {
return Api.dockerfileYml(query.name, this.requestFileSuccess.bind(this));
}
requestFileSuccess(file) {
return super.requestFileSuccess(file);
}
}
global.BlobDockerfileSelector = BlobDockerfileSelector;
})();
(() => {
const global = window.gl || (window.gl = {});
class BlobDockerfileSelectors {
constructor({ editor, $dropdowns } = {}) {
this.editor = editor;
this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
this.initSelectors();
}
initSelectors() {
const editor = this.editor;
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new gl.BlobDockerfileSelector({
editor,
pattern: /(Dockerfile)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
dropdown: $dropdown,
});
});
}
}
global.BlobDockerfileSelectors = BlobDockerfileSelectors;
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, max-len */ /* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
/* global Dropzone */ /* global Dropzone */
(function() { export default class BlobFileDropzone {
this.BlobFileDropzone = (function() { constructor(form, method) {
function BlobFileDropzone(form, method) { const formDropzone = form.find('.dropzone');
var dropzone, form_dropzone, submitButton; Dropzone.autoDiscover = false;
form_dropzone = form.find('.dropzone');
Dropzone.autoDiscover = false; const dropzone = formDropzone.dropzone({
dropzone = form_dropzone.dropzone({ autoDiscover: false,
autoDiscover: false, autoProcessQueue: false,
autoProcessQueue: false, url: form.attr('action'),
url: form.attr('action'), // Rails uses a hidden input field for PUT
// Rails uses a hidden input field for PUT // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
// http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails method: method,
method: method, clickable: true,
clickable: true, uploadMultiple: false,
uploadMultiple: false, paramName: 'file',
paramName: "file", maxFilesize: gon.max_file_size || 10,
maxFilesize: gon.max_file_size || 10, parallelUploads: 1,
parallelUploads: 1, maxFiles: 1,
maxFiles: 1, addRemoveLinks: true,
addRemoveLinks: true, previewsContainer: '.dropzone-previews',
previewsContainer: '.dropzone-previews', headers: {
headers: { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") },
}, init: function () {
init: function() { this.on('addedfile', function () {
this.on('addedfile', function(file) { $('.dropzone-alerts').html('').hide();
$('.dropzone-alerts').html('').hide(); });
}); this.on('success', function (header, response) {
this.on('success', function(header, response) { window.location.href = response.filePath;
window.location.href = response.filePath; });
}); this.on('maxfilesexceeded', function (file) {
this.on('maxfilesexceeded', function(file) {
this.removeFile(file);
});
return this.on('sending', function(file, xhr, formData) {
formData.append('target_branch', form.find('.js-target-branch').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
});
},
// Override behavior of adding error underneath preview
error: function(file, errorMessage) {
var stripped;
stripped = $("<div/>").html(errorMessage).text();
$('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show();
this.removeFile(file); this.removeFile(file);
} });
}); this.on('sending', function (file, xhr, formData) {
submitButton = form.find('#submit-all')[0]; formData.append('target_branch', form.find('input[name="target_branch"]').val());
submitButton.addEventListener('click', function(e) { formData.append('create_merge_request', form.find('.js-create-merge-request').val());
e.preventDefault(); formData.append('commit_message', form.find('.js-commit-message').val());
e.stopPropagation(); });
if (dropzone[0].dropzone.getQueuedFiles().length === 0) { },
alert("Please select a file"); // Override behavior of adding error underneath preview
} error: function (file, errorMessage) {
dropzone[0].dropzone.processQueue(); const stripped = $('<div/>').html(errorMessage).text();
return false; $('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show();
}); this.removeFile(file);
} },
});
return BlobFileDropzone; const submitButton = form.find('#submit-all')[0];
})(); submitButton.addEventListener('click', function (e) {
}).call(window); e.preventDefault();
e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
// eslint-disable-next-line no-alert
alert('Please select a file');
}
dropzone[0].dropzone.processQueue();
return false;
});
}
}
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */
/* global Api */
require('./template_selector');
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.BlobGitignoreSelector = (function(superClass) {
extend(BlobGitignoreSelector, superClass);
function BlobGitignoreSelector() {
return BlobGitignoreSelector.__super__.constructor.apply(this, arguments);
}
BlobGitignoreSelector.prototype.requestFile = function(query) {
return Api.gitignoreText(query.name, this.requestFileSuccess.bind(this));
};
return BlobGitignoreSelector;
})(gl.TemplateSelector);
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, comma-dangle, max-len */
/* global BlobGitignoreSelector */
(function() {
this.BlobGitignoreSelectors = (function() {
function BlobGitignoreSelectors(opts) {
var ref;
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitignore-selector'), this.editor = opts.editor;
this.$dropdowns.each((function(_this) {
return function(i, dropdown) {
var $dropdown;
$dropdown = $(dropdown);
return new BlobGitignoreSelector({
pattern: /(.gitignore)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
dropdown: $dropdown,
editor: _this.editor
});
};
})(this));
}
return BlobGitignoreSelectors;
})();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */
/* global Api */
require('./template_selector');
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.BlobLicenseSelector = (function(superClass) {
extend(BlobLicenseSelector, superClass);
function BlobLicenseSelector() {
return BlobLicenseSelector.__super__.constructor.apply(this, arguments);
}
BlobLicenseSelector.prototype.requestFile = function(query) {
var data;
data = {
project: this.dropdown.data('project'),
fullname: this.dropdown.data('fullname')
};
return Api.licenseText(query.id, data, this.requestFileSuccess.bind(this));
};
return BlobLicenseSelector;
})(gl.TemplateSelector);
}).call(window);
/* eslint-disable no-unused-vars, no-param-reassign */
/* global BlobLicenseSelector */
((global) => {
class BlobLicenseSelectors {
constructor({ $dropdowns, editor }) {
this.$dropdowns = $('.js-license-selector');
this.editor = editor;
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobLicenseSelector({
editor,
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-license-selector-wrap'),
dropdown: $dropdown,
});
});
}
}
global.BlobLicenseSelectors = BlobLicenseSelectors;
})(window.gl || (window.gl = {}));
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = gl.utils.getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
const href = permalinkButton.getAttribute('href');
permalinkButton.setAttribute('data-original-href', href);
return href;
})();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
});
}
};
function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, elementsToUpdate) {
const updateBlameAndBlobPermalinkCb = () => {
// Wait for the hash to update from the LineHighlighter callback
setTimeout(() => {
updateLineNumbersOnBlobPermalinks(elementsToUpdate);
}, 0);
};
blobContentHolder.addEventListener('click', (e) => {
if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb();
}
});
updateBlameAndBlobPermalinkCb();
}
export default BlobLinePermalinkUpdater;
class CreateBranchDropdown {
constructor(el, targetBranchDropdown) {
this.targetBranchDropdown = targetBranchDropdown;
this.el = el;
this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back');
this.cancelButton = this.el.querySelector('.js-cancel-branch-btn');
this.newBranchField = this.el.querySelector('#new_branch_name');
this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn');
this.newBranchCreateButton.setAttribute('disabled', '');
this.addBindings();
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper);
}
cleanup() {
this.cleanBindings();
document.removeEventListener('beforeunload', this.cleanupWrapper);
}
cleanBindings() {
this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper);
this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper);
this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper);
this.dropdownBack.removeEventListener('click', this.resetFormWrapper);
this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper);
this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper);
}
addBindings() {
this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this);
this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this);
this.resetFormWrapper = this.resetForm.bind(this);
this.handleCancelClickWrapper = this.handleCancelClick.bind(this);
this.createBranchWrapper = this.createBranch.bind(this);
this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper);
this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper);
this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper);
this.dropdownBack.addEventListener('click', this.resetFormWrapper);
this.cancelButton.addEventListener('click', this.handleCancelClickWrapper);
this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper);
}
handleCancelClick(e) {
e.preventDefault();
e.stopPropagation();
this.resetForm();
this.dropdownBack.click();
}
handleNewBranchKeydown(e) {
const keyCode = e.which;
const ENTER_KEYCODE = 13;
if (keyCode === ENTER_KEYCODE) {
this.createBranch(e);
}
}
enableBranchCreateButton() {
if (this.newBranchField.value !== '') {
this.newBranchCreateButton.removeAttribute('disabled');
} else {
this.newBranchCreateButton.setAttribute('disabled', '');
}
}
resetForm() {
this.newBranchField.value = '';
this.enableBranchCreateButtonWrapper();
}
createBranch(e) {
e.preventDefault();
if (this.newBranchCreateButton.getAttribute('disabled') === '') {
return;
}
const newBranchName = this.newBranchField.value;
this.targetBranchDropdown.setNewBranch(newBranchName);
this.resetForm();
}
}
window.gl = window.gl || {};
gl.CreateBranchDropdown = CreateBranchDropdown;
/* eslint-disable class-methods-use-this */
const SELECT_ITEM_MSG = 'Select';
class TargetBranchDropDown {
constructor(dropdown) {
this.dropdown = dropdown;
this.$dropdown = $(dropdown);
this.fieldName = this.dropdown.getAttribute('data-field-name');
this.form = this.dropdown.closest('form');
this.createDropdown();
}
static bootstrap() {
const dropdowns = document.querySelectorAll('.js-project-branches-dropdown');
[].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown));
}
createDropdown() {
const self = this;
this.$dropdown.glDropdown({
selectable: true,
filterable: true,
search: {
fields: ['title'],
},
data: (term, callback) => $.ajax({
url: self.dropdown.getAttribute('data-refs-url'),
data: {
ref: self.dropdown.getAttribute('data-ref'),
show_all: true,
},
dataType: 'json',
}).done(refs => callback(self.dropdownData(refs))),
toggleLabel(item, el) {
if (el.is('.is-active')) {
return item.text;
}
return SELECT_ITEM_MSG;
},
clicked(item, el, e) {
e.preventDefault();
self.onClick.call(self);
},
fieldName: self.fieldName,
});
return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this);
}
onClick() {
this.enableSubmit();
this.$dropdown.trigger('change.branch');
}
enableSubmit() {
const submitBtn = this.form.querySelector('[type="submit"]');
if (this.branchInput && this.branchInput.value) {
submitBtn.removeAttribute('disabled');
} else {
submitBtn.setAttribute('disabled', '');
}
}
dropdownData(refs) {
const branchList = this.dropdownItems(refs);
this.cachedRefs = refs;
this.addDefaultBranch(branchList);
this.addNewBranch(branchList);
return { Branches: branchList };
}
dropdownItems(refs) {
return refs.map(this.dropdownItem);
}
dropdownItem(ref) {
return { id: ref, text: ref, title: ref };
}
addDefaultBranch(branchList) {
// when no branch is selected do nothing
if (!this.branchInput) {
return;
}
const branchInputVal = this.branchInput.value;
const currentBranchIndex = this.searchBranch(branchList, branchInputVal);
if (currentBranchIndex === -1) {
this.unshiftBranch(branchList, this.dropdownItem(branchInputVal));
}
}
addNewBranch(branchList) {
if (this.newBranch) {
this.unshiftBranch(branchList, this.newBranch);
}
}
searchBranch(branchList, branchName) {
return _.findIndex(branchList, el => branchName === el.id);
}
unshiftBranch(branchList, branch) {
const branchIndex = this.searchBranch(branchList, branch.id);
if (branchIndex === -1) {
branchList.unshift(branch);
}
}
setNewBranch(newBranchName) {
this.newBranch = this.dropdownItem(newBranchName);
this.refreshData();
this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName));
}
refreshData() {
this.glDropdown.fullData = this.dropdownData(this.cachedRefs);
this.clearFilter();
}
clearFilter() {
// apply an empty filter in order to refresh the data
this.glDropdown.filter.filter('');
this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = '';
}
selectBranch(index) {
const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index];
if (!branch.classList.contains('is-active')) {
branch.click();
} else {
this.closeDropdown();
}
}
closeDropdown() {
this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click();
}
get branchInput() {
return this.form.querySelector(`input[name="${this.fieldName}"]`);
}
get glDropdown() {
return this.$dropdown.data('glDropdown');
}
}
window.gl = window.gl || {};
gl.TargetBranchDropDown = TargetBranchDropDown;
/* eslint-disable comma-dangle, object-shorthand, func-names, space-before-function-paren, arrow-parens, no-unused-vars, class-methods-use-this, no-var, consistent-return, no-param-reassign, max-len */
((global) => {
class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
this.onClick = this.onClick.bind(this);
this.dropdown = dropdown;
this.data = data;
this.pattern = pattern;
this.wrapper = wrapper;
this.editor = editor;
this.fileEndpoint = fileEndpoint;
this.$input = $input || $('#file_name');
this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
this.buildDropdown();
this.bindEvents();
this.onFilenameUpdate();
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
}
buildDropdown() {
return this.dropdown.glDropdown({
data: this.data,
filterable: true,
selectable: true,
toggleLabel: this.toggleLabel,
search: {
fields: ['name']
},
clicked: this.onClick,
text: function(item) {
return item.name;
}
});
}
bindEvents() {
return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
}
toggleLabel(item) {
return item.name;
}
onFilenameUpdate() {
var filenameMatches;
if (!this.$input.length) {
return;
}
filenameMatches = this.pattern.test(this.$input.val().trim());
if (!filenameMatches) {
this.wrapper.addClass('hidden');
return;
}
return this.wrapper.removeClass('hidden');
}
onClick(item, el, e) {
e.preventDefault();
return this.requestFile(item);
}
requestFile(item) {
// This `requestFile` method is an abstract method that should
// be added by all subclasses.
}
// To be implemented on the extending class
// e.g.
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
requestFileSuccess(file, { skipFocus } = {}) {
if (!file) return;
const oldValue = this.editor.getValue();
const newValue = file.content;
this.editor.setValue(newValue, 1);
if (!skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
}
startLoadingSpinner() {
this.dropdownIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
}
stopLoadingSpinner() {
this.dropdownIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
}
}
global.TemplateSelector = TemplateSelector;
})(window.gl || (window.gl = {}));
/* global Api */
import TemplateSelector from './template_selector';
export default class BlobCiYamlSelector extends TemplateSelector {
requestFile(query) {
return Api.gitlabCiYml(query.name, (file, config) => this.setEditorContent(file, config));
}
}
/* global Api */
import BlobCiYamlSelector from './blob_ci_yaml_selector';
export default class BlobCiYamlSelectors {
constructor({ editor, $dropdowns }) {
this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
this.initSelectors(editor);
}
initSelectors(editor) {
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobCiYamlSelector({
editor,
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown,
});
});
}
}
/* global Api */
import TemplateSelector from './template_selector';
export default class BlobDockerfileSelector extends TemplateSelector {
requestFile(query) {
return Api.dockerfileYml(query.name, (file, config) => this.setEditorContent(file, config));
}
}
import BlobDockerfileSelector from './blob_dockerfile_selector';
export default class BlobDockerfileSelectors {
constructor({ editor, $dropdowns }) {
this.editor = editor;
this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
this.initSelectors();
}
initSelectors() {
const editor = this.editor;
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobDockerfileSelector({
editor,
pattern: /(Dockerfile)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
dropdown: $dropdown,
});
});
}
}
/* global Api */
import TemplateSelector from './template_selector';
export default class BlobGitignoreSelector extends TemplateSelector {
requestFile(query) {
return Api.gitignoreText(query.name, (file, config) => this.setEditorContent(file, config));
}
}
import BlobGitignoreSelector from './blob_gitignore_selector';
export default class BlobGitignoreSelectors {
constructor({ editor, $dropdowns }) {
this.$dropdowns = $dropdowns || $('.js-gitignore-selector');
this.editor = editor;
this.initSelectors();
}
initSelectors() {
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobGitignoreSelector({
pattern: /(.gitignore)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
dropdown: $dropdown,
editor: this.editor,
});
});
}
}
/* global Api */
import TemplateSelector from './template_selector';
export default class BlobLicenseSelector extends TemplateSelector {
requestFile(query) {
const data = {
project: this.dropdown.data('project'),
fullname: this.dropdown.data('fullname'),
};
return Api.licenseText(query.id, data, (file, config) => this.setEditorContent(file, config));
}
}
/* eslint-disable no-unused-vars, no-param-reassign */
import BlobLicenseSelector from './blob_license_selector';
export default class BlobLicenseSelectors {
constructor({ $dropdowns, editor }) {
this.$dropdowns = $dropdowns || $('.js-license-selector');
this.initSelectors(editor);
}
initSelectors(editor) {
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobLicenseSelector({
editor,
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-license-selector-wrap'),
dropdown: $dropdown,
});
});
}
}
/* eslint-disable class-methods-use-this, no-unused-vars */
export default class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) {
this.pattern = pattern;
this.editor = editor;
this.dropdown = dropdown;
this.$dropdownContainer = wrapper;
this.$filenameInput = $input || $('#file_name');
this.$dropdownIcon = $('.fa-chevron-down', dropdown);
this.initDropdown(dropdown, data);
this.listenForFilenameInput();
this.renderMatchedDropdown();
this.initAutosizeUpdateEvent();
}
initDropdown(dropdown, data) {
return $(dropdown).glDropdown({
data,
filterable: true,
selectable: true,
toggleLabel: item => item.name,
search: {
fields: ['name'],
},
clicked: (item, el, e) => this.fetchFileTemplate(item, el, e),
text: item => item.name,
});
}
initAutosizeUpdateEvent() {
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
}
listenForFilenameInput() {
return this.$filenameInput.on('keyup blur', e => this.renderMatchedDropdown(e));
}
renderMatchedDropdown() {
if (!this.$filenameInput.length) {
return null;
}
const filenameMatches = this.pattern.test(this.$filenameInput.val().trim());
if (!filenameMatches) {
return this.$dropdownContainer.addClass('hidden');
}
return this.$dropdownContainer.removeClass('hidden');
}
fetchFileTemplate(item, el, e) {
e.preventDefault();
return this.requestFile(item);
}
requestFile(item) {
// This `requestFile` method is an abstract method that should
// be added by all subclasses.
}
// To be implemented on the extending class
// e.g. Api.gitlabCiYml(query.name, file => this.setEditorContent(file));
setEditorContent(file, { skipFocus } = {}) {
if (!file) return;
const newValue = file.content;
this.editor.setValue(newValue, 1);
if (!skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
}
startLoadingSpinner() {
this.$dropdownIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
}
stopLoadingSpinner() {
this.$dropdownIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
}
}
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
/* global EditBlob */
/* global NewCommitForm */
import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone';
$(() => {
const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form');
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relative-url-root');
const assetsPath = editBlobForm.data('assets-prefix');
const blobLanguage = editBlobForm.data('blob-language');
new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage);
new NewCommitForm(editBlobForm);
}
if (uploadBlobForm.length) {
const method = uploadBlobForm.data('method');
new BlobFileDropzone(uploadBlobForm, method);
new NewCommitForm(uploadBlobForm);
window.gl.utils.disableButtonIfEmptyField(
uploadBlobForm.find('.js-commit-message'),
'.btn-upload-file',
);
}
});
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
/* global EditBlob */
/* global NewCommitForm */
require('./edit_blob');
(function() {
$(function() {
var url = $(".js-edit-blob-form").data("relative-url-root");
url += $(".js-edit-blob-form").data("assets-prefix");
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
new NewCommitForm($('.js-edit-blob-form'));
});
}).call(window);
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, max-len */
/* global ace */ /* global ace */
/* global BlobGitignoreSelectors */
import BlobLicenseSelectors from '../blob/template_selectors/blob_license_selectors';
(function() { import BlobGitignoreSelectors from '../blob/template_selectors/blob_gitignore_selectors';
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; import BlobCiYamlSelectors from '../blob/template_selectors/blob_ci_yaml_selectors';
import BlobDockerfileSelectors from '../blob/template_selectors/blob_dockerfile_selectors';
this.EditBlob = (function() {
function EditBlob(assets_path, ace_mode) { export default class EditBlob {
if (ace_mode == null) { constructor(assetsPath, aceMode) {
ace_mode = null; this.configureAceEditor(aceMode, assetsPath);
} this.prepFileContentForSubmit();
this.editModeLinkClickHandler = bind(this.editModeLinkClickHandler, this); this.initModePanesAndLinks();
ace.config.set("modePath", assets_path + "/ace"); this.initSoftWrap();
ace.config.loadModule("ace/ext/searchbox"); this.initFileSelectors();
this.editor = ace.edit("editor"); }
this.editor.focus();
if (ace_mode) { configureAceEditor(aceMode, assetsPath) {
this.editor.getSession().setMode("ace/mode/" + ace_mode); ace.config.set('modePath', `${assetsPath}/ace`);
} ace.config.loadModule('ace/ext/searchbox');
$('form').submit((function(_this) {
return function() { this.editor = ace.edit('editor');
return $("#file-content").val(_this.editor.getValue()); this.editor.focus();
};
// Before a form submission, move the content from the Ace editor into the if (aceMode) {
// submitted textarea this.editor.getSession().setMode(`ace/mode/${aceMode}`);
})(this)); }
this.initModePanesAndLinks(); }
this.initSoftWrap();
new gl.BlobLicenseSelectors({ prepFileContentForSubmit() {
editor: this.editor $('form').submit(() => {
}); $('#file-content').val(this.editor.getValue());
});
}
initFileSelectors() {
this.blobTemplateSelectors = [
new BlobLicenseSelectors({
editor: this.editor,
}),
new BlobGitignoreSelectors({ new BlobGitignoreSelectors({
editor: this.editor editor: this.editor,
}); }),
new gl.BlobCiYamlSelectors({ new BlobCiYamlSelectors({
editor: this.editor editor: this.editor,
}); }),
new gl.BlobDockerfileSelectors({ new BlobDockerfileSelectors({
editor: this.editor editor: this.editor,
}),
];
}
initModePanesAndLinks() {
this.$editModePanes = $('.js-edit-mode-pane');
this.$editModeLinks = $('.js-edit-mode a');
this.$editModeLinks.on('click', e => this.editModeLinkClickHandler(e));
}
editModeLinkClickHandler(e) {
e.preventDefault();
const currentLink = $(e.target);
const paneId = currentLink.attr('href');
const currentPane = this.$editModePanes.filter(paneId);
this.$editModeLinks.parent().removeClass('active hover');
currentLink.parent().addClass('active hover');
this.$editModePanes.hide();
currentPane.fadeIn(200);
if (paneId === '#preview') {
this.$toggleButton.hide();
return $.post(currentLink.data('preview-url'), {
content: this.editor.getValue(),
}, (response) => {
currentPane.empty().append(response);
return currentPane.renderGFM();
}); });
} }
EditBlob.prototype.initModePanesAndLinks = function() { this.$toggleButton.show();
this.$editModePanes = $(".js-edit-mode-pane");
this.$editModeLinks = $(".js-edit-mode a"); return this.editor.focus();
return this.$editModeLinks.click(this.editModeLinkClickHandler); }
};
initSoftWrap() {
EditBlob.prototype.editModeLinkClickHandler = function(event) { this.isSoftWrapped = false;
var currentLink, currentPane, paneId; this.$toggleButton = $('.soft-wrap-toggle');
event.preventDefault(); this.$toggleButton.on('click', () => this.toggleSoftWrap());
currentLink = $(event.target); }
paneId = currentLink.attr("href");
currentPane = this.$editModePanes.filter(paneId); toggleSoftWrap() {
this.$editModeLinks.parent().removeClass("active hover"); this.isSoftWrapped = !this.isSoftWrapped;
currentLink.parent().addClass("active hover"); this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.$editModePanes.hide(); this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
currentPane.fadeIn(200); }
if (paneId === "#preview") { }
this.$toggleButton.hide();
return $.post(currentLink.data("preview-url"), {
content: this.editor.getValue()
}, function(response) {
currentPane.empty().append(response);
return currentPane.renderGFM();
});
} else {
this.$toggleButton.show();
return this.editor.focus();
}
};
EditBlob.prototype.initSoftWrap = function() {
this.isSoftWrapped = false;
this.$toggleButton = $('.soft-wrap-toggle');
this.$toggleButton.on('click', this.toggleSoftWrap.bind(this));
};
EditBlob.prototype.toggleSoftWrap = function(e) {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
};
return EditBlob;
})();
}).call(window);
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global Vue */
/* global BoardService */ /* global BoardService */
import Vue from 'vue';
import VueResource from 'vue-resource';
import FilteredSearchBoards from './filtered_search_boards'; import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub'; import eventHub from './eventhub';
import collapseIcon from './icons/fullscreen_collapse.svg';
import expandIcon from './icons/fullscreen_expand.svg';
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('./models/issue'); require('./models/issue');
require('./models/label'); require('./models/label');
require('./models/list'); require('./models/list');
...@@ -25,10 +26,13 @@ require('./components/new_list_dropdown'); ...@@ -25,10 +26,13 @@ require('./components/new_list_dropdown');
require('./components/modal/index'); require('./components/modal/index');
require('../vue_shared/vue_resource_interceptor'); require('../vue_shared/vue_resource_interceptor');
Vue.use(VueResource);
$(() => { $(() => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
const issueBoardsContent = document.querySelector('.js-focus-mode-board');
window.gl = window.gl || {}; window.gl = window.gl || {};
...@@ -116,7 +120,7 @@ $(() => { ...@@ -116,7 +120,7 @@ $(() => {
}, },
mounted () { mounted () {
gl.issueBoards.newListDropdownInit(); gl.issueBoards.newListDropdownInit();
} },
}); });
gl.IssueBoardsModalAddBtn = new Vue({ gl.IssueBoardsModalAddBtn = new Vue({
...@@ -125,6 +129,7 @@ $(() => { ...@@ -125,6 +129,7 @@ $(() => {
data: { data: {
modal: ModalStore.store, modal: ModalStore.store,
store: Store.state, store: Store.state,
isFullscreen: false,
}, },
watch: { watch: {
disabled() { disabled() {
...@@ -145,7 +150,7 @@ $(() => { ...@@ -145,7 +150,7 @@ $(() => {
}, },
methods: { methods: {
updateTooltip() { updateTooltip() {
const $tooltip = $(this.$el); const $tooltip = $(this.$refs.addIssuesButton);
this.$nextTick(() => { this.$nextTick(() => {
if (this.disabled) { if (this.disabled) {
...@@ -160,21 +165,45 @@ $(() => { ...@@ -160,21 +165,45 @@ $(() => {
this.toggleModal(true); this.toggleModal(true);
} }
}, },
toggleFocusMode() {
$(this.$refs.toggleFocusModeButton).tooltip('hide');
issueBoardsContent.classList.toggle('is-focused');
this.isFullscreen = !this.isFullscreen;
},
}, },
mounted() { mounted() {
this.updateTooltip(); this.updateTooltip();
}, },
template: ` template: `
<button <div class="board-extra-actions">
class="btn btn-create pull-right prepend-left-10" <button
type="button" class="btn btn-create prepend-left-10"
data-placement="bottom" type="button"
:class="{ 'disabled': disabled }" data-placement="bottom"
:title="tooltipTitle" ref="addIssuesButton"
:aria-disabled="disabled" :class="{ 'disabled': disabled }"
@click="openModal"> :title="tooltipTitle"
Add issues :aria-disabled="disabled"
</button> @click="openModal">
Add issues
</button>
<a
href="#"
class="btn btn-default has-tooltip prepend-left-10"
role="button"
aria-label="Toggle focus mode"
title="Toggle focus mode"
ref="toggleFocusModeButton"
@click="toggleFocusMode">
<span v-show="isFullscreen">
${collapseIcon}
</span>
<span v-show="!isFullscreen">
${expandIcon}
</span>
</a>
</div>
`, `,
}); });
......
/* eslint-disable comma-dangle, space-before-function-paren, one-var */ /* eslint-disable comma-dangle, space-before-function-paren, one-var */
/* global Vue */
/* global Sortable */ /* global Sortable */
require('./board_blank_state'); import Vue from 'vue';
import boardBlankState from './board_blank_state';
require('./board_delete'); require('./board_delete');
require('./board_list'); require('./board_list');
...@@ -17,7 +18,7 @@ require('./board_list'); ...@@ -17,7 +18,7 @@ require('./board_list');
components: { components: {
'board-list': gl.issueBoards.BoardList, 'board-list': gl.issueBoards.BoardList,
'board-delete': gl.issueBoards.BoardDelete, 'board-delete': gl.issueBoards.BoardDelete,
'board-blank-state': gl.issueBoards.BoardBlankState boardBlankState,
}, },
props: { props: {
list: Object, list: Object,
......
/* eslint-disable space-before-function-paren, comma-dangle */
/* global Vue */
/* global ListLabel */ /* global ListLabel */
(() => { import Cookies from 'js-cookie';
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {}; const Store = gl.issueBoards.BoardsStore;
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardBlankState = Vue.extend({ export default {
data () { template: `
return { <div class="board-blank-state">
predefinedLabels: [ <p>
new ListLabel({ title: 'To Do', color: '#F0AD4E' }), Add the following default lists to your Issue Board with one click:
new ListLabel({ title: 'Doing', color: '#5CB85C' }) </p>
] <ul class="board-blank-state-list">
}; <li v-for="label in predefinedLabels">
}, <span
methods: { class="label-color"
addDefaultLists () { :style="{ backgroundColor: label.color }">
this.clearBlankState(); </span>
{{ label.title }}
</li>
</ul>
<p>
Starting out with the default set of lists will get you right on the way to making the most of your board.
</p>
<button
class="btn btn-create btn-inverted btn-block"
type="button"
@click.stop="addDefaultLists">
Add default lists
</button>
<button
class="btn btn-default btn-block"
type="button"
@click.stop="clearBlankState">
Nevermind, I'll use my own
</button>
</div>
`,
data() {
return {
predefinedLabels: [
new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
new ListLabel({ title: 'Doing', color: '#5CB85C' }),
],
};
},
methods: {
addDefaultLists() {
this.clearBlankState();
this.predefinedLabels.forEach((label, i) => { this.predefinedLabels.forEach((label, i) => {
Store.addList({ Store.addList({
title: label.title,
position: i,
list_type: 'label',
label: {
title: label.title, title: label.title,
position: i, color: label.color,
list_type: 'label', },
label: {
title: label.title,
color: label.color
}
});
}); });
});
Store.state.lists = _.sortBy(Store.state.lists, 'position'); Store.state.lists = _.sortBy(Store.state.lists, 'position');
// Save the labels // Save the labels
gl.boardService.generateDefaultLists() gl.boardService.generateDefaultLists()
.then((resp) => { .then((resp) => {
resp.json().forEach((listObj) => { resp.json().forEach((listObj) => {
const list = Store.findList('title', listObj.title); const list = Store.findList('title', listObj.title);
list.id = listObj.id; list.id = listObj.id;
list.label.id = listObj.label.id; list.label.id = listObj.label.id;
list.getIssues(); list.getIssues();
}); });
})
.catch(() => {
Store.removeList(undefined, 'label');
Cookies.remove('issue_board_welcome_hidden', {
path: '',
}); });
}, Store.addBlankState();
clearBlankState: Store.removeBlankState.bind(Store) });
} },
}); clearBlankState: Store.removeBlankState.bind(Store),
})(); },
};
/* global Vue */
require('./issue_card_inner'); require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
......
/* eslint-disable comma-dangle, space-before-function-paren, no-alert */ /* eslint-disable comma-dangle, space-before-function-paren, no-alert */
/* global Vue */
import Vue from 'vue';
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/* eslint-disable comma-dangle, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, space-before-function-paren, max-len */
/* global Vue */
/* global Sortable */ /* global Sortable */
import Vue from 'vue';
import boardNewIssue from './board_new_issue'; import boardNewIssue from './board_new_issue';
import boardCard from './board_card'; import boardCard from './board_card';
...@@ -48,7 +48,7 @@ import boardCard from './board_card'; ...@@ -48,7 +48,7 @@ import boardCard from './board_card';
this.list.getIssues(false); this.list.getIssues(false);
} }
if (this.scrollHeight() > this.listHeight()) { if (this.scrollHeight() > Math.ceil(this.listHeight())) {
this.showCount = true; this.showCount = true;
} else { } else {
this.showCount = false; this.showCount = false;
......
/* global Vue */
/* global BoardService */ /* global BoardService */
import Vue from 'vue';
const boardMilestoneSelect = require('./milestone_select'); const boardMilestoneSelect = require('./milestone_select');
const extraMilestones = require('../mixins/extra_milestones'); const extraMilestones = require('../mixins/extra_milestones');
......
...@@ -26,6 +26,10 @@ export default { ...@@ -26,6 +26,10 @@ export default {
subscribed: true, subscribed: true,
}); });
if (Store.state.currentBoard) {
issue.milestone_id = Store.state.currentBoard.milestone_id;
}
this.list.newIssue(issue) this.list.newIssue(issue)
.then(() => { .then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions // Need this because our jQuery very kindly disables buttons on ALL form submissions
......
/* eslint-disable comma-dangle, space-before-function-paren, no-new */ /* eslint-disable comma-dangle, space-before-function-paren, no-new */
/* global Vue */
/* global IssuableContext */ /* global IssuableContext */
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global LabelsSelect */ /* global LabelsSelect */
/* global Sidebar */ /* global Sidebar */
import Vue from 'vue';
require('./sidebar/remove_issue'); require('./sidebar/remove_issue');
(() => { (() => {
......
/* global Vue */ import Vue from 'vue';
require('./board_new_form'); require('./board_new_form');
......
/* global Vue */ import Vue from 'vue';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
(() => { (() => {
......
/* global BoardService */ /* global BoardService */
/* global Vue */
const extraMilestones = require('../mixins/extra_milestones'); const extraMilestones = require('../mixins/extra_milestones');
module.exports = { module.exports = {
......
/* global Vue */ import Vue from 'vue';
(() => { (() => {
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
...@@ -30,7 +31,7 @@ ...@@ -30,7 +31,7 @@
if (this.activeTab === 'selected') { if (this.activeTab === 'selected') {
obj.title = 'You haven\'t selected any issues yet'; obj.title = 'You haven\'t selected any issues yet';
obj.content = ` obj.content = `
Go back to <strong>All issues</strong> and select some issues Go back to <strong>Open issues</strong> and select some issues
to add to your board. to add to your board.
`; `;
} }
...@@ -59,7 +60,7 @@ ...@@ -59,7 +60,7 @@
class="btn btn-default" class="btn btn-default"
@click="changeTab('all')" @click="changeTab('all')"
v-if="activeTab === 'selected'"> v-if="activeTab === 'selected'">
All issues Open issues
</button> </button>
</div> </div>
</div> </div>
......
...@@ -15,7 +15,7 @@ export default { ...@@ -15,7 +15,7 @@ export default {
this.filteredSearch = new FilteredSearchBoards(this.store); this.filteredSearch = new FilteredSearchBoards(this.store);
this.filteredSearch.removeTokens(); this.filteredSearch.removeTokens();
}, },
beforeDestroy() { destroyed() {
this.filteredSearch.cleanup(); this.filteredSearch.cleanup();
FilteredSearchContainer.container = document; FilteredSearchContainer.container = document;
this.store.path = ''; this.store.path = '';
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Vue */
/* global Flash */ /* global Flash */
import Vue from 'vue';
require('./lists_dropdown'); require('./lists_dropdown');
(() => { (() => {
...@@ -34,6 +35,7 @@ require('./lists_dropdown'); ...@@ -34,6 +35,7 @@ require('./lists_dropdown');
// Post the data to the backend // Post the data to the backend
gl.boardService.bulkUpdate(issueIds, { gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id], add_label_ids: [list.label.id],
milestone_id: this.state.currentBoard.milestone_id,
}).catch(() => { }).catch(() => {
new Flash('Failed to update issues, please try again.', 'alert'); new Flash('Failed to update issues, please try again.', 'alert');
......
/* global Vue */
/* global ListIssue */ /* global ListIssue */
import Vue from 'vue';
import queryData from '../../utils/query_data'; import queryData from '../../utils/query_data';
require('./header'); require('./header');
...@@ -64,7 +65,15 @@ require('./empty_state'); ...@@ -64,7 +65,15 @@ require('./empty_state');
}, },
filter: { filter: {
handler() { handler() {
this.loadIssues(true); if (this.$el.tagName) {
this.page = 1;
this.filterLoading = true;
this.loadIssues(true)
.then(() => {
this.filterLoading = false;
});
}
}, },
deep: true, deep: true,
}, },
...@@ -115,6 +124,9 @@ require('./empty_state'); ...@@ -115,6 +124,9 @@ require('./empty_state');
return this.activeTab === 'selected' && this.selectedIssues.length === 0; return this.activeTab === 'selected' && this.selectedIssues.length === 0;
}, },
}, },
created() {
this.page = 1;
},
components: { components: {
'modal-header': gl.issueBoards.ModalHeader, 'modal-header': gl.issueBoards.ModalHeader,
'modal-list': gl.issueBoards.ModalList, 'modal-list': gl.issueBoards.ModalList,
...@@ -135,14 +147,14 @@ require('./empty_state'); ...@@ -135,14 +147,14 @@ require('./empty_state');
:image="blankStateImage" :image="blankStateImage"
:issue-link-base="issueLinkBase" :issue-link-base="issueLinkBase"
:root-path="rootPath" :root-path="rootPath"
v-if="!loading && showList"></modal-list> v-if="!loading && showList && !filterLoading"></modal-list>
<empty-state <empty-state
v-if="showEmptyState" v-if="showEmptyState"
:image="blankStateImage" :image="blankStateImage"
:new-issue-path="newIssuePath"></empty-state> :new-issue-path="newIssuePath"></empty-state>
<section <section
class="add-issues-list text-center" class="add-issues-list text-center"
v-if="loading"> v-if="loading || filterLoading">
<div class="add-issues-list-loading"> <div class="add-issues-list-loading">
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
</div> </div>
......
/* global Vue */
/* global ListIssue */ /* global ListIssue */
/* global bp */ /* global bp */
import Vue from 'vue';
(() => { (() => {
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
......
/* global Vue */ import Vue from 'vue';
(() => { (() => {
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
......
/* global Vue */ import Vue from 'vue';
(() => { (() => {
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
...@@ -23,7 +24,7 @@ ...@@ -23,7 +24,7 @@
href="#" href="#"
role="button" role="button"
@click.prevent="changeTab('all')"> @click.prevent="changeTab('all')">
All issues Open issues
<span class="badge"> <span class="badge">
{{ issuesCount }} {{ issuesCount }}
</span> </span>
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Vue */
/* global Flash */ /* global Flash */
import Vue from 'vue';
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -23,11 +25,16 @@ ...@@ -23,11 +25,16 @@
const issue = this.issue; const issue = this.issue;
const lists = issue.getLists(); const lists = issue.getLists();
const labelIds = lists.map(list => list.label.id); const labelIds = lists.map(list => list.label.id);
const data = {
remove_label_ids: labelIds,
};
if (Store.state.currentBoard.milestone_id) {
data.milestone_id = -1;
}
// Post the remove data // Post the remove data
gl.boardService.bulkUpdate([issue.globalId], { gl.boardService.bulkUpdate([issue.globalId], data).catch(() => {
remove_label_ids: labelIds,
}).catch(() => {
new Flash('Failed to remove issue from board, please try again.', 'alert'); new Flash('Failed to remove issue from board, please try again.', 'alert');
lists.forEach((list) => { lists.forEach((list) => {
......
/* global Vue */
/* global dateFormat */ /* global dateFormat */
import Vue from 'vue';
Vue.filter('due-date', (value) => { Vue.filter('due-date', (value) => {
const date = new Date(value); const date = new Date(value);
return dateFormat(date, 'mmm d, yyyy', true); return dateFormat(date, 'mmm d, yyyy', true);
......
<svg width="17" height="17" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg"><path d="M.147 15.496l2.146-2.146-1.286-1.286a.55.55 0 0 1-.125-.616c.101-.238.277-.357.527-.357h4a.55.55 0 0 1 .402.17.55.55 0 0 1 .17.401v4c0 .25-.12.426-.358.527-.232.101-.437.06-.616-.125l-1.286-1.286-2.146 2.146-1.428-1.428zM14.996.646l1.428 1.43-2.146 2.145 1.286 1.286c.185.179.226.384.125.616-.101.238-.277.357-.527.357h-4a.55.55 0 0 1-.402-.17.55.55 0 0 1-.17-.401v-4c0-.25.12-.426.358-.527a.553.553 0 0 1 .616.125l1.286 1.286L14.996.647zm-13.42 0L3.72 2.794l1.286-1.286a.55.55 0 0 1 .616-.125c.238.101.357.277.357.527v4a.55.55 0 0 1-.17.402.55.55 0 0 1-.401.17h-4c-.25 0-.426-.12-.527-.358-.101-.232-.06-.437.125-.616l1.286-1.286L.147 2.075 1.575.647zm14.848 14.85l-1.428 1.428-2.146-2.146-1.286 1.286c-.179.185-.384.226-.616.125-.238-.101-.357-.277-.357-.527v-4a.55.55 0 0 1 .17-.402.55.55 0 0 1 .401-.17h4c.25 0 .426.12.527.358a.553.553 0 0 1-.125.616l-1.286 1.286 2.146 2.146z" fill-rule="evenodd"/></svg>
<svg width="15" height="15" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path d="M8.591 5.056l2.147-2.146-1.286-1.286a.55.55 0 0 1-.125-.616c.101-.238.277-.357.527-.357h4a.55.55 0 0 1 .402.17.55.55 0 0 1 .17.401v4c0 .25-.12.426-.358.527-.232.101-.437.06-.616-.125l-1.286-1.286-2.146 2.147-1.429-1.43zM5.018 8.553l1.429 1.43L4.3 12.127l1.286 1.286c.185.179.226.384.125.616-.101.238-.277.357-.527.357h-4a.55.55 0 0 1-.402-.17.55.55 0 0 1-.17-.401v-4c0-.25.12-.426.358-.527a.553.553 0 0 1 .616.125L2.872 10.7l2.146-2.147zm4.964 0l2.146 2.147 1.286-1.286a.55.55 0 0 1 .616-.125c.238.101.357.277.357.527v4a.55.55 0 0 1-.17.402.55.55 0 0 1-.401.17h-4c-.25 0-.426-.12-.527-.358-.101-.232-.06-.437.125-.616l1.286-1.286-2.147-2.146 1.43-1.429zM6.447 5.018l-1.43 1.429L2.873 4.3 1.586 5.586c-.179.185-.384.226-.616.125-.238-.101-.357-.277-.357-.527v-4a.55.55 0 0 1 .17-.402.55.55 0 0 1 .401-.17h4c.25 0 .426.12.527.358a.553.553 0 0 1-.125.616L4.3 2.872l2.147 2.146z" fill-rule="evenodd"/></svg>
/* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, arrow-parens, comma-dangle, max-len */ /* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, arrow-parens, comma-dangle, max-len */
/* global Vue */
/* global ListLabel */ /* global ListLabel */
/* global ListMilestone */ /* global ListMilestone */
/* global ListUser */ /* global ListUser */
import Vue from 'vue';
class ListIssue { class ListIssue {
constructor (obj) { constructor (obj) {
this.globalId = obj.id; this.globalId = obj.id;
...@@ -16,6 +17,7 @@ class ListIssue { ...@@ -16,6 +17,7 @@ class ListIssue {
this.selected = false; this.selected = false;
this.assignee = false; this.assignee = false;
this.position = obj.relative_position || Infinity; this.position = obj.relative_position || Infinity;
this.milestone_id = obj.milestone_id;
if (obj.assignee) { if (obj.assignee) {
this.assignee = new ListUser(obj.assignee); this.assignee = new ListUser(obj.assignee);
......
...@@ -97,6 +97,7 @@ class List { ...@@ -97,6 +97,7 @@ class List {
.then((resp) => { .then((resp) => {
const data = resp.json(); const data = resp.json();
issue.id = data.iid; issue.id = data.iid;
issue.milestone = data.milestone;
}); });
} }
......
/* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, max-len, no-unused-vars, no-else-return */ /* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, max-len, no-unused-vars */
/* global Vue */
import Vue from 'vue';
class BoardService { class BoardService {
constructor (root, bulkUpdatePath, boardId) { constructor (root, bulkUpdatePath, boardId) {
...@@ -36,9 +37,8 @@ class BoardService { ...@@ -36,9 +37,8 @@ class BoardService {
createBoard (board) { createBoard (board) {
if (board.id) { if (board.id) {
return this.boards.update({ id: board.id }, board); return this.boards.update({ id: board.id }, board);
} else {
return this.boards.save({}, board);
} }
return this.boards.save({}, board);
} }
all () { all () {
......
/* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */ /* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */
/* global Cookies */
/* global List */ /* global List */
import Cookies from 'js-cookie';
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
searchTerm: '', searchTerm: '',
loading: false, loading: false,
loadingNewPage: false, loadingNewPage: false,
filterLoading: false,
page: 1, page: 1,
perPage: 50, perPage: 50,
filter: { filter: {
......
/* eslint-disable no-new, no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
import Vue from 'vue';
import VueResource from 'vue-resource';
import CommitPipelinesTable from './pipelines_table';
Vue.use(VueResource);
window.Vue = require('vue');
require('./pipelines_table');
/** /**
* Commits View > Pipelines Tab > Pipelines Table. * Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table. * Merge Request View > Pipelines Tab > Pipelines Table.
...@@ -21,7 +24,7 @@ $(() => { ...@@ -21,7 +24,7 @@ $(() => {
} }
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView(); gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) { if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl); gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
......
/* globals Vue */
/* eslint-disable no-unused-vars, no-param-reassign */
/**
* Pipelines service.
*
* Used to fetch the data used to render the pipelines table.
* Uses Vue.Resource
*/
class PipelinesService {
/**
* FIXME: The url provided to request the pipelines in the new merge request
* page already has `.json`.
* This should be fixed when the endpoint is improved.
*
* @param {String} root
*/
constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`;
} else {
endpoint = root;
}
this.pipelines = Vue.resource(endpoint);
}
/**
* Given the root param provided when the class is initialized, will
* make a GET request.
*
* @return {Promise}
*/
all() {
return this.pipelines.get();
}
}
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesService = PipelinesService;
/* eslint-disable no-new, no-param-reassign */ /* eslint-disable no-new*/
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */ /* global Flash */
import Vue from 'vue';
window.Vue = require('vue'); import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
window.Vue.use(require('vue-resource')); import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
require('../../lib/utils/common_utils'); import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
require('../../vue_shared/vue_resource_interceptor'); import eventHub from '../../vue_pipelines_index/event_hub';
require('../../vue_shared/components/pipelines_table'); import '../../lib/utils/common_utils';
require('./pipelines_service'); import '../../vue_shared/vue_resource_interceptor';
const PipelineStore = require('./pipelines_store');
/** /**
* *
...@@ -20,48 +19,59 @@ const PipelineStore = require('./pipelines_store'); ...@@ -20,48 +19,59 @@ const PipelineStore = require('./pipelines_store');
* as soon as we have Webpack and can load them directly into JS files. * as soon as we have Webpack and can load them directly into JS files.
*/ */
(() => { export default Vue.component('pipelines-table', {
window.gl = window.gl || {}; components: {
gl.commits = gl.commits || {}; 'pipelines-table-component': PipelinesTableComponent,
gl.commits.pipelines = gl.commits.pipelines || {}; },
gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', { /**
* Accesses the DOM to provide the needed data.
* Returns the necessary props to render `pipelines-table-component` component.
*
* @return {Object}
*/
data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const store = new PipelineStore();
components: { return {
'pipelines-table-component': gl.pipelines.PipelinesTableComponent, endpoint: pipelinesTableData.endpoint,
}, store,
state: store.state,
isLoading: false,
};
},
/** /**
* Accesses the DOM to provide the needed data. * When the component is about to be mounted, tell the service to fetch the data
* Returns the necessary props to render `pipelines-table-component` component. *
* * A request to fetch the pipelines will be made.
* @return {Object} * In case of a successfull response we will store the data in the provided
*/ * store, in case of a failed response we need to warn the user.
data() { *
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; */
const store = new PipelineStore(); beforeMount() {
this.service = new PipelinesService(this.endpoint);
return { this.fetchPipelines();
endpoint: pipelinesTableData.endpoint,
store, eventHub.$on('refreshPipelines', this.fetchPipelines);
state: store.state, },
isLoading: false,
}; beforeUpdate() {
}, if (this.state.pipelines.length && this.$children) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
/** beforeDestroyed() {
* When the component is about to be mounted, tell the service to fetch the data eventHub.$off('refreshPipelines');
* },
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
beforeMount() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
methods: {
fetchPipelines() {
this.isLoading = true; this.isLoading = true;
return pipelinesService.all() return this.service.getPipelines()
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
// depending of the endpoint the response can either bring a `pipelines` key or not. // depending of the endpoint the response can either bring a `pipelines` key or not.
...@@ -71,34 +81,30 @@ const PipelineStore = require('./pipelines_store'); ...@@ -71,34 +81,30 @@ const PipelineStore = require('./pipelines_store');
}) })
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert'); new Flash('An error occurred while fetching the pipelines, please reload the page again.');
}); });
}, },
},
beforeUpdate() { template: `
if (this.state.pipelines.length && this.$children) { <div class="pipelines">
PipelineStore.startTimeAgoLoops.call(this, Vue); <div class="realtime-loading" v-if="isLoading">
} <i class="fa fa-spinner fa-spin"></i>
}, </div>
template: `
<div class="pipelines">
<div class="realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="blank-state blank-state-no-icon" <div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.pipelines.length === 0"> v-if="!isLoading && state.pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title"> <h2 class="blank-state-title js-blank-state-title">
No pipelines to show No pipelines to show
</h2> </h2>
</div> </div>
<div class="table-holder pipelines" <div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0"> v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component :pipelines="state.pipelines"/> <pipelines-table-component
</div> :pipelines="state.pipelines"
:service="service" />
</div> </div>
`, </div>
}); `,
})(); });
import 'jquery'; import $ from 'jquery';
// bootstrap jQuery plugins // bootstrap jQuery plugins
import 'bootstrap-sass/assets/javascripts/bootstrap/affix'; import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
...@@ -8,3 +8,9 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal'; ...@@ -8,3 +8,9 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition'; import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip'; import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
// custom jQuery functions
$.fn.extend({
disable() { return $(this).attr('disabled', 'disabled').addClass('disabled'); },
enable() { return $(this).removeAttr('disabled').removeClass('disabled'); },
});
import './polyfills';
import './jquery'; import './jquery';
import './bootstrap'; import './bootstrap';
// ECMAScript polyfills
import 'core-js/fn/array/find';
import 'core-js/fn/object/assign';
import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
// Browser polyfills
import './polyfills/custom_event';
import './polyfills/element';
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function CustomEvent(event, params) {
const evt = document.createEvent('CustomEvent');
const evtParams = params || { bubbles: false, cancelable: false, detail: undefined };
evt.initCustomEvent(event, evtParams.bubbles, evtParams.cancelable, evtParams.detail);
return evt;
};
window.CustomEvent.prototype = Event;
}
/* global Element */ Element.prototype.closest = Element.prototype.closest ||
/* eslint-disable consistent-return, max-len, no-empty, func-names */ function closest(selector, selectedElement = this) {
if (!selectedElement) return null;
Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) { return selectedElement.matches(selector) ?
if (!selectedElement) return; selectedElement :
return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement); Element.prototype.closest(selector, selectedElement.parentElement);
}; };
Element.prototype.matches = Element.prototype.matches || Element.prototype.matches = Element.prototype.matches ||
Element.prototype.matchesSelector || Element.prototype.matchesSelector ||
...@@ -12,9 +12,9 @@ Element.prototype.matches = Element.prototype.matches || ...@@ -12,9 +12,9 @@ Element.prototype.matches = Element.prototype.matches ||
Element.prototype.msMatchesSelector || Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector || Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector || Element.prototype.webkitMatchesSelector ||
function (s) { function matches(selector) {
const matches = (this.document || this.ownerDocument).querySelectorAll(s); const elms = (this.document || this.ownerDocument).querySelectorAll(selector);
let i = matches.length - 1; let i = elms.length - 1;
while (i >= 0 && matches.item(i) !== this) { i -= 1; } while (i >= 0 && elms.item(i) !== this) { i -= 1; }
return i > -1; return i > -1;
}; };
...@@ -18,7 +18,8 @@ window.CompareAutocomplete = (function() { ...@@ -18,7 +18,8 @@ window.CompareAutocomplete = (function() {
return $.ajax({ return $.ajax({
url: $dropdown.data('refs-url'), url: $dropdown.data('refs-url'),
data: { data: {
ref: $dropdown.data('ref') ref: $dropdown.data('ref'),
search: term,
} }
}).done(function(refs) { }).done(function(refs) {
return callback(refs); return callback(refs);
...@@ -26,7 +27,7 @@ window.CompareAutocomplete = (function() { ...@@ -26,7 +27,7 @@ window.CompareAutocomplete = (function() {
}, },
selectable: true, selectable: true,
filterable: true, filterable: true,
filterByText: true, filterRemote: true,
fieldName: $dropdown.data('field-name'), fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]', filterInput: 'input[type="search"]',
renderRow: function(ref) { renderRow: function(ref) {
......
...@@ -118,10 +118,10 @@ const gfmRules = { ...@@ -118,10 +118,10 @@ const gfmRules = {
}, },
SyntaxHighlightFilter: { SyntaxHighlightFilter: {
'pre.code.highlight'(el, t) { 'pre.code.highlight'(el, t) {
const text = t.trim(); const text = t.trimRight();
let lang = el.getAttribute('lang'); let lang = el.getAttribute('lang');
if (lang === 'plaintext') { if (!lang || lang === 'plaintext') {
lang = ''; lang = '';
} }
...@@ -157,7 +157,7 @@ const gfmRules = { ...@@ -157,7 +157,7 @@ const gfmRules = {
const backticks = Array(backtickCount + 1).join('`'); const backticks = Array(backtickCount + 1).join('`');
const spaceOrNoSpace = backtickCount > 1 ? ' ' : ''; const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks; return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
}, },
'blockquote'(el, text) { 'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
...@@ -273,28 +273,29 @@ const gfmRules = { ...@@ -273,28 +273,29 @@ const gfmRules = {
class CopyAsGFM { class CopyAsGFM {
constructor() { constructor() {
$(document).on('copy', '.md, .wiki', this.handleCopy); $(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('paste', '.js-gfm-input', this.handlePaste); $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this));
} }
handleCopy(e) { copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
const documentFragment = window.gl.utils.getSelectedFragment(); const documentFragment = window.gl.utils.getSelectedFragment();
if (!documentFragment) return; if (!documentFragment) return;
// If the documentFragment contains more than just Markdown, don't copy as GFM. const el = transformer(documentFragment.cloneNode(true));
if (documentFragment.querySelector('.md, .wiki')) return; if (!el) return;
e.preventDefault(); e.preventDefault();
clipboardData.setData('text/plain', documentFragment.textContent); e.stopPropagation();
const gfm = CopyAsGFM.nodeToGFM(documentFragment); clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', gfm); clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el));
} }
handlePaste(e) { pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
...@@ -306,7 +307,47 @@ class CopyAsGFM { ...@@ -306,7 +307,47 @@ class CopyAsGFM {
window.gl.utils.insertText(e.target, gfm); window.gl.utils.insertText(e.target, gfm);
} }
static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return null;
return documentFragment;
}
static transformCodeSelection(documentFragment) {
const lineEls = documentFragment.querySelectorAll('.line');
let codeEl;
if (lineEls.length > 1) {
codeEl = document.createElement('pre');
codeEl.className = 'code highlight';
const lang = lineEls[0].getAttribute('lang');
if (lang) {
codeEl.setAttribute('lang', lang);
}
} else {
codeEl = document.createElement('code');
}
if (lineEls.length > 0) {
for (let i = 0; i < lineEls.length; i += 1) {
const lineEl = lineEls[i];
codeEl.appendChild(lineEl);
codeEl.appendChild(document.createTextNode('\n'));
}
} else {
codeEl.appendChild(documentFragment);
}
return codeEl;
}
static nodeToGFM(node) { static nodeToGFM(node) {
if (node.nodeType === Node.COMMENT_NODE) {
return '';
}
if (node.nodeType === Node.TEXT_NODE) { if (node.nodeType === Node.TEXT_NODE) {
return node.textContent; return node.textContent;
} }
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* global Vue */
/* global Cookies */
/* global Flash */ /* global Flash */
window.Vue = require('vue'); import Vue from 'vue';
window.Cookies = require('js-cookie'); import Cookies from 'js-cookie';
require('./components/stage_code_component'); require('./components/stage_code_component');
require('./components/stage_issue_component'); require('./components/stage_issue_component');
require('./components/stage_plan_component'); require('./components/stage_plan_component');
......
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */ /* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
/* global CommentsStore */ /* global CommentsStore */
const Vue = require('vue');
import Vue from 'vue';
(() => { (() => {
const CommentAndResolveBtn = Vue.extend({ const CommentAndResolveBtn = Vue.extend({
......
/* global CommentsStore Cookies notes */ /* global CommentsStore */
/* global notes */
import Vue from 'vue'; import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg'; import collapseIcon from '../icons/collapse_icon.svg';
...@@ -25,6 +27,7 @@ import collapseIcon from '../icons/collapse_icon.svg'; ...@@ -25,6 +27,7 @@ import collapseIcon from '../icons/collapse_icon.svg';
role="button" role="button"
data-container="body" data-container="body"
data-placement="top" data-placement="top"
data-html="true"
:data-line-type="lineType" :data-line-type="lineType"
:title="note.authorName + ': ' + note.noteTruncated" :title="note.authorName + ': ' + note.noteTruncated"
:src="note.authorAvatar" :src="note.authorAvatar"
......
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */ /* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */
/* global DiscussionMixins */ /* global DiscussionMixins */
/* global CommentsStore */ /* global CommentsStore */
const Vue = require('vue');
import Vue from 'vue';
(() => { (() => {
const JumpToDiscussion = Vue.extend({ const JumpToDiscussion = Vue.extend({
......
/* global CommentsStore */
import Vue from 'vue';
(() => {
const NewIssueForDiscussion = Vue.extend({
props: {
discussionId: {
type: String,
required: true,
},
},
data() {
return {
discussions: CommentsStore.state,
};
},
computed: {
discussion() {
return this.discussions[this.discussionId];
},
showButton() {
if (this.discussion) return !this.discussion.isResolved();
return false;
},
},
});
Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
})();
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
/* global CommentsStore */ /* global CommentsStore */
/* global ResolveService */ /* global ResolveService */
/* global Flash */ /* global Flash */
const Vue = require('vue');
import Vue from 'vue';
(() => { (() => {
const ResolveBtn = Vue.extend({ const ResolveBtn = Vue.extend({
......
/* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */ /* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */
/* global DiscussionMixins */ /* global DiscussionMixins */
/* global CommentsStore */ /* global CommentsStore */
const Vue = require('vue');
import Vue from 'vue';
((w) => { ((w) => {
w.ResolveCount = Vue.extend({ w.ResolveCount = Vue.extend({
......
/* eslint-disable object-shorthand, func-names, space-before-function-paren, comma-dangle, no-else-return, quotes, max-len */ /* eslint-disable object-shorthand, func-names, space-before-function-paren, comma-dangle, no-else-return, quotes, max-len */
/* global CommentsStore */ /* global CommentsStore */
/* global ResolveService */ /* global ResolveService */
const Vue = require('vue');
import Vue from 'vue';
(() => { (() => {
const ResolveDiscussionBtn = Vue.extend({ const ResolveDiscussionBtn = Vue.extend({
......
/* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */ /* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */
/* global Vue */
/* global ResolveCount */ /* global ResolveCount */
/* global ResolveServiceClass */ /* global ResolveServiceClass */
const Vue = require('vue'); import Vue from 'vue';
require('./models/discussion'); require('./models/discussion');
require('./models/note'); require('./models/note');
require('./stores/comments'); require('./stores/comments');
...@@ -15,10 +15,11 @@ require('./components/resolve_btn'); ...@@ -15,10 +15,11 @@ require('./components/resolve_btn');
require('./components/resolve_count'); require('./components/resolve_count');
require('./components/resolve_discussion_btn'); require('./components/resolve_discussion_btn');
require('./components/diff_note_avatars'); require('./components/diff_note_avatars');
require('./components/new_issue_for_discussion');
$(() => { $(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath; const projectPath = document.querySelector('.merge-request').dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn'; const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.diffNoteApps = {}; window.gl.diffNoteApps = {};
......
/* eslint-disable space-before-function-paren, camelcase, guard-for-in, no-restricted-syntax, no-unused-vars, max-len */ /* eslint-disable space-before-function-paren, camelcase, guard-for-in, no-restricted-syntax, no-unused-vars, max-len */
/* global Vue */
/* global NoteModel */ /* global NoteModel */
import Vue from 'vue';
class DiscussionModel { class DiscussionModel {
constructor (discussionId) { constructor (discussionId) {
this.id = discussionId; this.id = discussionId;
......
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
/* global Flash */ /* global Flash */
/* global CommentsStore */ /* global CommentsStore */
const Vue = window.Vue = require('vue'); import Vue from 'vue';
window.Vue.use(require('vue-resource')); import VueResource from 'vue-resource';
require('../../vue_shared/vue_resource_interceptor'); require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource);
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/* eslint-disable object-shorthand, func-names, camelcase, no-restricted-syntax, guard-for-in, comma-dangle, max-len, no-param-reassign */ /* eslint-disable object-shorthand, func-names, camelcase, no-restricted-syntax, guard-for-in, comma-dangle, max-len, no-param-reassign */
/* global Vue */
/* global DiscussionModel */ /* global DiscussionModel */
import Vue from 'vue';
((w) => { ((w) => {
w.CommentsStore = { w.CommentsStore = {
state: {}, state: {},
......
import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make this a bundle
/* 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 */ /* 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 UsernameValidator */
/* global ActiveTabMemoizer */ /* global ActiveTabMemoizer */
...@@ -39,12 +38,16 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make ...@@ -39,12 +38,16 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make
import Issue from './issue'; import Issue from './issue';
import BindInOut from './behaviors/bind_in_out'; import BindInOut from './behaviors/bind_in_out';
import GroupName from './group_name';
import GroupsList from './groups_list'; import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import UserCallout from './user_callout';
import GeoNodes from './geo_nodes';
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -61,13 +64,32 @@ const UserCallout = require('./user_callout'); ...@@ -61,13 +64,32 @@ const UserCallout = require('./user_callout');
} }
Dispatcher.prototype.initPageScripts = function() { Dispatcher.prototype.initPageScripts = function() {
var page, path, shortcut_handler; var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
page = $('body').attr('data-page'); page = $('body').attr('data-page');
if (!page) { if (!page) {
return false; return false;
} }
path = page.split(':'); path = page.split(':');
shortcut_handler = null; shortcut_handler = null;
function initBlob() {
new LineHighlighter();
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
'.diff-line-num[data-line-number]',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
}
switch (page) { switch (page) {
case 'sessions:new': case 'sessions:new':
new UsernameValidator(); new UsernameValidator();
...@@ -183,10 +205,13 @@ const UserCallout = require('./user_callout'); ...@@ -183,10 +205,13 @@ const UserCallout = require('./user_callout');
new gl.Diff(); new gl.Diff();
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
break; break;
case 'projects:commit:pipelines': case 'projects:commit:pipelines':
new MiniPipelineGraph({ new MiniPipelineGraph({
container: '.js-pipeline-table', container: '.js-commit-pipeline-graph',
}).bindEvents(); }).bindEvents();
break; break;
case 'projects:commits:show': case 'projects:commits:show':
...@@ -248,20 +273,26 @@ const UserCallout = require('./user_callout'); ...@@ -248,20 +273,26 @@ const UserCallout = require('./user_callout');
case 'projects:tree:show': case 'projects:tree:show':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new TreeView(); new TreeView();
gl.TargetBranchDropDown.bootstrap();
break; break;
case 'projects:find_file:show': case 'projects:find_file:show':
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'projects:blob:new':
gl.TargetBranchDropDown.bootstrap();
break;
case 'projects:blob:create':
gl.TargetBranchDropDown.bootstrap();
break;
case 'projects:blob:show': case 'projects:blob:show':
gl.TargetBranchDropDown.bootstrap();
initBlob();
break;
case 'projects:blob:edit':
gl.TargetBranchDropDown.bootstrap();
break;
case 'projects:blame:show': case 'projects:blame:show':
new LineHighlighter(); initBlob();
shortcut_handler = new ShortcutsNavigation();
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
break; break;
case 'groups:labels:new': case 'groups:labels:new':
case 'groups:labels:edit': case 'groups:labels:edit':
...@@ -309,8 +340,6 @@ const UserCallout = require('./user_callout'); ...@@ -309,8 +340,6 @@ const UserCallout = require('./user_callout');
case 'ci:lints:show': case 'ci:lints:show':
new gl.CILintEditor(); new gl.CILintEditor();
break; break;
case 'projects:environments:metrics':
new PrometheusGraph();
case 'users:show': case 'users:show':
new UserCallout(); new UserCallout();
break; break;
...@@ -348,6 +377,9 @@ const UserCallout = require('./user_callout'); ...@@ -348,6 +377,9 @@ const UserCallout = require('./user_callout');
case 'abuse_reports': case 'abuse_reports':
new gl.AbuseReports(); new gl.AbuseReports();
break; break;
case 'geo_nodes':
new GeoNodes($('.geo-nodes'));
break;
} }
break; break;
case 'dashboard': case 'dashboard':
...@@ -355,6 +387,9 @@ const UserCallout = require('./user_callout'); ...@@ -355,6 +387,9 @@ const UserCallout = require('./user_callout');
shortcut_handler = new ShortcutsDashboardNavigation(); shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout(); new UserCallout();
break; break;
case 'groups':
new GroupName();
break;
case 'profiles': case 'profiles':
new NotificationsForm(); new NotificationsForm();
new NotificationsDropdown(); new NotificationsDropdown();
...@@ -362,6 +397,7 @@ const UserCallout = require('./user_callout'); ...@@ -362,6 +397,7 @@ const UserCallout = require('./user_callout');
case 'projects': case 'projects':
new Project(); new Project();
new ProjectAvatar(); new ProjectAvatar();
new GroupName();
switch (path[1]) { switch (path[1]) {
case 'compare': case 'compare':
new CompareAutocomplete(); new CompareAutocomplete();
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment