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 ...
|
@ -586,6 +586,7 @@ codequality:
|
|||
paths: [codeclimate.json]
|
||||
|
||||
qa:internal:
|
||||
<<: *except-docs
|
||||
stage: test
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
|
|
52
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.55.0
|
||||
0.60.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
5.9.4
|
||||
5.10.3
|
||||
|
|
18
Gemfile
|
@ -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'
|
||||
|
|
150
Gemfile.lock
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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"]}
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
@ -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 |
|
@ -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 |
|
@ -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 |
After Width: | Height: | Size: 5.4 KiB |
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
})();
|
73
app/assets/javascripts/behaviors/copy_to_clipboard.js
Normal 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 = '⌘'; // 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);
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}`;
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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() {
|
||||
|
|
58
app/assets/javascripts/clusters/clusters_index.js
Normal 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.'));
|
||||
});
|
||||
}));
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -3,3 +3,4 @@ import './polyfills';
|
|||
import './jquery';
|
||||
import './bootstrap';
|
||||
import './vue';
|
||||
import '../lib/utils/axios_utils';
|
||||
|
|
|
@ -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;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = '⌘'; // 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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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 || {};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.'));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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?')"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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...');
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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(/"/g, '"'));
|
||||
const props = JSON.parse(initialDataEl.innerHTML.replace(/"/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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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'))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
/* eslint-disable import/prefer-default-export */
|
||||
export const BYTES_IN_KIB = 1024;
|
||||
export const HIDDEN_CLASS = 'hidden';
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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'));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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">
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'issueNoteAttachment',
|
||||
name: 'noteAttachment',
|
||||
props: {
|
||||
attachment: {
|
||||
type: Object,
|
|
@ -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"
|
||||
/>
|
|
@ -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
|
|
@ -2,7 +2,6 @@
|
|||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'singInLinksNotes',
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'getNotesDataByProp',
|
|
@ -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>
|
|
@ -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"
|
|
@ -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>
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -15,7 +15,7 @@ export default new Vuex.Store({
|
|||
// holds endpoints and permissions provided through haml
|
||||
notesData: {},
|
||||
userData: {},
|
||||
issueData: {},
|
||||
noteableData: {},
|
||||
},
|
||||
actions,
|
||||
getters,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|