Commit 98716228 authored by Dinesh Sawant's avatar Dinesh Sawant

Merge branch 'master' of https://gitlab.com/dinsaw/gitlab-ce into...

Merge branch 'master' of https://gitlab.com/dinsaw/gitlab-ce into removed-unused-parameter-status-only
parents 1d57be40 412ab17d
/builds/
/coverage/ /coverage/
/coverage-javascript/ /coverage-javascript/
/node_modules/ /node_modules/
/public/ /public/
/tmp/ /tmp/
/vendor/ /vendor/
/builds/ karma.config.js
webpack.config.js
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
], ],
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"], "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
"no-multiple-empty-lines": ["error", { "max": 1 }] "no-multiple-empty-lines": ["error", { "max": 1 }],
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off"
} }
} }
*.erb *.erb
lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb
...@@ -107,11 +107,13 @@ setup-test-env: ...@@ -107,11 +107,13 @@ setup-test-env:
<<: *dedicated-runner <<: *dedicated-runner
stage: prepare stage: prepare
script: script:
- bundle exec rake gitlab:assets:compile 2>/dev/null - npm install
- bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts: artifacts:
expire_in: 7d expire_in: 7d
paths: paths:
- node_modules
- public/assets - public/assets
- tmp/tests - tmp/tests
...@@ -232,7 +234,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21 ...@@ -232,7 +234,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
script: script:
- bundle exec $CI_BUILD_NAME - bundle exec $CI_BUILD_NAME
rubocop: rubocop:
<<: *ruby-static-analysis <<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
stage: test stage: test
...@@ -291,18 +293,17 @@ rake db:seed_fu: ...@@ -291,18 +293,17 @@ rake db:seed_fu:
paths: paths:
- log/development.log - log/development.log
teaspoon: karma:
cache: cache:
paths: paths:
- vendor/ruby - vendor/ruby
- node_modules/ - node_modules
stage: test stage: test
<<: *use-db <<: *use-db
<<: *dedicated-runner <<: *dedicated-runner
script: script:
- npm install
- npm link istanbul - npm link istanbul
- bundle exec rake teaspoon - bundle exec rake karma
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
expire_in: 31d expire_in: 31d
...@@ -444,7 +445,7 @@ pages: ...@@ -444,7 +445,7 @@ pages:
<<: *dedicated-runner <<: *dedicated-runner
dependencies: dependencies:
- coverage - coverage
- teaspoon - karma
- lint:javascript:report - lint:javascript:report
script: script:
- mv public/ .public/ - mv public/ .public/
......
...@@ -17,6 +17,7 @@ AllCops: ...@@ -17,6 +17,7 @@ AllCops:
# Exclude some GitLab files # Exclude some GitLab files
Exclude: Exclude:
- 'vendor/**/*' - 'vendor/**/*'
- 'node_modules/**/*'
- 'db/*' - 'db/*'
- 'db/fixtures/**/*' - 'db/fixtures/**/*'
- 'tmp/**/*' - 'tmp/**/*'
......
...@@ -2,6 +2,21 @@ ...@@ -2,6 +2,21 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.16.4 (2017-02-02)
- Support non-ASCII characters in GFM autocomplete. !8729
- Fix search bar search param encoding. !8753
- Fix project name label's for reference in project settings. !8795
- Fix filtering with multiple words. !8830
- Fixed services form cancel not redirecting back the integrations settings view. !8843
- Fix filtering usernames with multiple words. !8851
- Improve performance of slash commands. !8876
- Remove old project members when retrying an export.
- Fix permalink discussion note being collapsed.
- Add project ID index to `project_authorizations` table to optimize queries.
- Check public snippets for spam.
- 19164 Add settings dropdown to mobile screens.
## 8.16.3 (2017-01-27) ## 8.16.3 (2017-01-27)
- Add caching of droplab ajax requests. !8725 - Add caching of droplab ajax requests. !8725
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
- [Issue weight](#issue-weight) - [Issue weight](#issue-weight)
- [Regression issues](#regression-issues) - [Regression issues](#regression-issues)
- [Technical debt](#technical-debt) - [Technical debt](#technical-debt)
- [Stewardship][#stewardship]
- [Merge requests](#merge-requests) - [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines) - [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria) - [Contribution acceptance criteria](#contribution-acceptance-criteria)
...@@ -88,6 +89,27 @@ contributing to GitLab. ...@@ -88,6 +89,27 @@ contributing to GitLab.
Please see the [UX Guide for GitLab]. Please see the [UX Guide for GitLab].
## Release retrospective and kickoff
### Retrospective
After each release (usually on the 22nd of each month), we have a retrospective
call where we discuss what went well, what went wrong, and what we can improve
for the next release. The [retrospective notes] are public and you are invited
to comment them.
If you're interested, you can even join the [retrospective call][retro-kickoff-call].
### Kickoff
Before working on the next release (usually on the 8th of each month), we have a
kickoff call to explain what we expect to ship in the next release. The
[kickoff notes] are public and you are invited to comment them.
If you're interested, you can even join the [kickoff call][retro-kickoff-call].
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
[retro-kickoff-call]: https://gitlab.zoom.us/j/918821206
## Issue tracker ## Issue tracker
To get support for your particular problem please use the To get support for your particular problem please use the
...@@ -209,6 +231,21 @@ for a release by the appropriate person. ...@@ -209,6 +231,21 @@ for a release by the appropriate person.
Make sure to mention the merge request that the `technical debt` issue is Make sure to mention the merge request that the `technical debt` issue is
associated with in the description of the issue. associated with in the description of the issue.
### Stewardship
For issues related to the open source stewardship of GitLab,
there is the ~"stewardship" label.
This label is to be used for issues in which the stewardship of GitLab
is a topic of discussion. For instance if GitLab Inc. is planning to remove
features from GitLab CE to make exclusive in GitLab EE, related issues
would be labelled with ~"stewardship".
A recent example of this was the issue for
[bringing the time tracking API to GitLab CE][time-tracking-issue].
[time-tracking-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084
## Merge requests ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
......
...@@ -7,7 +7,6 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3' ...@@ -7,7 +7,6 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.7.0' gem 'sprockets', '~> 3.7.0'
gem 'sprockets-es6', '~> 0.9.2'
# Default values for AR models # Default values for AR models
gem 'default_value_for', '~> 3.0.0' gem 'default_value_for', '~> 3.0.0'
...@@ -48,6 +47,9 @@ gem 'rqrcode-rails3', '~> 0.1.7' ...@@ -48,6 +47,9 @@ gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 3.0.0' gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
# Browser detection # Browser detection
gem 'browser', '~> 2.2' gem 'browser', '~> 2.2'
...@@ -109,7 +111,7 @@ gem 'org-ruby', '~> 0.9.12' ...@@ -109,7 +111,7 @@ gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2' gem 'asciidoctor', '~> 1.5.2'
gem 'asciidoctor-plantuml', '0.0.6' gem 'asciidoctor-plantuml', '0.0.7'
gem 'rouge', '~> 2.0' gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.8' gem 'truncato', '~> 0.7.8'
...@@ -219,10 +221,12 @@ gem 'oj', '~> 2.17.4' ...@@ -219,10 +221,12 @@ gem 'oj', '~> 2.17.4'
gem 'chronic', '~> 0.10.2' gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
gem 'webpack-rails', '~> 0.9.9'
gem 'rack-proxy', '~> 0.6.0'
gem 'sass-rails', '~> 5.0.6' gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0' gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2' gem 'uglifier', '~> 2.7.2'
gem 'gitlab-turbolinks-classic', '~> 2.5', '>= 2.5.6'
gem 'addressable', '~> 2.3.8' gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0' gem 'bootstrap-sass', '~> 3.3.0'
...@@ -292,13 +296,9 @@ group :development, :test do ...@@ -292,13 +296,9 @@ group :development, :test do
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0' gem 'poltergeist', '~> 1.9.0'
gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.7.0' gem 'spring', '~> 1.7.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.46.0', require: false gem 'rubocop', '~> 0.46.0', require: false
gem 'rubocop-rspec', '~> 1.9.1', require: false gem 'rubocop-rspec', '~> 1.9.1', require: false
......
...@@ -54,7 +54,7 @@ GEM ...@@ -54,7 +54,7 @@ GEM
faraday_middleware-multi_json (~> 0.0) faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0) oauth2 (~> 1.0)
asciidoctor (1.5.3) asciidoctor (1.5.3)
asciidoctor-plantuml (0.0.6) asciidoctor-plantuml (0.0.7)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
ast (2.3.0) ast (2.3.0)
attr_encrypted (3.0.3) attr_encrypted (3.0.3)
...@@ -72,10 +72,6 @@ GEM ...@@ -72,10 +72,6 @@ GEM
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
babosa (1.0.2) babosa (1.0.2)
base32 (0.3.2) base32 (0.3.2)
bcrypt (3.1.11) bcrypt (3.1.11)
...@@ -266,8 +262,6 @@ GEM ...@@ -266,8 +262,6 @@ GEM
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-markup (1.5.1) gitlab-markup (1.5.1)
gitlab-turbolinks-classic (2.5.6)
coffee-rails
gitlab_omniauth-ldap (1.2.1) gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9) net-ldap (~> 0.9)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -548,6 +542,8 @@ GEM ...@@ -548,6 +542,8 @@ GEM
rack (>= 1.1) rack (>= 1.1)
rack-protection (1.5.3) rack-protection (1.5.3)
rack rack
rack-proxy (0.6.0)
rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.7.1) rails (4.2.7.1)
...@@ -735,15 +731,9 @@ GEM ...@@ -735,15 +731,9 @@ GEM
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.1.0) spring-commands-spinach (1.1.0)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
sprockets (3.7.0) sprockets (3.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-es6 (0.9.2)
babel-source (>= 5.8.11)
babel-transpiler
sprockets (>= 3.0.0)
sprockets-rails (3.1.1) sprockets-rails (3.1.1)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
...@@ -761,10 +751,6 @@ GEM ...@@ -761,10 +751,6 @@ GEM
sys-filesystem (1.1.6) sys-filesystem (1.1.6)
ffi ffi
sysexits (1.2.0) sysexits (1.2.0)
teaspoon (1.1.5)
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.7) temple (0.7.7)
test_after_commit (1.1.0) test_after_commit (1.1.0)
activerecord (>= 3.2) activerecord (>= 3.2)
...@@ -799,6 +785,9 @@ GEM ...@@ -799,6 +785,9 @@ GEM
get_process_mem (~> 0) get_process_mem (~> 0)
unicorn (>= 4, < 6) unicorn (>= 4, < 6)
uniform_notifier (1.10.0) uniform_notifier (1.10.0)
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
version_sorter (2.1.0) version_sorter (2.1.0)
virtus (1.0.5) virtus (1.0.5)
axiom-types (~> 0.1) axiom-types (~> 0.1)
...@@ -816,6 +805,8 @@ GEM ...@@ -816,6 +805,8 @@ GEM
webmock (1.21.0) webmock (1.21.0)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
webpack-rails (0.9.9)
rails (>= 3.2.0)
websocket-driver (0.6.3) websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2) websocket-extensions (0.1.2)
...@@ -841,7 +832,7 @@ DEPENDENCIES ...@@ -841,7 +832,7 @@ DEPENDENCIES
allocations (~> 1.0) allocations (~> 1.0)
asana (~> 0.4.0) asana (~> 0.4.0)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.2)
asciidoctor-plantuml (= 0.0.6) asciidoctor-plantuml (= 0.0.7)
attr_encrypted (~> 3.0.0) attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
babosa (~> 1.0.2) babosa (~> 1.0.2)
...@@ -891,7 +882,6 @@ DEPENDENCIES ...@@ -891,7 +882,6 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1) gitlab-markup (~> 1.5.1)
gitlab-turbolinks-classic (~> 2.5, >= 2.5.6)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
...@@ -955,6 +945,7 @@ DEPENDENCIES ...@@ -955,6 +945,7 @@ DEPENDENCIES
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 4.2.7.1) rails (= 4.2.7.1)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0) rainbow (~> 2.1.0)
...@@ -996,14 +987,10 @@ DEPENDENCIES ...@@ -996,14 +987,10 @@ DEPENDENCIES
spring (~> 1.7.0) spring (~> 1.7.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0) spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sprockets-es6 (~> 0.9.2)
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 1.1) test_after_commit (~> 1.1)
thin (~> 1.7.0) thin (~> 1.7.0)
timecop (~> 0.8.0) timecop (~> 0.8.0)
...@@ -1014,11 +1001,13 @@ DEPENDENCIES ...@@ -1014,11 +1001,13 @@ DEPENDENCIES
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 5.1.0) unicorn (~> 5.1.0)
unicorn-worker-killer (~> 0.4.4) unicorn-worker-killer (~> 0.4.4)
validates_hostname (~> 1.0.6)
version_sorter (~> 2.1.0) version_sorter (~> 2.1.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
vmstat (~> 2.3.0) vmstat (~> 2.3.0)
web-console (~> 2.0) web-console (~> 2.0)
webmock (~> 1.21.0) webmock (~> 1.21.0)
webpack-rails (~> 0.9.9)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
......
...@@ -33,7 +33,7 @@ core team members will mention this person. ...@@ -33,7 +33,7 @@ core team members will mention this person.
### Merge request coaching ### Merge request coaching
Several people from the [GitLab team][team] are helping community members to get Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done]. their contributions accepted by meeting our [Definition of done](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done).
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/. What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
...@@ -79,47 +79,47 @@ not be merged into any stable branches. ...@@ -79,47 +79,47 @@ not be merged into any stable branches.
### Improperly formatted issue ### Improperly formatted issue
Thanks for the issue report. Please reformat your issue to conform to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. Please reformat your issue to conform to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Issue report for old version ### Issue report for old version
Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Support requests and configuration questions ### Support requests and configuration questions
Thanks for your interest in GitLab. We don't use the issue tracker for support Thanks for your interest in GitLab. We don't use the issue tracker for support
requests and configuration questions. Please check our requests and configuration questions. Please check our
\[getting help\]\(https://about.gitlab.com/getting-help/) page to see all of the available [getting help](https://about.gitlab.com/getting-help/) page to see all of the available
support options. Also, have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) support options. Also, have a look at the [contribution guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md)
for more information. for more information.
### Code format ### Code format
Please use ``` to format console output, logs, and code as it's very hard to read otherwise. Please use \`\`\` to format console output, logs, and code as it's very hard to read otherwise.
### Issue fixed in newer version ### Issue fixed in newer version
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please [upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request ### Improperly formatted merge request
Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines). Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines).
### Inactivity close of an issue ### Inactivity close of an issue
It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Inactivity close of a merge request ### Inactivity close of a merge request
This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request. This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request.
### Accepting merge requests ### Accepting merge requests
Is there an issue on the Is there an issue on the
\[issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues) that is [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
similar to this? Could you please link it here? similar to this? Could you please link it here?
Please be aware that new functionality that is not marked Please be aware that new functionality that is not marked
\[accepting merge requests\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests) [accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
might not make it into GitLab. might not make it into GitLab.
### Only accepting merge requests with green tests ### Only accepting merge requests with green tests
...@@ -134,7 +134,7 @@ rebase with master to see if that solves the issue. ...@@ -134,7 +134,7 @@ rebase with master to see if that solves the issue.
We are currently in the process of closing down the issue tracker on GitHub, to We are currently in the process of closing down the issue tracker on GitHub, to
prevent duplication with the GitLab.com issue tracker. prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is Since this is an older issue I'll be closing this for now. If you think this is
still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues). still an issue I encourage you to open it on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues).
[team]: https://about.gitlab.com/team/ [team]: https://about.gitlab.com/team/
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria [contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
/* global Turbolinks */
(function() { (function() {
this.Admin = (function() { this.Admin = (function() {
...@@ -42,10 +41,10 @@ ...@@ -42,10 +41,10 @@
return $('.change-owner-link').show(); return $('.change-owner-link').show();
}); });
$('li.project_member').bind('ajax:success', function() { $('li.project_member').bind('ajax:success', function() {
return Turbolinks.visit(location.href); return gl.utils.refreshCurrentPage();
}); });
$('li.group_member').bind('ajax:success', function() { $('li.group_member').bind('ajax:success', function() {
return Turbolinks.visit(location.href); return gl.utils.refreshCurrentPage();
}); });
showBlacklistType = function() { showBlacklistType = function() {
if ($("input[name='blacklist_type']:checked").val() === 'file') { if ($("input[name='blacklist_type']:checked").val() === 'file') {
......
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import */
/* global bp */ /* global bp */
/* global Cookies */ /* global Cookies */
/* global Flash */ /* global Flash */
...@@ -6,65 +6,60 @@ ...@@ -6,65 +6,60 @@
/* global AwardsHandler */ /* global AwardsHandler */
/* global Aside */ /* global Aside */
// This is a manifest file that'll be compiled into including all the files listed below. function requireAll(context) { return context.keys().map(context); }
// Add new JavaScript code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js window.$ = window.jQuery = require('jquery');
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the require('jquery-ui/ui/autocomplete');
// the compiled file. require('jquery-ui/ui/datepicker');
// require('jquery-ui/ui/draggable');
/*= require jquery2 */ require('jquery-ui/ui/effect-highlight');
/*= require jquery-ui/autocomplete */ require('jquery-ui/ui/sortable');
/*= require jquery-ui/datepicker */ require('jquery-ujs');
/*= require jquery-ui/draggable */ require('vendor/jquery.endless-scroll');
/*= require jquery-ui/effect-highlight */ require('vendor/jquery.highlight');
/*= require jquery-ui/sortable */ require('vendor/jquery.waitforimages');
/*= require jquery_ujs */ require('vendor/jquery.caret');
/*= require jquery.endless-scroll */ require('vendor/jquery.atwho');
/*= require jquery.highlight */ require('vendor/jquery.scrollTo');
/*= require jquery.waitforimages */ window.Cookies = require('vendor/js.cookie');
/*= require jquery.atwho */ require('./autosave');
/*= require jquery.scrollTo */ require('bootstrap/js/affix');
/*= require jquery.turbolinks */ require('bootstrap/js/alert');
/*= require js.cookie */ require('bootstrap/js/button');
/*= require turbolinks */ require('bootstrap/js/collapse');
/*= require autosave */ require('bootstrap/js/dropdown');
/*= require bootstrap/affix */ require('bootstrap/js/modal');
/*= require bootstrap/alert */ require('bootstrap/js/scrollspy');
/*= require bootstrap/button */ require('bootstrap/js/tab');
/*= require bootstrap/collapse */ require('bootstrap/js/transition');
/*= require bootstrap/dropdown */ require('bootstrap/js/tooltip');
/*= require bootstrap/modal */ require('bootstrap/js/popover');
/*= require bootstrap/scrollspy */ require('select2/select2.js');
/*= require bootstrap/tab */ window._ = require('underscore');
/*= require bootstrap/transition */ window.Dropzone = require('dropzone');
/*= require bootstrap/tooltip */ require('mousetrap');
/*= require bootstrap/popover */ require('mousetrap/plugins/pause/mousetrap-pause');
/*= require select2 */ require('./shortcuts');
/*= require underscore */ require('./shortcuts_navigation');
/*= require dropzone */ require('./shortcuts_dashboard_navigation');
/*= require mousetrap */ require('./shortcuts_issuable');
/*= require mousetrap/pause */ require('./shortcuts_network');
/*= require shortcuts */ require('vendor/jquery.nicescroll');
/*= require shortcuts_navigation */ requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
/*= require shortcuts_dashboard_navigation */ requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/));
/*= require shortcuts_issuable */ requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/));
/*= require shortcuts_network */ requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/));
/*= require jquery.nicescroll */ requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/));
/*= require date.format */ requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/));
/*= require_directory ./behaviors */ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
/*= require_directory ./blob */ requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
/*= require_directory ./templates */ requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
/*= require_directory ./commit */ require('vendor/fuzzaldrin-plus');
/*= require_directory ./extensions */ window.ES6Promise = require('vendor/es6-promise.auto');
/*= require_directory ./lib/utils */ window.ES6Promise.polyfill();
/*= require_directory ./u2f */
/*= require_directory ./droplab */
/*= require_directory . */
/*= require fuzzaldrin-plus */
/*= require es6-promise.auto */
(function () { (function () {
document.addEventListener('page:fetch', function () { document.addEventListener('beforeunload', function () {
// Unbind scroll events // Unbind scroll events
$(document).off('scroll'); $(document).off('scroll');
// Close any open tooltips // Close any open tooltips
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */
/* global Cookies */ /* global Cookies */
var emojiAliases = require('emoji-aliases');
(function() { (function() {
this.AwardsHandler = (function() { this.AwardsHandler = (function() {
var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
function AwardsHandler() { function AwardsHandler() {
this.aliases = gl.emojiAliases(); this.aliases = emojiAliases;
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) { $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
return function(e) { return function(e) {
e.stopPropagation(); e.stopPropagation();
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
/* global autosize */ /* global autosize */
/*= require autosize */ var autosize = require('vendor/autosize');
(function() { (function() {
$(function() { $(function() {
......
...@@ -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 */ require('../extensions/jquery');
// //
// ### 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 */ require('../extensions/jquery');
// //
// ### Example Markup // ### Example Markup
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */ /* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
(function(w) { (function(w) {
$(function() { $(function() {
var toggleContainer = function(container, /* optional */toggleState) {
var $container = $(container);
$container
.find('.js-toggle-button .fa')
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
$container
.find('.js-toggle-content')
.toggle(toggleState);
};
// Toggle button. Show/hide content inside parent container. // Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style. // Button does not change visibility. If button has icon - it changes chevron style.
// //
...@@ -10,14 +23,7 @@ ...@@ -10,14 +23,7 @@
// //
$('body').on('click', '.js-toggle-button', function(e) { $('body').on('click', '.js-toggle-button', function(e) {
e.preventDefault(); e.preventDefault();
$(this) toggleContainer($(this).closest('.js-toggle-container'));
.find('.fa')
.toggleClass('fa-chevron-down fa-chevron-up')
.end()
.closest('.js-toggle-container')
.find('.js-toggle-content')
.toggle()
;
}); });
// If we're accessing a permalink, ensure it is not inside a // If we're accessing a permalink, ensure it is not inside a
...@@ -26,8 +32,8 @@ ...@@ -26,8 +32,8 @@
var anchor = hash && document.getElementById(hash); var anchor = hash && document.getElementById(hash);
var container = anchor && $(anchor).closest('.js-toggle-container'); var container = anchor && $(anchor).closest('.js-toggle-container');
if (container && container.find('.js-toggle-content').is(':hidden')) { if (container) {
container.find('.js-toggle-button').trigger('click'); toggleContainer(container, true);
anchor.scrollIntoView(); anchor.scrollIntoView();
} }
}); });
......
/* eslint-disable no-param-reassign, comma-dangle */ /* eslint-disable no-param-reassign, comma-dangle */
/* global Api */ /* global Api */
/*= require blob/template_selector */ require('./template_selector');
((global) => { ((global) => {
class BlobCiYamlSelector extends gl.TemplateSelector { class BlobCiYamlSelector extends gl.TemplateSelector {
requestFile(query) { requestFile(query) {
......
/* global Api */ /* global Api */
/*= require blob/template_selector */
require('./template_selector');
(() => { (() => {
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
......
/* 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 */ /* 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 */ /* global Api */
/*= require blob/template_selector */ require('./template_selector');
(function() { (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; }, 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; },
......
/* 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 */ /* 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 */ /* global Api */
/*= require blob/template_selector */ require('./template_selector');
(function() { (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; }, 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; },
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* global EditBlob */ /* global EditBlob */
/* global NewCommitForm */ /* global NewCommitForm */
/*= require_tree . */ require('./edit_blob');
(function() { (function() {
$(function() { $(function() {
......
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren, import/newline-after-import, no-multi-spaces, max-len */
/* global Vue */ /* global Vue */
/* global BoardService */ /* global BoardService */
//= require vue function requireAll(context) { return context.keys().map(context); }
//= require vue-resource
//= require Sortable window.Vue = require('vue');
//= require_tree ./models window.Vue.use(require('vue-resource'));
//= require_tree ./stores window.Sortable = require('vendor/Sortable');
//= require_tree ./services requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/));
//= require_tree ./mixins requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/));
//= require_tree ./filters requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/));
//= require ./components/board requireAll(require.context('./mixins', true, /^\.\/.*\.(js|es6)$/));
//= require ./components/board_sidebar requireAll(require.context('./filters', true, /^\.\/.*\.(js|es6)$/));
//= require ./components/new_list_dropdown require('./components/board');
//= require ./vue_resource_interceptor require('./components/board_sidebar');
require('./components/new_list_dropdown');
require('./components/modal/index');
require('../vue_shared/vue_resource_interceptor');
$(() => { $(() => {
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;
window.gl = window.gl || {}; window.gl = window.gl || {};
...@@ -31,7 +35,8 @@ $(() => { ...@@ -31,7 +35,8 @@ $(() => {
el: $boardApp, el: $boardApp,
components: { components: {
'board': gl.issueBoards.Board, 'board': gl.issueBoards.Board,
'board-sidebar': gl.issueBoards.BoardSidebar 'board-sidebar': gl.issueBoards.BoardSidebar,
'board-add-issues-modal': gl.issueBoards.IssuesModal,
}, },
data: { data: {
state: Store.state, state: Store.state,
...@@ -40,6 +45,8 @@ $(() => { ...@@ -40,6 +45,8 @@ $(() => {
boardId: $boardApp.dataset.boardId, boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true', disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase, issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
detailIssue: Store.detail detailIssue: Store.detail
}, },
computed: { computed: {
...@@ -48,7 +55,7 @@ $(() => { ...@@ -48,7 +55,7 @@ $(() => {
}, },
}, },
created () { created () {
gl.boardService = new BoardService(this.endpoint, this.boardId); gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
}, },
mounted () { mounted () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
...@@ -59,8 +66,6 @@ $(() => { ...@@ -59,8 +66,6 @@ $(() => {
if (list.type === 'done') { if (list.type === 'done') {
list.position = Infinity; list.position = Infinity;
} else if (list.type === 'backlog') {
list.position = -1;
} }
}); });
...@@ -73,7 +78,7 @@ $(() => { ...@@ -73,7 +78,7 @@ $(() => {
}); });
gl.IssueBoardsSearch = new Vue({ gl.IssueBoardsSearch = new Vue({
el: '#js-boards-search', el: document.getElementById('js-boards-search'),
data: { data: {
filters: Store.state.filters filters: Store.state.filters
}, },
...@@ -81,4 +86,27 @@ $(() => { ...@@ -81,4 +86,27 @@ $(() => {
gl.issueBoards.newListDropdownInit(); gl.issueBoards.newListDropdownInit();
} }
}); });
gl.IssueBoardsModalAddBtn = new Vue({
mixins: [gl.issueBoards.ModalMixins],
el: document.getElementById('js-add-issues-btn'),
data: {
modal: ModalStore.store,
store: Store.state,
},
computed: {
disabled() {
return Store.shouldAddBlankState();
},
},
template: `
<button
class="btn btn-create pull-right prepend-left-10 has-tooltip"
type="button"
:disabled="disabled"
@click="toggleModal(true)">
Add issues
</button>
`,
});
}); });
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
/* global Vue */ /* global Vue */
/* global Sortable */ /* global Sortable */
//= require ./board_blank_state require('./board_blank_state');
//= require ./board_delete require('./board_delete');
//= require ./board_list require('./board_list');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
props: { props: {
list: Object, list: Object,
disabled: Boolean, disabled: Boolean,
issueLinkBase: String issueLinkBase: String,
rootPath: String,
}, },
data () { data () {
return { return {
......
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */ /* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
/* global Vue */ /* global Vue */
require('./issue_card_inner');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -9,12 +11,16 @@ ...@@ -9,12 +11,16 @@
gl.issueBoards.BoardCard = Vue.extend({ gl.issueBoards.BoardCard = Vue.extend({
template: '#js-board-list-card', template: '#js-board-list-card',
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: { props: {
list: Object, list: Object,
issue: Object, issue: Object,
issueLinkBase: String, issueLinkBase: String,
disabled: Boolean, disabled: Boolean,
index: Number index: Number,
rootPath: String,
}, },
data () { data () {
return { return {
...@@ -28,31 +34,6 @@ ...@@ -28,31 +34,6 @@
} }
}, },
methods: { methods: {
filterByLabel (label, e) {
let labelToggleText = label.title;
const labelIndex = Store.state.filters['label_name'].indexOf(label.title);
$(e.target).tooltip('hide');
if (labelIndex === -1) {
Store.state.filters['label_name'].push(label.title);
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
} else {
Store.state.filters['label_name'].splice(labelIndex, 1);
labelToggleText = Store.state.filters['label_name'][0];
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
}
const selectedLabels = Store.state.filters['label_name'];
if (selectedLabels.length === 0) {
labelToggleText = 'Label';
} else if (selectedLabels.length > 1) {
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
}
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
Store.updateFiltersUrl();
},
mouseDown () { mouseDown () {
this.showDetail = true; this.showDetail = true;
}, },
...@@ -71,6 +52,7 @@ ...@@ -71,6 +52,7 @@
Store.detail.issue = {}; Store.detail.issue = {};
} else { } else {
Store.detail.issue = this.issue; Store.detail.issue = this.issue;
Store.detail.list = this.list;
} }
} }
} }
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
/* global Vue */ /* global Vue */
/* global Sortable */ /* global Sortable */
//= require ./board_card require('./board_card');
//= require ./board_new_issue require('./board_new_issue');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
issues: Array, issues: Array,
loading: Boolean, loading: Boolean,
issueLinkBase: String, issueLinkBase: String,
rootPath: String,
}, },
data () { data () {
return { return {
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
$(this.$refs.submitButton).enable(); $(this.$refs.submitButton).enable();
Store.detail.issue = issue; Store.detail.issue = issue;
Store.detail.list = this.list;
}) })
.catch(() => { .catch(() => {
// 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
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
/* global LabelsSelect */ /* global LabelsSelect */
/* global Sidebar */ /* global Sidebar */
require('./sidebar/remove_issue');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -18,7 +20,8 @@ ...@@ -18,7 +20,8 @@
data() { data() {
return { return {
detail: Store.detail, detail: Store.detail,
issue: {} issue: {},
list: {},
}; };
}, },
computed: { computed: {
...@@ -36,6 +39,7 @@ ...@@ -36,6 +39,7 @@
} }
this.issue = this.detail.issue; this.issue = this.detail.issue;
this.list = this.detail.list;
}, },
deep: true deep: true
}, },
...@@ -60,6 +64,9 @@ ...@@ -60,6 +64,9 @@
new LabelsSelect(); new LabelsSelect();
new Sidebar(); new Sidebar();
gl.Subscription.bindAll('.subscription'); gl.Subscription.bindAll('.subscription');
} },
components: {
removeBtn: gl.issueBoards.RemoveIssueBtn,
},
}); });
})(); })();
/* global Vue */
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.IssueCardInner = Vue.extend({
props: {
issue: {
type: Object,
required: true,
},
issueLinkBase: {
type: String,
required: true,
},
list: {
type: Object,
required: false,
},
rootPath: {
type: String,
required: true,
},
},
methods: {
showLabel(label) {
if (!this.list) return true;
return !this.list.label || label.id !== this.list.label.id;
},
filterByLabel(label, e) {
let labelToggleText = label.title;
const labelIndex = Store.state.filters.label_name.indexOf(label.title);
$(e.currentTarget).tooltip('hide');
if (labelIndex === -1) {
Store.state.filters.label_name.push(label.title);
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
} else {
Store.state.filters.label_name.splice(labelIndex, 1);
labelToggleText = Store.state.filters.label_name[0];
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
}
const selectedLabels = Store.state.filters.label_name;
if (selectedLabels.length === 0) {
labelToggleText = 'Label';
} else if (selectedLabels.length > 1) {
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
}
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
Store.updateFiltersUrl();
},
labelStyle(label) {
return {
backgroundColor: label.color,
color: label.textColor,
};
},
},
template: `
<div>
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"></i>
<a
:href="issueLinkBase + '/' + issue.id"
:title="issue.title">
{{ issue.title }}
</a>
</h4>
<div class="card-footer">
<span
class="card-number"
v-if="issue.id">
#{{ issue.id }}
</span>
<a
class="card-assignee has-tooltip"
:href="rootPath + issue.assignee.username"
:title="'Assigned to ' + issue.assignee.name"
v-if="issue.assignee"
data-container="body">
<img
class="avatar avatar-inline s20"
:src="issue.assignee.avatar"
width="20"
height="20"
:alt="'Avatar for ' + issue.assignee.name" />
</a>
<button
class="label color-label has-tooltip"
v-for="label in issue.labels"
type="button"
v-if="showLabel(label)"
@click="filterByLabel(label, $event)"
:style="labelStyle(label)"
:title="label.description"
data-container="body">
{{ label.title }}
</button>
</div>
</div>
`,
});
})();
/* global Vue */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalEmptyState = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return ModalStore.store;
},
props: {
image: {
type: String,
required: true,
},
newIssuePath: {
type: String,
required: true,
},
},
computed: {
contents() {
const obj = {
title: 'You haven\'t added any issues to your project yet',
content: `
An issue can be a bug, a todo or a feature request that needs to be
discussed in a project. Besides, issues are searchable and filterable.
`,
};
if (this.activeTab === 'selected') {
obj.title = 'You haven\'t selected any issues yet';
obj.content = `
Go back to <strong>All issues</strong> and select some issues
to add to your board.
`;
}
return obj;
},
},
template: `
<section class="empty-state">
<div class="row">
<div class="col-xs-12 col-sm-6 col-sm-push-6">
<aside class="svg-content" v-html="image"></aside>
</div>
<div class="col-xs-12 col-sm-6 col-sm-pull-6">
<div class="text-content">
<h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p>
<a
:href="newIssuePath"
class="btn btn-success btn-inverted"
v-if="activeTab === 'all'">
New issue
</a>
<button
type="button"
class="btn btn-default"
@click="changeTab('all')"
v-if="activeTab === 'selected'">
All issues
</button>
</div>
</div>
</div>
</section>
`,
});
})();
/* global Vue */
const userFilter = require('./filters/user');
const milestoneFilter = require('./filters/milestone');
const labelFilter = require('./filters/label');
module.exports = Vue.extend({
name: 'modal-filters',
props: {
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
destroyed() {
gl.issueBoards.ModalStore.setDefaultFilter();
},
components: {
userFilter,
milestoneFilter,
labelFilter,
},
template: `
<div class="modal-filters">
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-user-search js-author-search"
toggle-label="Author"
field-name="author_id"
:project-id="projectId"></user-filter>
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-assignee-search"
toggle-label="Assignee"
field-name="assignee_id"
:null-user="true"
:project-id="projectId"></user-filter>
<milestone-filter :milestone-path="milestonePath"></milestone-filter>
<label-filter :label-path="labelPath"></label-filter>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global LabelsSelect */
module.exports = Vue.extend({
name: 'filter-label',
props: {
labelPath: {
type: String,
required: true,
},
},
mounted() {
new LabelsSelect(this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-no="true"
:data-labels="labelPath"
ref="dropdown">
<span class="dropdown-toggle-text">
Label
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
<div class="dropdown-title">
Filter by label
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global MilestoneSelect */
module.exports = Vue.extend({
name: 'filter-milestone',
props: {
milestonePath: {
type: String,
required: true,
},
},
mounted() {
new MilestoneSelect(null, this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-milestone-select"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-upcoming="true"
data-field-name="milestone_title"
:data-milestones="milestonePath"
ref="dropdown">
<span class="dropdown-toggle-text">
Milestone
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
<div class="dropdown-title">
<span>Filter by milestone</span>
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search milestones"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global UsersSelect */
module.exports = Vue.extend({
name: 'filter-user',
props: {
toggleClassName: {
type: String,
required: true,
},
dropdownClassName: {
type: String,
required: false,
default: '',
},
toggleLabel: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
nullUser: {
type: Boolean,
required: false,
default: false,
},
projectId: {
type: Number,
required: true,
},
},
mounted() {
new UsersSelect(null, this.$refs.dropdown);
},
computed: {
currentUsername() {
return gon.current_username;
},
dropdownTitle() {
return `Filter by ${this.toggleLabel.toLowerCase()}`;
},
inputPlaceholder() {
return `Search ${this.toggleLabel.toLowerCase()}`;
},
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-user-search"
:class="toggleClassName"
type="button"
data-toggle="dropdown"
data-current-user="true"
:data-any-user="'Any ' + toggleLabel"
:data-null-user="nullUser"
:data-field-name="fieldName"
:data-project-id="projectId"
:data-first-user="currentUsername"
ref="dropdown">
<span class="dropdown-toggle-text">
{{ toggleLabel }}
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div
class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
:class="dropdownClassName">
<div class="dropdown-title">
{{ dropdownTitle }}
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
autocomplete="off"
:placeholder="inputPlaceholder" />
<i class="fa fa-search dropdown-input-search"></i>
<i
role="button"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global Flash */
require('./lists_dropdown');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalFooter = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return {
modal: ModalStore.store,
state: gl.issueBoards.BoardsStore.state,
};
},
computed: {
submitDisabled() {
return !ModalStore.selectedCount();
},
submitText() {
const count = ModalStore.selectedCount();
return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
},
},
methods: {
addIssues() {
const list = this.modal.selectedList || this.state.lists[0];
const selectedIssues = ModalStore.getSelectedIssues();
const issueIds = selectedIssues.map(issue => issue.globalId);
// Post the data to the backend
gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id],
}).catch(() => {
new Flash('Failed to update issues, please try again.', 'alert');
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
list.issuesSize -= 1;
});
});
// Add the issues on the frontend
selectedIssues.forEach((issue) => {
list.addIssue(issue);
list.issuesSize += 1;
});
this.toggleModal(false);
},
},
components: {
'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
},
template: `
<footer
class="form-actions add-issues-footer">
<div class="pull-left">
<button
class="btn btn-success"
type="button"
:disabled="submitDisabled"
@click="addIssues">
{{ submitText }}
</button>
<span class="inline add-issues-footer-to-list">
to list
</span>
<lists-dropdown></lists-dropdown>
</div>
<button
class="btn btn-default pull-right"
type="button"
@click="toggleModal(false)">
Cancel
</button>
</footer>
`,
});
})();
/* global Vue */
require('./tabs');
const modalFilters = require('./filters');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalHeader = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
props: {
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
},
computed: {
selectAllText() {
if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
return 'Select all';
}
return 'Deselect all';
},
showSearch() {
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
},
},
methods: {
toggleAll() {
this.$refs.selectAllBtn.blur();
ModalStore.toggleAll();
},
},
components: {
'modal-tabs': gl.issueBoards.ModalTabs,
modalFilters,
},
template: `
<div>
<header class="add-issues-header form-actions">
<h2>
Add issues
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
@click="toggleModal(false)">
<span aria-hidden="true">×</span>
</button>
</h2>
</header>
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
<modal-filters
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input
placeholder="Search issues..."
class="form-control"
type="search"
v-model="searchTerm" />
<button
type="button"
class="btn btn-success btn-inverted prepend-left-10"
ref="selectAllBtn"
@click="toggleAll">
{{ selectAllText }}
</button>
</div>
</div>
`,
});
})();
/* global Vue */
/* global ListIssue */
require('./header');
require('./list');
require('./footer');
require('./empty_state');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.IssuesModal = Vue.extend({
props: {
blankStateImage: {
type: String,
required: true,
},
newIssuePath: {
type: String,
required: true,
},
issueLinkBase: {
type: String,
required: true,
},
rootPath: {
type: String,
required: true,
},
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
},
watch: {
page() {
this.loadIssues();
},
searchTerm() {
this.searchOperation();
},
showAddIssuesModal() {
if (this.showAddIssuesModal && !this.issues.length) {
this.loading = true;
this.loadIssues()
.then(() => {
this.loading = false;
});
} else if (!this.showAddIssuesModal) {
this.issues = [];
this.selectedIssues = [];
this.issuesCount = false;
}
},
filter: {
handler() {
this.loadIssues(true);
},
deep: true,
},
},
methods: {
searchOperation: _.debounce(function searchOperationDebounce() {
this.loadIssues(true);
}, 500),
loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false;
const queryData = Object.assign({}, this.filter, {
search: this.searchTerm,
page: this.page,
per: this.perPage,
});
return gl.boardService.getBacklog(queryData).then((res) => {
const data = res.json();
if (clearIssues) {
this.issues = [];
}
data.issues.forEach((issueObj) => {
const issue = new ListIssue(issueObj);
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
issue.selected = !!foundSelectedIssue;
this.issues.push(issue);
});
this.loadingNewPage = false;
if (!this.issuesCount) {
this.issuesCount = data.size;
}
});
},
},
computed: {
showList() {
if (this.activeTab === 'selected') {
return this.selectedIssues.length > 0;
}
return this.issuesCount > 0;
},
showEmptyState() {
if (!this.loading && this.issuesCount === 0) {
return true;
}
return this.activeTab === 'selected' && this.selectedIssues.length === 0;
},
},
components: {
'modal-header': gl.issueBoards.ModalHeader,
'modal-list': gl.issueBoards.ModalList,
'modal-footer': gl.issueBoards.ModalFooter,
'empty-state': gl.issueBoards.ModalEmptyState,
},
template: `
<div
class="add-issues-modal"
v-if="showAddIssuesModal">
<div class="add-issues-container">
<modal-header
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-header>
<modal-list
:image="blankStateImage"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
v-if="!loading && showList"></modal-list>
<empty-state
v-if="showEmptyState"
:image="blankStateImage"
:new-issue-path="newIssuePath"></empty-state>
<section
class="add-issues-list text-center"
v-if="loading">
<div class="add-issues-list-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>
</section>
<modal-footer></modal-footer>
</div>
</div>
`,
});
})();
/* global Vue */
/* global ListIssue */
/* global bp */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalList = Vue.extend({
props: {
issueLinkBase: {
type: String,
required: true,
},
rootPath: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
},
watch: {
activeTab() {
if (this.activeTab === 'all') {
ModalStore.purgeUnselectedIssues();
}
},
},
computed: {
loopIssues() {
if (this.activeTab === 'all') {
return this.issues;
}
return this.selectedIssues;
},
groupedIssues() {
const groups = [];
this.loopIssues.forEach((issue, i) => {
const index = i % this.columns;
if (!groups[index]) {
groups.push([]);
}
groups[index].push(issue);
});
return groups;
},
},
methods: {
scrollHandler() {
const currentPage = Math.floor(this.issues.length / this.perPage);
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
&& currentPage === this.page) {
this.loadingNewPage = true;
this.page += 1;
}
},
toggleIssue(e, issue) {
if (e.target.tagName !== 'A') {
ModalStore.toggleIssue(issue);
}
},
listHeight() {
return this.$refs.list.getBoundingClientRect().height;
},
scrollHeight() {
return this.$refs.list.scrollHeight;
},
scrollTop() {
return this.$refs.list.scrollTop + this.listHeight();
},
showIssue(issue) {
if (this.activeTab === 'all') return true;
const index = ModalStore.selectedIssueIndex(issue);
return index !== -1;
},
setColumnCount() {
const breakpoint = bp.getBreakpointSize();
if (breakpoint === 'lg' || breakpoint === 'md') {
this.columns = 3;
} else if (breakpoint === 'sm') {
this.columns = 2;
} else {
this.columns = 1;
}
},
},
mounted() {
this.scrollHandlerWrapper = this.scrollHandler.bind(this);
this.setColumnCountWrapper = this.setColumnCount.bind(this);
this.setColumnCount();
this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
window.addEventListener('resize', this.setColumnCountWrapper);
},
beforeDestroy() {
this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
window.removeEventListener('resize', this.setColumnCountWrapper);
},
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
template: `
<section
class="add-issues-list add-issues-list-columns"
ref="list">
<div
class="empty-state add-issues-empty-state-filter text-center"
v-if="issuesCount > 0 && issues.length === 0">
<div
class="svg-content"
v-html="image">
</div>
<div class="text-content">
<h4>
There are no issues to show.
</h4>
</div>
</div>
<div
v-for="group in groupedIssues"
class="add-issues-list-column">
<div
v-for="issue in group"
v-if="showIssue(issue)"
class="card-parent">
<div
class="card"
:class="{ 'is-active': issue.selected }"
@click="toggleIssue($event, issue)">
<issue-card-inner
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath">
</issue-card-inner>
<span
:aria-label="'Issue #' + issue.id + ' selected'"
aria-checked="true"
v-if="issue.selected"
class="issue-card-selected text-center">
<i class="fa fa-check"></i>
</span>
</div>
</div>
</div>
</section>
`,
});
})();
/* global Vue */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
data() {
return {
modal: ModalStore.store,
state: gl.issueBoards.BoardsStore.state,
};
},
computed: {
selected() {
return this.modal.selectedList || this.state.lists[0];
},
},
destroyed() {
this.modal.selectedList = null;
},
template: `
<div class="dropdown inline">
<button
class="dropdown-menu-toggle"
type="button"
data-toggle="dropdown"
aria-expanded="false">
<span
class="dropdown-label-box"
:style="{ backgroundColor: selected.label.color }">
</span>
{{ selected.title }}
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="list in state.lists"
v-if="list.type == 'label'">
<a
href="#"
role="button"
:class="{ 'is-active': list.id == selected.id }"
@click.prevent="modal.selectedList = list">
<span
class="dropdown-label-box"
:style="{ backgroundColor: list.label.color }">
</span>
{{ list.title }}
</a>
</li>
</ul>
</div>
</div>
`,
});
})();
/* global Vue */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalTabs = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return ModalStore.store;
},
computed: {
selectedCount() {
return ModalStore.selectedCount();
},
},
destroyed() {
this.activeTab = 'all';
},
template: `
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ 'active': activeTab == 'all' }">
<a
href="#"
role="button"
@click.prevent="changeTab('all')">
All issues
<span class="badge">
{{ issuesCount }}
</span>
</a>
</li>
<li :class="{ 'active': activeTab == 'selected' }">
<a
href="#"
role="button"
@click.prevent="changeTab('selected')">
Selected issues
<span class="badge">
{{ selectedCount }}
</span>
</a>
</li>
</ul>
</div>
`,
});
})();
/* eslint-disable no-new */
/* global Vue */
/* global Flash */
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.RemoveIssueBtn = Vue.extend({
props: {
issue: {
type: Object,
required: true,
},
list: {
type: Object,
required: true,
},
},
methods: {
removeIssue() {
const issue = this.issue;
const lists = issue.getLists();
const labelIds = lists.map(list => list.label.id);
// Post the remove data
gl.boardService.bulkUpdate([issue.globalId], {
remove_label_ids: labelIds,
}).catch(() => {
new Flash('Failed to remove issue from board, please try again.', 'alert');
lists.forEach((list) => {
list.addIssue(issue);
});
});
// Remove from the frontend store
lists.forEach((list) => {
list.removeIssue(issue);
});
Store.detail.issue = {};
},
},
template: `
<div
class="block list"
v-if="list.type !== 'done'">
<button
class="btn btn-default btn-block"
type="button"
@click="removeIssue">
Remove from board
</button>
</div>
`,
});
})();
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalMixins = {
methods: {
toggleModal(toggle) {
ModalStore.store.showAddIssuesModal = toggle;
},
changeTab(tab) {
ModalStore.store.activeTab = tab;
},
},
};
})();
...@@ -6,12 +6,15 @@ ...@@ -6,12 +6,15 @@
class ListIssue { class ListIssue {
constructor (obj) { constructor (obj) {
this.globalId = obj.id;
this.id = obj.iid; this.id = obj.iid;
this.title = obj.title; this.title = obj.title;
this.confidential = obj.confidential; this.confidential = obj.confidential;
this.dueDate = obj.due_date; this.dueDate = obj.due_date;
this.subscribed = obj.subscribed; this.subscribed = obj.subscribed;
this.labels = []; this.labels = [];
this.selected = false;
this.assignee = false;
if (obj.assignee) { if (obj.assignee) {
this.assignee = new ListUser(obj.assignee); this.assignee = new ListUser(obj.assignee);
......
...@@ -9,7 +9,7 @@ class List { ...@@ -9,7 +9,7 @@ class List {
this.position = obj.position; this.position = obj.position;
this.title = obj.title; this.title = obj.title;
this.type = obj.list_type; this.type = obj.list_type;
this.preset = ['backlog', 'done', 'blank'].indexOf(this.type) > -1; this.preset = ['done', 'blank'].indexOf(this.type) > -1;
this.filters = gl.issueBoards.BoardsStore.state.filters; this.filters = gl.issueBoards.BoardsStore.state.filters;
this.page = 1; this.page = 1;
this.loading = true; this.loading = true;
......
...@@ -2,7 +2,13 @@ ...@@ -2,7 +2,13 @@
/* global Vue */ /* global Vue */
class BoardService { class BoardService {
constructor (root, boardId) { constructor (root, bulkUpdatePath, boardId) {
this.boards = Vue.resource(`${root}{/id}.json`, {}, {
issues: {
method: 'GET',
url: `${root}/${boardId}/issues.json`
}
});
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, { this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
generate: { generate: {
method: 'POST', method: 'POST',
...@@ -10,7 +16,12 @@ class BoardService { ...@@ -10,7 +16,12 @@ class BoardService {
} }
}); });
this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {}); this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}); this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}, {
bulkUpdate: {
method: 'POST',
url: bulkUpdatePath,
},
});
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken(); request.headers['X-CSRF-Token'] = $.rails.csrfToken();
...@@ -65,6 +76,20 @@ class BoardService { ...@@ -65,6 +76,20 @@ class BoardService {
issue issue
}); });
} }
getBacklog(data) {
return this.boards.issues(data);
}
bulkUpdate(issueIds, extraData = {}) {
const data = {
update: Object.assign(extraData, {
issuable_ids: issueIds.join(','),
}),
};
return this.issues.bulkUpdate(data);
}
} }
window.BoardService = BoardService; window.BoardService = BoardService;
...@@ -34,15 +34,10 @@ ...@@ -34,15 +34,10 @@
}, },
new (listObj) { new (listObj) {
const list = this.addList(listObj); const list = this.addList(listObj);
const backlogList = this.findList('type', 'backlog', 'backlog');
list list
.save() .save()
.then(() => { .then(() => {
// Remove any new issues from the backlog
// as they will be visible in the new list
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
this.state.lists = _.sortBy(this.state.lists, 'position'); this.state.lists = _.sortBy(this.state.lists, 'position');
}); });
this.removeBlankState(); this.removeBlankState();
...@@ -52,7 +47,7 @@ ...@@ -52,7 +47,7 @@
}, },
shouldAddBlankState () { shouldAddBlankState () {
// Decide whether to add the blank state // Decide whether to add the blank state
return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'done')[0]); return !(this.state.lists.filter(list => list.type !== 'done')[0]);
}, },
addBlankState () { addBlankState () {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
...@@ -102,7 +97,7 @@ ...@@ -102,7 +97,7 @@
listTo.addIssue(issue, listFrom, newIndex); listTo.addIssue(issue, listFrom, newIndex);
} }
if (listTo.type === 'done' && listFrom.type !== 'backlog') { if (listTo.type === 'done') {
issueLists.forEach((list) => { issueLists.forEach((list) => {
list.removeIssue(issue); list.removeIssue(issue);
}); });
......
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
class ModalStore {
constructor() {
this.store = {
columns: 3,
issues: [],
issuesCount: false,
selectedIssues: [],
showAddIssuesModal: false,
activeTab: 'all',
selectedList: null,
searchTerm: '',
loading: false,
loadingNewPage: false,
page: 1,
perPage: 50,
};
this.setDefaultFilter();
}
setDefaultFilter() {
this.store.filter = {
author_id: '',
assignee_id: '',
milestone_title: '',
label_name: [],
};
}
selectedCount() {
return this.getSelectedIssues().length;
}
toggleIssue(issueObj) {
const issue = issueObj;
const selected = issue.selected;
issue.selected = !selected;
if (!selected) {
this.addSelectedIssue(issue);
} else {
this.removeSelectedIssue(issue);
}
}
toggleAll() {
const select = this.selectedCount() !== this.store.issues.length;
this.store.issues.forEach((issue) => {
const issueUpdate = issue;
if (issueUpdate.selected !== select) {
issueUpdate.selected = select;
if (select) {
this.addSelectedIssue(issue);
} else {
this.removeSelectedIssue(issue);
}
}
});
}
getSelectedIssues() {
return this.store.selectedIssues.filter(issue => issue.selected);
}
addSelectedIssue(issue) {
const index = this.selectedIssueIndex(issue);
if (index === -1) {
this.store.selectedIssues.push(issue);
}
}
removeSelectedIssue(issue, forcePurge = false) {
if (this.store.activeTab === 'all' || forcePurge) {
this.store.selectedIssues = this.store.selectedIssues
.filter(fIssue => fIssue.id !== issue.id);
}
}
purgeUnselectedIssues() {
this.store.selectedIssues.forEach((issue) => {
if (!issue.selected) {
this.removeSelectedIssue(issue, true);
}
});
}
selectedIssueIndex(issue) {
return this.store.selectedIssues.indexOf(issue);
}
findSelectedIssue(issue) {
return this.store.selectedIssues
.filter(filteredIssue => filteredIssue.id === issue.id)[0];
}
}
gl.issueBoards.ModalStore = new ModalStore();
})();
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */
/* global Vue */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next(function (response) {
Vue.activeResources -= 1;
});
});
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
BreakpointInstance.prototype.getBreakpointSize = function() { BreakpointInstance.prototype.getBreakpointSize = function() {
var $visibleDevice; var $visibleDevice;
$visibleDevice = this.visibleDevice; $visibleDevice = this.visibleDevice;
// TODO: Consider refactoring in light of turbolinks removal.
// the page refreshed via turbolinks // the page refreshed via turbolinks
if (!$visibleDevice().length) { if (!$visibleDevice().length) {
this.setup(); this.setup();
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */
/* global Breakpoints */ /* global Breakpoints */
/* global Turbolinks */
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
...@@ -127,7 +126,7 @@ ...@@ -127,7 +126,7 @@
pageUrl += DOWN_BUILD_TRACE; pageUrl += DOWN_BUILD_TRACE;
} }
return Turbolinks.visit(pageUrl); return gl.utils.visitUrl(pageUrl);
} }
}; };
})(this) })(this)
......
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue');
require('./pipelines_table');
/**
* Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table.
*
* Renders Pipelines table in pipelines tab in the commits show view.
* Renders Pipelines table in pipelines tab in the merge request show view.
*/
$(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) {
gl.commits.PipelinesTableBundle.$destroy(true);
}
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
});
});
/* 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 {
constructor(endpoint) {
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-underscore-dangle*/
/**
* Pipelines' Store for commits view.
*
* Used to store the Pipelines rendered in the commit view in the pipelines table.
*/
class PipelinesStore {
constructor() {
this.state = {};
this.state.pipelines = [];
}
storePipelines(pipelines = []) {
this.state.pipelines = pipelines;
return pipelines;
}
/**
* Once the data is received we will start the time ago loops.
*
* Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
* update the time to show how long as passed.
*
*/
startTimeAgoLoops() {
const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => {
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
acc.push(timeAgoComponent);
return acc;
}, []).forEach(e => e.changeTime());
}, 10000);
};
startTimeLoops();
const removeIntervals = () => clearInterval(this.timeLoopInterval);
const startIntervals = () => startTimeLoops();
gl.VueRealtimeListener(removeIntervals, startIntervals);
}
}
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesStore = PipelinesStore;
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/pipelines_table');
require('../../vue_realtime_listener/index');
require('./pipelines_service');
require('./pipelines_store');
/**
*
* Uses `pipelines-table-component` to render Pipelines table with an API call.
* Endpoint is provided in HTML and passed as `endpoint`.
* We need a store to store the received environemnts.
* We need a service to communicate with the server.
*
* Necessary SVG in the table are provided as props. This should be refactored
* as soon as we have Webpack and can load them directly into JS files.
*/
(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', {
components: {
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
},
/**
* 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 svgsData = document.querySelector('.pipeline-svgs').dataset;
const store = new gl.commits.pipelines.PipelinesStore();
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
return {
endpoint: pipelinesTableData.endpoint,
svgs: svgsObject,
store,
state: store.state,
isLoading: false,
};
},
/**
* When the component is created the service to fetch the data will be
* initialized with the correct endpoint.
*
* 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.
*
*/
created() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
this.isLoading = true;
return pipelinesService.all()
.then(response => response.json())
.then((json) => {
this.store.storePipelines(json);
this.store.startTimeAgoLoops.call(this, Vue);
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
});
},
template: `
<div>
<div class="pipelines realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title">
No pipelines to show
</h2>
</div>
<div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component
:pipelines="state.pipelines"
:svgs="svgs">
</pipelines-table-component>
</div>
</div>
`,
});
})();
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */ /* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
/* jshint esversion: 6 */ /* jshint esversion: 6 */
/*= require lib/utils/common_utils */ require('./lib/utils/common_utils');
(() => { (() => {
const gfmRules = { const gfmRules = {
......
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, max-len */
/* global Clipboard */ /* global Clipboard */
/*= require clipboard */ window.Clipboard = require('vendor/clipboard');
(function() { (function() {
var genericError, genericSuccess, showTooltip; var genericError, genericSuccess, showTooltip;
......
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
/* global Cookies */ /* global Cookies */
/* global Flash */ /* global Flash */
//= require vue window.Vue = require('vue');
//= require_tree ./svg window.Cookies = require('vendor/js.cookie');
//= require_tree .
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', true, /^\.\/(?!cycle_analytics_bundle).*\.(js|es6)$/));
$(() => { $(() => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
//= require lib/utils/url_utility */ require('./lib/utils/url_utility');
(() => { (() => {
const UNFOLD_COUNT = 20; const UNFOLD_COUNT = 20;
let isBound = false;
class Diff { class Diff {
constructor() { constructor() {
...@@ -17,10 +18,12 @@ ...@@ -17,10 +18,12 @@
$('.content-wrapper .container-fluid').removeClass('container-limited'); $('.content-wrapper .container-fluid').removeClass('container-limited');
} }
$(document) if (!isBound) {
.off('click', '.js-unfold, .diff-line-num a') $(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this)) .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this)); .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
isBound = true;
}
this.openAnchoredDiff(); this.openAnchoredDiff();
} }
......
/* eslint-disable func-names, comma-dangle, new-cap, no-new */ /* eslint-disable func-names, comma-dangle, new-cap, no-new, import/newline-after-import, no-multi-spaces, max-len */
/* global Vue */ /* global Vue */
/* global ResolveCount */ /* global ResolveCount */
//= require_directory ./models function requireAll(context) { return context.keys().map(context); }
//= require_directory ./stores requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/));
//= require_directory ./services requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/));
//= require_directory ./mixins requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/));
//= require_directory ./components requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/));
$(() => { $(() => {
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';
......
...@@ -159,11 +159,6 @@ ...@@ -159,11 +159,6 @@
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
case 'projects:commit:pipelines':
new gl.MiniPipelineGraph({
container: '.js-pipeline-table',
});
break;
case 'projects:commits:show': case 'projects:commits:show':
case 'projects:activity': case 'projects:activity':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */
/* global Dropzone */ /* global Dropzone */
/*= require preview_markdown */ require('./preview_markdown');
(function() { (function() {
this.DropzoneInput = (function() { this.DropzoneInput = (function() {
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
/* global EnvironmentsService */ /* global EnvironmentsService */
/* global Flash */ /* global Flash */
//= require vue window.Vue = require('vue');
//= require vue-resource window.Vue.use(require('vue-resource'));
//= require_tree ../services/ require('../services/environments_service');
//= require ./environment_item require('./environment_item');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
...@@ -180,9 +180,9 @@ ...@@ -180,9 +180,9 @@
<tr> <tr>
<th class="environments-name">Environment</th> <th class="environments-name">Environment</th>
<th class="environments-deploy">Last deployment</th> <th class="environments-deploy">Last deployment</th>
<th class="environments-build">Build</th> <th class="environments-build">Job</th>
<th class="environments-commit">Commit</th> <th class="environments-commit">Commit</th>
<th class="environments-date">Created</th> <th class="environments-date">Updated</th>
<th class="hidden-xs environments-actions"></th> <th class="hidden-xs environments-actions"></th>
</tr> </tr>
</thead> </thead>
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/* global Vue */ /* global Vue */
/* global timeago */ /* global timeago */
/*= require timeago */ window.Vue = require('vue');
/*= require lib/utils/text_utility */ window.timeago = require('vendor/timeago');
/*= require vue_common_component/commit */ require('../../lib/utils/text_utility');
/*= require ./environment_actions */ require('../../vue_shared/components/commit');
/*= require ./environment_external_url */ require('./environment_actions');
/*= require ./environment_stop */ require('./environment_external_url');
/*= require ./environment_rollback */ require('./environment_stop');
/*= require ./environment_terminal_button */ require('./environment_rollback');
require('./environment_terminal_button');
(() => { (() => {
/** /**
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
//= require vue window.Vue = require('vue');
//= require_tree ./stores/ require('./stores/environments_store');
//= require ./components/environment require('./components/environment');
//= require ./vue_resource_interceptor require('../vue_shared/vue_resource_interceptor');
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/* globals Vue */ /* globals Vue */
/* eslint-disable no-unused-vars, no-param-reassign */ /* eslint-disable no-unused-vars, no-param-reassign */
class EnvironmentsService { class EnvironmentsService {
constructor(root) { constructor(root) {
......
/* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, max-len */ /* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, strict, max-len */
'use strict';
Array.prototype.first = function() { Array.prototype.first = function() {
return this[0]; return this[0];
}; };
......
/*= require filtered_search/filtered_search_dropdown */ require('./filtered_search_dropdown');
/* global droplabFilter */ /* global droplabFilter */
......
/*= require filtered_search/filtered_search_dropdown */ require('./filtered_search_dropdown');
/* global droplabAjax */ /* global droplabAjax */
/* global droplabFilter */ /* global droplabFilter */
......
/*= require filtered_search/filtered_search_dropdown */ require('./filtered_search_dropdown');
/* global droplabAjaxFilter */ /* global droplabAjaxFilter */
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
droplabAjaxFilter: { droplabAjaxFilter: {
endpoint: '/autocomplete/users.json', endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search', searchKey: 'search',
params: { params: {
per_page: 20, per_page: 20,
...@@ -39,8 +39,15 @@ ...@@ -39,8 +39,15 @@
getSearchInput() { getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input); const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
let value = lastToken.value || '';
return lastToken.value || ''; // Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
return value;
} }
init() { init() {
......
// This is a manifest file that'll be compiled into including all the files listed below. function requireAll(context) { return context.keys().map(context); }
// Add new JavaScript code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js requireAll(require.context('./', true, /^\.\/(?!filtered_search_bundle).*\.(js|es6)$/));
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
/*= require_tree . */
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
this.setupMapping(); this.setupMapping();
this.cleanupWrapper = this.cleanup.bind(this); this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('page:fetch', this.cleanupWrapper); document.addEventListener('beforeunload', this.cleanupWrapper);
} }
cleanup() { cleanup() {
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
this.setupMapping(); this.setupMapping();
document.removeEventListener('page:fetch', this.cleanupWrapper); document.removeEventListener('beforeunload', this.cleanupWrapper);
} }
setupMapping() { setupMapping() {
......
/* global Turbolinks */
(() => { (() => {
class FilteredSearchManager { class FilteredSearchManager {
constructor() { constructor() {
...@@ -15,13 +13,13 @@ ...@@ -15,13 +13,13 @@
this.dropdownManager.setDropdown(); this.dropdownManager.setDropdown();
this.cleanupWrapper = this.cleanup.bind(this); this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('page:fetch', this.cleanupWrapper); document.addEventListener('beforeunload', this.cleanupWrapper);
} }
} }
cleanup() { cleanup() {
this.unbindEvents(); this.unbindEvents();
document.removeEventListener('page:fetch', this.cleanupWrapper); document.removeEventListener('beforeunload', this.cleanupWrapper);
} }
bindEvents() { bindEvents() {
...@@ -200,7 +198,9 @@ ...@@ -200,7 +198,9 @@
paths.push(`search=${sanitized}`); paths.push(`search=${sanitized}`);
} }
Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`); const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
gl.utils.visitUrl(parameterizedUrl);
} }
getUsernameParams() { getUsernameParams() {
......
...@@ -83,12 +83,12 @@ ...@@ -83,12 +83,12 @@
_a = decodeURI("%C3%80"); _a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF"); _y = decodeURI("%C3%BF");
regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)$", 'gi'); regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
match = regexp.exec(subtext); match = regexp.exec(subtext);
if (match) { if (match) {
return match[2] || match[1]; return (match[1] || match[1] === "") ? match[1] : match[2];
} else { } else {
return null; return null;
} }
......
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
/* global fuzzaldrinPlus */ /* global fuzzaldrinPlus */
/* global Turbolinks */
(function() { (function() {
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
...@@ -249,7 +248,7 @@ ...@@ -249,7 +248,7 @@
_this.fullData = data; _this.fullData = data;
_this.parseData(_this.fullData); _this.parseData(_this.fullData);
_this.focusTextInput(); _this.focusTextInput();
if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val().trim() !== '') { if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
return _this.filter.input.trigger('input'); return _this.filter.input.trigger('input');
} }
}; };
...@@ -723,7 +722,7 @@ ...@@ -723,7 +722,7 @@
if ($el.length) { if ($el.length) {
var href = $el.attr('href'); var href = $el.attr('href');
if (href && href !== '#') { if (href && href !== '#') {
Turbolinks.visit(href); gl.utils.visitUrl(href);
} else { } else {
$el.first().trigger('click'); $el.first().trigger('click');
} }
......
/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */ /* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */
//= require gl_field_error require('./gl_field_error');
((global) => { ((global) => {
const customValidationFlag = 'gl-field-error-ignore'; const customValidationFlag = 'gl-field-error-ignore';
......
/* eslint-disable func-names, space-before-function-paren */ // require everything else in this directory
// This is a manifest file that'll be compiled into including all the files listed below. function requireAll(context) { return context.keys().map(context); }
// Add new JavaScript code in separate files in this directory and they'll automatically requireAll(require.context('.', false, /^\.\/(?!graphs_bundle).*\.(js|es6)$/));
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
/*= require_tree . */
(function() {
}).call(this);
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
/* global ContributorsStatGraphUtil */ /* global ContributorsStatGraphUtil */
/* global d3 */ /* global d3 */
/*= require d3 */ window.d3 = require('d3');
(function() { (function() {
this.ContributorsStatGraph = (function() { this.ContributorsStatGraph = (function() {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* global d3 */ /* global d3 */
/* global ContributorsGraph */ /* global ContributorsGraph */
/*= require d3 */ window.d3 = require('d3');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
......
/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */ /* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */
/* global Issuable */ /* global Issuable */
/* global Turbolinks */
((global) => { ((global) => {
var issuable_created; var issuable_created;
...@@ -119,7 +118,7 @@ ...@@ -119,7 +118,7 @@
issuesUrl = formAction; issuesUrl = formAction;
issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&'); issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
issuesUrl += formData; issuesUrl += formData;
return Turbolinks.visit(issuesUrl); return gl.utils.visitUrl(issuesUrl);
}; };
})(this), })(this),
initResetFilters: function() { initResetFilters: function() {
...@@ -130,7 +129,7 @@ ...@@ -130,7 +129,7 @@
const baseIssuesUrl = target.href; const baseIssuesUrl = target.href;
$form.attr('action', baseIssuesUrl); $form.attr('action', baseIssuesUrl);
Turbolinks.visit(baseIssuesUrl); gl.utils.visitUrl(baseIssuesUrl);
}); });
}, },
initChecks: function() { initChecks: function() {
......
//= require ./time_tracking/time_tracking_bundle require('./time_tracking/time_tracking_bundle');
/* global Vue */ /* global Vue */
//= require lib/utils/pretty_time require('../../../lib/utils/pretty_time');
(() => { (() => {
Vue.component('time-tracking-collapsed-state', { Vue.component('time-tracking-collapsed-state', {
...@@ -39,4 +39,3 @@ ...@@ -39,4 +39,3 @@
`, `,
}); });
})(); })();
/* global Vue */ /* global Vue */
//= require lib/utils/pretty_time require('../../../lib/utils/pretty_time');
(() => { (() => {
const prettyTime = gl.utils.prettyTime; const prettyTime = gl.utils.prettyTime;
......
/* global Vue */ /* global Vue */
//= require ./help_state
//= require ./collapsed_state require('./help_state');
//= require ./spent_only_pane require('./collapsed_state');
//= require ./no_tracking_pane require('./spent_only_pane');
//= require ./estimate_only_pane require('./no_tracking_pane');
//= require ./comparison_pane require('./estimate_only_pane');
require('./comparison_pane');
(() => { (() => {
Vue.component('issuable-time-tracker', { Vue.component('issuable-time-tracker', {
......
/* global Vue */ /* global Vue */
//= require ./components/time_tracker
//= require smart_interval require('./components/time_tracker');
//= require subbable_resource require('../../smart_interval');
require('../../subbable_resource');
(() => { (() => {
/* This Vue instance represents what will become the parent instance for the /* This Vue instance represents what will become the parent instance for the
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */ /* global Flash */
/*= require flash */ require('./flash');
/*= require jquery.waitforimages */ require('vendor/jquery.waitforimages');
/*= require task_list */ require('vendor/task_list');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
......
...@@ -4,10 +4,17 @@ ...@@ -4,10 +4,17 @@
(function() { (function() {
this.LabelsSelect = (function() { this.LabelsSelect = (function() {
function LabelsSelect() { function LabelsSelect(els) {
var _this; var _this, $els;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) {
$els = $(els);
if (!els) {
$els = $('.js-label-select');
}
$els.each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer; var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer;
$dropdown = $(dropdown); $dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter'); $dropdownContainer = $dropdown.closest('.labels-filter');
...@@ -324,7 +331,7 @@ ...@@ -324,7 +331,7 @@
multiSelect: $dropdown.hasClass('js-multiselect'), multiSelect: $dropdown.hasClass('js-multiselect'),
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(label, $el, e, isMarking) { clicked: function(label, $el, e, isMarking) {
var isIssueIndex, isMRIndex, page; var isIssueIndex, isMRIndex, page, boardsModel;
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
...@@ -346,22 +353,31 @@ ...@@ -346,22 +353,31 @@
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
!$dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.BoardsStore.state.filters;
} else if ($dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.ModalStore.store.filter;
}
if (boardsModel) {
if (label.isAny) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; boardsModel['label_name'] = [];
} }
else if ($el.hasClass('is-active')) { else if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); boardsModel['label_name'].push(label.title);
} }
else { else {
var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; var filters = boardsModel['label_name'];
filters = filters.filter(function (filteredLabel) { filters = filters.filter(function (filteredLabel) {
return filteredLabel !== label.title; return filteredLabel !== label.title;
}); });
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters; boardsModel['label_name'] = filters;
} }
gl.issueBoards.BoardsStore.updateFiltersUrl(); if (!$dropdown.closest('.add-issues-modal').length) {
gl.issueBoards.BoardsStore.updateFiltersUrl();
}
e.preventDefault(); e.preventDefault();
return; return;
} }
......
...@@ -7,19 +7,28 @@ ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort. ...@@ -7,19 +7,28 @@ ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.
File.basename(file, '.js').sub(/^mode-/, '') File.basename(file, '.js').sub(/^mode-/, '')
end end
%> %>
// Lazy-load configuration when ace.edit is called
(function() { (function() {
window.gon = window.gon || {}; var basePath;
var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace'; var ace = window.ace;
ace.config.set('basePath', basePath); var edit = ace.edit;
ace.edit = function() {
window.gon = window.gon || {};
basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace';
ace.config.set('basePath', basePath);
// configure paths for all worker modules // configure paths for all worker modules
<% ace_workers.each do |worker| %> <% ace_workers.each do |worker| %>
ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js'); ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/<%= File.basename(asset_path("ace/worker-#{worker}.js")) %>');
<% end %> <% end %>
// configure paths for all mode modules // configure paths for all mode modules
<% ace_modes.each do |mode| %> <% ace_modes.each do |mode| %>
ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js'); ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/<%= File.basename(asset_path("ace/mode-#{mode}.js")) %>');
<% end %> <% end %>
// restore original method
ace.edit = edit;
return ace.edit.apply(ace, arguments);
};
})(); })();
/* eslint-disable func-names, space-before-function-paren */ /* eslint-disable func-names, space-before-function-paren */
/*= require Chart */ window.Chart = require('vendor/Chart');
(function() {
}).call(this);
/* eslint-disable func-names, space-before-function-paren */ /* eslint-disable func-names, space-before-function-paren */
/*= require d3 */ window.d3 = require('d3');
(function() {
}).call(this);
...@@ -95,7 +95,6 @@ ...@@ -95,7 +95,6 @@
const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`; const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
history.replaceState({ history.replaceState({
turbolinks: true,
url: newState, url: newState,
}, document.title, newState); }, document.title, newState);
return newState; return newState;
......
...@@ -162,6 +162,7 @@ ...@@ -162,6 +162,7 @@
w.gl.utils.getSelectedFragment = () => { w.gl.utils.getSelectedFragment = () => {
const selection = window.getSelection(); const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
const documentFragment = selection.getRangeAt(0).cloneContents(); const documentFragment = selection.getRangeAt(0).cloneContents();
if (documentFragment.textContent.length === 0) return null; if (documentFragment.textContent.length === 0) return null;
...@@ -229,5 +230,16 @@ ...@@ -229,5 +230,16 @@
return upperCaseHeaders; return upperCaseHeaders;
}; };
/**
* Transforms a DOMStringMap into a plain object.
*
* @param {DOMStringMap} DOMStringMapObject
* @returns {Object}
*/
w.gl.utils.DOMStringMapToObject = DOMStringMapObject => Object.keys(DOMStringMapObject).reduce((acc, element) => {
acc[element] = DOMStringMapObject[element];
return acc;
}, {});
})(window); })(window);
}).call(this); }).call(this);
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
/* global timeago */ /* global timeago */
/* global dateFormat */ /* global dateFormat */
/*= require timeago */ window.timeago = require('vendor/timeago');
/*= require date.format */ window.dateFormat = require('vendor/date.format');
(function() { (function() {
(function(w) { (function(w) {
......
(function() {
gl.emojiAliases = function() {
return JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>');
};
}).call(this);
...@@ -161,6 +161,9 @@ ...@@ -161,6 +161,9 @@
gl.text.humanize = function(string) { gl.text.humanize = function(string) {
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1); return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
}; };
gl.text.pluralize = function(str, count) {
return str + (count > 1 || count === 0 ? 's' : '');
};
return gl.text.truncate = function(string, maxLength) { return gl.text.truncate = function(string, maxLength) {
return string.substr(0, (maxLength - 3)) + '...'; return string.substr(0, (maxLength - 3)) + '...';
}; };
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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