Merge remote-tracking branch 'origin/master' into 24147-delete-env-button
This commit is contained in:
commit
a8825f0c7f
1340 changed files with 19545 additions and 7605 deletions
|
@ -1,7 +1,9 @@
|
|||
/builds/
|
||||
/coverage/
|
||||
/coverage-javascript/
|
||||
/node_modules/
|
||||
/public/
|
||||
/tmp/
|
||||
/vendor/
|
||||
/builds/
|
||||
karma.config.js
|
||||
webpack.config.js
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
"filenames"
|
||||
],
|
||||
"rules": {
|
||||
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"]
|
||||
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
|
||||
"no-multiple-empty-lines": ["error", { "max": 1 }],
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"import/no-unresolved": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,11 +107,13 @@ setup-test-env:
|
|||
<<: *dedicated-runner
|
||||
stage: prepare
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- npm install
|
||||
- bundle exec rake gitlab:assets:compile
|
||||
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
paths:
|
||||
- node_modules
|
||||
- public/assets
|
||||
- tmp/tests
|
||||
|
||||
|
@ -232,7 +234,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
|
|||
script:
|
||||
- bundle exec $CI_BUILD_NAME
|
||||
|
||||
rubocop:
|
||||
rubocop:
|
||||
<<: *ruby-static-analysis
|
||||
<<: *dedicated-runner
|
||||
stage: test
|
||||
|
@ -271,7 +273,7 @@ rake db:migrate:reset:
|
|||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
script:
|
||||
- rake db:migrate:reset
|
||||
- bundle exec rake db:migrate:reset
|
||||
|
||||
rake db:seed_fu:
|
||||
stage: test
|
||||
|
@ -291,18 +293,17 @@ rake db:seed_fu:
|
|||
paths:
|
||||
- log/development.log
|
||||
|
||||
teaspoon:
|
||||
karma:
|
||||
cache:
|
||||
paths:
|
||||
- vendor/ruby
|
||||
- node_modules/
|
||||
- node_modules
|
||||
stage: test
|
||||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
script:
|
||||
- npm install
|
||||
- npm link istanbul
|
||||
- rake teaspoon
|
||||
- bundle exec rake karma
|
||||
artifacts:
|
||||
name: coverage-javascript
|
||||
expire_in: 31d
|
||||
|
@ -353,10 +354,10 @@ migration paths:
|
|||
- cp config/resque.yml.example config/resque.yml
|
||||
- sed -i 's/localhost/redis/g' config/resque.yml
|
||||
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
|
||||
- rake db:drop db:create db:schema:load db:seed_fu
|
||||
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
|
||||
- git checkout $CI_BUILD_REF
|
||||
- source scripts/prepare_build.sh
|
||||
- rake db:migrate
|
||||
- bundle exec rake db:migrate
|
||||
|
||||
coverage:
|
||||
stage: post-test
|
||||
|
@ -444,7 +445,7 @@ pages:
|
|||
<<: *dedicated-runner
|
||||
dependencies:
|
||||
- coverage
|
||||
- teaspoon
|
||||
- karma
|
||||
- lint:javascript:report
|
||||
script:
|
||||
- mv public/ .public/
|
||||
|
|
|
@ -46,7 +46,7 @@ linters:
|
|||
max: 80
|
||||
|
||||
MultilinePipe:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
MultilineScript:
|
||||
enabled: true
|
||||
|
@ -77,7 +77,7 @@ linters:
|
|||
- Style/WhileUntilModifier
|
||||
|
||||
RubyComments:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
SpaceBeforeScript:
|
||||
enabled: true
|
||||
|
@ -97,7 +97,7 @@ linters:
|
|||
enabled: true
|
||||
|
||||
UnnecessaryInterpolation:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
UnnecessaryStringOutput:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
|
|
@ -17,6 +17,7 @@ AllCops:
|
|||
# Exclude some GitLab files
|
||||
Exclude:
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'db/*'
|
||||
- 'db/fixtures/**/*'
|
||||
- 'tmp/**/*'
|
||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -2,7 +2,34 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 8.16.0 (2017-02-22)
|
||||
## 8.16.3 (2017-01-27)
|
||||
|
||||
- Add caching of droplab ajax requests. !8725
|
||||
- Fix access to the wiki code via HTTP when repository feature disabled. !8758
|
||||
- Revert 3f17f29a. !8785
|
||||
- Fix race conditions for AuthorizedProjectsWorker.
|
||||
- Fix autocomplete initial undefined state.
|
||||
- Fix Error 500 when repositories contain annotated tags pointing to blobs.
|
||||
- Fix /explore sorting.
|
||||
- Fixed label dropdown toggle text not correctly updating.
|
||||
|
||||
## 8.16.2 (2017-01-25)
|
||||
|
||||
- allow issue filter bar to be operated with mouse only. !8681
|
||||
- Fix CI requests concurrency for newer runners that prevents from picking pending builds (from 1.9.0-rc5). !8760
|
||||
- Add some basic fixes for IE11/Edge.
|
||||
- Remove blue border from comment box hover.
|
||||
- Fixed bug where links in merge dropdown wouldn't work.
|
||||
|
||||
## 8.16.1 (2017-01-23)
|
||||
|
||||
- Ensure export files are removed after a namespace is deleted.
|
||||
- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
|
||||
- Prevent users from creating notes on resources they can't access.
|
||||
- Prevent users from deleting system deploy keys via the project deploy key API.
|
||||
- Upgrade omniauth gem to 1.3.2.
|
||||
|
||||
## 8.16.0 (2017-01-22)
|
||||
|
||||
- Add LDAP Rake task to rename a provider. !2181
|
||||
- Validate label's title length. !5767 (Tomáš Kukrál)
|
||||
|
@ -395,6 +422,14 @@ entry.
|
|||
- Whitelist next project names: help, ci, admin, search. !8227
|
||||
- Adds back CSS for progress-bars. !8237
|
||||
|
||||
## 8.14.8 (2017-01-25)
|
||||
|
||||
- Accept environment variables from the `pre-receive` script. !7967
|
||||
- Milestoneish SQL performance partially improved and memoized. !8146
|
||||
- Fix N+1 queries on milestone show pages. !8185
|
||||
- Speed up group milestone index by passing group_id to IssuesFinder. !8363
|
||||
- Ensure issuable state changes only fire webhooks once.
|
||||
|
||||
## 8.14.6 (2017-01-10)
|
||||
|
||||
- Update the gitlab-markup gem to the version 1.5.1. !8509
|
||||
|
|
|
@ -80,16 +80,35 @@ the remaining issues on the GitHub issue tracker.
|
|||
## I want to contribute!
|
||||
|
||||
If you want to contribute to GitLab, but are not sure where to start,
|
||||
look for [issues with the label `up-for-grabs`][up-for-grabs]. These issues
|
||||
will be of reasonable size and challenge, for anyone to start contributing to
|
||||
GitLab.
|
||||
|
||||
This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
|
||||
look for [issues with the label `Accepting Merge Requests` and weight < 5][accepting-mrs-weight].
|
||||
These issues will be of reasonable size and challenge, for anyone to start
|
||||
contributing to GitLab.
|
||||
|
||||
## Implement design & UI elements
|
||||
|
||||
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
|
||||
|
||||
To get support for your particular problem please use the
|
||||
|
@ -214,16 +233,19 @@ associated with in the description of the issue.
|
|||
## Merge requests
|
||||
|
||||
We welcome merge requests with fixes and improvements to GitLab code, tests,
|
||||
and/or documentation. The features we would really like a merge request for are
|
||||
listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
|
||||
and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note
|
||||
that if an issue is marked for the current milestone either before or while you
|
||||
are working on it, a team member may take over the merge request in order to
|
||||
ensure the work is finished before the release date.
|
||||
and/or documentation. The issues that are specifically suitable for
|
||||
community contributions are listed with the label
|
||||
[`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
|
||||
and [EE][accepting-mrs-ee], but you are free to contribute to any other issue
|
||||
you want.
|
||||
|
||||
Please note that if an issue is marked for the current milestone either before
|
||||
or while you are working on it, a team member may take over the merge request
|
||||
in order to ensure the work is finished before the release date.
|
||||
|
||||
If you want to add a new feature that is not labeled it is best to first create
|
||||
a feedback issue (if there isn't one already) and leave a comment asking for it
|
||||
to be marked as `Accepting merge requests`. Please include screenshots or
|
||||
to be marked as `Accepting Merge Requests`. Please include screenshots or
|
||||
wireframes if the feature will also change the UI.
|
||||
|
||||
Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
|
||||
|
@ -285,14 +307,6 @@ request is as follows:
|
|||
1. For tests that use Capybara or PhantomJS, see this [article on how
|
||||
to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
|
||||
|
||||
The **official merge window** is in the beginning of the month from the 1st to
|
||||
the 7th day of the month. This is the best time to submit an MR and get
|
||||
feedback fast. Before this time the GitLab Inc. team is still dealing with work
|
||||
that is created by the monthly release such as regressions requiring patch
|
||||
releases. After the 7th it is already getting closer to the release date of the
|
||||
next version. This means there is less time to fix the issues created by
|
||||
merging large new features.
|
||||
|
||||
Please keep the change in a single MR **as small as possible**. If you want to
|
||||
contribute a large feature think very hard what the minimum viable change is.
|
||||
Can you split the functionality? Can you only submit the backend/API code? Can
|
||||
|
@ -450,8 +464,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
[core team]: https://about.gitlab.com/core-team/
|
||||
[getting-help]: https://about.gitlab.com/getting-help/
|
||||
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
|
||||
[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
|
||||
[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc
|
||||
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
|
||||
[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
|
||||
[google-group]: https://groups.google.com/forum/#!forum/gitlabhq
|
||||
|
|
16
Gemfile
16
Gemfile
|
@ -7,7 +7,6 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
|||
gem 'responders', '~> 2.0'
|
||||
|
||||
gem 'sprockets', '~> 3.7.0'
|
||||
gem 'sprockets-es6', '~> 0.9.2'
|
||||
|
||||
# Default values for AR models
|
||||
gem 'default_value_for', '~> 3.0.0'
|
||||
|
@ -36,7 +35,7 @@ gem 'omniauth-twitter', '~> 1.2.0'
|
|||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
gem 'omniauth-authentiq', '~> 0.2.0'
|
||||
gem 'rack-oauth2', '~> 1.2.1'
|
||||
gem 'jwt'
|
||||
gem 'jwt', '~> 1.5.6'
|
||||
|
||||
# Spam and anti-bot protection
|
||||
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
|
||||
|
@ -109,7 +108,7 @@ gem 'org-ruby', '~> 0.9.12'
|
|||
gem 'creole', '~> 0.5.0'
|
||||
gem 'wikicloth', '0.8.1'
|
||||
gem 'asciidoctor', '~> 1.5.2'
|
||||
gem 'asciidoctor-plantuml', '0.0.6'
|
||||
gem 'asciidoctor-plantuml', '0.0.7'
|
||||
gem 'rouge', '~> 2.0'
|
||||
gem 'truncato', '~> 0.7.8'
|
||||
|
||||
|
@ -219,10 +218,12 @@ gem 'oj', '~> 2.17.4'
|
|||
gem 'chronic', '~> 0.10.2'
|
||||
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 'coffee-rails', '~> 4.1.0'
|
||||
gem 'uglifier', '~> 2.7.2'
|
||||
gem 'gitlab-turbolinks-classic', '~> 2.5', '>= 2.5.6'
|
||||
|
||||
gem 'addressable', '~> 2.3.8'
|
||||
gem 'bootstrap-sass', '~> 3.3.0'
|
||||
|
@ -280,6 +281,7 @@ group :development, :test do
|
|||
gem 'rspec-retry', '~> 0.4.5'
|
||||
gem 'spinach-rails', '~> 0.2.1'
|
||||
gem 'spinach-rerun-reporter', '~> 0.0.2'
|
||||
gem 'rspec_profiling'
|
||||
|
||||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||
gem 'minitest', '~> 5.7.0'
|
||||
|
@ -291,13 +293,9 @@ group :development, :test do
|
|||
gem 'capybara-screenshot', '~> 1.0.0'
|
||||
gem 'poltergeist', '~> 1.9.0'
|
||||
|
||||
gem 'teaspoon', '~> 1.1.0'
|
||||
gem 'teaspoon-jasmine', '~> 2.2.0'
|
||||
|
||||
gem 'spring', '~> 1.7.0'
|
||||
gem 'spring-commands-rspec', '~> 1.0.4'
|
||||
gem 'spring-commands-spinach', '~> 1.1.0'
|
||||
gem 'spring-commands-teaspoon', '~> 0.0.2'
|
||||
|
||||
gem 'rubocop', '~> 0.46.0', require: false
|
||||
gem 'rubocop-rspec', '~> 1.9.1', require: false
|
||||
|
@ -322,7 +320,7 @@ group :test do
|
|||
gem 'email_spec', '~> 1.6.0'
|
||||
gem 'json-schema', '~> 2.6.2'
|
||||
gem 'webmock', '~> 1.21.0'
|
||||
gem 'test_after_commit', '~> 0.4.2'
|
||||
gem 'test_after_commit', '~> 1.1'
|
||||
gem 'sham_rack', '~> 1.3.6'
|
||||
gem 'timecop', '~> 0.8.0'
|
||||
end
|
||||
|
|
48
Gemfile.lock
48
Gemfile.lock
|
@ -54,7 +54,7 @@ GEM
|
|||
faraday_middleware-multi_json (~> 0.0)
|
||||
oauth2 (~> 1.0)
|
||||
asciidoctor (1.5.3)
|
||||
asciidoctor-plantuml (0.0.6)
|
||||
asciidoctor-plantuml (0.0.7)
|
||||
asciidoctor (~> 1.5)
|
||||
ast (2.3.0)
|
||||
attr_encrypted (3.0.3)
|
||||
|
@ -72,10 +72,6 @@ GEM
|
|||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
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)
|
||||
base32 (0.3.2)
|
||||
bcrypt (3.1.11)
|
||||
|
@ -266,8 +262,6 @@ GEM
|
|||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab-markup (1.5.1)
|
||||
gitlab-turbolinks-classic (2.5.6)
|
||||
coffee-rails
|
||||
gitlab_omniauth-ldap (1.2.1)
|
||||
net-ldap (~> 0.9)
|
||||
omniauth (~> 1.0)
|
||||
|
@ -379,7 +373,7 @@ GEM
|
|||
json (1.8.3)
|
||||
json-schema (2.6.2)
|
||||
addressable (~> 2.3.8)
|
||||
jwt (1.5.4)
|
||||
jwt (1.5.6)
|
||||
kaminari (0.17.0)
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -548,6 +542,8 @@ GEM
|
|||
rack (>= 1.1)
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rack-proxy (0.6.0)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.7.1)
|
||||
|
@ -642,6 +638,11 @@ GEM
|
|||
rspec-retry (0.4.5)
|
||||
rspec-core
|
||||
rspec-support (3.5.0)
|
||||
rspec_profiling (0.0.4)
|
||||
activerecord
|
||||
pg
|
||||
rails
|
||||
sqlite3
|
||||
rubocop (0.46.0)
|
||||
parser (>= 2.3.1.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
|
@ -730,19 +731,14 @@ GEM
|
|||
spring (>= 0.9.1)
|
||||
spring-commands-spinach (1.1.0)
|
||||
spring (>= 0.9.1)
|
||||
spring-commands-teaspoon (0.0.2)
|
||||
spring (>= 0.9.1)
|
||||
sprockets (3.7.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-es6 (0.9.2)
|
||||
babel-source (>= 5.8.11)
|
||||
babel-transpiler
|
||||
sprockets (>= 3.0.0)
|
||||
sprockets-rails (3.1.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.3.11)
|
||||
stackprof (0.2.10)
|
||||
state_machines (0.4.0)
|
||||
state_machines-activemodel (0.4.0)
|
||||
|
@ -755,12 +751,8 @@ GEM
|
|||
sys-filesystem (1.1.6)
|
||||
ffi
|
||||
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)
|
||||
test_after_commit (0.4.2)
|
||||
test_after_commit (1.1.0)
|
||||
activerecord (>= 3.2)
|
||||
thin (1.7.0)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
|
@ -810,6 +802,8 @@ GEM
|
|||
webmock (1.21.0)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
webpack-rails (0.9.9)
|
||||
rails (>= 3.2.0)
|
||||
websocket-driver (0.6.3)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
|
@ -835,7 +829,7 @@ DEPENDENCIES
|
|||
allocations (~> 1.0)
|
||||
asana (~> 0.4.0)
|
||||
asciidoctor (~> 1.5.2)
|
||||
asciidoctor-plantuml (= 0.0.6)
|
||||
asciidoctor-plantuml (= 0.0.7)
|
||||
attr_encrypted (~> 3.0.0)
|
||||
awesome_print (~> 1.2.0)
|
||||
babosa (~> 1.0.2)
|
||||
|
@ -885,7 +879,6 @@ DEPENDENCIES
|
|||
github-linguist (~> 4.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.5.1)
|
||||
gitlab-turbolinks-classic (~> 2.5, >= 2.5.6)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.2)
|
||||
|
@ -906,7 +899,7 @@ DEPENDENCIES
|
|||
jquery-rails (~> 4.1.0)
|
||||
jquery-ui-rails (~> 5.0.0)
|
||||
json-schema (~> 2.6.2)
|
||||
jwt
|
||||
jwt (~> 1.5.6)
|
||||
kaminari (~> 0.17.0)
|
||||
knapsack (~> 1.11.0)
|
||||
kubeclient (~> 2.2.0)
|
||||
|
@ -949,6 +942,7 @@ DEPENDENCIES
|
|||
rack-attack (~> 4.4.1)
|
||||
rack-cors (~> 0.4.0)
|
||||
rack-oauth2 (~> 1.2.1)
|
||||
rack-proxy (~> 0.6.0)
|
||||
rails (= 4.2.7.1)
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rainbow (~> 2.1.0)
|
||||
|
@ -965,6 +959,7 @@ DEPENDENCIES
|
|||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-rails (~> 3.5.0)
|
||||
rspec-retry (~> 0.4.5)
|
||||
rspec_profiling
|
||||
rubocop (~> 0.46.0)
|
||||
rubocop-rspec (~> 1.9.1)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
|
@ -989,15 +984,11 @@ DEPENDENCIES
|
|||
spring (~> 1.7.0)
|
||||
spring-commands-rspec (~> 1.0.4)
|
||||
spring-commands-spinach (~> 1.1.0)
|
||||
spring-commands-teaspoon (~> 0.0.2)
|
||||
sprockets (~> 3.7.0)
|
||||
sprockets-es6 (~> 0.9.2)
|
||||
stackprof (~> 0.2.10)
|
||||
state_machines-activerecord (~> 0.4.0)
|
||||
sys-filesystem (~> 1.1.6)
|
||||
teaspoon (~> 1.1.0)
|
||||
teaspoon-jasmine (~> 2.2.0)
|
||||
test_after_commit (~> 0.4.2)
|
||||
test_after_commit (~> 1.1)
|
||||
thin (~> 1.7.0)
|
||||
timecop (~> 0.8.0)
|
||||
truncato (~> 0.7.8)
|
||||
|
@ -1012,7 +1003,8 @@ DEPENDENCIES
|
|||
vmstat (~> 2.3.0)
|
||||
web-console (~> 2.0)
|
||||
webmock (~> 1.21.0)
|
||||
webpack-rails (~> 0.9.9)
|
||||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.13.7
|
||||
1.14.2
|
||||
|
|
102
PROCESS.md
102
PROCESS.md
|
@ -12,106 +12,54 @@ etc.).
|
|||
|
||||
## Common actions
|
||||
|
||||
### Issue team
|
||||
### Issue triaging
|
||||
|
||||
- Looks for issues without [workflow labels](#how-we-handle-issues) and triages
|
||||
issue
|
||||
- Closes invalid issues with a comment (duplicates,
|
||||
[fixed in newer version](#issue-fixed-in-newer-version),
|
||||
[issue report for old version](#issue-report-for-old-version), not a problem
|
||||
in GitLab, etc.)
|
||||
- Asks for feedback from issue reporter
|
||||
([invalid issue reports](#improperly-formatted-issue),
|
||||
[format code](#code-format), etc.)
|
||||
- Monitors all issues for feedback (but especially ones commented on since
|
||||
automatically watching them)
|
||||
- Closes issues with no feedback from the reporter for two weeks
|
||||
|
||||
### Merge marshall & merge request coach
|
||||
|
||||
- Responds to merge requests the issue team mentions them in and monitors for
|
||||
new merge requests
|
||||
- Provides feedback to the merge request submitter to improve the merge request
|
||||
(style, tests, etc.)
|
||||
- Mark merge requests `Ready for Merge` when they meet the
|
||||
[contribution acceptance criteria]
|
||||
- Mention developer(s) based on the
|
||||
[list of members and their specialities][team]
|
||||
- Closes merge requests with no feedback from the reporter for two weeks
|
||||
|
||||
## Priorities of the issue team
|
||||
|
||||
1. Mentioning people (critical)
|
||||
1. Workflow labels (normal)
|
||||
1. Functional labels (minor)
|
||||
1. Assigning issues (avoid if possible)
|
||||
|
||||
## Mentioning people
|
||||
Our issue triage policies are [described in our handbook]. You are very welcome
|
||||
to help the GitLab team triage issues. We also organize [issue bash events] once
|
||||
every quarter.
|
||||
|
||||
The most important thing is making sure valid issues receive feedback from the
|
||||
development team. Therefore the priority is mentioning developers that can help
|
||||
on those issues. Please select someone with relevant experience from
|
||||
[GitLab core team][core-team]. If there is nobody mentioned with that expertise
|
||||
[GitLab team][team]. If there is nobody mentioned with that expertise
|
||||
look in the commit history for the affected files to find someone. Avoid
|
||||
mentioning the lead developer, this is the person that is least likely to give a
|
||||
timely response. If the involvement of the lead developer is needed the other
|
||||
core team members will mention this person.
|
||||
|
||||
[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/
|
||||
[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
|
||||
|
||||
### Merge request coaching
|
||||
|
||||
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].
|
||||
|
||||
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
|
||||
|
||||
## Workflow labels
|
||||
|
||||
Workflow labels are purposely not very detailed since that would be hard to keep
|
||||
updated as you would need to re-evaluate them after every comment. We optionally
|
||||
use functional labels on demand when we want to group related issues to get an
|
||||
overview (for example all issues related to RVM, to tackle them in one go) and
|
||||
to add details to the issue.
|
||||
Labelling issues is described in the [GitLab Inc engineering workflow].
|
||||
|
||||
- ~"Awaiting Feedback" Feedback pending from the reporter
|
||||
- ~UX needs help from a UX designer
|
||||
- ~Frontend needs help from a Front-end engineer. Please follow the
|
||||
["Implement design & UI elements" guidelines].
|
||||
- ~"Accepting Merge Requests" is a low priority, well-defined issue that we
|
||||
encourage people to contribute to. Not exclusive with other labels.
|
||||
- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
|
||||
in support or comment for further detail. Do not use `feature request`.
|
||||
- ~bug is an issue reporting undesirable or incorrect behavior.
|
||||
- ~customer is an issue reported by enterprise subscribers. This label should
|
||||
be accompanied by *bug* or *feature proposal* labels.
|
||||
|
||||
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
|
||||
|
||||
## Functional labels
|
||||
|
||||
These labels describe what development specialities are involved such as: `CI`,
|
||||
`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
|
||||
`Release`, `Repository`, `UX`.
|
||||
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
|
||||
|
||||
## Assigning issues
|
||||
|
||||
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
|
||||
|
||||
## Label colors
|
||||
|
||||
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting
|
||||
feedback, awaiting confirmation of fix)
|
||||
- Bright orange `#eb6420`: workflow labels for core team members (attached MR,
|
||||
awaiting developer action/feedback)
|
||||
- Light blue `#82C5FF`: functional labels
|
||||
- Green labels `#009800`: issues that can generally be ignored. For example,
|
||||
issues given the following labels normally can be closed immediately:
|
||||
- Support (see copy & paste response:
|
||||
[Support requests and configuration questions](#support-requests-and-configuration-questions)
|
||||
|
||||
## Be kind
|
||||
|
||||
Be kind to people trying to contribute. Be aware that people may be a non-native
|
||||
English speaker, they might not understand things or they might be very
|
||||
sensitive as to how you word things. Use Emoji to express your feelings (heart,
|
||||
star, smile, etc.). Some good tips about giving feedback to merge requests is in
|
||||
the [Thoughtbot code review guide].
|
||||
star, smile, etc.). Some good tips about code reviews can be found in our
|
||||
[Code Review Guidelines].
|
||||
|
||||
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
|
||||
|
||||
## Feature Freeze
|
||||
|
||||
5 working days before the 22nd the stable branches for the upcoming release will
|
||||
On the 7th of each month, the stable branches for the upcoming release will
|
||||
be frozen for major changes. Merge requests may still be merged into master
|
||||
during this period. By freezing the stable branches prior to a release there's
|
||||
no need to worry about last minute merge requests potentially breaking a lot of
|
||||
|
@ -120,10 +68,9 @@ things.
|
|||
What is considered to be a major change is determined on a case by case basis as
|
||||
this definition depends very much on the context of changes. For example, a 5
|
||||
line change might have a big impact on the entire application. Ultimately the
|
||||
decision will be made by those reviewing a merge request and the release
|
||||
manager.
|
||||
decision will be made by the maintainers and the release managers.
|
||||
|
||||
During the feature freeze all merge requests that are meant to go into the next
|
||||
During the feature freeze all merge requests that are meant to go into the upcoming
|
||||
release should have the correct milestone assigned _and_ have the label
|
||||
~"Pick into Stable" set. Merge requests without a milestone and this label will
|
||||
not be merged into any stable branches.
|
||||
|
@ -189,7 +136,6 @@ 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
|
||||
still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues).
|
||||
|
||||
[core-team]: https://about.gitlab.com/core-team/
|
||||
[team]: https://about.gitlab.com/team/
|
||||
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
|
||||
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* 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() {
|
||||
this.Admin = (function() {
|
||||
|
@ -42,10 +41,10 @@
|
|||
return $('.change-owner-link').show();
|
||||
});
|
||||
$('li.project_member').bind('ajax:success', function() {
|
||||
return Turbolinks.visit(location.href);
|
||||
return gl.utils.refreshCurrentPage();
|
||||
});
|
||||
$('li.group_member').bind('ajax:success', function() {
|
||||
return Turbolinks.visit(location.href);
|
||||
return gl.utils.refreshCurrentPage();
|
||||
});
|
||||
showBlacklistType = function() {
|
||||
if ($("input[name='blacklist_type']:checked").val() === 'file') {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* 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 Cookies */
|
||||
/* global Flash */
|
||||
|
@ -6,65 +6,60 @@
|
|||
/* global AwardsHandler */
|
||||
/* global Aside */
|
||||
|
||||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// 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
|
||||
// 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 jquery2 */
|
||||
/*= require jquery-ui/autocomplete */
|
||||
/*= require jquery-ui/datepicker */
|
||||
/*= require jquery-ui/draggable */
|
||||
/*= require jquery-ui/effect-highlight */
|
||||
/*= require jquery-ui/sortable */
|
||||
/*= require jquery_ujs */
|
||||
/*= require jquery.endless-scroll */
|
||||
/*= require jquery.highlight */
|
||||
/*= require jquery.waitforimages */
|
||||
/*= require jquery.atwho */
|
||||
/*= require jquery.scrollTo */
|
||||
/*= require jquery.turbolinks */
|
||||
/*= require js.cookie */
|
||||
/*= require turbolinks */
|
||||
/*= require autosave */
|
||||
/*= require bootstrap/affix */
|
||||
/*= require bootstrap/alert */
|
||||
/*= require bootstrap/button */
|
||||
/*= require bootstrap/collapse */
|
||||
/*= require bootstrap/dropdown */
|
||||
/*= require bootstrap/modal */
|
||||
/*= require bootstrap/scrollspy */
|
||||
/*= require bootstrap/tab */
|
||||
/*= require bootstrap/transition */
|
||||
/*= require bootstrap/tooltip */
|
||||
/*= require bootstrap/popover */
|
||||
/*= require select2 */
|
||||
/*= require underscore */
|
||||
/*= require dropzone */
|
||||
/*= require mousetrap */
|
||||
/*= require mousetrap/pause */
|
||||
/*= require shortcuts */
|
||||
/*= require shortcuts_navigation */
|
||||
/*= require shortcuts_dashboard_navigation */
|
||||
/*= require shortcuts_issuable */
|
||||
/*= require shortcuts_network */
|
||||
/*= require jquery.nicescroll */
|
||||
/*= require date.format */
|
||||
/*= require_directory ./behaviors */
|
||||
/*= require_directory ./blob */
|
||||
/*= require_directory ./templates */
|
||||
/*= require_directory ./commit */
|
||||
/*= require_directory ./extensions */
|
||||
/*= require_directory ./lib/utils */
|
||||
/*= require_directory ./u2f */
|
||||
/*= require_directory ./droplab */
|
||||
/*= require_directory . */
|
||||
/*= require fuzzaldrin-plus */
|
||||
/*= require es6-promise.auto */
|
||||
function requireAll(context) { return context.keys().map(context); }
|
||||
|
||||
window.$ = window.jQuery = require('jquery');
|
||||
require('jquery-ui/ui/autocomplete');
|
||||
require('jquery-ui/ui/datepicker');
|
||||
require('jquery-ui/ui/draggable');
|
||||
require('jquery-ui/ui/effect-highlight');
|
||||
require('jquery-ui/ui/sortable');
|
||||
require('jquery-ujs');
|
||||
require('vendor/jquery.endless-scroll');
|
||||
require('vendor/jquery.highlight');
|
||||
require('vendor/jquery.waitforimages');
|
||||
require('vendor/jquery.caret');
|
||||
require('vendor/jquery.atwho');
|
||||
require('vendor/jquery.scrollTo');
|
||||
window.Cookies = require('vendor/js.cookie');
|
||||
require('./autosave');
|
||||
require('bootstrap/js/affix');
|
||||
require('bootstrap/js/alert');
|
||||
require('bootstrap/js/button');
|
||||
require('bootstrap/js/collapse');
|
||||
require('bootstrap/js/dropdown');
|
||||
require('bootstrap/js/modal');
|
||||
require('bootstrap/js/scrollspy');
|
||||
require('bootstrap/js/tab');
|
||||
require('bootstrap/js/transition');
|
||||
require('bootstrap/js/tooltip');
|
||||
require('bootstrap/js/popover');
|
||||
require('select2/select2.js');
|
||||
window._ = require('underscore');
|
||||
window.Dropzone = require('dropzone');
|
||||
require('mousetrap');
|
||||
require('mousetrap/plugins/pause/mousetrap-pause');
|
||||
require('./shortcuts');
|
||||
require('./shortcuts_navigation');
|
||||
require('./shortcuts_dashboard_navigation');
|
||||
require('./shortcuts_issuable');
|
||||
require('./shortcuts_network');
|
||||
require('vendor/jquery.nicescroll');
|
||||
requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
|
||||
require('vendor/fuzzaldrin-plus');
|
||||
window.ES6Promise = require('vendor/es6-promise.auto');
|
||||
window.ES6Promise.polyfill();
|
||||
|
||||
(function () {
|
||||
document.addEventListener('page:fetch', function () {
|
||||
document.addEventListener('beforeunload', function () {
|
||||
// Unbind scroll events
|
||||
$(document).off('scroll');
|
||||
// Close any open tooltips
|
||||
|
@ -84,7 +79,6 @@
|
|||
var $sidebarGutterToggle = $('.js-sidebar-toggle');
|
||||
var $flash = $('.flash-container');
|
||||
var bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
var checkInitialSidebarSize;
|
||||
var fitSidebarForSize;
|
||||
|
||||
// Set the default path for all cookies to GitLab's root directory
|
||||
|
@ -246,19 +240,11 @@
|
|||
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
|
||||
}
|
||||
};
|
||||
checkInitialSidebarSize = function () {
|
||||
bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
if (bootstrapBreakpoint === 'xs' || 'sm') {
|
||||
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
|
||||
}
|
||||
};
|
||||
$window.off('resize.app').on('resize.app', function () {
|
||||
return fitSidebarForSize();
|
||||
});
|
||||
gl.awardsHandler = new AwardsHandler();
|
||||
checkInitialSidebarSize();
|
||||
new Aside();
|
||||
|
||||
// bind sidebar events
|
||||
new gl.Sidebar();
|
||||
});
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
/* 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 */
|
||||
|
||||
var emojiAliases = require('emoji-aliases');
|
||||
|
||||
(function() {
|
||||
this.AwardsHandler = (function() {
|
||||
var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
|
||||
function AwardsHandler() {
|
||||
this.aliases = gl.emojiAliases();
|
||||
this.aliases = emojiAliases;
|
||||
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
|
||||
return function(e) {
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
|
||||
/* global autosize */
|
||||
|
||||
/*= require jquery.ba-resize */
|
||||
/*= require autosize */
|
||||
var autosize = require('vendor/autosize');
|
||||
|
||||
(function() {
|
||||
$(function() {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
|
||||
// is submitted.
|
||||
//
|
||||
/*= require extensions/jquery */
|
||||
require('../extensions/jquery');
|
||||
|
||||
//
|
||||
// ### Example Markup
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
// 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.
|
||||
//
|
||||
/*= require extensions/jquery */
|
||||
require('../extensions/jquery');
|
||||
|
||||
//
|
||||
// ### Example Markup
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
|
||||
(function(w) {
|
||||
$(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.
|
||||
// Button does not change visibility. If button has icon - it changes chevron style.
|
||||
//
|
||||
|
@ -10,14 +23,7 @@
|
|||
//
|
||||
$('body').on('click', '.js-toggle-button', function(e) {
|
||||
e.preventDefault();
|
||||
$(this)
|
||||
.find('.fa')
|
||||
.toggleClass('fa-chevron-down fa-chevron-up')
|
||||
.end()
|
||||
.closest('.js-toggle-container')
|
||||
.find('.js-toggle-content')
|
||||
.toggle()
|
||||
;
|
||||
toggleContainer($(this).closest('.js-toggle-container'));
|
||||
});
|
||||
|
||||
// If we're accessing a permalink, ensure it is not inside a
|
||||
|
@ -26,8 +32,8 @@
|
|||
var anchor = hash && document.getElementById(hash);
|
||||
var container = anchor && $(anchor).closest('.js-toggle-container');
|
||||
|
||||
if (container && container.find('.js-toggle-content').is(':hidden')) {
|
||||
container.find('.js-toggle-button').trigger('click');
|
||||
if (container) {
|
||||
toggleContainer(container, true);
|
||||
anchor.scrollIntoView();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/* eslint-disable no-param-reassign, comma-dangle */
|
||||
/* global Api */
|
||||
|
||||
/*= require blob/template_selector */
|
||||
require('./template_selector');
|
||||
|
||||
((global) => {
|
||||
class BlobCiYamlSelector extends gl.TemplateSelector {
|
||||
requestFile(query) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* global Api */
|
||||
/*= require blob/template_selector */
|
||||
|
||||
require('./template_selector');
|
||||
|
||||
(() => {
|
||||
const global = window.gl || (window.gl = {});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */
|
||||
/* global Api */
|
||||
|
||||
/*= require blob/template_selector */
|
||||
require('./template_selector');
|
||||
|
||||
(function() {
|
||||
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */
|
||||
/* global Api */
|
||||
|
||||
/*= require blob/template_selector */
|
||||
require('./template_selector');
|
||||
|
||||
(function() {
|
||||
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* global EditBlob */
|
||||
/* global NewCommitForm */
|
||||
|
||||
/*= require_tree . */
|
||||
require('./edit_blob');
|
||||
|
||||
(function() {
|
||||
$(function() {
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
/* 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 BoardService */
|
||||
|
||||
//= require vue
|
||||
//= require vue-resource
|
||||
//= require Sortable
|
||||
//= require_tree ./models
|
||||
//= require_tree ./stores
|
||||
//= require_tree ./services
|
||||
//= require_tree ./mixins
|
||||
//= require_tree ./filters
|
||||
//= require ./components/board
|
||||
//= require ./components/board_sidebar
|
||||
//= require ./components/new_list_dropdown
|
||||
//= require ./vue_resource_interceptor
|
||||
function requireAll(context) { return context.keys().map(context); }
|
||||
|
||||
window.Vue = require('vue');
|
||||
window.Vue.use(require('vue-resource'));
|
||||
window.Sortable = require('vendor/Sortable');
|
||||
requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./mixins', true, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./filters', true, /^\.\/.*\.(js|es6)$/));
|
||||
require('./components/board');
|
||||
require('./components/board_sidebar');
|
||||
require('./components/new_list_dropdown');
|
||||
require('./components/modal/index');
|
||||
require('./vue_resource_interceptor');
|
||||
|
||||
$(() => {
|
||||
const $boardApp = document.getElementById('board-app');
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
|
||||
|
@ -31,7 +35,8 @@ $(() => {
|
|||
el: $boardApp,
|
||||
components: {
|
||||
'board': gl.issueBoards.Board,
|
||||
'board-sidebar': gl.issueBoards.BoardSidebar
|
||||
'board-sidebar': gl.issueBoards.BoardSidebar,
|
||||
'board-add-issues-modal': gl.issueBoards.IssuesModal,
|
||||
},
|
||||
data: {
|
||||
state: Store.state,
|
||||
|
@ -40,6 +45,8 @@ $(() => {
|
|||
boardId: $boardApp.dataset.boardId,
|
||||
disabled: $boardApp.dataset.disabled === 'true',
|
||||
issueLinkBase: $boardApp.dataset.issueLinkBase,
|
||||
rootPath: $boardApp.dataset.rootPath,
|
||||
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
|
||||
detailIssue: Store.detail
|
||||
},
|
||||
computed: {
|
||||
|
@ -48,7 +55,7 @@ $(() => {
|
|||
},
|
||||
},
|
||||
created () {
|
||||
gl.boardService = new BoardService(this.endpoint, this.boardId);
|
||||
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
|
||||
},
|
||||
mounted () {
|
||||
Store.disabled = this.disabled;
|
||||
|
@ -59,8 +66,6 @@ $(() => {
|
|||
|
||||
if (list.type === 'done') {
|
||||
list.position = Infinity;
|
||||
} else if (list.type === 'backlog') {
|
||||
list.position = -1;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -73,7 +78,7 @@ $(() => {
|
|||
});
|
||||
|
||||
gl.IssueBoardsSearch = new Vue({
|
||||
el: '#js-boards-search',
|
||||
el: document.getElementById('js-boards-search'),
|
||||
data: {
|
||||
filters: Store.state.filters
|
||||
},
|
||||
|
@ -81,4 +86,27 @@ $(() => {
|
|||
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 @@
|
|||
/* global Vue */
|
||||
/* global Sortable */
|
||||
|
||||
//= require ./board_blank_state
|
||||
//= require ./board_delete
|
||||
//= require ./board_list
|
||||
require('./board_blank_state');
|
||||
require('./board_delete');
|
||||
require('./board_list');
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
@ -22,7 +22,8 @@
|
|||
props: {
|
||||
list: Object,
|
||||
disabled: Boolean,
|
||||
issueLinkBase: String
|
||||
issueLinkBase: String,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
|
||||
/* global Vue */
|
||||
|
||||
require('./issue_card_inner');
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
|
@ -9,12 +11,16 @@
|
|||
|
||||
gl.issueBoards.BoardCard = Vue.extend({
|
||||
template: '#js-board-list-card',
|
||||
components: {
|
||||
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||
},
|
||||
props: {
|
||||
list: Object,
|
||||
issue: Object,
|
||||
issueLinkBase: String,
|
||||
disabled: Boolean,
|
||||
index: Number
|
||||
index: Number,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -28,31 +34,6 @@
|
|||
}
|
||||
},
|
||||
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 () {
|
||||
this.showDetail = true;
|
||||
},
|
||||
|
@ -71,6 +52,7 @@
|
|||
Store.detail.issue = {};
|
||||
} else {
|
||||
Store.detail.issue = this.issue;
|
||||
Store.detail.list = this.list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
/* global Vue */
|
||||
/* global Sortable */
|
||||
|
||||
//= require ./board_card
|
||||
//= require ./board_new_issue
|
||||
require('./board_card');
|
||||
require('./board_new_issue');
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
@ -23,6 +23,7 @@
|
|||
issues: Array,
|
||||
loading: Boolean,
|
||||
issueLinkBase: String,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
$(this.$refs.submitButton).enable();
|
||||
|
||||
Store.detail.issue = issue;
|
||||
Store.detail.list = this.list;
|
||||
})
|
||||
.catch(() => {
|
||||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
/* global LabelsSelect */
|
||||
/* global Sidebar */
|
||||
|
||||
require('./sidebar/remove_issue');
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
|
@ -18,7 +20,8 @@
|
|||
data() {
|
||||
return {
|
||||
detail: Store.detail,
|
||||
issue: {}
|
||||
issue: {},
|
||||
list: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -29,7 +32,14 @@
|
|||
watch: {
|
||||
detail: {
|
||||
handler () {
|
||||
if (this.issue.id !== this.detail.issue.id) {
|
||||
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
|
||||
$(el).data('glDropdown').clearMenu();
|
||||
});
|
||||
}
|
||||
|
||||
this.issue = this.detail.issue;
|
||||
this.list = this.detail.list;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
@ -54,6 +64,9 @@
|
|||
new LabelsSelect();
|
||||
new Sidebar();
|
||||
gl.Subscription.bindAll('.subscription');
|
||||
}
|
||||
},
|
||||
components: {
|
||||
removeBtn: gl.issueBoards.RemoveIssueBtn,
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
|
111
app/assets/javascripts/boards/components/issue_card_inner.js.es6
Normal file
111
app/assets/javascripts/boards/components/issue_card_inner.js.es6
Normal file
|
@ -0,0 +1,111 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,70 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
||||
})();
|
83
app/assets/javascripts/boards/components/modal/footer.js.es6
Normal file
83
app/assets/javascripts/boards/components/modal/footer.js.es6
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
||||
})();
|
70
app/assets/javascripts/boards/components/modal/header.js.es6
Normal file
70
app/assets/javascripts/boards/components/modal/header.js.es6
Normal file
|
@ -0,0 +1,70 @@
|
|||
/* global Vue */
|
||||
|
||||
require('./tabs');
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalHeader = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
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,
|
||||
},
|
||||
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">
|
||||
<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>
|
||||
`,
|
||||
});
|
||||
})();
|
136
app/assets/javascripts/boards/components/modal/index.js.es6
Normal file
136
app/assets/javascripts/boards/components/modal/index.js.es6
Normal file
|
@ -0,0 +1,136 @@
|
|||
/* 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,
|
||||
},
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
searchOperation: _.debounce(function searchOperationDebounce() {
|
||||
this.loadIssues(true);
|
||||
}, 500),
|
||||
loadIssues(clearIssues = false) {
|
||||
return gl.boardService.getBacklog({
|
||||
search: this.searchTerm,
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
}).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></modal-header>
|
||||
<modal-list
|
||||
: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>
|
||||
`,
|
||||
});
|
||||
})();
|
142
app/assets/javascripts/boards/components/modal/list.js.es6
Normal file
142
app/assets/javascripts/boards/components/modal/list.js.es6
Normal file
|
@ -0,0 +1,142 @@
|
|||
/* 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,
|
||||
},
|
||||
},
|
||||
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
|
||||
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>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,56 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
||||
})();
|
47
app/assets/javascripts/boards/components/modal/tabs.js.es6
Normal file
47
app/assets/javascripts/boards/components/modal/tabs.js.es6
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,59 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
||||
})();
|
14
app/assets/javascripts/boards/mixins/modal_mixins.js.es6
Normal file
14
app/assets/javascripts/boards/mixins/modal_mixins.js.es6
Normal file
|
@ -0,0 +1,14 @@
|
|||
(() => {
|
||||
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 @@
|
|||
|
||||
class ListIssue {
|
||||
constructor (obj) {
|
||||
this.globalId = obj.id;
|
||||
this.id = obj.iid;
|
||||
this.title = obj.title;
|
||||
this.confidential = obj.confidential;
|
||||
this.dueDate = obj.due_date;
|
||||
this.subscribed = obj.subscribed;
|
||||
this.labels = [];
|
||||
this.selected = false;
|
||||
this.assignee = false;
|
||||
|
||||
if (obj.assignee) {
|
||||
this.assignee = new ListUser(obj.assignee);
|
||||
|
|
|
@ -9,7 +9,7 @@ class List {
|
|||
this.position = obj.position;
|
||||
this.title = obj.title;
|
||||
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.page = 1;
|
||||
this.loading = true;
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
/* global Vue */
|
||||
|
||||
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}`, {}, {
|
||||
generate: {
|
||||
method: 'POST',
|
||||
|
@ -10,7 +16,12 @@ class BoardService {
|
|||
}
|
||||
});
|
||||
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) => {
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
|
@ -65,6 +76,20 @@ class BoardService {
|
|||
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;
|
||||
|
|
|
@ -34,15 +34,10 @@
|
|||
},
|
||||
new (listObj) {
|
||||
const list = this.addList(listObj);
|
||||
const backlogList = this.findList('type', 'backlog', 'backlog');
|
||||
|
||||
list
|
||||
.save()
|
||||
.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.removeBlankState();
|
||||
|
@ -52,7 +47,7 @@
|
|||
},
|
||||
shouldAddBlankState () {
|
||||
// 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 () {
|
||||
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
|
||||
|
@ -102,7 +97,7 @@
|
|||
listTo.addIssue(issue, listFrom, newIndex);
|
||||
}
|
||||
|
||||
if (listTo.type === 'done' && listFrom.type !== 'backlog') {
|
||||
if (listTo.type === 'done') {
|
||||
issueLists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
|
|
96
app/assets/javascripts/boards/stores/modal_store.js.es6
Normal file
96
app/assets/javascripts/boards/stores/modal_store.js.es6
Normal file
|
@ -0,0 +1,96 @@
|
|||
(() => {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
})();
|
|
@ -43,6 +43,7 @@
|
|||
BreakpointInstance.prototype.getBreakpointSize = function() {
|
||||
var $visibleDevice;
|
||||
$visibleDevice = this.visibleDevice;
|
||||
// TODO: Consider refactoring in light of turbolinks removal.
|
||||
// the page refreshed via turbolinks
|
||||
if (!$visibleDevice().length) {
|
||||
this.setup();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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 Turbolinks */
|
||||
|
||||
(function() {
|
||||
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
|
||||
|
@ -127,7 +126,7 @@
|
|||
pageUrl += DOWN_BUILD_TRACE;
|
||||
}
|
||||
|
||||
return Turbolinks.visit(pageUrl);
|
||||
return gl.utils.visitUrl(pageUrl);
|
||||
}
|
||||
};
|
||||
})(this)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
(function() {
|
||||
this.CommitsList = (function() {
|
||||
function CommitsList() {}
|
||||
var CommitsList = {};
|
||||
|
||||
CommitsList.timer = null;
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
|||
});
|
||||
this.content = $("#commits-list");
|
||||
this.searchField = $("#commits-search");
|
||||
this.lastSearch = this.searchField.val();
|
||||
return this.initSearch();
|
||||
};
|
||||
|
||||
|
@ -37,6 +38,7 @@
|
|||
var commitsUrl, form, search;
|
||||
form = $(".commits-search-form");
|
||||
search = CommitsList.searchField.val();
|
||||
if (search === CommitsList.lastSearch) return;
|
||||
commitsUrl = form.attr("action") + '?' + form.serialize();
|
||||
CommitsList.content.fadeTo('fast', 0.5);
|
||||
return $.ajax({
|
||||
|
@ -47,12 +49,16 @@
|
|||
return CommitsList.content.fadeTo('fast', 1.0);
|
||||
},
|
||||
success: function(data) {
|
||||
CommitsList.lastSearch = search;
|
||||
CommitsList.content.html(data.html);
|
||||
return history.replaceState({
|
||||
page: commitsUrl
|
||||
// Change url so if user reload a page - search results are saved
|
||||
}, document.title, commitsUrl);
|
||||
},
|
||||
error: function() {
|
||||
CommitsList.lastSearch = null;
|
||||
},
|
||||
dataType: "json"
|
||||
});
|
||||
};
|
||||
|
|
355
app/assets/javascripts/copy_as_gfm.js.es6
Normal file
355
app/assets/javascripts/copy_as_gfm.js.es6
Normal file
|
@ -0,0 +1,355 @@
|
|||
/* 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 */
|
||||
|
||||
require('./lib/utils/common_utils');
|
||||
|
||||
(() => {
|
||||
const gfmRules = {
|
||||
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
|
||||
// GitLab Flavored Markdown (GFM) to HTML.
|
||||
// These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
|
||||
// Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
|
||||
// from GFM should have a handler here, in reverse order.
|
||||
// The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
|
||||
InlineDiffFilter: {
|
||||
'span.idiff.addition'(el, text) {
|
||||
return `{+${text}+}`;
|
||||
},
|
||||
'span.idiff.deletion'(el, text) {
|
||||
return `{-${text}-}`;
|
||||
},
|
||||
},
|
||||
TaskListFilter: {
|
||||
'input[type=checkbox].task-list-item-checkbox'(el, text) {
|
||||
return `[${el.checked ? 'x' : ' '}]`;
|
||||
},
|
||||
},
|
||||
ReferenceFilter: {
|
||||
'a.gfm:not([data-link=true])'(el, text) {
|
||||
return el.dataset.original || text;
|
||||
},
|
||||
},
|
||||
AutolinkFilter: {
|
||||
'a'(el, text) {
|
||||
// Fallback on the regular MarkdownFilter's `a` handler.
|
||||
if (text !== el.getAttribute('href')) return false;
|
||||
|
||||
return text;
|
||||
},
|
||||
},
|
||||
TableOfContentsFilter: {
|
||||
'ul.section-nav'(el, text) {
|
||||
return '[[_TOC_]]';
|
||||
},
|
||||
},
|
||||
EmojiFilter: {
|
||||
'img.emoji'(el, text) {
|
||||
return el.getAttribute('alt');
|
||||
},
|
||||
},
|
||||
ImageLinkFilter: {
|
||||
'a.no-attachment-icon'(el, text) {
|
||||
return text;
|
||||
},
|
||||
},
|
||||
VideoLinkFilter: {
|
||||
'.video-container'(el, text) {
|
||||
const videoEl = el.querySelector('video');
|
||||
if (!videoEl) return false;
|
||||
|
||||
return CopyAsGFM.nodeToGFM(videoEl);
|
||||
},
|
||||
'video'(el, text) {
|
||||
return `![${el.dataset.title}](${el.getAttribute('src')})`;
|
||||
},
|
||||
},
|
||||
MathFilter: {
|
||||
'pre.code.math[data-math-style=display]'(el, text) {
|
||||
return `\`\`\`math\n${text.trim()}\n\`\`\``;
|
||||
},
|
||||
'code.code.math[data-math-style=inline]'(el, text) {
|
||||
return `$\`${text}\`$`;
|
||||
},
|
||||
'span.katex-display span.katex-mathml'(el, text) {
|
||||
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
|
||||
if (!mathAnnotation) return false;
|
||||
|
||||
return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
|
||||
},
|
||||
'span.katex-mathml'(el, text) {
|
||||
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
|
||||
if (!mathAnnotation) return false;
|
||||
|
||||
return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
|
||||
},
|
||||
'span.katex-html'(el, text) {
|
||||
// We don't want to include the content of this element in the copied text.
|
||||
return '';
|
||||
},
|
||||
'annotation[encoding="application/x-tex"]'(el, text) {
|
||||
return text.trim();
|
||||
},
|
||||
},
|
||||
SanitizationFilter: {
|
||||
'dl'(el, text) {
|
||||
let lines = text.trim().split('\n');
|
||||
// Add two spaces to the front of subsequent list items lines,
|
||||
// or leave the line entirely blank.
|
||||
lines = lines.map((l) => {
|
||||
const line = l.trim();
|
||||
if (line.length === 0) return '';
|
||||
|
||||
return ` ${line}`;
|
||||
});
|
||||
|
||||
return `<dl>\n${lines.join('\n')}\n</dl>`;
|
||||
},
|
||||
'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr'(el, text) {
|
||||
const tag = el.nodeName.toLowerCase();
|
||||
return `<${tag}>${text}</${tag}>`;
|
||||
},
|
||||
},
|
||||
SyntaxHighlightFilter: {
|
||||
'pre.code.highlight'(el, t) {
|
||||
const text = t.trim();
|
||||
|
||||
let lang = el.getAttribute('lang');
|
||||
if (lang === 'plaintext') {
|
||||
lang = '';
|
||||
}
|
||||
|
||||
// Prefixes lines with 4 spaces if the code contains triple backticks
|
||||
if (lang === '' && text.match(/^```/gm)) {
|
||||
return text.split('\n').map((l) => {
|
||||
const line = l.trim();
|
||||
if (line.length === 0) return '';
|
||||
|
||||
return ` ${line}`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
return `\`\`\`${lang}\n${text}\n\`\`\``;
|
||||
},
|
||||
'pre > code'(el, text) {
|
||||
// Don't wrap code blocks in ``
|
||||
return text;
|
||||
},
|
||||
},
|
||||
MarkdownFilter: {
|
||||
'br'(el, text) {
|
||||
// Two spaces at the end of a line are turned into a BR
|
||||
return ' ';
|
||||
},
|
||||
'code'(el, text) {
|
||||
let backtickCount = 1;
|
||||
const backtickMatch = text.match(/`+/);
|
||||
if (backtickMatch) {
|
||||
backtickCount = backtickMatch[0].length + 1;
|
||||
}
|
||||
|
||||
const backticks = Array(backtickCount + 1).join('`');
|
||||
const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
|
||||
|
||||
return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks;
|
||||
},
|
||||
'blockquote'(el, text) {
|
||||
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
|
||||
},
|
||||
'img'(el, text) {
|
||||
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
|
||||
},
|
||||
'a.anchor'(el, text) {
|
||||
// Don't render a Markdown link for the anchor link inside a heading
|
||||
return text;
|
||||
},
|
||||
'a'(el, text) {
|
||||
return `[${text}](${el.getAttribute('href')})`;
|
||||
},
|
||||
'li'(el, text) {
|
||||
const lines = text.trim().split('\n');
|
||||
const firstLine = `- ${lines.shift()}`;
|
||||
// Add four spaces to the front of subsequent list items lines,
|
||||
// or leave the line entirely blank.
|
||||
const nextLines = lines.map((s) => {
|
||||
if (s.trim().length === 0) return '';
|
||||
|
||||
return ` ${s}`;
|
||||
});
|
||||
|
||||
return `${firstLine}\n${nextLines.join('\n')}`;
|
||||
},
|
||||
'ul'(el, text) {
|
||||
return text;
|
||||
},
|
||||
'ol'(el, text) {
|
||||
// LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
|
||||
return text.replace(/^- /mg, '1. ');
|
||||
},
|
||||
'h1'(el, text) {
|
||||
return `# ${text.trim()}`;
|
||||
},
|
||||
'h2'(el, text) {
|
||||
return `## ${text.trim()}`;
|
||||
},
|
||||
'h3'(el, text) {
|
||||
return `### ${text.trim()}`;
|
||||
},
|
||||
'h4'(el, text) {
|
||||
return `#### ${text.trim()}`;
|
||||
},
|
||||
'h5'(el, text) {
|
||||
return `##### ${text.trim()}`;
|
||||
},
|
||||
'h6'(el, text) {
|
||||
return `###### ${text.trim()}`;
|
||||
},
|
||||
'strong'(el, text) {
|
||||
return `**${text}**`;
|
||||
},
|
||||
'em'(el, text) {
|
||||
return `_${text}_`;
|
||||
},
|
||||
'del'(el, text) {
|
||||
return `~~${text}~~`;
|
||||
},
|
||||
'sup'(el, text) {
|
||||
return `^${text}`;
|
||||
},
|
||||
'hr'(el, text) {
|
||||
return '-----';
|
||||
},
|
||||
'table'(el, text) {
|
||||
const theadEl = el.querySelector('thead');
|
||||
const tbodyEl = el.querySelector('tbody');
|
||||
if (!theadEl || !tbodyEl) return false;
|
||||
|
||||
const theadText = CopyAsGFM.nodeToGFM(theadEl);
|
||||
const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
|
||||
|
||||
return theadText + tbodyText;
|
||||
},
|
||||
'thead'(el, text) {
|
||||
const cells = _.map(el.querySelectorAll('th'), (cell) => {
|
||||
let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2;
|
||||
|
||||
let before = '';
|
||||
let after = '';
|
||||
switch (cell.style.textAlign) {
|
||||
case 'center':
|
||||
before = ':';
|
||||
after = ':';
|
||||
chars -= 2;
|
||||
break;
|
||||
case 'right':
|
||||
after = ':';
|
||||
chars -= 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
chars = Math.max(chars, 3);
|
||||
|
||||
const middle = Array(chars + 1).join('-');
|
||||
|
||||
return before + middle + after;
|
||||
});
|
||||
|
||||
return `${text}|${cells.join('|')}|`;
|
||||
},
|
||||
'tr'(el, text) {
|
||||
const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim());
|
||||
return `| ${cells.join(' | ')} |`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
class CopyAsGFM {
|
||||
constructor() {
|
||||
$(document).on('copy', '.md, .wiki', this.handleCopy);
|
||||
$(document).on('paste', '.js-gfm-input', this.handlePaste);
|
||||
}
|
||||
|
||||
handleCopy(e) {
|
||||
const clipboardData = e.originalEvent.clipboardData;
|
||||
if (!clipboardData) return;
|
||||
|
||||
const documentFragment = window.gl.utils.getSelectedFragment();
|
||||
if (!documentFragment) return;
|
||||
|
||||
// If the documentFragment contains more than just Markdown, don't copy as GFM.
|
||||
if (documentFragment.querySelector('.md, .wiki')) return;
|
||||
|
||||
e.preventDefault();
|
||||
clipboardData.setData('text/plain', documentFragment.textContent);
|
||||
|
||||
const gfm = CopyAsGFM.nodeToGFM(documentFragment);
|
||||
clipboardData.setData('text/x-gfm', gfm);
|
||||
}
|
||||
|
||||
handlePaste(e) {
|
||||
const clipboardData = e.originalEvent.clipboardData;
|
||||
if (!clipboardData) return;
|
||||
|
||||
const gfm = clipboardData.getData('text/x-gfm');
|
||||
if (!gfm) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
window.gl.utils.insertText(e.target, gfm);
|
||||
}
|
||||
|
||||
static nodeToGFM(node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
const text = this.innerGFM(node);
|
||||
|
||||
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
||||
return text;
|
||||
}
|
||||
|
||||
for (const filter in gfmRules) {
|
||||
const rules = gfmRules[filter];
|
||||
|
||||
for (const selector in rules) {
|
||||
const func = rules[selector];
|
||||
|
||||
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
|
||||
|
||||
const result = func(node, text);
|
||||
if (result === false) continue;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
static innerGFM(parentNode) {
|
||||
const nodes = parentNode.childNodes;
|
||||
|
||||
const clonedParentNode = parentNode.cloneNode(true);
|
||||
const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
|
||||
|
||||
for (let i = 0; i < nodes.length; i += 1) {
|
||||
const node = nodes[i];
|
||||
const clonedNode = clonedNodes[i];
|
||||
|
||||
const text = this.nodeToGFM(node);
|
||||
|
||||
// `clonedNode.replaceWith(text)` is not yet widely supported
|
||||
clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
|
||||
}
|
||||
|
||||
return clonedParentNode.innerText || clonedParentNode.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.CopyAsGFM = CopyAsGFM;
|
||||
|
||||
new CopyAsGFM();
|
||||
})();
|
|
@ -1,7 +1,7 @@
|
|||
/* 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 */
|
||||
|
||||
/*= require clipboard */
|
||||
window.Clipboard = require('vendor/clipboard');
|
||||
|
||||
(function() {
|
||||
var genericError, genericSuccess, showTooltip;
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
/* global Cookies */
|
||||
/* global Flash */
|
||||
|
||||
//= require vue
|
||||
//= require_tree ./svg
|
||||
//= require_tree .
|
||||
window.Vue = require('vue');
|
||||
window.Cookies = require('vendor/js.cookie');
|
||||
|
||||
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';
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
require('./lib/utils/url_utility');
|
||||
|
||||
(() => {
|
||||
const UNFOLD_COUNT = 20;
|
||||
let isBound = false;
|
||||
|
||||
class Diff {
|
||||
constructor() {
|
||||
|
@ -15,10 +18,12 @@
|
|||
$('.content-wrapper .container-fluid').removeClass('container-limited');
|
||||
}
|
||||
|
||||
$(document)
|
||||
.off('click', '.js-unfold, .diff-line-num a')
|
||||
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
|
||||
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
|
||||
if (!isBound) {
|
||||
$(document)
|
||||
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
|
||||
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
|
||||
isBound = true;
|
||||
}
|
||||
|
||||
this.openAnchoredDiff();
|
||||
}
|
||||
|
@ -104,11 +109,11 @@
|
|||
}
|
||||
|
||||
highlighSelectedLine() {
|
||||
const hash = gl.utils.getLocationHash();
|
||||
const $diffFiles = $('.diff-file');
|
||||
$diffFiles.find('.hll').removeClass('hll');
|
||||
|
||||
if (window.location.hash !== '') {
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
if (hash) {
|
||||
$diffFiles
|
||||
.find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
|
||||
.addClass('hll');
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
/* 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 ResolveCount */
|
||||
|
||||
//= require_directory ./models
|
||||
//= require_directory ./stores
|
||||
//= require_directory ./services
|
||||
//= require_directory ./mixins
|
||||
//= require_directory ./components
|
||||
function requireAll(context) { return context.keys().map(context); }
|
||||
requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/));
|
||||
requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/));
|
||||
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';
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
/* global ShortcutsIssuable */
|
||||
/* global ZenMode */
|
||||
/* global Milestone */
|
||||
/* global GLForm */
|
||||
/* global IssuableForm */
|
||||
/* global LabelsSelect */
|
||||
/* global MilestoneSelect */
|
||||
|
@ -64,17 +63,6 @@
|
|||
new UsernameValidator();
|
||||
new ActiveTabMemoizer();
|
||||
break;
|
||||
case 'sessions:create':
|
||||
if (!gon.u2f) break;
|
||||
window.gl.u2fAuthenticate = new gl.U2FAuthenticate(
|
||||
$("#js-authenticate-u2f"),
|
||||
'#js-login-u2f-form',
|
||||
gon.u2f,
|
||||
document.querySelector('#js-login-2fa-device'),
|
||||
document.querySelector('.js-2fa-form'),
|
||||
);
|
||||
window.gl.u2fAuthenticate.start();
|
||||
break;
|
||||
case 'projects:boards:show':
|
||||
case 'projects:boards:index':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
@ -110,7 +98,7 @@
|
|||
case 'projects:milestones:edit':
|
||||
new ZenMode();
|
||||
new gl.DueDateSelectors();
|
||||
new GLForm($('.milestone-form'));
|
||||
new gl.GLForm($('.milestone-form'));
|
||||
break;
|
||||
case 'groups:milestones:new':
|
||||
new ZenMode();
|
||||
|
@ -121,7 +109,7 @@
|
|||
case 'projects:issues:new':
|
||||
case 'projects:issues:edit':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.issue-form'));
|
||||
new gl.GLForm($('.issue-form'));
|
||||
new IssuableForm($('.issue-form'));
|
||||
new LabelsSelect();
|
||||
new MilestoneSelect();
|
||||
|
@ -131,7 +119,7 @@
|
|||
case 'projects:merge_requests:edit':
|
||||
new gl.Diff();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.merge-request-form'));
|
||||
new gl.GLForm($('.merge-request-form'));
|
||||
new IssuableForm($('.merge-request-form'));
|
||||
new LabelsSelect();
|
||||
new MilestoneSelect();
|
||||
|
@ -139,11 +127,11 @@
|
|||
break;
|
||||
case 'projects:tags:new':
|
||||
new ZenMode();
|
||||
new GLForm($('.tag-form'));
|
||||
new gl.GLForm($('.tag-form'));
|
||||
break;
|
||||
case 'projects:releases:edit':
|
||||
new ZenMode();
|
||||
new GLForm($('.release-form'));
|
||||
new gl.GLForm($('.release-form'));
|
||||
break;
|
||||
case 'projects:merge_requests:show':
|
||||
new gl.Diff();
|
||||
|
@ -261,6 +249,9 @@
|
|||
case 'projects:artifacts:browse':
|
||||
new BuildArtifacts();
|
||||
break;
|
||||
case 'help:index':
|
||||
gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
|
||||
break;
|
||||
case 'search:show':
|
||||
new Search();
|
||||
break;
|
||||
|
@ -277,6 +268,17 @@
|
|||
break;
|
||||
}
|
||||
switch (path.first()) {
|
||||
case 'sessions':
|
||||
case 'omniauth_callbacks':
|
||||
if (!gon.u2f) break;
|
||||
gl.u2fAuthenticate = new gl.U2FAuthenticate(
|
||||
$('#js-authenticate-u2f'),
|
||||
'#js-login-u2f-form',
|
||||
gon.u2f,
|
||||
document.querySelector('#js-login-2fa-device'),
|
||||
document.querySelector('.js-2fa-form'),
|
||||
);
|
||||
gl.u2fAuthenticate.start();
|
||||
case 'admin':
|
||||
new Admin();
|
||||
switch (path[1]) {
|
||||
|
@ -329,7 +331,7 @@
|
|||
new gl.Wikis();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new ZenMode();
|
||||
new GLForm($('.wiki-form'));
|
||||
new gl.GLForm($('.wiki-form'));
|
||||
break;
|
||||
case 'snippets':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
@ -354,7 +356,7 @@
|
|||
}
|
||||
// If we haven't installed a custom shortcut handler, install the default one
|
||||
if (!shortcut_handler) {
|
||||
return new Shortcuts();
|
||||
new Shortcuts();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill');
|
|||
var utils = require('./utils');
|
||||
|
||||
var DropDown = function(list) {
|
||||
this.currentIndex = 0;
|
||||
this.hidden = true;
|
||||
this.list = list;
|
||||
this.items = [];
|
||||
|
@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, {
|
|||
},
|
||||
|
||||
show: function() {
|
||||
// debugger
|
||||
this.list.style.display = 'block';
|
||||
this.hidden = false;
|
||||
if (this.hidden) {
|
||||
// debugger
|
||||
this.list.style.display = 'block';
|
||||
this.currentIndex = 0;
|
||||
this.hidden = false;
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
// debugger
|
||||
this.list.style.display = 'none';
|
||||
this.hidden = true;
|
||||
if (!this.hidden) {
|
||||
// debugger
|
||||
this.list.style.display = 'none';
|
||||
this.currentIndex = 0;
|
||||
this.hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
|
@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, {
|
|||
this.input = function input(e) {
|
||||
if(self.hasRemovedEvents) return;
|
||||
|
||||
self.list.show();
|
||||
|
||||
var inputEvent = new CustomEvent('input.dl', {
|
||||
detail: {
|
||||
hook: self,
|
||||
|
@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, {
|
|||
cancelable: true
|
||||
});
|
||||
e.target.dispatchEvent(inputEvent);
|
||||
self.list.show();
|
||||
}
|
||||
|
||||
this.keyup = function keyup(e) {
|
||||
|
@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, {
|
|||
}
|
||||
|
||||
function keyEvent(e, keyEventName){
|
||||
self.list.show();
|
||||
|
||||
var keyEvent = new CustomEvent(keyEventName, {
|
||||
detail: {
|
||||
hook: self,
|
||||
|
@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, {
|
|||
cancelable: true
|
||||
});
|
||||
e.target.dispatchEvent(keyEvent);
|
||||
self.list.show();
|
||||
}
|
||||
|
||||
this.events = this.events || {};
|
||||
|
@ -572,24 +581,43 @@ require('./window')(function(w){
|
|||
module.exports = function(){
|
||||
var currentKey;
|
||||
var currentFocus;
|
||||
var currentIndex = 0;
|
||||
var isUpArrow = false;
|
||||
var isDownArrow = false;
|
||||
var removeHighlight = function removeHighlight(list) {
|
||||
var listItems = list.list.querySelectorAll('li');
|
||||
var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
|
||||
var listItemsTmp = [];
|
||||
for(var i = 0; i < listItems.length; i++) {
|
||||
listItems[i].classList.remove('dropdown-active');
|
||||
var listItem = listItems[i];
|
||||
listItem.classList.remove('dropdown-active');
|
||||
|
||||
if (listItem.style.display !== 'none') {
|
||||
listItemsTmp.push(listItem);
|
||||
}
|
||||
}
|
||||
return listItems;
|
||||
return listItemsTmp;
|
||||
};
|
||||
|
||||
var setMenuForArrows = function setMenuForArrows(list) {
|
||||
var listItems = removeHighlight(list);
|
||||
if(currentIndex>0){
|
||||
if(!listItems[currentIndex-1]){
|
||||
currentIndex = currentIndex-1;
|
||||
if(list.currentIndex>0){
|
||||
if(!listItems[list.currentIndex-1]){
|
||||
list.currentIndex = list.currentIndex-1;
|
||||
}
|
||||
|
||||
if (listItems[list.currentIndex-1]) {
|
||||
var el = listItems[list.currentIndex-1];
|
||||
var filterDropdownEl = el.closest('.filter-dropdown');
|
||||
el.classList.add('dropdown-active');
|
||||
|
||||
if (filterDropdownEl) {
|
||||
var filterDropdownBottom = filterDropdownEl.offsetHeight;
|
||||
var elOffsetTop = el.offsetTop - 30;
|
||||
|
||||
if (elOffsetTop > filterDropdownBottom) {
|
||||
filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
listItems[currentIndex-1].classList.add('dropdown-active');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -597,13 +625,13 @@ require('./window')(function(w){
|
|||
var list = e.detail.hook.list;
|
||||
removeHighlight(list);
|
||||
list.show();
|
||||
currentIndex = 0;
|
||||
list.currentIndex = 0;
|
||||
isUpArrow = false;
|
||||
isDownArrow = false;
|
||||
};
|
||||
var selectItem = function selectItem(list) {
|
||||
var listItems = removeHighlight(list);
|
||||
var currentItem = listItems[currentIndex-1];
|
||||
var currentItem = listItems[list.currentIndex-1];
|
||||
var listEvent = new CustomEvent('click.dl', {
|
||||
detail: {
|
||||
list: list,
|
||||
|
@ -617,6 +645,8 @@ require('./window')(function(w){
|
|||
|
||||
var keydown = function keydown(e){
|
||||
var typedOn = e.target;
|
||||
var list = e.detail.hook.list;
|
||||
var currentIndex = list.currentIndex;
|
||||
isUpArrow = false;
|
||||
isDownArrow = false;
|
||||
|
||||
|
@ -648,6 +678,7 @@ require('./window')(function(w){
|
|||
if(isUpArrow){ currentIndex--; }
|
||||
if(isDownArrow){ currentIndex++; }
|
||||
if(currentIndex < 0){ currentIndex = 0; }
|
||||
list.currentIndex = currentIndex;
|
||||
setMenuForArrows(e.detail.hook.list);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ require('../window')(function(w){
|
|||
|
||||
w.droplabAjax = {
|
||||
_loadUrlData: function _loadUrlData(url) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open('GET', url, true);
|
||||
|
@ -16,6 +17,7 @@ require('../window')(function(w){
|
|||
if(xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
self.cache[url] = data;
|
||||
return resolve(data);
|
||||
} else {
|
||||
return reject([xhr.responseText, xhr.status]);
|
||||
|
@ -26,9 +28,23 @@ require('../window')(function(w){
|
|||
});
|
||||
},
|
||||
|
||||
_loadData: function _loadData(data, config, self) {
|
||||
if (config.loadingTemplate) {
|
||||
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
|
||||
|
||||
if (dataLoadingTemplate) {
|
||||
dataLoadingTemplate.outerHTML = self.listTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
self.hook.list[config.method].call(self.hook.list, data);
|
||||
},
|
||||
|
||||
init: function init(hook) {
|
||||
var self = this;
|
||||
self.cache = self.cache || {};
|
||||
var config = hook.config.droplabAjax;
|
||||
this.hook = hook;
|
||||
|
||||
if (!config || !config.endpoint || !config.method) {
|
||||
return;
|
||||
|
@ -49,22 +65,23 @@ require('../window')(function(w){
|
|||
dynamicList.outerHTML = loadingTemplate.outerHTML;
|
||||
}
|
||||
|
||||
this._loadUrlData(config.endpoint)
|
||||
.then(function(d) {
|
||||
if (config.loadingTemplate) {
|
||||
var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]');
|
||||
|
||||
if (dataLoadingTemplate) {
|
||||
dataLoadingTemplate.outerHTML = self.listTemplate;
|
||||
}
|
||||
}
|
||||
hook.list[config.method].call(hook.list, d);
|
||||
}).catch(function(e) {
|
||||
throw new droplabAjaxException(e.message || e);
|
||||
});
|
||||
if (self.cache[config.endpoint]) {
|
||||
self._loadData(self.cache[config.endpoint], config, self);
|
||||
} else {
|
||||
this._loadUrlData(config.endpoint)
|
||||
.then(function(d) {
|
||||
self._loadData(d, config, self);
|
||||
}).catch(function(e) {
|
||||
throw new droplabAjaxException(e.message || e);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this.listTemplate) {
|
||||
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
|
||||
dynamicList.outerHTML = this.listTemplate;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -76,4 +93,4 @@ module.exports = function(callback) {
|
|||
};
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -72,31 +72,22 @@ require('../window')(function(w){
|
|||
var params = config.params || {};
|
||||
params[config.searchKey] = searchValue;
|
||||
var self = this;
|
||||
this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) {
|
||||
if (config.loadingTemplate && self.hook.list.data === undefined ||
|
||||
self.hook.list.data.length === 0) {
|
||||
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
|
||||
self.cache = self.cache || {};
|
||||
var url = config.endpoint + this.buildParams(params);
|
||||
var urlCachedData = self.cache[url];
|
||||
|
||||
if (dataLoadingTemplate) {
|
||||
dataLoadingTemplate.outerHTML = self.listTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.destroyed) {
|
||||
var hookListChildren = self.hook.list.list.children;
|
||||
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
|
||||
|
||||
if (onlyDynamicList && data.length === 0) {
|
||||
self.hook.list.hide();
|
||||
}
|
||||
|
||||
self.hook.list.setData.call(self.hook.list, data);
|
||||
}
|
||||
self.notLoading();
|
||||
});
|
||||
if (urlCachedData) {
|
||||
self._loadData(urlCachedData, config, self);
|
||||
} else {
|
||||
this._loadUrlData(url)
|
||||
.then(function(data) {
|
||||
self._loadData(data, config, self);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_loadUrlData: function _loadUrlData(url) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open('GET', url, true);
|
||||
|
@ -104,6 +95,7 @@ require('../window')(function(w){
|
|||
if(xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
self.cache[url] = data;
|
||||
return resolve(data);
|
||||
} else {
|
||||
return reject([xhr.responseText, xhr.status]);
|
||||
|
@ -114,6 +106,30 @@ require('../window')(function(w){
|
|||
});
|
||||
},
|
||||
|
||||
_loadData: function _loadData(data, config, self) {
|
||||
if (config.loadingTemplate && self.hook.list.data === undefined ||
|
||||
self.hook.list.data.length === 0) {
|
||||
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
|
||||
|
||||
if (dataLoadingTemplate) {
|
||||
dataLoadingTemplate.outerHTML = self.listTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.destroyed) {
|
||||
var hookListChildren = self.hook.list.list.children;
|
||||
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
|
||||
|
||||
if (onlyDynamicList && data.length === 0) {
|
||||
self.hook.list.hide();
|
||||
}
|
||||
|
||||
self.hook.list.setData.call(self.hook.list, data);
|
||||
}
|
||||
self.notLoading();
|
||||
self.hook.list.currentIndex = 0;
|
||||
},
|
||||
|
||||
buildParams: function(params) {
|
||||
if (!params) return '';
|
||||
var paramsArray = Object.keys(params).map(function(param) {
|
||||
|
@ -142,4 +158,4 @@ module.exports = function(callback) {
|
|||
};
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,8 @@ require('../window')(function(w){
|
|||
w.droplabFilter = {
|
||||
|
||||
keydownWrapper: function(e){
|
||||
var hiddenCount = 0;
|
||||
var dataHiddenCount = 0;
|
||||
var list = e.detail.hook.list;
|
||||
var data = list.data;
|
||||
var value = e.detail.hook.trigger.value.toLowerCase();
|
||||
|
@ -27,10 +29,22 @@ require('../window')(function(w){
|
|||
};
|
||||
}
|
||||
|
||||
dataHiddenCount = data.filter(function(o) {
|
||||
return !o.droplab_hidden;
|
||||
}).length;
|
||||
|
||||
matches = data.map(function(o) {
|
||||
return filterFunction(o, value);
|
||||
});
|
||||
list.render(matches);
|
||||
|
||||
hiddenCount = matches.filter(function(o) {
|
||||
return !o.droplab_hidden;
|
||||
}).length;
|
||||
|
||||
if (dataHiddenCount !== hiddenCount) {
|
||||
list.render(matches);
|
||||
list.currentIndex = 0;
|
||||
}
|
||||
},
|
||||
|
||||
init: function init(hookInput) {
|
||||
|
@ -57,4 +71,4 @@ module.exports = function(callback) {
|
|||
};
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* 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 */
|
||||
|
||||
/*= require preview_markdown */
|
||||
require('./preview_markdown');
|
||||
|
||||
(function() {
|
||||
this.DropzoneInput = (function() {
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
/* global EnvironmentsService */
|
||||
/* global Flash */
|
||||
|
||||
//= require vue
|
||||
//= require vue-resource
|
||||
//= require_tree ../services/
|
||||
//= require ./environment_item
|
||||
window.Vue = require('vue');
|
||||
window.Vue.use(require('vue-resource'));
|
||||
require('../services/environments_service');
|
||||
require('./environment_item');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
@ -180,9 +180,9 @@
|
|||
<tr>
|
||||
<th class="environments-name">Environment</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-date">Created</th>
|
||||
<th class="environments-date">Updated</th>
|
||||
<th class="hidden-xs environments-actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
/* global Vue */
|
||||
/* global timeago */
|
||||
|
||||
/*= require timeago */
|
||||
/*= require lib/utils/text_utility */
|
||||
/*= require vue_common_component/commit */
|
||||
/*= require ./environment_actions */
|
||||
/*= require ./environment_external_url */
|
||||
/*= require ./environment_stop */
|
||||
/*= require ./environment_rollback */
|
||||
/*= require ./environment_terminal_button */
|
||||
window.Vue = require('vue');
|
||||
window.timeago = require('vendor/timeago');
|
||||
require('../../lib/utils/text_utility');
|
||||
require('../../vue_common_component/commit');
|
||||
require('./environment_actions');
|
||||
require('./environment_external_url');
|
||||
require('./environment_stop');
|
||||
require('./environment_rollback');
|
||||
require('./environment_terminal_button');
|
||||
|
||||
(() => {
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//= require vue
|
||||
//= require_tree ./stores/
|
||||
//= require ./components/environment
|
||||
//= require ./vue_resource_interceptor
|
||||
window.Vue = require('vue');
|
||||
|
||||
require('./stores/environments_store');
|
||||
require('./components/environment');
|
||||
require('./vue_resource_interceptor');
|
||||
|
||||
$(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* globals Vue */
|
||||
/* eslint-disable no-unused-vars, no-param-reassign */
|
||||
|
||||
class EnvironmentsService {
|
||||
|
||||
constructor(root) {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
/* 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() {
|
||||
return this[0];
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*= require filtered_search/filtered_search_dropdown */
|
||||
require('./filtered_search_dropdown');
|
||||
|
||||
/* global droplabFilter */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*= require filtered_search/filtered_search_dropdown */
|
||||
require('./filtered_search_dropdown');
|
||||
|
||||
/* global droplabAjax */
|
||||
/* global droplabFilter */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*= require filtered_search/filtered_search_dropdown */
|
||||
require('./filtered_search_dropdown');
|
||||
|
||||
/* global droplabAjaxFilter */
|
||||
|
||||
|
@ -39,8 +39,15 @@
|
|||
getSearchInput() {
|
||||
const query = gl.DropdownUtils.getSearchInput(this.input);
|
||||
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() {
|
||||
|
|
|
@ -28,7 +28,12 @@
|
|||
if (lastToken !== searchToken) {
|
||||
const title = updatedItem.title.toLowerCase();
|
||||
let value = lastToken.value.toLowerCase();
|
||||
value = value.replace(/"(.*?)"/g, str => str.slice(1).slice(0, -1));
|
||||
|
||||
// Removes the first character if it is a quotation so that we can search
|
||||
// with multiple words
|
||||
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
|
||||
value = value.slice(1);
|
||||
}
|
||||
|
||||
// Eg. filterSymbol = ~ for labels
|
||||
const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
|
||||
|
@ -83,8 +88,9 @@
|
|||
const selectionStart = input.selectionStart;
|
||||
let inputValue = input.value;
|
||||
// Replace all spaces inside quote marks with underscores
|
||||
// (will continue to match entire string until an end quote is found if any)
|
||||
// This helps with matching the beginning & end of a token:key
|
||||
inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_'));
|
||||
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
|
||||
|
||||
// Get the right position for the word selected
|
||||
// Regex matches first space
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// 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
|
||||
// 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 requireAll(context) { return context.keys().map(context); }
|
||||
|
||||
requireAll(require.context('./', true, /^\.\/(?!filtered_search_bundle).*\.(js|es6)$/));
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
this.setupMapping();
|
||||
|
||||
this.cleanupWrapper = this.cleanup.bind(this);
|
||||
document.addEventListener('page:fetch', this.cleanupWrapper);
|
||||
document.addEventListener('beforeunload', this.cleanupWrapper);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
|
@ -20,7 +20,7 @@
|
|||
|
||||
this.setupMapping();
|
||||
|
||||
document.removeEventListener('page:fetch', this.cleanupWrapper);
|
||||
document.removeEventListener('beforeunload', this.cleanupWrapper);
|
||||
}
|
||||
|
||||
setupMapping() {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* global Turbolinks */
|
||||
|
||||
(() => {
|
||||
class FilteredSearchManager {
|
||||
constructor() {
|
||||
|
@ -15,13 +13,13 @@
|
|||
this.dropdownManager.setDropdown();
|
||||
|
||||
this.cleanupWrapper = this.cleanup.bind(this);
|
||||
document.addEventListener('page:fetch', this.cleanupWrapper);
|
||||
document.addEventListener('beforeunload', this.cleanupWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.unbindEvents();
|
||||
document.removeEventListener('page:fetch', this.cleanupWrapper);
|
||||
document.removeEventListener('beforeunload', this.cleanupWrapper);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
|
@ -64,13 +62,26 @@
|
|||
}
|
||||
|
||||
checkForEnter(e) {
|
||||
if (e.keyCode === 38 || e.keyCode === 40) {
|
||||
const selectionStart = this.filteredSearchInput.selectionStart;
|
||||
|
||||
e.preventDefault();
|
||||
this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
|
||||
}
|
||||
|
||||
if (e.keyCode === 13) {
|
||||
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
|
||||
const dropdownEl = dropdown.element;
|
||||
const activeElements = dropdownEl.querySelectorAll('.dropdown-active');
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Prevent droplab from opening dropdown
|
||||
this.dropdownManager.destroyDroplab();
|
||||
if (!activeElements.length) {
|
||||
// Prevent droplab from opening dropdown
|
||||
this.dropdownManager.destroyDroplab();
|
||||
|
||||
this.search();
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,10 +194,13 @@
|
|||
});
|
||||
|
||||
if (searchToken) {
|
||||
paths.push(`search=${encodeURIComponent(searchToken)}`);
|
||||
const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
|
||||
paths.push(`search=${sanitized}`);
|
||||
}
|
||||
|
||||
Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`);
|
||||
const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
|
||||
|
||||
gl.utils.visitUrl(parameterizedUrl);
|
||||
}
|
||||
|
||||
getUsernameParams() {
|
||||
|
|
|
@ -21,6 +21,15 @@
|
|||
symbol: '~',
|
||||
}];
|
||||
|
||||
const alternativeTokenKeys = [{
|
||||
key: 'label',
|
||||
type: 'string',
|
||||
param: 'name',
|
||||
symbol: '~',
|
||||
}];
|
||||
|
||||
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
|
||||
|
||||
const conditions = [{
|
||||
url: 'assignee_id=0',
|
||||
tokenKey: 'assignee',
|
||||
|
@ -44,6 +53,10 @@
|
|||
return tokenKeys;
|
||||
}
|
||||
|
||||
static getAlternatives() {
|
||||
return alternativeTokenKeys;
|
||||
}
|
||||
|
||||
static getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
@ -57,7 +70,7 @@
|
|||
}
|
||||
|
||||
static searchByKeyParam(keyParam) {
|
||||
return tokenKeys.find((tokenKey) => {
|
||||
return tokenKeysWithAlternative.find((tokenKey) => {
|
||||
let tokenKeyParam = tokenKey.key;
|
||||
|
||||
if (tokenKey.param) {
|
||||
|
|
|
@ -83,12 +83,12 @@
|
|||
_a = decodeURI("%C3%80");
|
||||
_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);
|
||||
|
||||
if (match) {
|
||||
return match[2] || match[1];
|
||||
return (match[1] || match[1] === "") ? match[1] : match[2];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -367,9 +367,14 @@
|
|||
return $input.trigger('keyup');
|
||||
},
|
||||
isLoading(data) {
|
||||
if (!data || !data.length) return false;
|
||||
if (Array.isArray(data)) data = data[0];
|
||||
return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0];
|
||||
var dataToInspect = data;
|
||||
if (data && data.length > 0) {
|
||||
dataToInspect = data[0];
|
||||
}
|
||||
|
||||
var loadingState = this.defaultLoadingData[0];
|
||||
return dataToInspect &&
|
||||
(dataToInspect === loadingState || dataToInspect.name === loadingState);
|
||||
}
|
||||
};
|
||||
}).call(this);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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 Turbolinks */
|
||||
|
||||
(function() {
|
||||
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
|
||||
|
@ -249,7 +248,7 @@
|
|||
_this.fullData = data;
|
||||
_this.parseData(_this.fullData);
|
||||
_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');
|
||||
}
|
||||
};
|
||||
|
@ -512,12 +511,17 @@
|
|||
|
||||
// Append the menu into the dropdown
|
||||
GitLabDropdown.prototype.appendMenu = function(html) {
|
||||
return this.clearMenu().append(html);
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.clearMenu = function() {
|
||||
var selector;
|
||||
selector = '.dropdown-content';
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
selector = ".dropdown-page-one .dropdown-content";
|
||||
}
|
||||
return $(selector, this.dropdown).empty().append(html);
|
||||
|
||||
return $(selector, this.dropdown).empty();
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.renderItem = function(data, group, index) {
|
||||
|
@ -651,18 +655,14 @@
|
|||
isMarking = false;
|
||||
el.removeClass(ACTIVE_CLASS);
|
||||
if (field && field.length) {
|
||||
if (isInput) {
|
||||
field.val('');
|
||||
} else {
|
||||
field.remove();
|
||||
}
|
||||
this.clearField(field, isInput);
|
||||
}
|
||||
} else if (el.hasClass(INDETERMINATE_CLASS)) {
|
||||
isMarking = true;
|
||||
el.addClass(ACTIVE_CLASS);
|
||||
el.removeClass(INDETERMINATE_CLASS);
|
||||
if (field && field.length && value == null) {
|
||||
field.remove();
|
||||
this.clearField(field, isInput);
|
||||
}
|
||||
if ((!field || !field.length) && fieldName) {
|
||||
this.addInput(fieldName, value, selectedObject);
|
||||
|
@ -676,7 +676,7 @@
|
|||
}
|
||||
}
|
||||
if (field && field.length && value == null) {
|
||||
field.remove();
|
||||
this.clearField(field, isInput);
|
||||
}
|
||||
// Toggle active class for the tick mark
|
||||
el.addClass(ACTIVE_CLASS);
|
||||
|
@ -722,7 +722,7 @@
|
|||
if ($el.length) {
|
||||
var href = $el.attr('href');
|
||||
if (href && href !== '#') {
|
||||
Turbolinks.visit(href);
|
||||
gl.utils.visitUrl(href);
|
||||
} else {
|
||||
$el.first().trigger('click');
|
||||
}
|
||||
|
@ -826,6 +826,10 @@
|
|||
return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.clearField = function(field, isInput) {
|
||||
return isInput ? field.val('') : field.remove();
|
||||
};
|
||||
|
||||
return GitLabDropdown;
|
||||
})();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* 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) => {
|
||||
const customValidationFlag = 'gl-field-error-ignore';
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
|
||||
/* global GitLab */
|
||||
/* global DropzoneInput */
|
||||
/* global autosize */
|
||||
|
||||
(function() {
|
||||
this.GLForm = (function() {
|
||||
function GLForm(form) {
|
||||
this.form = form;
|
||||
this.textarea = this.form.find('textarea.js-gfm-input');
|
||||
// Before we start, we should clean up any previous data for this form
|
||||
this.destroy();
|
||||
// Setup the form
|
||||
this.setupForm();
|
||||
this.form.data('gl-form', this);
|
||||
}
|
||||
|
||||
GLForm.prototype.destroy = function() {
|
||||
// Clean form listeners
|
||||
this.clearEventListeners();
|
||||
return this.form.data('gl-form', null);
|
||||
};
|
||||
|
||||
GLForm.prototype.setupForm = function() {
|
||||
var isNewForm;
|
||||
isNewForm = this.form.is(':not(.gfm-form)');
|
||||
this.form.removeClass('js-new-note-form');
|
||||
if (isNewForm) {
|
||||
this.form.find('.div-dropzone').remove();
|
||||
this.form.addClass('gfm-form');
|
||||
// remove notify commit author checkbox for non-commit notes
|
||||
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
|
||||
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
|
||||
new DropzoneInput(this.form);
|
||||
autosize(this.textarea);
|
||||
// form and textarea event listeners
|
||||
this.addEventListeners();
|
||||
}
|
||||
gl.text.init(this.form);
|
||||
// hide discard button
|
||||
this.form.find('.js-note-discard').hide();
|
||||
return this.form.show();
|
||||
};
|
||||
|
||||
GLForm.prototype.clearEventListeners = function() {
|
||||
this.textarea.off('focus');
|
||||
this.textarea.off('blur');
|
||||
return gl.text.removeListeners(this.form);
|
||||
};
|
||||
|
||||
GLForm.prototype.addEventListeners = function() {
|
||||
this.textarea.on('focus', function() {
|
||||
return $(this).closest('.md-area').addClass('is-focused');
|
||||
});
|
||||
return this.textarea.on('blur', function() {
|
||||
return $(this).closest('.md-area').removeClass('is-focused');
|
||||
});
|
||||
};
|
||||
|
||||
return GLForm;
|
||||
})();
|
||||
}).call(this);
|
92
app/assets/javascripts/gl_form.js.es6
Normal file
92
app/assets/javascripts/gl_form.js.es6
Normal file
|
@ -0,0 +1,92 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
|
||||
/* global GitLab */
|
||||
/* global DropzoneInput */
|
||||
/* global autosize */
|
||||
|
||||
(() => {
|
||||
const global = window.gl || (window.gl = {});
|
||||
|
||||
function GLForm(form) {
|
||||
this.form = form;
|
||||
this.textarea = this.form.find('textarea.js-gfm-input');
|
||||
// Before we start, we should clean up any previous data for this form
|
||||
this.destroy();
|
||||
// Setup the form
|
||||
this.setupForm();
|
||||
this.form.data('gl-form', this);
|
||||
}
|
||||
|
||||
GLForm.prototype.destroy = function() {
|
||||
// Clean form listeners
|
||||
this.clearEventListeners();
|
||||
return this.form.data('gl-form', null);
|
||||
};
|
||||
|
||||
GLForm.prototype.setupForm = function() {
|
||||
var isNewForm;
|
||||
isNewForm = this.form.is(':not(.gfm-form)');
|
||||
this.form.removeClass('js-new-note-form');
|
||||
if (isNewForm) {
|
||||
this.form.find('.div-dropzone').remove();
|
||||
this.form.addClass('gfm-form');
|
||||
// remove notify commit author checkbox for non-commit notes
|
||||
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
|
||||
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
|
||||
new DropzoneInput(this.form);
|
||||
autosize(this.textarea);
|
||||
// form and textarea event listeners
|
||||
this.addEventListeners();
|
||||
}
|
||||
gl.text.init(this.form);
|
||||
// hide discard button
|
||||
this.form.find('.js-note-discard').hide();
|
||||
this.form.show();
|
||||
if (this.isAutosizeable) this.setupAutosize();
|
||||
};
|
||||
|
||||
GLForm.prototype.setupAutosize = function () {
|
||||
this.textarea.off('autosize:resized')
|
||||
.on('autosize:resized', this.setHeightData.bind(this));
|
||||
|
||||
this.textarea.off('mouseup.autosize')
|
||||
.on('mouseup.autosize', this.destroyAutosize.bind(this));
|
||||
|
||||
setTimeout(() => {
|
||||
autosize(this.textarea);
|
||||
this.textarea.css('resize', 'vertical');
|
||||
}, 0);
|
||||
};
|
||||
|
||||
GLForm.prototype.setHeightData = function () {
|
||||
this.textarea.data('height', this.textarea.outerHeight());
|
||||
};
|
||||
|
||||
GLForm.prototype.destroyAutosize = function () {
|
||||
const outerHeight = this.textarea.outerHeight();
|
||||
|
||||
if (this.textarea.data('height') === outerHeight) return;
|
||||
|
||||
autosize.destroy(this.textarea);
|
||||
|
||||
this.textarea.data('height', outerHeight);
|
||||
this.textarea.outerHeight(outerHeight);
|
||||
this.textarea.css('max-height', window.outerHeight);
|
||||
};
|
||||
|
||||
GLForm.prototype.clearEventListeners = function() {
|
||||
this.textarea.off('focus');
|
||||
this.textarea.off('blur');
|
||||
return gl.text.removeListeners(this.form);
|
||||
};
|
||||
|
||||
GLForm.prototype.addEventListeners = function() {
|
||||
this.textarea.on('focus', function() {
|
||||
return $(this).closest('.md-area').addClass('is-focused');
|
||||
});
|
||||
return this.textarea.on('blur', function() {
|
||||
return $(this).closest('.md-area').removeClass('is-focused');
|
||||
});
|
||||
};
|
||||
|
||||
global.GLForm = GLForm;
|
||||
})();
|
|
@ -1,12 +1,3 @@
|
|||
/* eslint-disable func-names, space-before-function-paren */
|
||||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// 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
|
||||
// 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);
|
||||
// require everything else in this directory
|
||||
function requireAll(context) { return context.keys().map(context); }
|
||||
requireAll(require.context('.', false, /^\.\/(?!graphs_bundle).*\.(js|es6)$/));
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
/* global ContributorsStatGraphUtil */
|
||||
/* global d3 */
|
||||
|
||||
/*= require d3 */
|
||||
window.d3 = require('d3');
|
||||
|
||||
(function() {
|
||||
this.ContributorsStatGraph = (function() {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* global d3 */
|
||||
/* global ContributorsGraph */
|
||||
|
||||
/*= require d3 */
|
||||
window.d3 = require('d3');
|
||||
|
||||
(function() {
|
||||
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
|
||||
|
|
|
@ -59,11 +59,11 @@
|
|||
} else {
|
||||
avatar = gon.default_avatar_url;
|
||||
}
|
||||
return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>";
|
||||
return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>";
|
||||
};
|
||||
|
||||
GroupsSelect.prototype.formatSelection = function(group) {
|
||||
return group.name;
|
||||
return group.full_name;
|
||||
};
|
||||
|
||||
return GroupsSelect;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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 Turbolinks */
|
||||
|
||||
((global) => {
|
||||
var issuable_created;
|
||||
|
@ -119,7 +118,7 @@
|
|||
issuesUrl = formAction;
|
||||
issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
|
||||
issuesUrl += formData;
|
||||
return Turbolinks.visit(issuesUrl);
|
||||
return gl.utils.visitUrl(issuesUrl);
|
||||
};
|
||||
})(this),
|
||||
initResetFilters: function() {
|
||||
|
@ -130,7 +129,7 @@
|
|||
const baseIssuesUrl = target.href;
|
||||
|
||||
$form.attr('action', baseIssuesUrl);
|
||||
Turbolinks.visit(baseIssuesUrl);
|
||||
gl.utils.visitUrl(baseIssuesUrl);
|
||||
});
|
||||
},
|
||||
initChecks: function() {
|
||||
|
|
|
@ -1 +1 @@
|
|||
//= require ./time_tracking/time_tracking_bundle
|
||||
require('./time_tracking/time_tracking_bundle');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* global Vue */
|
||||
//= require lib/utils/pretty_time
|
||||
require('../../../lib/utils/pretty_time');
|
||||
|
||||
(() => {
|
||||
Vue.component('time-tracking-collapsed-state', {
|
||||
|
@ -39,4 +39,3 @@
|
|||
`,
|
||||
});
|
||||
})();
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* global Vue */
|
||||
//= require lib/utils/pretty_time
|
||||
require('../../../lib/utils/pretty_time');
|
||||
|
||||
(() => {
|
||||
const prettyTime = gl.utils.prettyTime;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
/* global Vue */
|
||||
//= require ./help_state
|
||||
//= require ./collapsed_state
|
||||
//= require ./spent_only_pane
|
||||
//= require ./no_tracking_pane
|
||||
//= require ./estimate_only_pane
|
||||
//= require ./comparison_pane
|
||||
|
||||
require('./help_state');
|
||||
require('./collapsed_state');
|
||||
require('./spent_only_pane');
|
||||
require('./no_tracking_pane');
|
||||
require('./estimate_only_pane');
|
||||
require('./comparison_pane');
|
||||
|
||||
(() => {
|
||||
Vue.component('issuable-time-tracker', {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/* global Vue */
|
||||
//= require ./components/time_tracker
|
||||
//= require smart_interval
|
||||
//= require subbable_resource
|
||||
|
||||
require('./components/time_tracker');
|
||||
require('../../smart_interval');
|
||||
require('../../subbable_resource');
|
||||
|
||||
(() => {
|
||||
/* This Vue instance represents what will become the parent instance for the
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
|
||||
/* global UsersSelect */
|
||||
/* global Cookies */
|
||||
/* global bp */
|
||||
|
||||
(function() {
|
||||
this.IssuableContext = (function() {
|
||||
|
@ -37,6 +39,13 @@
|
|||
}, 0);
|
||||
}
|
||||
});
|
||||
window.addEventListener('beforeunload', function() {
|
||||
// collapsed_gutter cookie hides the sidebar
|
||||
var bpBreakpoint = bp.getBreakpointSize();
|
||||
if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
|
||||
Cookies.set('collapsed_gutter', true);
|
||||
}
|
||||
});
|
||||
$(".right-sidebar").niceScroll();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* 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 */
|
||||
|
||||
/*= require flash */
|
||||
/*= require jquery.waitforimages */
|
||||
/*= require task_list */
|
||||
require('./flash');
|
||||
require('vendor/jquery.waitforimages');
|
||||
require('vendor/task_list');
|
||||
|
||||
(function() {
|
||||
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
|
||||
|
|
|
@ -61,7 +61,6 @@
|
|||
return labels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Will return only labels that were marked previously and the user has unmarked
|
||||
* @return {Array} Label IDs
|
||||
|
@ -80,7 +79,6 @@
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple form serialization, it will return just what we need
|
||||
* Returns key/value pairs from form data
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
|
||||
this.otherLabels = otherLabels || $('.js-other-labels');
|
||||
this.errorMessage = 'Unable to update label prioritization at this time';
|
||||
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
|
||||
this.prioritizedLabels.sortable({
|
||||
items: 'li',
|
||||
placeholder: 'list-placeholder',
|
||||
|
@ -29,7 +30,12 @@
|
|||
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
|
||||
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
|
||||
$tooltip.tooltip('destroy');
|
||||
return _this.toggleLabelPriority($label, action);
|
||||
_this.toggleLabelPriority($label, action);
|
||||
_this.toggleEmptyState($label, $btn, action);
|
||||
}
|
||||
|
||||
toggleEmptyState($label, $btn, action) {
|
||||
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
|
||||
}
|
||||
|
||||
toggleLabelPriority($label, action, persistState) {
|
||||
|
|
|
@ -336,7 +336,11 @@
|
|||
.removeClass('is-active');
|
||||
}
|
||||
|
||||
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
if ($dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dropdown.hasClass('js-filter-bulk-update')) {
|
||||
_this.enableBulkLabelDropdown();
|
||||
_this.setDropdownData($dropdown, isMarking, this.id(label));
|
||||
return;
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
/*= require ace-rails-ap */
|
||||
/*= require ace/ace */
|
||||
/*= require ace/ext-searchbox */
|
||||
/*= require ./ace/ace_config_paths */
|
||||
|
|
34
app/assets/javascripts/lib/ace/ace_config_paths.js.erb
Normal file
34
app/assets/javascripts/lib/ace/ace_config_paths.js.erb
Normal file
|
@ -0,0 +1,34 @@
|
|||
<%
|
||||
ace_gem_path = Bundler.rubygems.find_name('ace-rails-ap').first.full_gem_path
|
||||
ace_workers = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/worker-*.js'].sort.map do |file|
|
||||
File.basename(file, '.js').sub(/^worker-/, '')
|
||||
end
|
||||
ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.map do |file|
|
||||
File.basename(file, '.js').sub(/^mode-/, '')
|
||||
end
|
||||
%>
|
||||
// Lazy-load configuration when ace.edit is called
|
||||
(function() {
|
||||
var basePath;
|
||||
var ace = window.ace;
|
||||
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
|
||||
<% ace_workers.each do |worker| %>
|
||||
ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/<%= File.basename(asset_path("ace/worker-#{worker}.js")) %>');
|
||||
<% end %>
|
||||
|
||||
// configure paths for all mode modules
|
||||
<% ace_modes.each do |mode| %>
|
||||
ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/<%= File.basename(asset_path("ace/mode-#{mode}.js")) %>');
|
||||
<% end %>
|
||||
|
||||
// restore original method
|
||||
ace.edit = edit;
|
||||
return ace.edit.apply(ace, arguments);
|
||||
};
|
||||
})();
|
|
@ -1,7 +1,3 @@
|
|||
/* eslint-disable func-names, space-before-function-paren */
|
||||
|
||||
/*= require Chart */
|
||||
|
||||
(function() {
|
||||
|
||||
}).call(this);
|
||||
window.Chart = require('vendor/Chart');
|
||||
|
|
6
app/assets/javascripts/lib/d3.js
vendored
6
app/assets/javascripts/lib/d3.js
vendored
|
@ -1,7 +1,3 @@
|
|||
/* eslint-disable func-names, space-before-function-paren */
|
||||
|
||||
/*= require d3 */
|
||||
|
||||
(function() {
|
||||
|
||||
}).call(this);
|
||||
window.d3 = require('d3');
|
||||
|
|
|
@ -95,7 +95,6 @@
|
|||
const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
|
||||
|
||||
history.replaceState({
|
||||
turbolinks: true,
|
||||
url: newState,
|
||||
}, document.title, newState);
|
||||
return newState;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue