Merge remote-tracking branch 'upstream/master' into no-ivar-in-modules

* upstream/master: (671 commits)
  Make rubocop happy
  Use guard clause
  Improve language
  Prettify
  Use temp branch
  Pass info about who started the job and which job triggered it
  Docs: add indexes for monitoring and performance monitoring
  clearer-documentation-on-inline-diffs
  Add docs for commit diff discussion in merge requests
  sorting for tags api
  Clear BatchLoader after each spec to prevent holding onto records longer than necessary
  Include project in BatchLoader key to prevent returning blobs for the wrong project
  moved lfs_blob_ids method into ExtractsPath module
  Converted JS modules into exported modules
  spec fixes
  Bump gitlab-shell version to 5.10.3
  Clear caches before updating MR diffs
  Use new Ruby version 2.4 in GitLab QA images
  moved lfs blob fetch from extractspath file
  Update GitLab QA dependencies
  ...
This commit is contained in:
Lin Jen-Shin 2017-12-15 17:14:26 +08:00
commit 59ac184fcf
1285 changed files with 25735 additions and 11486 deletions

View file

@ -586,6 +586,7 @@ codequality:
paths: [codeclimate.json]
qa:internal:
<<: *except-docs
stage: test
variables:
SETUP_DB: "false"

View file

@ -2,6 +2,36 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.2.4 (2017-12-07)
### Security (5 changes)
- Fix e-mail address disclosure through member search fields
- Prevent creating issues through API when user does not have permissions
- Prevent an information disclosure in the Groups API
- Fix user without access to private Wiki being able to see it on the project page
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
## 10.2.3 (2017-11-30)
### Fixed (7 changes)
- Fix hashed storage for Import/Export uploads. !15482
- Ensure that rake gitlab:cleanup:repos task does not mess with hashed repositories. !15520
- Ensure that rake gitlab:cleanup:dirs task does not mess with hashed repositories. !15600
- Fix WIP system note not being created.
- Fix link text from group context.
- Fix defaults for MR states and merge statuses.
- Fix pulling and pushing using a personal access token with the sudo scope.
### Performance (3 changes)
- Drastically improve project search performance by no longer searching namespace name.
- Reuse authors when rendering event Atom feeds.
- Optimise StuckCiJobsWorker using cheap SQL query outside, and expensive inside.
## 10.2.2 (2017-11-23)
### Fixed (5 changes)
@ -218,6 +248,17 @@ entry.
- Add Gitaly metrics to the performance bar.
## 10.1.5 (2017-12-07)
### Security (5 changes)
- Fix e-mail address disclosure through member search fields
- Prevent creating issues through API when user does not have permissions
- Prevent an information disclosure in the Groups API
- Fix user without access to private Wiki being able to see it on the project page
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
## 10.1.4 (2017-11-14)
### Fixed (4 changes)
@ -466,6 +507,17 @@ entry.
- creation of keys moved to services. !13331 (haseebeqx)
- Add username as GL_USERNAME in hooks.
## 10.0.7 (2017-12-07)
### Security (5 changes)
- Fix e-mail address disclosure through member search fields
- Prevent creating issues through API when user does not have permissions
- Prevent an information disclosure in the Groups API
- Fix user without access to private Wiki being able to see it on the project page
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
## 10.0.5 (2017-11-03)
- [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258

View file

@ -598,6 +598,7 @@ merge request:
present time and never use past tense (has been/was). For example instead
of _prohibited this user from being saved due to the following errors:_ the
text should be _sorry, we could not create your account because:_
1. Code should be written in [US English][us-english]
This is also the style used by linting tools such as
[RuboCop](https://github.com/bbatsov/rubocop),
@ -663,6 +664,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[testing]: doc/development/testing_guide/index.md
[us-english]: https://en.wikipedia.org/wiki/American_English
[^1]: Please note that specs other than JavaScript specs are considered backend
code.

View file

@ -1 +1 @@
0.55.0
0.60.0

View file

@ -1 +1 @@
5.9.4
5.10.3

18
Gemfile
View file

@ -1,6 +1,6 @@
source 'https://rubygems.org'
gem 'rails', '4.2.8'
gem 'rails', '4.2.10'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@ -111,7 +111,7 @@ gem 'google-api-client', '~> 0.13.6'
gem 'unf', '~> 0.1.4'
# Seed data
gem 'seed-fu', '~> 2.3.7'
gem 'seed-fu', '2.3.6' # Upgrade to > 2.3.7 once https://github.com/mbleigh/seed-fu/issues/123 is solved
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
@ -171,7 +171,7 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0'
# Cache
gem 'redis-rails', '~> 5.0.1'
gem 'redis-rails', '~> 5.0.2'
# Redis
gem 'redis', '~> 3.2'
@ -283,7 +283,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
gem 'prometheus-client-mmap', '~> 0.7.0.beta39'
gem 'prometheus-client-mmap', '~> 0.7.0.beta43'
gem 'raindrops', '~> 0.18'
end
@ -400,14 +400,18 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.54.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.61.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
# Feature toggles
gem 'flipper', '~> 0.10.2'
gem 'flipper-active_record', '~> 0.10.2'
gem 'flipper', '~> 0.11.0'
gem 'flipper-active_record', '~> 0.11.0'
gem 'flipper-active_support_cache_store', '~> 0.11.0'
# Structured logging
gem 'lograge', '~> 0.5'
gem 'grape_logging', '~> 1.7'
# Asset synchronization
gem 'asset_sync', '~> 2.2.0'

View file

@ -4,38 +4,38 @@ GEM
RedCloth (4.3.2)
abstract_type (0.0.7)
ace-rails-ap (4.1.2)
actionmailer (4.2.8)
actionpack (= 4.2.8)
actionview (= 4.2.8)
activejob (= 4.2.8)
actionmailer (4.2.10)
actionpack (= 4.2.10)
actionview (= 4.2.10)
activejob (= 4.2.10)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.8)
actionview (= 4.2.8)
activesupport (= 4.2.8)
actionpack (4.2.10)
actionview (= 4.2.10)
activesupport (= 4.2.10)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.8)
activesupport (= 4.2.8)
actionview (4.2.10)
activesupport (= 4.2.10)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (4.2.8)
activesupport (= 4.2.8)
activejob (4.2.10)
activesupport (= 4.2.10)
globalid (>= 0.3.0)
activemodel (4.2.8)
activesupport (= 4.2.8)
activemodel (4.2.10)
activesupport (= 4.2.10)
builder (~> 3.1)
activerecord (4.2.8)
activemodel (= 4.2.8)
activesupport (= 4.2.8)
activerecord (4.2.10)
activemodel (= 4.2.10)
activesupport (= 4.2.10)
arel (~> 6.0)
activerecord_sane_schema_dumper (0.2)
rails (>= 4, < 5)
activesupport (4.2.8)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
@ -58,6 +58,11 @@ GEM
asciidoctor (1.5.3)
asciidoctor-plantuml (0.0.7)
asciidoctor (~> 1.5)
asset_sync (2.2.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.3.0)
atomic (1.1.99)
attr_encrypted (3.0.3)
@ -210,10 +215,13 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
flipper (0.10.2)
flipper-active_record (0.10.2)
flipper (0.11.0)
flipper-active_record (0.11.0)
activerecord (>= 3.2, < 6)
flipper (~> 0.10.2)
flipper (~> 0.11.0)
flipper-active_support_cache_store (0.11.0)
activesupport (>= 3.2, < 6)
flipper (~> 0.11.0)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
@ -276,7 +284,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.54.0)
gitaly-proto (0.61.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@ -300,8 +308,8 @@ GEM
omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
globalid (0.3.7)
activesupport (>= 4.1.0)
globalid (0.4.1)
activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7)
@ -328,8 +336,6 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.4.1.1)
googleapis-common-protos-types (1.0.0)
google-protobuf (~> 3.0)
googleauth (0.5.3)
faraday (~> 0.12)
jwt (~> 1.4)
@ -356,10 +362,9 @@ GEM
rake
grape_logging (1.7.0)
grape
grpc (1.7.2)
grpc (1.4.5)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7)
googleauth (~> 0.5.1)
haml (4.0.7)
tilt
haml_lint (0.26.0)
@ -400,7 +405,8 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
i18n (0.8.6)
i18n (0.9.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
@ -474,8 +480,8 @@ GEM
railties (>= 4, < 5.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.6)
mime-types (>= 1.16, < 4)
mail (2.7.0)
mini_mime (>= 0.1.1)
mail_room (0.9.1)
memoist (0.16.0)
memoizable (0.4.2)
@ -488,7 +494,6 @@ GEM
mini_mime (0.1.4)
mini_portile2 (2.3.0)
minitest (5.7.0)
mmap2 (2.2.9)
mousetrap-rails (1.4.6)
multi_json (1.12.2)
multi_xml (0.6.0)
@ -573,8 +578,8 @@ GEM
parallel (1.12.0)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.0)
ast (~> 2.2)
parser (2.4.0.2)
ast (~> 2.3)
parslet (1.5.0)
blankslate (~> 2.0)
path_expander (1.0.1)
@ -625,8 +630,7 @@ GEM
parser
unparser
procto (0.0.3)
prometheus-client-mmap (0.7.0.beta39)
mmap2 (~> 2.2, >= 2.2.9)
prometheus-client-mmap (0.7.0.beta43)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@ -656,16 +660,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.8)
actionmailer (= 4.2.8)
actionpack (= 4.2.8)
actionview (= 4.2.8)
activejob (= 4.2.8)
activemodel (= 4.2.8)
activerecord (= 4.2.8)
activesupport (= 4.2.8)
rails (4.2.10)
actionmailer (= 4.2.10)
actionpack (= 4.2.10)
actionview (= 4.2.10)
activejob (= 4.2.10)
activemodel (= 4.2.10)
activerecord (= 4.2.10)
activesupport (= 4.2.10)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.8)
railties (= 4.2.10)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@ -678,15 +682,15 @@ GEM
rails-i18n (4.0.9)
i18n (~> 0.7)
railties (~> 4.0)
railties (4.2.8)
actionpack (= 4.2.8)
activesupport (= 4.2.8)
railties (4.2.10)
actionpack (= 4.2.10)
activesupport (= 4.2.10)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
raindrops (0.18.0)
rake (12.1.0)
rake (12.3.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
@ -701,24 +705,24 @@ GEM
recursive-open-struct (1.0.0)
redcarpet (3.4.0)
redis (3.3.3)
redis-actionpack (5.0.1)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 1.4.0)
redis-activesupport (5.0.1)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.0.4)
activesupport (>= 3, < 6)
redis-store (~> 1.2.0)
redis-store (>= 1.3, < 2)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
redis-rack (1.6.0)
rack (~> 1.5)
redis-store (~> 1.2.0)
redis-rails (5.0.1)
redis-actionpack (~> 5.0.0)
redis-activesupport (~> 5.0.0)
redis-store (~> 1.2.0)
redis-store (1.2.0)
redis (>= 2.2)
redis-rack (2.0.3)
rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@ -815,7 +819,7 @@ GEM
rake (>= 0.9, < 13)
sass (~> 3.4.20)
securecompare (1.0.0)
seed-fu (2.3.7)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
select2-rails (3.5.9.3)
@ -873,7 +877,7 @@ GEM
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.0)
sprockets-rails (3.2.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
@ -911,7 +915,7 @@ GEM
truncato (0.7.10)
htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.3)
tzinfo (1.2.4)
thread_safe (~> 0.1)
u2f (0.2.1)
uber (0.1.0)
@ -979,6 +983,7 @@ DEPENDENCIES
asana (~> 0.6.0)
asciidoctor (~> 1.5.2)
asciidoctor-plantuml (= 0.0.7)
asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
@ -1019,8 +1024,9 @@ DEPENDENCIES
faraday (~> 0.12)
ffaker (~> 2.4)
flay (~> 2.8.0)
flipper (~> 0.10.2)
flipper-active_record (~> 0.10.2)
flipper (~> 0.11.0)
flipper-active_record (~> 0.11.0)
flipper-active_support_cache_store (~> 0.11.0)
fog-aliyun (~> 0.2.0)
fog-aws (~> 1.4)
fog-core (~> 1.44)
@ -1036,7 +1042,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.54.0)
gitaly-proto (~> 0.61.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
@ -1111,14 +1117,14 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta39)
prometheus-client-mmap (~> 0.7.0.beta43)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 4.2.8)
rails (= 4.2.10)
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
@ -1132,7 +1138,7 @@ DEPENDENCIES
redcarpet (~> 3.4)
redis (~> 3.2)
redis-namespace (~> 1.5.2)
redis-rails (~> 5.0.1)
redis-rails (~> 5.0.2)
request_store (~> 1.3)
responders (~> 2.0)
rouge (~> 2.0)
@ -1153,7 +1159,7 @@ DEPENDENCIES
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.54.0)
seed-fu (~> 2.3.7)
seed-fu (= 2.3.6)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3)

View file

@ -130,7 +130,8 @@ freeze date (the 7th) should have a corresponding Enterprise Edition merge
request, even if there are no conflicts. This is to reduce the size of the
subsequent EE merge, as we often merge a lot to CE on the release date. For more
information, see
[limit conflicts with EE when developing on CE][limit_ee_conflicts].
[Automatic CE->EE merge][automatic_ce_ee_merge] and
[Guidelines for implementing Enterprise Edition features][ee_features].
### After the 7th
@ -281,4 +282,5 @@ still an issue I encourage you to open it on the [GitLab.com issue tracker](http
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
[done]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done
[limit_ee_conflicts]: https://docs.gitlab.com/ce/development/limit_ee_conflicts.html
[automatic_ce_ee_merge]: https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
[ee_features]: https://docs.gitlab.com/ce/development/ee_features.html

View file

@ -1 +0,0 @@
<svg width="24" height="30" viewBox="0 0 24 30" xmlns="http://www.w3.org/2000/svg"><title>cursor</title><g fill="none" fill-rule="evenodd"><path d="M24 12.105c0 6.686-5.74 11.58-12 17.895C5.74 23.684 0 18.79 0 12.105 0 5.42 5.373 0 12 0s12 5.42 12 12.105z" fill="#1F78D1" fill-rule="nonzero"/><path d="M15.28 25.249c1.458-1.475 2.539-2.635 3.474-3.747 2.851-3.394 4.203-6.265 4.203-9.397 0-6.111-4.908-11.062-10.957-11.062-6.05 0-10.957 4.951-10.957 11.062 0 3.132 1.352 6.003 4.203 9.397.935 1.112 2.016 2.272 3.474 3.747.511.517 2.216 2.213 3.28 3.275 1.064-1.062 2.769-2.758 3.28-3.275z" fill="#FFF"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787c-.91 0-1.763.156-2.558.469-.79.308-1.42.725-1.888 1.252-.465.527-.697 1.096-.697 1.708 0 .5.159.977.476 1.433.321.45.772.841 1.352 1.172l.583.334-.181.643c-.107.407-.263.79-.469 1.152a6.604 6.604 0 0 0 1.842-1.145l.288-.254.381.04c.309.035.599.053.871.053.91 0 1.761-.154 2.551-.462.795-.312 1.424-.732 1.889-1.259.468-.526.703-1.096.703-1.707 0-.612-.235-1.181-.703-1.708-.465-.527-1.094-.944-1.889-1.252zm2.645.81c.536.656.804 1.373.804 2.15 0 .776-.268 1.495-.804 2.156-.535.656-1.263 1.176-2.183 1.56-.92.38-1.924.57-3.013.57a9.16 9.16 0 0 1-.971-.054 7.32 7.32 0 0 1-3.08 1.62 5.044 5.044 0 0 1-.764.148h-.033a.26.26 0 0 1-.181-.074.324.324 0 0 1-.107-.18v-.007c-.014-.018-.016-.045-.007-.08.014-.037.018-.059.014-.068 0-.009.01-.031.033-.067a.645.645 0 0 0 .04-.06 1.73 1.73 0 0 0 .047-.054l.054-.06a53.034 53.034 0 0 1 .435-.489c.049-.049.118-.136.207-.26.094-.126.168-.24.221-.342.054-.103.114-.235.181-.395.067-.161.125-.33.174-.51-.7-.397-1.254-.888-1.66-1.473A3.261 3.261 0 0 1 6 11.216c0-.777.268-1.494.804-2.15.535-.66 1.263-1.18 2.183-1.56.92-.384 1.924-.576 3.013-.576 1.09 0 2.094.192 3.013.576.92.38 1.648.9 2.183 1.56z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1 +0,0 @@
<svg width="48" height="60" viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg"><title>cursor_2x</title><g fill="none" fill-rule="evenodd"><path d="M48 24.21C48 37.583 36.522 47.369 24 60 11.478 47.368 0 37.582 0 24.21 0 10.84 10.745 0 24 0s24 10.84 24 24.21z" fill="#1F78D1" fill-rule="nonzero"/><path d="M30.56 50.497c2.915-2.95 5.078-5.268 6.947-7.493 5.703-6.788 8.406-12.53 8.406-18.793 0-12.223-9.815-22.124-21.913-22.124S2.087 11.988 2.087 24.211c0 6.263 2.703 12.005 8.406 18.793 1.87 2.225 4.032 4.544 6.947 7.493 1.022 1.035 4.432 4.426 6.56 6.55 2.128-2.124 5.538-5.515 6.56-6.55z" fill="#FFF"/><path d="M29.103 16.512c-1.58-.625-3.282-.938-5.103-.938-1.821 0-3.527.313-5.116.938-1.58.616-2.84 1.45-3.777 2.504-.928 1.054-1.393 2.192-1.393 3.415 0 1 .317 1.956.951 2.866.643.902 1.545 1.684 2.706 2.344l1.165.67-.362 1.286a9.603 9.603 0 0 1-.937 2.303 13.208 13.208 0 0 0 3.683-2.29l.576-.509.763.08c.616.072 1.196.108 1.741.108 1.821 0 3.522-.308 5.103-.925 1.589-.625 2.848-1.464 3.776-2.517.938-1.054 1.407-2.192 1.407-3.416 0-1.223-.469-2.361-1.407-3.415-.928-1.053-2.187-1.888-3.776-2.504zm5.29 1.62c1.071 1.313 1.607 2.746 1.607 4.3 0 1.553-.536 2.99-1.607 4.312-1.072 1.312-2.527 2.353-4.366 3.12-1.84.76-3.848 1.139-6.027 1.139a18.32 18.32 0 0 1-1.942-.107c-1.768 1.562-3.821 2.643-6.16 3.24-.438.126-.947.224-1.527.295h-.067a.521.521 0 0 1-.362-.147.649.649 0 0 1-.214-.362v-.013c-.027-.036-.032-.09-.014-.16.027-.072.036-.117.027-.135 0-.017.022-.062.067-.133a1.29 1.29 0 0 0 .08-.121c.01-.009.04-.045.094-.107a106.068 106.068 0 0 1 .522-.59c.215-.232.367-.401.456-.508.098-.099.236-.273.415-.523.188-.25.335-.477.442-.683.107-.205.228-.468.362-.79.134-.321.25-.66.348-1.018-1.402-.794-2.51-1.777-3.322-2.946C12.402 25.025 12 23.77 12 22.43c0-1.553.536-2.986 1.607-4.299 1.072-1.321 2.527-2.361 4.366-3.12 1.84-.768 3.848-1.152 6.027-1.152 2.179 0 4.188.384 6.027 1.152 1.84.759 3.294 1.799 4.366 3.12z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1 +1 @@
{"iconCount":179,"spriteSize":81882,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
{"iconCount":181,"spriteSize":81482,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View file

@ -1 +1 @@
<svg height="128" viewBox="0 0 142 128" width="142" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M94 62h20v4H94z" fill="#f0edf8"/><path d="M84.828 84l17.678 17.678-2.828 2.828L82 86.828z" fill="#fee1d3"/><path d="M42.828 24l17.678 17.678-2.828 2.828L40 26.828zM40 101.678L57.678 84l2.828 2.828-17.678 17.678z" fill="#f0edf8"/><g fill="#fee1d3"><path d="M82 41.678L99.678 24l2.828 2.828-17.678 17.678zM28 62h20v4H28z"/><rect height="30" rx="5" width="30" y="49"/></g><rect height="26" rx="5" stroke="#fdc4a8" stroke-width="4" width="26" x="2" y="51"/><rect fill="#c3b8e3" height="50" rx="10" width="50" x="46" y="39"/><rect height="46" rx="10" stroke="#6b4fbb" stroke-width="4" width="46" x="48" y="41"/><rect fill="#fef0e8" height="30" rx="5" width="30" x="84"/><rect height="26" rx="5" stroke="#fee1d3" stroke-width="4" width="26" x="86" y="2"/><rect fill="#fee1d3" height="30" rx="5" width="30" x="84" y="98"/><rect height="26" rx="5" stroke="#fdc4a8" stroke-width="4" width="26" x="86" y="100"/><rect fill="#f0edf8" height="30" rx="5" width="30" x="112" y="49"/><rect height="26" rx="5" stroke="#e1dbf1" stroke-width="4" width="26" x="114" y="51"/><rect fill="#f0edf8" height="30" rx="5" width="30" x="28" y="98"/><rect height="26" rx="5" stroke="#e1dbf1" stroke-width="4" width="26" x="30" y="100"/><rect fill="#f0edf8" height="30" rx="5" width="30" x="28"/><rect height="26" rx="5" stroke="#e1dbf1" stroke-width="4" width="26" x="30" y="2"/></g></svg>
<svg height="128" viewBox="0 0 142 128" width="142" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M94 62h20v4H94z" fill="#f0edf8"/><path d="M84.828 84l17.678 17.678-2.828 2.828L82 86.828z" fill="#fee1d3"/><path d="M42.828 24l17.678 17.678-2.828 2.828L40 26.828zM40 101.678L57.678 84l2.828 2.828-17.678 17.678z" fill="#f0edf8"/><path d="M82 41.678L99.678 24l2.828 2.828-17.678 17.678zM28 62h20v4H28zM3 52h24v24H3z" fill="#fee1d3"/><path d="M31 3h24v24H31z" fill="#f0edf8"/><path d="M87 3h24v24H87z" fill="#fef0e8"/><path d="M115 52h24v24h-24z" fill="#f0edf8"/><path d="M87 101h24v24H87z" fill="#fee1d3"/><path d="M31 101h24v24H31z" fill="#f0edf8"/><path d="M49 42h44v44H49z" fill="#c3b8e3"/><g fill-rule="nonzero"><path d="M5 53a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V54a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H5a5 5 0 0 1-5-5V54a5 5 0 0 1 5-5z" fill="#fdc4a8"/><path d="M56 43a6 6 0 0 0-6 6v30a6 6 0 0 0 6 6h30a6 6 0 0 0 6-6V49a6 6 0 0 0-6-6zm0-4h30c5.523 0 10 4.477 10 10v30c0 5.523-4.477 10-10 10H56c-5.523 0-10-4.477-10-10V49c0-5.523 4.477-10 10-10z" fill="#6b4fbb"/><path d="M89 4a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H89a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z" fill="#fee1d3"/><path d="M89 102a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-20a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H89a5 5 0 0 1-5-5v-20a5 5 0 0 1 5-5z" fill="#fdc4a8"/><path d="M117 53a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V54a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5h-20a5 5 0 0 1-5-5V54a5 5 0 0 1 5-5zM33 102a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-20a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H33a5 5 0 0 1-5-5v-20a5 5 0 0 1 5-5zM33 4a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H33a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z" fill="#e1dbf1"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>

After

Width:  |  Height:  |  Size: 570 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>

After

Width:  |  Height:  |  Size: 570 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -2,8 +2,9 @@
/* global Pager */
import Cookies from 'js-cookie';
import { localTimeAgo } from './lib/utils/datetime_utility';
class Activities {
export default class Activities {
constructor() {
Pager.init(20, true, false, data => data, this.updateTooltips);
@ -15,7 +16,7 @@ class Activities {
}
updateTooltips() {
gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
localTimeAgo($('.js-timeago', '.content_list'));
}
reloadActivities() {
@ -33,6 +34,3 @@ class Activities {
$sender.closest('li').toggleClass('active');
}
}
window.gl = window.gl || {};
window.gl.Activities = Activities;

View file

@ -1,62 +1,59 @@
/* 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 */
import { refreshCurrentPage } from './lib/utils/url_utility';
window.Admin = (function() {
function Admin() {
var modal, showBlacklistType;
$('input#user_force_random_password').on('change', function(elem) {
var elems;
elems = $('#user_password, #user_password_confirmation');
if ($(this).attr('checked')) {
return elems.val('').attr('disabled', true);
} else {
return elems.removeAttr('disabled');
}
});
$('body').on('click', '.js-toggle-colors-link', function(e) {
e.preventDefault();
return $('.js-toggle-colors-container').toggle();
});
$('.log-tabs a').click(function(e) {
e.preventDefault();
return $(this).tab('show');
});
$('.log-bottom').click(function(e) {
var visible_log;
e.preventDefault();
visible_log = $(".file-content:visible");
return visible_log.animate({
scrollTop: visible_log.find('ol').height()
}, "fast");
});
modal = $('.change-owner-holder');
$('.change-owner-link').bind("click", function(e) {
e.preventDefault();
$(this).hide();
return modal.show();
});
$('.change-owner-cancel-link').bind("click", function(e) {
e.preventDefault();
modal.hide();
return $('.change-owner-link').show();
});
$('li.project_member').bind('ajax:success', function() {
return gl.utils.refreshCurrentPage();
});
$('li.group_member').bind('ajax:success', function() {
return gl.utils.refreshCurrentPage();
});
showBlacklistType = function() {
if ($("input[name='blacklist_type']:checked").val() === 'file') {
$('.blacklist-file').show();
return $('.blacklist-raw').hide();
} else {
$('.blacklist-file').hide();
return $('.blacklist-raw').show();
}
};
$("input[name='blacklist_type']").click(showBlacklistType);
showBlacklistType();
function showBlacklistType() {
if ($('input[name="blacklist_type"]:checked').val() === 'file') {
$('.blacklist-file').show();
$('.blacklist-raw').hide();
} else {
$('.blacklist-file').hide();
$('.blacklist-raw').show();
}
}
return Admin;
})();
export default function adminInit() {
const modal = $('.change-owner-holder');
$('input#user_force_random_password').on('change', function randomPasswordClick() {
const $elems = $('#user_password, #user_password_confirmation');
if ($(this).attr('checked')) {
$elems.val('').attr('disabled', true);
} else {
$elems.removeAttr('disabled');
}
});
$('body').on('click', '.js-toggle-colors-link', (e) => {
e.preventDefault();
$('.js-toggle-colors-container').toggle();
});
$('.log-tabs a').on('click', function logTabsClick(e) {
e.preventDefault();
$(this).tab('show');
});
$('.log-bottom').on('click', (e) => {
e.preventDefault();
const $visibleLog = $('.file-content:visible');
$visibleLog.animate({
scrollTop: $visibleLog.find('ol').height(),
}, 'fast');
});
$('.change-owner-link').on('click', function changeOwnerLinkClick(e) {
e.preventDefault();
$(this).hide();
modal.show();
});
$('.change-owner-cancel-link').on('click', (e) => {
e.preventDefault();
modal.hide();
$('.change-owner-link').show();
});
$('li.project_member, li.group_member').on('ajax:success', refreshCurrentPage);
$("input[name='blacklist_type']").on('click', showBlacklistType);
showBlacklistType();
}

View file

@ -1,24 +0,0 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */
window.Aside = (function() {
function Aside() {
$(document).off("click", "a.show-aside");
$(document).on("click", 'a.show-aside', function(e) {
var btn, icon;
e.preventDefault();
btn = $(e.currentTarget);
icon = btn.find('i');
if (icon.hasClass('fa-angle-left')) {
btn.parent().find('section').hide();
btn.parent().find('aside').fadeIn();
return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
} else {
btn.parent().find('aside').hide();
btn.parent().find('section').fadeIn();
return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
}
});
}
return Aside;
})();

View file

@ -0,0 +1,73 @@
import Clipboard from 'clipboard';
function showTooltip(target, title) {
const $target = $(target);
const originalTitle = $target.data('original-title');
if (!$target.data('hideTooltip')) {
$target
.attr('title', title)
.tooltip('fixTitle')
.tooltip('show')
.attr('title', originalTitle)
.tooltip('fixTitle');
}
}
function genericSuccess(e) {
showTooltip(e.trigger, 'Copied');
// Clear the selection and blur the trigger so it loses its border
e.clearSelection();
$(e.trigger).blur();
}
/**
* Safari > 10 doesn't support `execCommand`, so instead we inform the user to copy manually.
* See http://clipboardjs.com/#browser-support
*/
function genericError(e) {
let key;
if (/Mac/i.test(navigator.userAgent)) {
key = '&#8984;'; // Command
} else {
key = 'Ctrl';
}
showTooltip(e.trigger, `Press ${key}-C to copy`);
}
export default function initCopyToClipboard() {
const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess);
clipboard.on('error', genericError);
/**
* This a workaround around ClipboardJS limitations to allow the context-specific copy/pasting
* of plain text or GFM. The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and
* `gfm` keys into the `data-clipboard-text` attribute that ClipboardJS reads from.
* When ClipboardJS creates a new `textarea` (directly inside `body`, with a `readonly`
* attribute`), sets its value to the value of this data attribute, focusses on it, and finally
* programmatically issues the 'Copy' command, this code intercepts the copy command/event at
* the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy
* data types to the intended values.
*/
$(document).on('copy', 'body > textarea[readonly]', (e) => {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const text = e.target.value;
let json;
try {
json = JSON.parse(text);
} catch (ex) {
return;
}
if (!json.text || !json.gfm) return;
e.preventDefault();
clipboardData.setData('text/plain', json.text);
clipboardData.setData('text/x-gfm', json.gfm);
});
}

View file

@ -1,6 +1,7 @@
import './autosize';
import './bind_in_out';
import initCopyAsGFM from './copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior';
import installGlEmojiElement from './gl_emoji';
import './quick_submit';
@ -9,3 +10,4 @@ import './toggler_behavior';
installGlEmojiElement();
initCopyAsGFM();
initCopyToClipboard();

View file

@ -5,6 +5,7 @@
// %button.js-toggle-button
// %div.js-toggle-content
//
import { getLocationHash } from '../lib/utils/url_utility';
$(() => {
function toggleContainer(container, toggleState) {
@ -32,7 +33,7 @@ $(() => {
// If we're accessing a permalink, ensure it is not inside a
// closed js-toggle-container!
const hash = window.gl.utils.getLocationHash();
const hash = getLocationHash();
const anchor = hash && document.getElementById(hash);
const container = anchor && $(anchor).closest('.js-toggle-container');

View file

@ -1,6 +1,6 @@
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
import Dropzone from 'dropzone';
import '../lib/utils/url_utility';
import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
@ -49,7 +49,7 @@ export default class BlobFileDropzone {
});
this.on('success', function (header, response) {
$('#modal-upload-blob').modal('hide');
window.gl.utils.visitUrl(response.filePath);
visitUrl(response.filePath);
});
this.on('maxfilesexceeded', function (file) {
dropzoneMessage.addClass(HIDDEN_CLASS);

View file

@ -1,7 +1,9 @@
import { getLocationHash } from '../lib/utils/url_utility';
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = gl.utils.getLocationHash();
const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;

View file

@ -20,6 +20,7 @@ class ListIssue {
this.isFetching = {
subscriptions: true,
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
@ -86,6 +87,10 @@ class ListIssue {
this.isFetching[key] = value;
}
setLoadingState(key, value) {
this.isLoading[key] = value;
}
update (url) {
const data = {
issue: {

View file

@ -48,6 +48,7 @@ export default class Clusters {
this.toggle = this.toggle.bind(this);
this.installApplication = this.installApplication.bind(this);
this.showToken = this.showToken.bind(this);
this.toggleButton = document.querySelector('.js-toggle-cluster');
this.toggleInput = document.querySelector('.js-toggle-input');
@ -56,6 +57,8 @@ export default class Clusters {
this.creatingContainer = document.querySelector('.js-cluster-creating');
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
initSettingsPanels();
this.initApplications();
@ -97,11 +100,13 @@ export default class Clusters {
addListeners() {
this.toggleButton.addEventListener('click', this.toggle);
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
}
removeListeners() {
this.toggleButton.removeEventListener('click', this.toggle);
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication);
}
@ -145,8 +150,18 @@ export default class Clusters {
}
toggle() {
this.toggleButton.classList.toggle('checked');
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('checked').toString());
this.toggleButton.classList.toggle('is-checked');
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('is-checked').toString());
}
showToken() {
const type = this.tokenField.getAttribute('type');
if (type === 'password') {
this.tokenField.setAttribute('type', 'text');
} else {
this.tokenField.setAttribute('type', 'password');
}
}
hideAll() {

View file

@ -0,0 +1,58 @@
import Flash from '../flash';
import { s__ } from '../locale';
import ClustersService from './services/clusters_service';
/**
* Toggles loading and disabled classes.
* @param {HTMLElement} button
*/
const toggleLoadingButton = (button) => {
if (button.getAttribute('disabled')) {
button.removeAttribute('disabled');
} else {
button.setAttribute('disabled', true);
}
button.classList.toggle('is-loading');
};
/**
* Toggles checked class for the given button
* @param {HTMLElement} button
*/
const toggleValue = (button) => {
button.classList.toggle('is-checked');
};
/**
* Handles toggle buttons in the cluster's table.
*
* When the user clicks the toggle button for each cluster, it:
* - toggles the button
* - shows a loading and disables button
* - Makes a put request to the given endpoint
* Once we receive the response, either:
* 1) Show updated status in case of successfull response
* 2) Show initial status in case of failed response
*/
export default function setClusterTableToggles() {
document.querySelectorAll('.js-toggle-cluster-list')
.forEach(button => button.addEventListener('click', (e) => {
const toggleButton = e.currentTarget;
const endpoint = toggleButton.getAttribute('data-endpoint');
toggleValue(toggleButton);
toggleLoadingButton(toggleButton);
const value = toggleButton.classList.contains('is-checked');
ClustersService.updateCluster(endpoint, { cluster: { enabled: value } })
.then(() => {
toggleLoadingButton(toggleButton);
})
.catch(() => {
toggleLoadingButton(toggleButton);
toggleValue(toggleButton);
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
});
}));
}

View file

@ -17,4 +17,8 @@ export default class ClusterService {
installApplication(appId) {
return axios.post(this.appInstallEndpointMap[appId]);
}
static updateCluster(endpoint, data) {
return axios.put(endpoint, data);
}
}

View file

@ -1,217 +1,208 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
import 'vendor/jquery.waitforimages';
(function() {
gl.ImageFile = (function() {
var prepareFrames;
// Width where images must fits in, for 2-up this gets divided by 2
const availWidth = 900;
const viewModes = ['two-up', 'swipe'];
// Width where images must fits in, for 2-up this gets divided by 2
ImageFile.availWidth = 900;
export default class ImageFile {
constructor(file) {
this.file = file;
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
return function(deletedWidth, deletedHeight) {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
_this.initViewModes();
ImageFile.viewModes = ['two-up', 'swipe'];
// Load two-up view after images are loaded
// so that we can display the correct width and height information
const $images = $('.two-up.view img', _this.file);
function ImageFile(file) {
this.file = file;
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
return function(deletedWidth, deletedHeight) {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
_this.initViewModes();
$images.waitForImages(function() {
_this.initView('two-up');
});
});
};
})(this));
}
// Load two-up view after images are loaded
// so that we can display the correct width and height information
const $images = $('.two-up.view img', _this.file);
initViewModes() {
const viewMode = viewModes[0];
$('.view-modes', this.file).removeClass('hide');
$('.view-modes-menu', this.file).on('click', 'li', (function(_this) {
return function(event) {
if (!$(event.currentTarget).hasClass('active')) {
return _this.activateViewMode(event.currentTarget.className);
}
};
})(this));
return this.activateViewMode(viewMode);
}
$images.waitForImages(function() {
_this.initView('two-up');
});
activateViewMode(viewMode) {
$('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active');
return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) {
return function() {
$(".view." + viewMode, _this.file).fadeIn(200);
return _this.initView(viewMode);
};
})(this));
}
initView(viewMode) {
return this.views[viewMode].call(this);
}
// eslint-disable-next-line class-methods-use-this
initDraggable($el, padding, callback) {
var dragging = false;
var $body = $('body');
var $offsetEl = $el.parent();
$el.off('mousedown').on('mousedown', function() {
dragging = true;
$body.css('user-select', 'none');
});
$body.off('mouseup').off('mousemove').on('mouseup', function() {
dragging = false;
$body.css('user-select', '');
})
.on('mousemove', function(e) {
var left;
if (!dragging) return;
left = e.pageX - ($offsetEl.offset().left + padding);
callback(e, left);
});
}
prepareFrames(view) {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
$('.frame', view).each((function(_this) {
return function(index, frame) {
var height, width;
width = $(frame).width();
height = $(frame).height();
maxWidth = width > maxWidth ? width : maxWidth;
return maxHeight = height > maxHeight ? height : maxHeight;
};
})(this)).css({
width: maxWidth,
height: maxHeight
});
return [maxWidth, maxHeight];
}
views = {
'two-up': function() {
return $('.two-up.view .wrap', this.file).each((function(_this) {
return function(index, wrap) {
$('img', wrap).each(function() {
var currentWidth;
currentWidth = $(this).width();
if (currentWidth > availWidth / 2) {
return $(this).width(availWidth / 2);
}
});
return _this.requestImageInfo($('img', wrap), function(width, height) {
$('.image-info .meta-width', wrap).text(width + "px");
$('.image-info .meta-height', wrap).text(height + "px");
return $('.image-info', wrap).removeClass('hide');
});
};
})(this));
},
'swipe': function() {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
$swipeFrame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
$swipeWrap.css({
width: maxWidth + 1,
height: maxHeight + 2
});
// Set swipeBar left position to match image frame
$swipeBar.css({
left: 1
});
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, function(e, left) {
if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
$swipeWrap.width((maxWidth + 1) - left);
$swipeBar.css('left', left);
}
});
};
})(this));
},
'onion-skin': function() {
var dragTrackWidth, maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
$dragger = $('.dragger', $track);
$frame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
$('.swipe-wrap', view).css({
width: maxWidth + 1,
height: maxHeight + 2
});
$dragger.css({
left: dragTrackWidth
});
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
_this.initDraggable($dragger, framePadding, function(e, left) {
var opacity = left / dragTrackWidth;
if (opacity >= 0 && opacity <= 1) {
$dragger.css('left', left);
$frameAdded.css('opacity', opacity);
}
});
};
})(this));
}
}
ImageFile.prototype.initViewModes = function() {
var viewMode;
viewMode = ImageFile.viewModes[0];
$('.view-modes', this.file).removeClass('hide');
$('.view-modes-menu', this.file).on('click', 'li', (function(_this) {
return function(event) {
if (!$(event.currentTarget).hasClass('active')) {
return _this.activateViewMode(event.currentTarget.className);
}
};
})(this));
return this.activateViewMode(viewMode);
};
ImageFile.prototype.activateViewMode = function(viewMode) {
$('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active');
return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) {
return function() {
$(".view." + viewMode, _this.file).fadeIn(200);
return _this.initView(viewMode);
};
})(this));
};
ImageFile.prototype.initView = function(viewMode) {
return this.views[viewMode].call(this);
};
ImageFile.prototype.initDraggable = function($el, padding, callback) {
var dragging = false;
var $body = $('body');
var $offsetEl = $el.parent();
$el.off('mousedown').on('mousedown', function() {
dragging = true;
$body.css('user-select', 'none');
});
$body.off('mouseup').off('mousemove').on('mouseup', function() {
dragging = false;
$body.css('user-select', '');
})
.on('mousemove', function(e) {
var left;
if (!dragging) return;
left = e.pageX - ($offsetEl.offset().left + padding);
callback(e, left);
});
};
prepareFrames = function(view) {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
$('.frame', view).each((function(_this) {
return function(index, frame) {
var height, width;
width = $(frame).width();
height = $(frame).height();
maxWidth = width > maxWidth ? width : maxWidth;
return maxHeight = height > maxHeight ? height : maxHeight;
};
})(this)).css({
width: maxWidth,
height: maxHeight
});
return [maxWidth, maxHeight];
};
ImageFile.prototype.views = {
'two-up': function() {
return $('.two-up.view .wrap', this.file).each((function(_this) {
return function(index, wrap) {
$('img', wrap).each(function() {
var currentWidth;
currentWidth = $(this).width();
if (currentWidth > ImageFile.availWidth / 2) {
return $(this).width(ImageFile.availWidth / 2);
}
});
return _this.requestImageInfo($('img', wrap), function(width, height) {
$('.image-info .meta-width', wrap).text(width + "px");
$('.image-info .meta-height', wrap).text(height + "px");
return $('.image-info', wrap).removeClass('hide');
});
};
})(this));
},
'swipe': function() {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
$swipeFrame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
$swipeWrap.css({
width: maxWidth + 1,
height: maxHeight + 2
});
// Set swipeBar left position to match image frame
$swipeBar.css({
left: 1
});
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, function(e, left) {
if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
$swipeWrap.width((maxWidth + 1) - left);
$swipeBar.css('left', left);
}
});
};
})(this));
},
'onion-skin': function() {
var dragTrackWidth, maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
$dragger = $('.dragger', $track);
$frame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
$('.swipe-wrap', view).css({
width: maxWidth + 1,
height: maxHeight + 2
});
$dragger.css({
left: dragTrackWidth
});
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
_this.initDraggable($dragger, framePadding, function(e, left) {
var opacity = left / dragTrackWidth;
if (opacity >= 0 && opacity <= 1) {
$dragger.css('left', left);
$frameAdded.css('opacity', opacity);
}
});
requestImageInfo(img, callback) {
const domImg = img.get(0);
if (domImg) {
if (domImg.complete) {
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
} else {
return img.on('load', (function(_this) {
return function() {
return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
};
})(this));
}
};
ImageFile.prototype.requestImageInfo = function(img, callback) {
var domImg;
domImg = img.get(0);
if (domImg) {
if (domImg.complete) {
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
} else {
return img.on('load', (function(_this) {
return function() {
return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
};
})(this));
}
}
};
return ImageFile;
})();
}).call(window);
}
}
}

View file

@ -4,6 +4,7 @@
/* global Pager */
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
export default (function () {
const CommitsList = {};
@ -91,7 +92,7 @@ export default (function () {
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
}
gl.utils.localTimeAgo($processedData.find('.js-timeago'));
localTimeAgo($processedData.find('.js-timeago'));
return processedData;
};

View file

@ -3,3 +3,4 @@ import './polyfills';
import './jquery';
import './bootstrap';
import './vue';
import '../lib/utils/axios_utils';

View file

@ -1,7 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
import { localTimeAgo } from './lib/utils/datetime_utility';
window.Compare = (function() {
function Compare(opts) {
export default class Compare {
constructor(opts) {
this.opts = opts;
this.source_loading = $(".js-source-loading");
this.target_loading = $(".js-target-loading");
@ -34,12 +35,12 @@ window.Compare = (function() {
this.initialState();
}
Compare.prototype.initialState = function() {
initialState() {
this.getSourceHtml();
return this.getTargetHtml();
};
this.getTargetHtml();
}
Compare.prototype.getTargetProject = function() {
getTargetProject() {
return $.ajax({
url: this.opts.targetProjectUrl,
data: {
@ -52,22 +53,22 @@ window.Compare = (function() {
return $('.js-target-branch-dropdown .dropdown-content').html(html);
}
});
};
}
Compare.prototype.getSourceHtml = function() {
return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
getSourceHtml() {
return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
ref: $("input[name='merge_request[source_branch]']").val()
});
};
}
Compare.prototype.getTargetHtml = function() {
return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
getTargetHtml() {
return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
ref: $("input[name='merge_request[target_branch]']").val()
});
};
}
Compare.prototype.sendAjax = function(url, loading, target, data) {
static sendAjax(url, loading, target, data) {
var $target;
$target = $(target);
return $.ajax({
@ -81,10 +82,8 @@ window.Compare = (function() {
loading.hide();
$target.html(html);
var className = '.' + $target[0].className.replace(' ', '.');
gl.utils.localTimeAgo($('.js-timeago', className));
localTimeAgo($('.js-timeago', className));
}
});
};
return Compare;
})();
}
}

View file

@ -1,68 +1,60 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
window.CompareAutocomplete = (function() {
function CompareAutocomplete() {
this.initDropdown();
}
CompareAutocomplete.prototype.initDropdown = function() {
return $('.js-compare-dropdown').each(function() {
var $dropdown, selected;
$dropdown = $(this);
selected = $dropdown.data('selected');
const $dropdownContainer = $dropdown.closest('.dropdown');
const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown.glDropdown({
data: function(term, callback) {
return $.ajax({
url: $dropdown.data('refs-url'),
data: {
ref: $dropdown.data('ref'),
search: term,
}
}).done(function(refs) {
return callback(refs);
});
},
selectable: true,
filterable: true,
filterRemote: true,
fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
var link;
if (ref.header != null) {
return $('<li />').addClass('dropdown-header').text(ref.header);
} else {
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
return $('<li />').append(link);
export default function initCompareAutocomplete() {
$('.js-compare-dropdown').each(function() {
var $dropdown, selected;
$dropdown = $(this);
selected = $dropdown.data('selected');
const $dropdownContainer = $dropdown.closest('.dropdown');
const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown.glDropdown({
data: function(term, callback) {
return $.ajax({
url: $dropdown.data('refs-url'),
data: {
ref: $dropdown.data('ref'),
search: term,
}
},
id: function(obj, $el) {
return $el.attr('data-ref');
},
toggleLabel: function(obj, $el) {
return $el.text().trim();
}).done(function(refs) {
return callback(refs);
});
},
selectable: true,
filterable: true,
filterRemote: true,
fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
var link;
if (ref.header != null) {
return $('<li />').addClass('dropdown-header').text(ref.header);
} else {
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
return $('<li />').append(link);
}
});
$filterInput.on('keyup', (e) => {
const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return;
const text = $filterInput.val();
$fieldInput.val(text);
$('.dropdown-toggle-text', $dropdown).text(text);
$dropdownContainer.removeClass('open');
});
$dropdownContainer.on('click', '.dropdown-content a', (e) => {
$dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
if ($dropdown.hasClass('has-tooltip')) {
$dropdown.tooltip('fixTitle');
}
});
},
id: function(obj, $el) {
return $el.attr('data-ref');
},
toggleLabel: function(obj, $el) {
return $el.text().trim();
}
});
$filterInput.on('keyup', (e) => {
const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return;
const text = $filterInput.val();
$fieldInput.val(text);
$('.dropdown-toggle-text', $dropdown).text(text);
$dropdownContainer.removeClass('open');
});
};
return CompareAutocomplete;
})();
$dropdownContainer.on('click', '.dropdown-content a', (e) => {
$dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
if ($dropdown.hasClass('has-tooltip')) {
$dropdown.tooltip('fixTitle');
}
});
});
}

View file

@ -9,7 +9,7 @@ export default class ContextualSidebar {
}
initDomElements() {
this.$page = $('.page-with-sidebar');
this.$page = $('.layout-page');
this.$sidebar = $('.nav-sidebar');
this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar);
this.$overlay = $('.mobile-overlay');
@ -28,7 +28,7 @@ export default class ContextualSidebar {
this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
this.$overlay.on('click', () => this.toggleSidebarNav(false));
this.$sidebarToggle.on('click', () => {
const value = !this.$sidebar.hasClass('sidebar-icons-only');
const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop');
this.toggleCollapsedSidebar(value);
});
@ -43,16 +43,16 @@ export default class ContextualSidebar {
}
toggleSidebarNav(show) {
this.$sidebar.toggleClass('nav-sidebar-expanded', show);
this.$sidebar.toggleClass('sidebar-expanded-mobile', show);
this.$overlay.toggleClass('mobile-nav-open', show);
this.$sidebar.removeClass('sidebar-icons-only');
this.$sidebar.removeClass('sidebar-collapsed-desktop');
}
toggleCollapsedSidebar(collapsed) {
const breakpoint = bp.getBreakpointSize();
if (this.$sidebar.length) {
this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
}
ContextualSidebar.setCollapsedCookie(collapsed);

View file

@ -1,74 +0,0 @@
/* 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 */
import Clipboard from 'vendor/clipboard';
var genericError, genericSuccess, showTooltip;
genericSuccess = function(e) {
showTooltip(e.trigger, 'Copied');
// Clear the selection and blur the trigger so it loses its border
e.clearSelection();
return $(e.trigger).blur();
};
// Safari doesn't support `execCommand`, so instead we inform the user to
// copy manually.
//
// See http://clipboardjs.com/#browser-support
genericError = function(e) {
var key;
if (/Mac/i.test(navigator.userAgent)) {
key = '&#8984;'; // Command
} else {
key = 'Ctrl';
}
return showTooltip(e.trigger, "Press " + key + "-C to copy");
};
showTooltip = function(target, title) {
var $target = $(target);
var originalTitle = $target.data('original-title');
if (!$target.data('hideTooltip')) {
$target
.attr('title', 'Copied')
.tooltip('fixTitle')
.tooltip('show')
.attr('title', originalTitle)
.tooltip('fixTitle');
}
};
$(function() {
const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess);
clipboard.on('error', genericError);
// This a workaround around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
// The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and `gfm` keys into the `data-clipboard-text`
// attribute that ClipboardJS reads from.
// When ClipboardJS creates a new `textarea` (directly inside `body`, with a `readonly` attribute`), sets its value
// to the value of this data attribute, focusses on it, and finally programmatically issues the 'Copy' command,
// this code intercepts the copy command/event at the last minute to deconstruct this JSON hash and set the
// `text/plain` and `text/x-gfm` copy data types to the intended values.
$(document).on('copy', 'body > textarea[readonly]', function(e) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const text = e.target.value;
let json;
try {
json = JSON.parse(text);
} catch (ex) {
return;
}
if (!json.text || !json.gfm) return;
e.preventDefault();
clipboardData.setData('text/plain', json.text);
clipboardData.setData('text/x-gfm', json.gfm);
});
});

View file

@ -32,7 +32,9 @@
doAction() {
this.isLoading = true;
eventHub.$emit(`${this.type}.key`, this.deployKey);
eventHub.$emit(`${this.type}.key`, this.deployKey, () => {
this.isLoading = false;
});
},
},
computed: {
@ -50,6 +52,9 @@
:disabled="isLoading"
@click="doAction">
{{ text }}
<loading-icon v-if="isLoading" />
<loading-icon
v-if="isLoading"
:inline="true"
/>
</button>
</template>

View file

@ -47,12 +47,15 @@
.then(() => this.fetchKeys())
.catch(() => new Flash('Error enabling deploy key'));
},
disableKey(deployKey) {
disableKey(deployKey, callback) {
// eslint-disable-next-line no-alert
if (confirm('You are going to remove this deploy key. Are you sure?')) {
this.service.disableKey(deployKey.id)
.then(() => this.fetchKeys())
.then(callback)
.catch(() => new Flash('Error removing deploy key'));
} else {
callback();
}
},
},

View file

@ -1,5 +1,6 @@
<script>
import actionBtn from './action_btn.vue';
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
props: {
@ -21,7 +22,7 @@
},
computed: {
timeagoDate() {
return gl.utils.getTimeago().format(this.deployKey.created_at);
return getTimeago().format(this.deployKey.created_at);
},
editDeployKeyPath() {
return `${this.endpoint}/${this.deployKey.id}/edit`;

View file

@ -1,4 +1,4 @@
import './lib/utils/url_utility';
import { getLocationHash } from './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff';
import imageDiffHelper from './image_diff/helpers/index';
@ -31,7 +31,7 @@ export default class Diff {
isBound = true;
}
if (gl.utils.getLocationHash()) {
if (getLocationHash()) {
this.highlightSelectedLine();
}
@ -73,7 +73,7 @@ export default class Diff {
}
openAnchoredDiff(cb) {
const locationHash = gl.utils.getLocationHash();
const locationHash = getLocationHash();
const anchoredDiff = locationHash && locationHash.split('_')[0];
if (!anchoredDiff) return;
@ -128,7 +128,7 @@ export default class Diff {
}
// eslint-disable-next-line class-methods-use-this
highlightSelectedLine() {
const hash = gl.utils.getLocationHash();
const hash = getLocationHash();
const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll');

View file

@ -16,7 +16,8 @@ import './components/diff_note_avatars';
import './components/new_issue_for_discussion';
$(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath;
const projectPathHolder = document.querySelector('.merge-request') || document.querySelector('.commit-box');
const projectPath = projectPathHolder.dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {};

View file

@ -2,6 +2,7 @@
/* global NoteModel */
import Vue from 'vue';
import { localTimeAgo } from '../../lib/utils/datetime_utility';
class DiscussionModel {
constructor (discussionId) {
@ -71,7 +72,7 @@ class DiscussionModel {
$(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html);
}
gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`));
localTimeAgo($('.js-timeago', `${discussionSelector}`));
} else {
$discussionHeadline.remove();
}

View file

@ -43,7 +43,7 @@ class ResolveServiceClass {
discussion.resolveAllNotes(resolvedBy);
}
gl.mrWidget.checkStatus();
if (gl.mrWidget) gl.mrWidget.checkStatus();
discussion.updateHeadline(data);
})
.catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));

View file

@ -15,22 +15,22 @@ import GroupLabelSubscription from './group_label_subscription';
import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
/* global Search */
/* global Admin */
import Search from './search';
import initAdmin from './admin';
import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
/* global MergeRequest */
/* global Compare */
/* global CompareAutocomplete */
/* global ProjectFindFile */
import Compare from './compare';
import initCompareAutocomplete from './compare_autocomplete';
import ProjectFindFile from './project_find_file';
import ProjectNew from './project_new';
import projectImport from './project_import';
import Labels from './labels';
import LabelManager from './label_manager';
/* global Sidebar */
import IssuableTemplateSelectors from './templates/issuable_template_selectors';
import Flash from './flash';
import CommitsList from './commits';
import Issue from './issue';
@ -91,6 +91,8 @@ import DueDateSelectors from './due_date_select';
import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription';
import ProjectVariables from './project_variables';
import SearchAutocomplete from './search_autocomplete';
import Activities from './activities';
(function() {
var Dispatcher;
@ -264,7 +266,7 @@ import ProjectVariables from './project_variables';
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
new gl.IssuableTemplateSelectors();
new IssuableTemplateSelectors();
break;
case 'projects:merge_requests:creations:new':
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
@ -288,7 +290,7 @@ import ProjectVariables from './project_variables';
new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
new gl.IssuableTemplateSelectors();
new IssuableTemplateSelectors();
new AutoWidthDropdownSelect($('.js-target-branch-select')).init();
break;
case 'projects:tags:new':
@ -298,18 +300,21 @@ import ProjectVariables from './project_variables';
break;
case 'projects:snippets:show':
initNotes();
new ZenMode();
break;
case 'projects:snippets:new':
case 'projects:snippets:edit':
case 'projects:snippets:create':
case 'projects:snippets:update':
new GLForm($('.snippet-form'), true);
new ZenMode();
break;
case 'snippets:new':
case 'snippets:edit':
case 'snippets:create':
case 'snippets:update':
new GLForm($('.snippet-form'), false);
new ZenMode();
break;
case 'projects:releases:edit':
new ZenMode();
@ -330,7 +335,7 @@ import ProjectVariables from './project_variables';
shortcut_handler = new ShortcutsIssuable(true);
break;
case 'dashboard:activity':
new gl.Activities();
new Activities();
break;
case 'projects:commit:show':
new Diff();
@ -351,7 +356,7 @@ import ProjectVariables from './project_variables';
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:activity':
new gl.Activities();
new Activities();
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:commits:show':
@ -369,7 +374,7 @@ import ProjectVariables from './project_variables';
if ($('#tree-slider').length) new TreeView();
if ($('.blob-viewer').length) new BlobViewer();
if ($('.project-show-activity').length) new gl.Activities();
if ($('.project-show-activity').length) new Activities();
$('#tree-slider').waitForImages(function() {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
@ -403,7 +408,7 @@ import ProjectVariables from './project_variables';
});
break;
case 'groups:activity':
new gl.Activities();
new Activities();
break;
case 'groups:show':
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
@ -522,13 +527,6 @@ import ProjectVariables from './project_variables';
case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels
initSettingsPanels();
import(/* webpackChunkName: "ci-cd-settings" */ './projects/ci_cd_settings_bundle')
.then(ciCdSettings => ciCdSettings.default())
.catch((err) => {
Flash(s__('ProjectSettings|Problem setting up the CI/CD settings JavaScript'));
throw err;
});
case 'groups:settings:ci_cd:show':
new ProjectVariables();
break;
@ -546,6 +544,7 @@ import ProjectVariables from './project_variables';
new LineHighlighter();
new BlobViewer();
initNotes();
new ZenMode();
break;
case 'import:fogbugz:new_user_map':
new UsersSelect();
@ -558,7 +557,15 @@ import ProjectVariables from './project_variables';
import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
.then(cluster => new cluster.default()) // eslint-disable-line new-cap
.catch((err) => {
Flash(s__('ClusterIntegration|Problem setting up the cluster JavaScript'));
Flash(s__('ClusterIntegration|Problem setting up the cluster'));
throw err;
});
break;
case 'projects:clusters:index':
import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index')
.then(clusterIndex => clusterIndex.default())
.catch((err) => {
Flash(s__('ClusterIntegration|Problem setting up the clusters list'));
throw err;
});
break;
@ -578,7 +585,7 @@ import ProjectVariables from './project_variables';
// needed in rspec
gl.u2fAuthenticate = u2fAuthenticate;
case 'admin':
new Admin();
initAdmin();
switch (path[1]) {
case 'broadcast_messages':
initBroadcastMessagesForm();
@ -617,7 +624,7 @@ import ProjectVariables from './project_variables';
projectAvatar();
switch (path[1]) {
case 'compare':
new CompareAutocomplete();
initCompareAutocomplete();
break;
case 'edit':
shortcut_handler = new ShortcutsNavigation();
@ -678,7 +685,7 @@ import ProjectVariables from './project_variables';
Dispatcher.prototype.initSearch = function() {
// Only when search form is present
if ($('.search').length) {
return new gl.SearchAutocomplete();
return new SearchAutocomplete();
}
};

View file

@ -1,3 +1,4 @@
import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root';
@ -566,7 +567,7 @@ class FilteredSearchManager {
if (this.updateObject) {
this.updateObject(parameterizedUrl);
} else {
gl.utils.visitUrl(parameterizedUrl);
visitUrl(parameterizedUrl);
}
}

View file

@ -21,7 +21,7 @@ let headerHeight = 50;
export const getHeaderHeight = () => headerHeight;
export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-icons-only');
export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
export const canShowActiveSubItems = (el) => {
if (el.classList.contains('active') && !isSidebarCollapsed()) {

View file

@ -287,6 +287,10 @@ class GfmAutoComplete {
}
setupLabels($input) {
const fetchData = this.fetchData.bind(this);
const LABEL_COMMAND = { LABEL: '/label', UNLABEL: '/unlabel', RELABEL: '/relabel' };
let command = '';
$input.atwho({
at: '~',
alias: 'labels',
@ -309,8 +313,45 @@ class GfmAutoComplete {
title: sanitize(m.title),
color: m.color,
search: m.title,
set: m.set,
}));
},
matcher(flag, subtext) {
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
const subtextNodes = subtext.split(/\n+/g).pop().split(GfmAutoComplete.regexSubtext);
// Check if ~ is followed by '/label', '/relabel' or '/unlabel' commands.
command = subtextNodes.find((node) => {
if (node === LABEL_COMMAND.LABEL ||
node === LABEL_COMMAND.RELABEL ||
node === LABEL_COMMAND.UNLABEL) { return node; }
return null;
});
return match && match.length ? match[1] : null;
},
filter(query, data, searchKey) {
if (GfmAutoComplete.isLoading(data)) {
fetchData(this.$inputor, this.at);
return data;
}
if (data === GfmAutoComplete.defaultLoadingData) {
return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
}
// The `LABEL_COMMAND.RELABEL` is intentionally skipped
// because we want to return all the labels (unfiltered) for that command.
if (command === LABEL_COMMAND.LABEL) {
// Return labels with set: undefined.
return data.filter(label => !label.set);
} else if (command === LABEL_COMMAND.UNLABEL) {
// Return labels with set: true.
return data.filter(label => label.set);
}
return data;
},
},
});
}
@ -346,20 +387,7 @@ class GfmAutoComplete {
return resultantValue;
},
matcher(flag, subtext) {
// The below is taken from At.js source
// Tweaked to commands to start without a space only if char before is a non-word character
// https://github.com/ichord/At.js
const atSymbolsWithBar = Object.keys(this.app.controllers).join('|');
const atSymbolsWithoutBar = Object.keys(this.app.controllers).join('');
const targetSubtext = subtext.split(/\s+/g).pop();
const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
const accentAChar = decodeURI('%C3%80');
const accentYChar = decodeURI('%C3%BF');
const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
const match = regexp.exec(targetSubtext);
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
if (match) {
return match[1];
@ -420,8 +448,27 @@ class GfmAutoComplete {
return dataToInspect &&
(dataToInspect === loadingState || dataToInspect.name === loadingState);
}
static defaultMatcher(flag, subtext, controllers) {
// The below is taken from At.js source
// Tweaked to commands to start without a space only if char before is a non-word character
// https://github.com/ichord/At.js
const atSymbolsWithBar = Object.keys(controllers).join('|');
const atSymbolsWithoutBar = Object.keys(controllers).join('');
const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop();
const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
const accentAChar = decodeURI('%C3%80');
const accentYChar = decodeURI('%C3%BF');
const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
return regexp.exec(targetSubtext);
}
}
GfmAutoComplete.regexSubtext = new RegExp(/\s+/g);
GfmAutoComplete.defaultLoadingData = ['loading'];
GfmAutoComplete.atTypeMap = {

View file

@ -2,6 +2,7 @@
/* global fuzzaldrinPlus */
import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { visitUrl } from './lib/utils/url_utility';
import { isObject } from './lib/utils/type_utility';
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput;
@ -514,10 +515,11 @@ GitLabDropdown = (function() {
const dropdownToggle = this.dropdown.find('.dropdown-menu-toggle');
const hasFilterBulkUpdate = dropdownToggle.hasClass('js-filter-bulk-update');
const shouldRefreshOnOpen = dropdownToggle.hasClass('js-gl-dropdown-refresh-on-open');
const hasMultiSelect = dropdownToggle.hasClass('js-multiselect');
// Makes indeterminate items effective
if (this.fullData && hasFilterBulkUpdate) {
if (this.fullData && (shouldRefreshOnOpen || hasFilterBulkUpdate)) {
this.parseData(this.fullData);
}
@ -851,7 +853,7 @@ GitLabDropdown = (function() {
if ($el.length) {
var href = $el.attr('href');
if (href && href !== '#') {
gl.utils.visitUrl(href);
visitUrl(href);
} else {
$el.trigger('click');
}

View file

@ -5,7 +5,7 @@ import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants';
import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue';
export default {
@ -93,7 +93,7 @@ export default {
this.isLoading = false;
$.scrollTo(0);
const currentPath = gl.utils.mergeUrlParams({ page }, window.location.href);
const currentPath = mergeUrlParams({ page }, window.location.href);
window.history.replaceState({
page: currentPath,
}, document.title, currentPath);

View file

@ -1,4 +1,6 @@
<script>
import { visitUrl } from '../../lib/utils/url_utility';
import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub';
@ -8,6 +10,9 @@ import itemStats from './item_stats.vue';
import itemActions from './item_actions.vue';
export default {
directives: {
tooltip,
},
components: {
identicon,
itemCaret,
@ -56,7 +61,7 @@ export default {
if (this.hasChildren) {
eventHub.$emit('toggleChildren', this.group);
} else {
gl.utils.visitUrl(this.group.relativePath);
visitUrl(this.group.relativePath);
}
}
},
@ -112,19 +117,30 @@ export default {
</a>
</div>
<div
class="title">
class="title namespace-title">
<a
v-tooltip
:href="group.relativePath"
class="no-expand">{{group.fullName}}</a>
:title="group.fullName"
class="no-expand"
data-placement="top"
>{{
// ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending
group.name
}}</a>
<span
v-if="group.permission"
class="access-type"
class="user-access-role"
>
{{s__('GroupsTreeRole|as')}} {{group.permission}}
{{group.permission}}
</span>
</div>
<div
class="description">{{group.description}}</div>
v-if="group.description"
class="description">
{{group.description}}
</div>
</div>
<group-folder
v-if="group.isOpen && hasChildren"

View file

@ -1,7 +1,7 @@
<script>
import { s__ } from '../../locale';
import tooltip from '../../vue_shared/directives/tooltip';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import modal from '../../vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
import Icon from '../../vue_shared/components/icon.vue';
@ -9,7 +9,7 @@ import Icon from '../../vue_shared/components/icon.vue';
export default {
components: {
Icon,
PopupDialog,
modal,
},
directives: {
tooltip,
@ -27,7 +27,7 @@ export default {
},
data() {
return {
dialogStatus: false,
modalStatus: false,
};
},
computed: {
@ -43,10 +43,10 @@ export default {
},
methods: {
onLeaveGroup() {
this.dialogStatus = true;
this.modalStatus = true;
},
leaveGroup(leaveConfirmed) {
this.dialogStatus = false;
this.modalStatus = false;
if (leaveConfirmed) {
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}
@ -82,8 +82,8 @@ export default {
class="fa fa-sign-out"
aria-hidden="true"/>
</a>
<popup-dialog
v-show="dialogStatus"
<modal
v-show="modalStatus"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"

View file

@ -1,3 +1,4 @@
import { visitUrl } from '../lib/utils/url_utility';
import DropLab from '../droplab/drop_lab';
import ISetter from '../droplab/plugins/input_setter';
@ -54,9 +55,9 @@ export default class NewGroupChild {
onClickNewGroupChildButton(e) {
if (e.target.dataset.action === NEW_PROJECT) {
gl.utils.visitUrl(this.newGroupPath);
visitUrl(this.newGroupPath);
} else if (e.target.dataset.action === NEW_SUBGROUP) {
gl.utils.visitUrl(this.subgroupPath);
visitUrl(this.subgroupPath);
}
}
}

View file

@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
}
export function addImageCommentBadge(containerEl, { coordinate, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']);
const iconEl = document.createElement('i');
iconEl.className = 'fa fa-comment-o';
iconEl.setAttribute('aria-label', 'comment');
const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge']);
buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
buttonEl.appendChild(iconEl);
containerEl.appendChild(buttonEl);
}

View file

@ -1,7 +1,7 @@
import ImageBadge from '../image_badge';
import ImageDiff from '../image_diff';
import ReplacedImageDiff from '../replaced_image_diff';
import '../../commit/image_file';
import ImageFile from '../../commit/image_file';
export function resizeCoordinatesToImageElement(imageEl, meta) {
const { x, y, width, height } = meta;
@ -81,7 +81,7 @@ export function initImageDiff(fileEl, canCreateNote, renderCommentBadge) {
// ImageFile needs to be invoked before initImageDiff so that badges
// can mount to the correct location
new gl.ImageFile(fileEl); // eslint-disable-line no-new
new ImageFile(fileEl); // eslint-disable-line no-new
if (fileEl.querySelector('.diff-file .js-single-image')) {
diff = new ImageDiff(fileEl, options);

View file

@ -21,7 +21,7 @@ export default class IssuableBulkUpdateSidebar {
}
initDomElements() {
this.$page = $('.page-with-sidebar');
this.$page = $('.layout-page');
this.$sidebar = $('.right-sidebar');
this.$sidebarInnerContainer = this.$sidebar.find('.issuable-sidebar');
this.$bulkEditCancelBtn = $('.js-bulk-update-menu-hide');

View file

@ -28,7 +28,7 @@ export default class IssuableIndex {
url: $('.incoming-email-token-reset').attr('href'),
dataType: 'json',
success(response) {
$('#issue_email').val(response.new_issue_address).focus();
$('#issuable_email').val(response.new_address).focus();
},
beforeSend() {
$('.incoming-email-token-reset').text('resetting...');

View file

@ -8,18 +8,7 @@ import IssuablesHelper from './helpers/issuables_helper';
export default class Issue {
constructor() {
if ($('a.btn-close').length) {
this.taskList = new TaskList({
dataType: 'issue',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
});
this.initIssueBtnEventListeners();
}
if ($('a.btn-close').length) this.initIssueBtnEventListeners();
Issue.$btnNewBranch = $('#new-branch');
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
@ -59,7 +48,7 @@ export default class Issue {
})
.fail(() => new Flash(issueFailMessage))
.done((data) => {
const isClosedBadge = $('div.status-box-closed');
const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');

View file

@ -1,5 +1,6 @@
<script>
import Visibility from 'visibilityjs';
import { visitUrl } from '../../lib/utils/url_utility';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import Service from '../services/index';
@ -8,7 +9,7 @@ import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
import editedComponent from './edited.vue';
import formComponent from './form.vue';
import '../../lib/utils/url_utility';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default {
props: {
@ -149,6 +150,11 @@ export default {
editedComponent,
formComponent,
},
mixins: [
recaptchaModalImplementor,
],
methods: {
openForm() {
if (!this.showForm) {
@ -164,12 +170,14 @@ export default {
closeForm() {
this.showForm = false;
},
updateIssuable() {
this.service.updateIssuable(this.store.formState)
.then(res => res.json())
.then(data => this.checkForSpam(data))
.then((data) => {
if (location.pathname !== data.web_url) {
gl.utils.visitUrl(data.web_url);
visitUrl(data.web_url);
}
return this.service.getData();
@ -179,11 +187,24 @@ export default {
this.store.updateState(data);
eventHub.$emit('close.form');
})
.catch(() => {
eventHub.$emit('close.form');
window.Flash(`Error updating ${this.issuableType}`);
.catch((error) => {
if (error && error.name === 'SpamError') {
this.openRecaptcha();
} else {
eventHub.$emit('close.form');
window.Flash(`Error updating ${this.issuableType}`);
}
});
},
closeRecaptchaModal() {
this.store.setFormState({
updateLoading: false,
});
this.closeRecaptcha();
},
deleteIssuable() {
this.service.deleteIssuable()
.then(res => res.json())
@ -191,7 +212,7 @@ export default {
// Stop the poll so we don't get 404's with the issuable not existing
this.poll.stop();
gl.utils.visitUrl(data.web_url);
visitUrl(data.web_url);
})
.catch(() => {
eventHub.$emit('close.form');
@ -237,9 +258,9 @@ export default {
</script>
<template>
<div>
<div>
<div v-if="canUpdate && showForm">
<form-component
v-if="canUpdate && showForm"
:form-state="formState"
:can-destroy="canDestroy"
:issuable-templates="issuableTemplates"
@ -251,30 +272,37 @@ export default {
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/>
<div v-else>
<title-component
:issuable-ref="issuableRef"
:can-update="canUpdate"
:title-html="state.titleHtml"
:title-text="state.titleText"
:show-inline-edit-button="showInlineEditButton"
/>
<description-component
v-if="state.descriptionHtml"
:can-update="canUpdate"
:description-html="state.descriptionHtml"
:description-text="state.descriptionText"
:updated-at="state.updatedAt"
:task-status="state.taskStatus"
:issuable-type="issuableType"
:update-url="updateEndpoint"
/>
<edited-component
v-if="hasUpdated"
:updated-at="state.updatedAt"
:updated-by-name="state.updatedByName"
:updated-by-path="state.updatedByPath"
/>
</div>
<recaptcha-modal
v-show="showRecaptcha"
:html="recaptchaHTML"
@close="closeRecaptchaModal"
/>
</div>
<div v-else>
<title-component
:issuable-ref="issuableRef"
:can-update="canUpdate"
:title-html="state.titleHtml"
:title-text="state.titleText"
:show-inline-edit-button="showInlineEditButton"
/>
<description-component
v-if="state.descriptionHtml"
:can-update="canUpdate"
:description-html="state.descriptionHtml"
:description-text="state.descriptionText"
:updated-at="state.updatedAt"
:task-status="state.taskStatus"
:issuable-type="issuableType"
:update-url="updateEndpoint"
/>
<edited-component
v-if="hasUpdated"
:updated-at="state.updatedAt"
:updated-by-name="state.updatedByName"
:updated-by-path="state.updatedByPath"
/>
</div>
</div>
</template>

View file

@ -1,9 +1,14 @@
<script>
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default {
mixins: [animateMixin],
mixins: [
animateMixin,
recaptchaModalImplementor,
],
props: {
canUpdate: {
type: Boolean,
@ -51,6 +56,7 @@
this.updateTaskStatusText();
},
},
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
@ -61,9 +67,19 @@
dataType: this.issuableType,
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: this.taskListUpdateSuccess.bind(this),
});
}
},
taskListUpdateSuccess(data) {
try {
this.checkForSpam(data);
} catch (error) {
if (error && error.name === 'SpamError') this.openRecaptcha();
}
},
updateTaskStatusText() {
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
const $issuableHeader = $('.issuable-meta');
@ -109,5 +125,11 @@
:data-update-url="updateUrl"
>
</textarea>
<recaptcha-modal
v-show="showRecaptcha"
:html="recaptchaHTML"
@close="closeRecaptcha"
/>
</div>
</template>

View file

@ -53,7 +53,7 @@
<textarea
id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area"
data-supports-quick-actionss="false"
data-supports-quick-actions="false"
aria-label="Description"
v-model="formState.description"
ref="textarea"

View file

@ -1,4 +1,6 @@
<script>
import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
export default {
props: {
formState: {
@ -32,7 +34,7 @@
};
editor.getValue = () => this.formState.description;
this.issuableTemplate = new gl.IssuableTemplateSelectors({
this.issuableTemplate = new IssuableTemplateSelectors({
$dropdowns: $(this.$refs.toggle),
editor,
});

View file

@ -5,9 +5,9 @@ import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const initialData = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => {
$('.js-issuable-edit').on('click', (e) => {
e.preventDefault();
eventHub.$emit('open.form');
@ -18,32 +18,9 @@ document.addEventListener('DOMContentLoaded', () => {
components: {
issuableApp,
},
data() {
return {
...initialData,
};
},
render(createElement) {
return createElement('issuable-app', {
props: {
canUpdate: this.canUpdate,
canDestroy: this.canDestroy,
endpoint: this.endpoint,
issuableRef: this.issuableRef,
initialTitleHtml: this.initialTitleHtml,
initialTitleText: this.initialTitleText,
initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText,
issuableTemplates: this.issuableTemplates,
markdownPreviewPath: this.markdownPreviewPath,
markdownDocsPath: this.markdownDocsPath,
projectPath: this.projectPath,
projectNamespace: this.projectNamespace,
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,
updatedByPath: this.updatedByPath,
initialTaskStatus: this.initialTaskStatus,
},
props,
});
},
});

View file

@ -1,7 +1,9 @@
import _ from 'underscore';
import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
import { timeFor } from './lib/utils/datetime_utility';
export default class Job {
constructor(options) {
@ -9,7 +11,7 @@ export default class Job {
this.state = null;
this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl;
this.pagePath = this.options.pagePath;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
@ -167,11 +169,11 @@ export default class Job {
getBuildTrace() {
return $.ajax({
url: `${this.pageUrl}/trace.json`,
url: `${this.pagePath}/trace.json`,
data: { state: this.state },
})
.done((log) => {
setCiStatusFavicon(`${this.pageUrl}/status.json`);
setCiStatusFavicon(`${this.pagePath}/status.json`);
if (log.state) {
this.state = log.state;
@ -209,7 +211,7 @@ export default class Job {
}
if (log.status !== this.buildStatus) {
gl.utils.visitUrl(this.pageUrl);
visitUrl(this.pagePath);
}
})
.fail(() => {
@ -260,7 +262,7 @@ export default class Job {
if ($date.length) {
const date = $date.text();
return $date.text(
gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3'))),
);
}
}

View file

@ -1,4 +1,4 @@
class Cache {
export default class Cache {
constructor() {
this.internalStorage = { };
}
@ -15,5 +15,3 @@ class Cache {
delete this.internalStorage[key];
}
}
export default Cache;

View file

@ -1,3 +1,4 @@
import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
@ -65,7 +66,7 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
export const handleLocationHash = () => {
let hash = window.gl.utils.getLocationHash();
let hash = getLocationHash();
if (!hash) return;
// This is required to handle non-unicode characters in hash

View file

@ -1,3 +1,2 @@
/* eslint-disable import/prefer-default-export */
export const BYTES_IN_KIB = 1024;
export const HIDDEN_CLASS = 'hidden';

View file

@ -1,9 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility';
import {
lang,
s__,
@ -12,123 +9,125 @@ import {
window.timeago = timeago;
window.dateFormat = dateFormat;
(function() {
(function(w) {
var base;
var timeagoInstance;
/**
* Given a date object returns the day of the week in English
* @param {date} date
* @returns {String}
*/
export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
if (w.gl == null) {
w.gl = {};
}
if ((base = w.gl).utils == null) {
base.utils = {};
}
w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
/**
* @example
* dateFormat('2017-12-05','mmm d, yyyy h:MMtt Z' ) -> "Dec 5, 2017 12:00am GMT+0000"
* @param {date} datetime
* @returns {String}
*/
export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
w.gl.utils.formatDate = function(datetime) {
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
let timeagoInstance;
/**
* Sets a timeago Instance
*/
export function getTimeago() {
if (!timeagoInstance) {
const localeRemaining = function getLocaleRemaining(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
[s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
[s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
[s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
[s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
[s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
[s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
[s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
[s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
[s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
[s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
][index];
};
const locale = function getLocale(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
[s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
[s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
[s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
[s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
[s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
[s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
[s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
[s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
[s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
[s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
][index];
};
w.gl.utils.getDayName = function(date) {
return this.days[date.getDay()];
};
timeago.register(lang, locale);
timeago.register(`${lang}-remaining`, localeRemaining);
timeagoInstance = timeago();
}
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
$timeagoEls.each((i, el) => {
el.setAttribute('title', el.getAttribute('title'));
return timeagoInstance;
}
if (setTimeago) {
// Recreate with custom template
$(el).tooltip({
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
});
}
/**
* For the given element, renders a timeago instance.
* @param {jQuery} $els
*/
export const renderTimeago = ($els) => {
const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
el.classList.add('js-timeago-render');
// timeago.js sets timeouts internally for each timeago value to be updated in real time
getTimeago().render(timeagoEls, lang);
};
/**
* For the given elements, sets a tooltip with a formatted date.
* @param {jQuery}
* @param {Boolean} setTimeago
*/
export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
$timeagoEls.each((i, el) => {
if (setTimeago) {
// Recreate with custom template
$(el).tooltip({
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
});
}
gl.utils.renderTimeago($timeagoEls);
};
el.classList.add('js-timeago-render');
});
w.gl.utils.getTimeago = function() {
var locale;
renderTimeago($timeagoEls);
};
if (!timeagoInstance) {
const localeRemaining = function(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
[s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
[s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
[s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
[s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
[s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
[s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
[s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
[s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
[s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
[s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')]
][index];
};
locale = function(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
[s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
[s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
[s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
[s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
[s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
[s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
[s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
[s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
[s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
[s__('Timeago|%s years ago'), s__('Timeago|in %s years')]
][index];
};
/**
* Returns remaining or passed time over the given time.
* @param {*} time
* @param {*} expiredLabel
*/
export const timeFor = (time, expiredLabel) => {
if (!time) {
return '';
}
if (new Date(time) < new Date()) {
return expiredLabel || s__('Timeago|Past due');
}
return getTimeago().format(time, `${lang}-remaining`).trim();
};
timeago.register(lang, locale);
timeago.register(`${lang}-remaining`, localeRemaining);
timeagoInstance = timeago();
}
export const getDayDifference = (a, b) => {
const millisecondsPerDay = 1000 * 60 * 60 * 24;
const date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return timeagoInstance;
};
w.gl.utils.timeFor = function(time, suffix, expiredLabel) {
var timefor;
if (!time) {
return '';
}
if (new Date(time) < new Date()) {
expiredLabel || (expiredLabel = s__('Timeago|Past due'));
timefor = expiredLabel;
} else {
timefor = gl.utils.getTimeago().format(time, `${lang}-remaining`).trim();
}
return timefor;
};
w.gl.utils.renderTimeago = function($els) {
const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
// timeago.js sets timeouts internally for each timeago value to be updated in real time
gl.utils.getTimeago().render(timeagoEls, lang);
};
w.gl.utils.getDayDifference = function(a, b) {
var millisecondsPerDay = 1000 * 60 * 60 * 24;
var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((date2 - date1) / millisecondsPerDay);
};
})(window);
}).call(window);
return Math.floor((date2 - date1) / millisecondsPerDay);
};
/**
* Port of ruby helper time_interval_in_words.
@ -164,3 +163,10 @@ export function dateInWords(date, abbreviated = false) {
return `${monthName} ${date.getDate()}, ${year}`;
}
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
getTimeago,
localTimeAgo,
};

View file

@ -1,93 +1,69 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
var base;
var w = window;
if (w.gl == null) {
w.gl = {};
}
if ((base = w.gl).utils == null) {
base.utils = {};
}
// Returns an array containing the value(s) of the
// of the key passed as an argument
w.gl.utils.getParameterValues = function(sParam) {
var i, sPageURL, sParameterName, sURLVariables, values;
sPageURL = decodeURIComponent(window.location.search.substring(1));
sURLVariables = sPageURL.split('&');
sParameterName = void 0;
values = [];
i = 0;
while (i < sURLVariables.length) {
sParameterName = sURLVariables[i].split('=');
export function getParameterValues(sParam) {
const sPageURL = decodeURIComponent(window.location.search.substring(1));
return sPageURL.split('&').reduce((acc, urlParam) => {
const sParameterName = urlParam.split('=');
if (sParameterName[0] === sParam) {
values.push(sParameterName[1].replace(/\+/g, ' '));
acc.push(sParameterName[1].replace(/\+/g, ' '));
}
i += 1;
}
return values;
};
return acc;
}, []);
}
// @param {Object} params - url keys and value to merge
// @param {String} url
w.gl.utils.mergeUrlParams = function(params, url) {
var lastChar, newUrl, paramName, paramValue, pattern;
newUrl = decodeURIComponent(url);
for (paramName in params) {
paramValue = params[paramName];
pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
if (paramValue == null) {
newUrl = newUrl.replace(pattern, '');
export function mergeUrlParams(params, url) {
let newUrl = Object.keys(params).reduce((acc, paramName) => {
const paramValue = params[paramName];
const pattern = new RegExp(`\\b(${paramName}=).*?(&|$)`);
if (paramValue === null) {
return acc.replace(pattern, '');
} else if (url.search(pattern) !== -1) {
newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
} else {
newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
return acc.replace(pattern, `$1${paramValue}$2`);
}
}
return `${acc}${acc.indexOf('?') > 0 ? '&' : '?'}${paramName}=${paramValue}`;
}, decodeURIComponent(url));
// Remove a trailing ampersand
lastChar = newUrl[newUrl.length - 1];
const lastChar = newUrl[newUrl.length - 1];
if (lastChar === '&') {
newUrl = newUrl.slice(0, -1);
}
return newUrl;
};
// removes parameter query string from url. returns the modified url
w.gl.utils.removeParamQueryString = function(url, param) {
var urlVariables, variables;
url = decodeURIComponent(url);
urlVariables = url.split('&');
return ((function() {
var j, len, results;
results = [];
for (j = 0, len = urlVariables.length; j < len; j += 1) {
variables = urlVariables[j];
if (variables.indexOf(param) === -1) {
results.push(variables);
}
}
return results;
})()).join('&');
};
w.gl.utils.removeParams = (params) => {
}
export function removeParamQueryString(url, param) {
const decodedUrl = decodeURIComponent(url);
const urlVariables = decodedUrl.split('&');
return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
}
export function removeParams(params) {
const url = document.createElement('a');
url.href = window.location.href;
params.forEach((param) => {
url.search = w.gl.utils.removeParamQueryString(url.search, param);
url.search = removeParamQueryString(url.search, param);
});
return url.href;
};
w.gl.utils.getLocationHash = function(url) {
var hashIndex;
if (typeof url === 'undefined') {
// Note: We can't use window.location.hash here because it's
// not consistent across browsers - Firefox will pre-decode it
url = window.location.href;
}
hashIndex = url.indexOf('#');
}
export function getLocationHash(url = window.location.href) {
const hashIndex = url.indexOf('#');
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
};
}
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(window.location.href);
// eslint-disable-next-line import/prefer-default-export
export function visitUrl(url, external = false) {
if (external) {
// Simulate `target="blank" rel="noopener noreferrer"`
@ -100,12 +76,10 @@ export function visitUrl(url, external = false) {
}
}
export function refreshCurrentPage() {
visitUrl(window.location.href);
}
export function redirectTo(url) {
return window.location.assign(url);
}
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
visitUrl,
};

View file

@ -1,6 +1,5 @@
/* 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, import/first */
/* global ConfirmDangerModal */
/* global Aside */
import jQuery from 'jquery';
import _ from 'underscore';
@ -28,8 +27,8 @@ import './commit/image_file';
// lib/utils
import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility';
import './lib/utils/url_utility';
import { localTimeAgo, renderTimeago } from './lib/utils/datetime_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors
import './behaviors/';
@ -37,14 +36,9 @@ import './behaviors/';
// everything else
import './activities';
import './admin';
import './aside';
import loadAwardsHandler from './awards_handler';
import bp from './breakpoints';
import './commits';
import './compare';
import './compare_autocomplete';
import './confirm_danger_modal';
import './copy_to_clipboard';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';
import './gl_field_error';
@ -64,15 +58,10 @@ import './notifications_dropdown';
import './notifications_form';
import './pager';
import './preview_markdown';
import './project_find_file';
import './project_import';
import './projects_dropdown';
import './projects_list';
import './syntax_highlight';
import './render_gfm';
import './right_sidebar';
import './search';
import './search_autocomplete';
import initBreadcrumbs from './breadcrumb';
import './dispatcher';
@ -123,13 +112,13 @@ $(function () {
// `hashchange` is not triggered when link target is already in window.location
$body.on('click', 'a[href^="#"]', function() {
var href = this.getAttribute('href');
if (href.substr(1) === gl.utils.getLocationHash()) {
if (href.substr(1) === getLocationHash()) {
setTimeout(handleLocationHash, 1);
}
});
if (bootstrapBreakpoint === 'xs') {
const $rightSidebar = $('aside.right-sidebar, .page-with-sidebar');
const $rightSidebar = $('aside.right-sidebar, .layout-page');
$rightSidebar
.removeClass('right-sidebar-expanded')
@ -189,13 +178,13 @@ $(function () {
trigger: 'focus',
// set the viewport to the main content, excluding the navigation bar, so
// the navigation can't overlap the popover
viewport: '.page-with-sidebar'
viewport: '.layout-page'
});
$('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
// Form submitter
});
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
localTimeAgo($('abbr.timeago, .js-timeago'), true);
// Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons;
@ -282,9 +271,8 @@ $(function () {
return fitSidebarForSize();
});
loadAwardsHandler();
new Aside();
gl.utils.renderTimeago();
renderTimeago();
$(document).trigger('init.scrolling-tabs');
@ -295,7 +283,7 @@ $(function () {
const action = `${this.action}${link.search === '' ? '?' : '&'}`;
event.preventDefault();
gl.utils.visitUrl(`${action}${$(this).serialize()}`);
visitUrl(`${action}${$(this).serialize()}`);
});
const flashContainer = document.querySelector('.flash-container');

View file

@ -10,6 +10,7 @@ import './mixins/line_conflict_actions';
import './components/diff_file_editor';
import './components/inline_conflict_lines';
import './components/parallel_conflict_lines';
import syntaxHighlight from '../syntax_highlight';
$(() => {
const INTERACTIVE_RESOLVE_MODE = 'interactive';
@ -53,7 +54,7 @@ $(() => {
mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => {
$('.js-syntax-highlight').syntaxHighlight();
syntaxHighlight($('.js-syntax-highlight'));
});
});
},

View file

@ -129,7 +129,7 @@ import { addDelimiter } from './lib/utils/text_utility';
};
MergeRequest.prototype.hideCloseButton = function() {
const el = document.querySelector('.merge-request .issuable-actions');
const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
closeDropdownItem.classList.add('hidden');

View file

@ -11,8 +11,11 @@ import {
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
import { localTimeAgo } from './lib/utils/datetime_utility';
import syntaxHighlight from './syntax_highlight';
/* eslint-disable max-len */
// MergeRequestTabs
@ -246,7 +249,7 @@ import Diff from './diff';
url: `${source}.json`,
success: (data) => {
document.querySelector('div#commits').innerHTML = data.html;
gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
localTimeAgo($('.js-timeago', 'div#commits'));
this.commitsLoaded = true;
this.scrollToElement('#commits');
},
@ -293,8 +296,8 @@ import Diff from './diff';
gl.diffNotesCompileComponents();
}
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
$('#diffs .js-syntax-highlight').syntaxHighlight();
localTimeAgo($('.js-timeago', 'div#diffs'));
syntaxHighlight($('#diffs .js-syntax-highlight'));
if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
this.expandViewContainer();
@ -317,7 +320,7 @@ import Diff from './diff';
// Scroll any linked note into view
// Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash();
const hash = getLocationHash();
const anchor = hash && $container.find(`.note[id="${hash}"]`);
if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content');

View file

@ -2,6 +2,7 @@
/* global Issuable */
/* global ListMilestone */
import _ from 'underscore';
import { timeFor } from './lib/utils/datetime_utility';
(function() {
this.MilestoneSelect = (function() {
@ -216,7 +217,7 @@ import _ from 'underscore';
$value.css('display', '');
if (data.milestone != null) {
data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
data.milestone.remaining = timeFor(data.milestone.due_date);
data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));

View file

@ -21,6 +21,8 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
tagsPath: metricsData.tagsPath,
projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
deploymentEndpoint: metricsData.deploymentEndpoint,
emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath,
@ -112,6 +114,8 @@
:hover-data="hoverData"
:update-aspect-ratio="updateAspectRatio"
:deployment-data="store.deploymentData"
:project-path="projectPath"
:tags-path="tagsPath"
/>
</graph-group>
</div>

View file

@ -30,6 +30,14 @@
required: false,
default: () => ({}),
},
projectPath: {
type: String,
required: true,
},
tagsPath: {
type: String,
required: true,
},
},
mixins: [MonitoringMixin],
@ -251,6 +259,14 @@
:line-color="path.lineColor"
:area-color="path.areaColor"
/>
<rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
transform="translate(-5, 20)"
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
<graph-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
@ -267,14 +283,6 @@
:graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent"
/>
<rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
transform="translate(-5, 20)"
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
</svg>
</svg>
</div>

View file

@ -1,5 +1,6 @@
<script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
@ -25,6 +26,10 @@
},
},
components: {
Icon,
},
computed: {
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
@ -33,7 +38,7 @@
methods: {
refText(d) {
return d.tag ? d.ref : d.sha.slice(0, 6);
return d.tag ? d.ref : d.sha.slice(0, 8);
},
formatTime(deploymentTime) {
@ -41,7 +46,7 @@
},
formatDate(deploymentTime) {
return dateFormat(deploymentTime);
return dateFormatWithName(deploymentTime);
},
nameDeploymentClass(deployment) {
@ -54,11 +59,19 @@
positionFlag(deployment) {
let xPosition = 3;
if (deployment.xPos > (this.graphWidth - 200)) {
xPosition = -97;
if (deployment.xPos > (this.graphWidth - 225)) {
xPosition = -142;
}
return xPosition;
},
svgContainerHeight(tag) {
let svgHeight = 80;
if (!tag) {
svgHeight -= 20;
}
return svgHeight;
},
},
};
</script>
@ -91,35 +104,75 @@
class="js-deploy-info-box"
:x="positionFlag(deployment)"
y="0"
width="92"
height="60">
width="134"
:height="svgContainerHeight(deployment.tag)">
<rect
class="rect-text-metric deploy-info-rect rect-metric"
x="1"
y="1"
rx="2"
width="90"
height="58">
width="132"
:height="svgContainerHeight(deployment.tag) - 2">
</rect>
<g
transform="translate(5, 2)">
<text
class="deploy-info-text text-metric-bold">
{{refText(deployment)}}
</text>
</g>
<text
class="deploy-info-text"
y="18"
transform="translate(5, 2)">
{{formatDate(deployment.time)}}
</text>
<text
class="deploy-info-text text-metric-bold"
y="38"
transform="translate(5, 2)">
{{formatTime(deployment.time)}}
Deployed
</text>
<!--The date info-->
<g transform="translate(5, 20)">
<text class="deploy-info-text">
{{formatDate(deployment.time)}}
</text>
<text
class="deploy-info-text text-metric-bold"
x="62">
{{formatTime(deployment.time)}}
</text>
</g>
<line
class="divider-line"
x1="0"
y1="38"
x2="132"
:y2="38"
stroke="#000">
</line>
<!--Commit information-->
<g transform="translate(5, 40)">
<icon
name="commit"
:width="12"
:height="12"
:y="3">
</icon>
<a :xlink:href="deployment.commitUrl">
<text
class="deploy-info-text deploy-info-text-link"
transform="translate(20, 2)">
{{refText(deployment)}}
</text>
</a>
</g>
<!--Tag information-->
<g
transform="translate(5, 55)"
v-if="deployment.tag">
<icon
name="label"
:width="12"
:height="12"
:y="5">
</icon>
<a :xlink:href="deployment.tagUrl">
<text
class="deploy-info-text deploy-info-text-link"
transform="translate(20, 2)"
y="2">
{{deployment.tag}}
</text>
</a>
</g>
</svg>
</g>
<svg

View file

@ -33,7 +33,9 @@ const mixins = {
id: deployment.id,
time,
sha: deployment.sha,
commitUrl: `${this.projectPath}/commit/${deployment.sha}`,
tag: deployment.tag,
tagUrl: `${this.tagsPath}/${deployment.tag}`,
ref: deployment.ref.name,
xPos,
showDeploymentFlag: false,

View file

@ -1,6 +1,7 @@
import d3 from 'd3';
export const dateFormat = d3.time.format('%b %-d, %Y');
export const dateFormatWithName = d3.time.format('%a, %b %-d');
export const timeFormat = d3.time.format('%-I:%M%p');
export const bisectDate = d3.bisector(d => d.time).left;

View file

@ -1,6 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
import Api from './api';
import './lib/utils/url_utility';
import { mergeUrlParams } from './lib/utils/url_utility';
export default class NamespaceSelect {
constructor(opts) {
@ -50,7 +50,7 @@ export default class NamespaceSelect {
}
},
url(namespace) {
return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
return mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
},
});
}

View file

@ -16,6 +16,7 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form';
@ -24,6 +25,7 @@ import Autosave from './autosave';
import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
window.autosize = Autosize;
@ -310,7 +312,7 @@ export default class Notes {
setupNewNote($note) {
// Update datetime format on the recent note
gl.utils.localTimeAgo($note.find('.js-timeago'), false);
localTimeAgo($note.find('.js-timeago'), false);
this.collapseLongCommitList();
this.taskList.init();
@ -330,7 +332,7 @@ export default class Notes {
}
static updateNoteTargetSelector($note) {
const hash = gl.utils.getLocationHash();
const hash = getLocationHash();
// Needs to be an explicit true/false for the jQuery `toggleClass(force)`
const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
$note.toggleClass('target', addTargetClass);
@ -462,7 +464,7 @@ export default class Notes {
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
}
gl.utils.localTimeAgo($('.js-timeago'), false);
localTimeAgo($('.js-timeago'), false);
Notes.checkMergeRequestStatus();
return this.updateNotesCount(1);
}

View file

@ -8,29 +8,29 @@
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
import issueDiscussionLockedWidget from './issue_discussion_locked_widget.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import issuableStateMixin from '../mixins/issuable_state';
export default {
name: 'issueCommentForm',
name: 'commentForm',
data() {
return {
note: '',
noteType: constants.COMMENT,
// Can't use mapGetters,
// this needs to be in the data object because it belongs to the state
issueState: this.$store.getters.getIssueData.state,
issueState: this.$store.getters.getNoteableData.state,
isSubmitting: false,
isSubmitButtonDisabled: true,
};
},
components: {
issueWarning,
issueNoteSignedOutWidget,
issueDiscussionLockedWidget,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
userAvatarLink,
},
@ -46,7 +46,7 @@
...mapGetters([
'getCurrentUserLastNote',
'getUserData',
'getIssueData',
'getNoteableData',
'getNotesData',
]),
isLoggedIn() {
@ -59,7 +59,7 @@
return this.issueState === constants.OPENED || this.issueState === constants.REOPENED;
},
canCreateNote() {
return this.getIssueData.current_user.can_create_note;
return this.getNoteableData.current_user.can_create_note;
},
issueActionButtonTitle() {
if (this.note.length) {
@ -85,16 +85,16 @@
return this.getNotesData.quickActionsDocsPath;
},
markdownPreviewPath() {
return this.getIssueData.preview_note_path;
return this.getNoteableData.preview_note_path;
},
author() {
return this.getUserData;
},
canUpdateIssue() {
return this.getIssueData.current_user.can_update;
return this.getNoteableData.current_user.can_update;
},
endpoint() {
return this.getIssueData.create_note_path;
return this.getNoteableData.create_note_path;
},
},
methods: {
@ -119,7 +119,7 @@
data: {
note: {
noteable_type: constants.NOTEABLE_TYPE,
noteable_id: this.getIssueData.id,
noteable_id: this.getNoteableData.id,
note: this.note,
},
},
@ -207,7 +207,7 @@
},
initAutoSave() {
if (this.isLoggedIn) {
this.autosave = new Autosave($(this.$refs.textarea), ['Note', 'Issue', this.getIssueData.id], 'issue');
this.autosave = new Autosave($(this.$refs.textarea), ['Note', 'Issue', this.getNoteableData.id], 'issue');
}
},
initTaskList() {
@ -240,8 +240,11 @@
<template>
<div>
<issue-note-signed-out-widget v-if="!isLoggedIn" />
<issue-discussion-locked-widget v-else-if="!canCreateNote" />
<note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget
issuable-type="issue"
v-else-if="!canCreateNote"
/>
<ul
v-else
class="notes notes-form timeline">
@ -266,9 +269,9 @@
<div class="error-alert"></div>
<issue-warning
v-if="hasWarning(getIssueData)"
:is-locked="isLocked(getIssueData)"
:is-confidential="isConfidential(getIssueData)"
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
/>
<markdown-field

View file

@ -1,8 +1,12 @@
<script>
import Icon from '../../vue_shared/components/icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import Issuable from '~/vue_shared/mixins/issuable';
export default {
component: {
mixins: [
Issuable,
],
components: {
Icon,
},
};
@ -16,7 +20,7 @@
:size="16"
class="icon">
</icon>
<span>This issue is locked. Only <b>project members</b> can comment.</span>
<span>This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.</span>
</span>
</div>
</template>

View file

@ -5,11 +5,11 @@
import emojiSmiley from 'icons/_emoji_smiley.svg';
import editSvg from 'icons/_icon_pencil.svg';
import ellipsisSvg from 'icons/_ellipsis_v.svg';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
name: 'issueNoteActions',
name: 'noteActions',
props: {
authorId: {
type: Number,
@ -86,7 +86,7 @@
<div class="note-actions">
<span
v-if="accessLevel"
class="note-role note-role-access">{{accessLevel}}</span>
class="note-role user-access-role">{{accessLevel}}</span>
<div
v-if="canAddAwardEmoji"
class="note-actions-item">

View file

@ -1,6 +1,6 @@
<script>
export default {
name: 'issueNoteAttachment',
name: 'noteAttachment',
props: {
attachment: {
type: Object,

View file

@ -1,8 +1,8 @@
<script>
import issueNoteEditedText from './issue_note_edited_text.vue';
import issueNoteAwardsList from './issue_note_awards_list.vue';
import issueNoteAttachment from './issue_note_attachment.vue';
import issueNoteForm from './issue_note_form.vue';
import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue';
import noteForm from './note_form.vue';
import TaskList from '../../task_list';
import autosave from '../mixins/autosave';
@ -26,10 +26,10 @@
autosave,
],
components: {
issueNoteEditedText,
issueNoteAwardsList,
issueNoteAttachment,
issueNoteForm,
noteEditedText,
noteAwardsList,
noteAttachment,
noteForm,
},
computed: {
noteBody() {
@ -87,7 +87,7 @@
<div
v-html="note.note_html"
class="note-text md"></div>
<issue-note-form
<note-form
v-if="isEditing"
ref="noteForm"
@handleFormUpdate="handleFormUpdate"
@ -101,20 +101,20 @@
v-model="note.note"
:data-update-url="note.path"
class="hidden js-task-list-field"></textarea>
<issue-note-edited-text
<note-edited-text
v-if="note.last_edited_at"
:edited-at="note.last_edited_at"
:edited-by="note.last_edited_by"
action-text="Edited"
/>
<issue-note-awards-list
<note-awards-list
v-if="note.award_emoji.length"
:note-id="note.id"
:note-author-id="note.author.id"
:awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path"
/>
<issue-note-attachment
<note-attachment
v-if="note.attachment"
:attachment="note.attachment"
/>

View file

@ -46,8 +46,8 @@
computed: {
...mapGetters([
'getDiscussionLastNote',
'getIssueData',
'getIssueDataByProp',
'getNoteableData',
'getNoteableDataByProp',
'getNotesDataByProp',
'getUserDataByProp',
]),
@ -55,7 +55,7 @@
return `#note_${this.noteId}`;
},
markdownPreviewPath() {
return this.getIssueDataByProp('preview_note_path');
return this.getNoteableDataByProp('preview_note_path');
},
markdownDocsPath() {
return this.getNotesDataByProp('markdownDocsPath');
@ -129,9 +129,9 @@
class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning
v-if="hasWarning(getIssueData)"
:is-locked="isLocked(getIssueData)"
:is-confidential="isConfidential(getIssueData)"
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
/>
<markdown-field

View file

@ -2,7 +2,6 @@
import { mapGetters } from 'vuex';
export default {
name: 'singInLinksNotes',
computed: {
...mapGetters([
'getNotesDataByProp',

View file

@ -2,13 +2,12 @@
import { mapActions, mapGetters } from 'vuex';
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants';
import issueNote from './issue_note.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import issueNoteHeader from './issue_note_header.vue';
import issueNoteActions from './issue_note_actions.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
import issueNoteEditedText from './issue_note_edited_text.vue';
import issueNoteForm from './issue_note_form.vue';
import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import autosave from '../mixins/autosave';
@ -26,13 +25,12 @@
};
},
components: {
issueNote,
noteableNote,
userAvatarLink,
issueNoteHeader,
issueNoteActions,
issueNoteSignedOutWidget,
issueNoteEditedText,
issueNoteForm,
noteHeader,
noteSignedOutWidget,
noteEditedText,
noteForm,
placeholderNote,
placeholderSystemNote,
},
@ -41,7 +39,7 @@
],
computed: {
...mapGetters([
'getIssueData',
'getNoteableData',
]),
discussion() {
return this.note.notes[0];
@ -50,10 +48,10 @@
return this.discussion.author;
},
canReply() {
return this.getIssueData.current_user.can_create_note;
return this.getNoteableData.current_user.can_create_note;
},
newNotePath() {
return this.getIssueData.create_note_path;
return this.getNoteableData.create_note_path;
},
lastUpdatedBy() {
const { notes } = this.note;
@ -88,7 +86,7 @@
return placeholderNote;
}
return issueNote;
return noteableNote;
},
componentData(note) {
return note.isPlaceholderNote ? note.notes[0] : note;
@ -171,7 +169,7 @@
<div class="timeline-content">
<div class="discussion">
<div class="discussion-header">
<issue-note-header
<note-header
:author="author"
:created-at="discussion.created_at"
:note-id="discussion.id"
@ -179,8 +177,8 @@
@toggleHandler="toggleDiscussionHandler"
action-text="started a discussion"
class="discussion"
/>
<issue-note-edited-text
/>
<note-edited-text
v-if="lastUpdatedAt"
:edited-at="lastUpdatedAt"
:edited-by="lastUpdatedBy"
@ -211,7 +209,7 @@
type="button"
class="js-vue-discussion-reply btn btn-text-field"
title="Add a reply">Reply...</button>
<issue-note-form
<note-form
v-if="isReplying"
save-button-title="Comment"
:discussion="note"
@ -220,7 +218,7 @@
@cancelFormEdition="cancelReplyForm"
ref="noteForm"
/>
<issue-note-signed-out-widget v-if="!canReply" />
<note-signed-out-widget v-if="!canReply" />
</div>
</div>
</div>

View file

@ -1,10 +1,11 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import issueNoteHeader from './issue_note_header.vue';
import issueNoteActions from './issue_note_actions.vue';
import issueNoteBody from './issue_note_body.vue';
import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue';
import noteBody from './note_body.vue';
import eventHub from '../event_hub';
export default {
@ -23,9 +24,9 @@
},
components: {
userAvatarLink,
issueNoteHeader,
issueNoteActions,
issueNoteBody,
noteHeader,
noteActions,
noteBody,
},
computed: {
...mapGetters([
@ -85,7 +86,7 @@
};
this.isRequesting = true;
this.oldContent = this.note.note_html;
this.note.note_html = noteText;
this.note.note_html = escape(noteText);
this.updateNote(data)
.then(() => {
@ -122,9 +123,7 @@
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this.note.note = noteText;
if (this.$refs.noteBody) {
this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
}
this.$refs.noteBody.$refs.noteForm.note = noteText;
},
},
created() {
@ -155,13 +154,13 @@
</div>
<div class="timeline-content">
<div class="note-header">
<issue-note-header
<note-header
:author="author"
:created-at="note.created_at"
:note-id="note.id"
action-text="commented"
/>
<issue-note-actions
<note-actions
:author-id="author.id"
:note-id="note.id"
:access-level="note.human_access"
@ -173,7 +172,7 @@
@handleDelete="deleteHandler"
/>
</div>
<issue-note-body
<note-body
:note="note"
:can-edit="note.current_user.can_edit"
:is-editing="isEditing"

View file

@ -1,20 +1,21 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import { getLocationHash } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import store from '../stores/';
import * as constants from '../constants';
import issueNote from './issue_note.vue';
import issueDiscussion from './issue_discussion.vue';
import noteableNote from './noteable_note.vue';
import noteableDiscussion from './noteable_discussion.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue';
import issueCommentForm from './issue_comment_form.vue';
import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
name: 'issueNotesApp',
name: 'notesApp',
props: {
issueData: {
noteableData: {
type: Object,
required: true,
},
@ -35,10 +36,10 @@
};
},
components: {
issueNote,
issueDiscussion,
noteableNote,
noteableDiscussion,
systemNote,
issueCommentForm,
commentForm,
loadingIcon,
placeholderNote,
placeholderSystemNote,
@ -56,7 +57,7 @@
actionToggleAward: 'toggleAward',
scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
setNotesData: 'setNotesData',
setIssueData: 'setIssueData',
setNoteableData: 'setNoteableData',
setUserData: 'setUserData',
setLastFetchedAt: 'setLastFetchedAt',
setTargetNoteHash: 'setTargetNoteHash',
@ -68,10 +69,10 @@
}
return placeholderNote;
} else if (note.individual_note) {
return note.notes[0].system ? systemNote : issueNote;
return note.notes[0].system ? systemNote : noteableNote;
}
return issueDiscussion;
return noteableDiscussion;
},
getComponentData(note) {
return note.individual_note ? note.notes[0] : note;
@ -86,7 +87,7 @@
.then(() => this.checkLocationHash())
.catch(() => {
this.isLoading = false;
Flash('Something went wrong while fetching issue comments. Please try again.');
Flash('Something went wrong while fetching comments. Please try again.');
});
},
initPolling() {
@ -95,7 +96,7 @@
this.poll();
},
checkLocationHash() {
const hash = gl.utils.getLocationHash();
const hash = getLocationHash();
const element = document.getElementById(hash);
if (hash && element) {
@ -106,7 +107,7 @@
},
created() {
this.setNotesData(this.notesData);
this.setIssueData(this.issueData);
this.setNoteableData(this.noteableData);
this.setUserData(this.userData);
},
mounted() {
@ -146,6 +147,6 @@
/>
</ul>
<issue-comment-form />
<comment-form />
</div>
</template>

View file

@ -1,17 +1,25 @@
import Vue from 'vue';
import issueNotesApp from './components/issue_notes_app.vue';
import notesApp from './components/notes_app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-notes',
components: {
issueNotesApp,
notesApp,
},
data() {
const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData);
const currentUserData = parsedUserData ? {
id: parsedUserData.id,
name: parsedUserData.name,
username: parsedUserData.username,
avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
path: parsedUserData.path,
} : {};
return {
issueData: JSON.parse(notesDataset.issueData),
currentUserData: JSON.parse(notesDataset.currentUserData),
noteableData: JSON.parse(notesDataset.noteableData),
currentUserData,
notesData: {
lastFetchedAt: notesDataset.lastFetchedAt,
discussionsPath: notesDataset.discussionsPath,
@ -24,9 +32,9 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
};
},
render(createElement) {
return createElement('issue-notes-app', {
return createElement('notes-app', {
props: {
issueData: this.issueData,
noteableData: this.noteableData,
notesData: this.notesData,
userData: this.currentUserData,
},

View file

@ -4,7 +4,7 @@ import Poll from '../../lib/utils/poll';
import * as types from './mutation_types';
import * as utils from './utils';
import * as constants from '../constants';
import service from '../services/issue_notes_service';
import service from '../services/notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
@ -12,7 +12,7 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
let eTagPoll;
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
export const setIssueData = ({ commit }, data) => commit(types.SET_ISSUE_DATA, data);
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data);

View file

@ -6,8 +6,8 @@ export const targetNoteHash = state => state.targetNoteHash;
export const getNotesData = state => state.notesData;
export const getNotesDataByProp = state => prop => state.notesData[prop];
export const getIssueData = state => state.issueData;
export const getIssueDataByProp = state => prop => state.issueData[prop];
export const getNoteableData = state => state.noteableData;
export const getNoteableDataByProp = state => prop => state.noteableData[prop];
export const getUserData = state => state.userData || {};
export const getUserDataByProp = state => prop => state.userData && state.userData[prop];

View file

@ -15,7 +15,7 @@ export default new Vuex.Store({
// holds endpoints and permissions provided through haml
notesData: {},
userData: {},
issueData: {},
noteableData: {},
},
actions,
getters,

View file

@ -3,7 +3,7 @@ export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION';
export const DELETE_NOTE = 'DELETE_NOTE';
export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES';
export const SET_NOTES_DATA = 'SET_NOTES_DATA';
export const SET_ISSUE_DATA = 'SET_ISSUE_DATA';
export const SET_NOTEABLE_DATA = 'SET_NOTEABLE_DATA';
export const SET_USER_DATA = 'SET_USER_DATA';
export const SET_INITIAL_NOTES = 'SET_INITIAL_NOTES';
export const SET_LAST_FETCHED_AT = 'SET_LAST_FETCHED_AT';

View file

@ -66,8 +66,8 @@ export default {
Object.assign(state, { notesData: data });
},
[types.SET_ISSUE_DATA](state, data) {
Object.assign(state, { issueData: data });
[types.SET_NOTEABLE_DATA](state, data) {
Object.assign(state, { noteableData: data });
},
[types.SET_USER_DATA](state, data) {

View file

@ -1,5 +1,5 @@
import { getParameterByName } from '~/lib/utils/common_utils';
import '~/lib/utils/url_utility';
import { removeParams } from './lib/utils/url_utility';
(() => {
const ENDLESS_SCROLL_BOTTOM_PX = 400;
@ -7,7 +7,7 @@ import '~/lib/utils/url_utility';
const Pager = {
init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']);
this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;

Some files were not shown because too many files have changed in this diff Show more