New upstream version 8.13.3+dfsg1
|
@ -6,7 +6,7 @@
|
|||
"always-semicolon": true,
|
||||
"color-case": "lower",
|
||||
"block-indent": " ",
|
||||
"color-shorthand": true,
|
||||
"color-shorthand": false,
|
||||
"element-case": "lower",
|
||||
"space-before-colon": "",
|
||||
"space-after-colon": " ",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
*.erb
|
||||
lib/gitlab/sanitizers/svg/whitelist.rb
|
||||
lib/gitlab/diff/position_tracer.rb
|
||||
|
|
2
.gitattributes
vendored
|
@ -1,2 +1,2 @@
|
|||
CHANGELOG merge=union
|
||||
CHANGELOG.md merge=union
|
||||
*.js.es6 gitlab-language=javascript
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
image: "ruby:2.3.1"
|
||||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1"
|
||||
|
||||
cache:
|
||||
key: "ruby-231"
|
||||
paths:
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
||||
variables:
|
||||
|
@ -12,7 +11,7 @@ variables:
|
|||
RSPEC_RETRY_RETRY_COUNT: "3"
|
||||
RAILS_ENV: "test"
|
||||
SIMPLECOV: "true"
|
||||
USE_DB: "true"
|
||||
SETUP_DB: "true"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
GIT_DEPTH: "20"
|
||||
PHANTOMJS_VERSION: "2.1.1"
|
||||
|
@ -23,7 +22,7 @@ before_script:
|
|||
- bundle --version
|
||||
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
|
||||
- retry gem install knapsack
|
||||
- '[ "$USE_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
|
||||
- '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql'
|
||||
|
||||
stages:
|
||||
- prepare
|
||||
|
@ -35,7 +34,7 @@ stages:
|
|||
.knapsack-state: &knapsack-state
|
||||
services: []
|
||||
variables:
|
||||
USE_DB: "false"
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
cache:
|
||||
key: "knapsack"
|
||||
|
@ -100,7 +99,7 @@ update-knapsack:
|
|||
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
|
||||
- export KNAPSACK_GENERATE_REPORT=true
|
||||
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
|
||||
- knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
|
||||
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
paths:
|
||||
|
@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack
|
|||
|
||||
# Execute all testing suites against Ruby 2.1
|
||||
.ruby-21: &ruby-21
|
||||
image: "ruby:2.1"
|
||||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1"
|
||||
<<: *use-db
|
||||
only:
|
||||
- master
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
||||
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
|
||||
|
@ -196,7 +194,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
|
|||
.ruby-static-analysis: &ruby-static-analysis
|
||||
variables:
|
||||
SIMPLECOV: "false"
|
||||
USE_DB: "false"
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
|
||||
.exec: &exec
|
||||
|
@ -209,14 +207,16 @@ rubocop: *exec
|
|||
rake haml_lint: *exec
|
||||
rake scss_lint: *exec
|
||||
rake brakeman: *exec
|
||||
rake flog:
|
||||
<<: *exec
|
||||
allow_failure: yes
|
||||
rake flay:
|
||||
<<: *exec
|
||||
allow_failure: yes
|
||||
rake flay: *exec
|
||||
license_finder: *exec
|
||||
rake downtime_check: *exec
|
||||
rake ce_to_ee_merge_check:
|
||||
<<: *exec
|
||||
only:
|
||||
- branches
|
||||
except:
|
||||
- tags
|
||||
allow_failure: yes
|
||||
|
||||
rake db:migrate:reset:
|
||||
stage: test
|
||||
|
@ -224,6 +224,23 @@ rake db:migrate:reset:
|
|||
script:
|
||||
- rake db:migrate:reset
|
||||
|
||||
rake db:seed_fu:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
variables:
|
||||
SIZE: "1"
|
||||
SETUP_DB: "false"
|
||||
RAILS_ENV: "development"
|
||||
script:
|
||||
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
|
||||
/home/git/repositories/gitlab-org/gitlab-test.git
|
||||
- bundle exec rake db:setup db:seed_fu
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expire_in: 1d
|
||||
paths:
|
||||
- log/development.log
|
||||
|
||||
teaspoon:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
|
@ -245,6 +262,12 @@ lint-doc:
|
|||
script:
|
||||
- scripts/lint-doc.sh
|
||||
|
||||
bundler:check:
|
||||
stage: test
|
||||
<<: *ruby-static-analysis
|
||||
script:
|
||||
- bundle check
|
||||
|
||||
bundler:audit:
|
||||
stage: test
|
||||
<<: *ruby-static-analysis
|
||||
|
@ -272,7 +295,7 @@ coverage:
|
|||
stage: post-test
|
||||
services: []
|
||||
variables:
|
||||
USE_DB: "false"
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
script:
|
||||
- bundle exec scripts/merge-simplecov
|
||||
|
@ -283,12 +306,23 @@ coverage:
|
|||
- coverage/index.html
|
||||
- coverage/assets/
|
||||
|
||||
# Trigger docs build
|
||||
trigger_docs:
|
||||
stage: post-test
|
||||
before_script: []
|
||||
cache: {}
|
||||
artifacts: {}
|
||||
script:
|
||||
- "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds"
|
||||
only:
|
||||
- master
|
||||
|
||||
# Notify slack in the end
|
||||
|
||||
notify:slack:
|
||||
stage: post-test
|
||||
variables:
|
||||
USE_DB: "false"
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
script:
|
||||
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
|
||||
|
@ -315,3 +349,16 @@ pages:
|
|||
- public
|
||||
only:
|
||||
- master
|
||||
|
||||
# Insurance in case a gem needed by one of our releases gets yanked from
|
||||
# rubygems.org in the future.
|
||||
cache gems:
|
||||
only:
|
||||
- tags
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
script:
|
||||
- bundle package --all --all-platforms
|
||||
artifacts:
|
||||
paths:
|
||||
- vendor/cache
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html.
|
||||
See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html
|
||||
|
||||
## What does this MR do?
|
||||
|
||||
|
|
|
@ -453,6 +453,10 @@ Style/VariableName:
|
|||
EnforcedStyle: snake_case
|
||||
Enabled: true
|
||||
|
||||
# Use the configured style when numbering variables.
|
||||
Style/VariableNumber:
|
||||
Enabled: false
|
||||
|
||||
# Use when x then ... for one-line cases.
|
||||
Style/WhenThen:
|
||||
Enabled: true
|
||||
|
@ -639,6 +643,10 @@ Lint/RescueException:
|
|||
Lint/ShadowedException:
|
||||
Enabled: false
|
||||
|
||||
# Checks for Object#to_s usage in string interpolation.
|
||||
Lint/StringConversionInInterpolation:
|
||||
Enabled: true
|
||||
|
||||
# Do not use prefix `_` for a variable that is used.
|
||||
Lint/UnderscorePrefixedVariableName:
|
||||
Enabled: true
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config --exclude-limit 0`
|
||||
# on 2016-09-14 15:44:53 -0400 using RuboCop version 0.42.0.
|
||||
# on 2016-10-04 13:16:20 +0200 using RuboCop version 0.43.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 158
|
||||
# Offense count: 160
|
||||
Lint/AmbiguousRegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 41
|
||||
# Offense count: 40
|
||||
# Configuration parameters: AllowSafeAssignment.
|
||||
Lint/AssignmentInCondition:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 16
|
||||
# Offense count: 18
|
||||
Lint/HandleExceptions:
|
||||
Enabled: false
|
||||
|
||||
|
@ -23,16 +23,21 @@ Lint/HandleExceptions:
|
|||
Lint/Loop:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 16
|
||||
# Offense count: 19
|
||||
Lint/ShadowingOuterLocalVariable:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 6
|
||||
# Offense count: 9
|
||||
# Cop supports --auto-correct.
|
||||
Lint/StringConversionInInterpolation:
|
||||
Lint/UnifiedInteger:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 49
|
||||
# Offense count: 13
|
||||
# Cop supports --auto-correct.
|
||||
Lint/UnneededSplatExpansion:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 69
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
|
||||
Lint/UnusedBlockArgument:
|
||||
|
@ -44,32 +49,81 @@ Lint/UnusedBlockArgument:
|
|||
Lint/UnusedMethodArgument:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 9
|
||||
# Cop supports --auto-correct.
|
||||
Performance/PushSplat:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Performance/RedundantBlockCall:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Offense count: 5
|
||||
# Cop supports --auto-correct.
|
||||
Performance/RedundantMatch:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 27
|
||||
# Offense count: 26
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: MaxKeyValuePairs.
|
||||
Performance/RedundantMerge:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 61
|
||||
# Offense count: 7
|
||||
RSpec/BeEql:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 20
|
||||
# Configuration parameters: CustomIncludeMethods.
|
||||
RSpec/EmptyExampleGroup:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 16
|
||||
RSpec/ExpectActual:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 34
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: implicit, each, example
|
||||
RSpec/HookArgument:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 168
|
||||
RSpec/LeadingSubject:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 162
|
||||
RSpec/LetSetup:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 10
|
||||
RSpec/MessageChain:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 714
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: allow, expect
|
||||
RSpec/MessageExpectation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2423
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 36
|
||||
|
||||
# Offense count: 1504
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1335
|
||||
# Configuration parameters: MaxNesting.
|
||||
RSpec/NestedGroups:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 99
|
||||
RSpec/SubjectStub:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 64
|
||||
Rails/OutputSafety:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 129
|
||||
# Offense count: 151
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: strict, flexible
|
||||
Rails/TimeZone:
|
||||
|
@ -82,58 +136,63 @@ Rails/TimeZone:
|
|||
Rails/Validation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 273
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Security/JSONLoad:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 284
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
|
||||
# SupportedStyles: with_first_parameter, with_fixed_indentation
|
||||
Style/AlignParameters:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 30
|
||||
# Offense count: 28
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: always, conditionals
|
||||
Style/AndOr:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 50
|
||||
# Offense count: 52
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: percent_q, bare_percent
|
||||
Style/BarePercentLiterals:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 289
|
||||
# Offense count: 291
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: braces, no_braces, context_dependent
|
||||
Style/BracesAroundHashParameters:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5
|
||||
# Offense count: 6
|
||||
Style/CaseEquality:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 19
|
||||
# Offense count: 26
|
||||
# Cop supports --auto-correct.
|
||||
Style/ColonMethodCall:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: Keywords.
|
||||
# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW
|
||||
Style/CommentAnnotation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 33
|
||||
# Offense count: 30
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly.
|
||||
# SupportedStyles: assign_to_condition, assign_inside_condition
|
||||
Style/ConditionalAssignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 881
|
||||
# Offense count: 957
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: leading, trailing
|
||||
|
@ -144,12 +203,12 @@ Style/DotPosition:
|
|||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Offense count: 6
|
||||
# Cop supports --auto-correct.
|
||||
Style/EachWithObject:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 25
|
||||
# Offense count: 26
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: empty, nil, both
|
||||
|
@ -161,24 +220,24 @@ Style/EmptyElse:
|
|||
Style/EmptyLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 135
|
||||
# Offense count: 140
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
|
||||
Style/ExtraSpacing:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 7
|
||||
# Offense count: 6
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: format, sprintf, percent
|
||||
Style/FormatString:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 51
|
||||
# Offense count: 201
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 9
|
||||
# Offense count: 11
|
||||
Style/IfInsideElse:
|
||||
Enabled: false
|
||||
|
||||
|
@ -188,21 +247,21 @@ Style/IfInsideElse:
|
|||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 52
|
||||
# Offense count: 53
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
|
||||
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
|
||||
Style/IndentArray:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 97
|
||||
# Offense count: 95
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
|
||||
# SupportedStyles: special_inside_parentheses, consistent, align_braces
|
||||
Style/IndentHash:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 12
|
||||
# Offense count: 29
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: line_count_dependent, lambda, literal
|
||||
|
@ -214,7 +273,7 @@ Style/Lambda:
|
|||
Style/LineEndConcatenation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 13
|
||||
# Offense count: 15
|
||||
# Cop supports --auto-correct.
|
||||
Style/MethodCallParentheses:
|
||||
Enabled: false
|
||||
|
@ -223,7 +282,7 @@ Style/MethodCallParentheses:
|
|||
Style/MethodMissing:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 85
|
||||
# Offense count: 95
|
||||
# Cop supports --auto-correct.
|
||||
Style/MutableConstant:
|
||||
Enabled: false
|
||||
|
@ -240,14 +299,14 @@ Style/NestedParenthesizedCalls:
|
|||
Style/Next:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 8
|
||||
# Offense count: 12
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
|
||||
# SupportedOctalStyles: zero_with_o, zero_only
|
||||
Style/NumericLiteralPrefix:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 64
|
||||
# Offense count: 53
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: predicate, comparison
|
||||
|
@ -259,7 +318,7 @@ Style/NumericPredicate:
|
|||
Style/ParallelAssignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 264
|
||||
# Offense count: 294
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: PreferredDelimiters.
|
||||
Style/PercentLiteralDelimiters:
|
||||
|
@ -277,7 +336,7 @@ Style/PercentQLiterals:
|
|||
Style/PerlBackrefs:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 35
|
||||
# Offense count: 38
|
||||
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
|
||||
# NamePrefix: is_, has_, have_
|
||||
# NamePrefixBlacklist: is_, has_, have_
|
||||
|
@ -285,7 +344,7 @@ Style/PerlBackrefs:
|
|||
Style/PredicateName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 27
|
||||
# Offense count: 26
|
||||
# Cop supports --auto-correct.
|
||||
Style/PreferredHashMethods:
|
||||
Enabled: false
|
||||
|
@ -317,12 +376,12 @@ Style/RedundantException:
|
|||
Style/RedundantFreeze:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 408
|
||||
# Offense count: 427
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantSelf:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 93
|
||||
# Offense count: 97
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
|
||||
# SupportedStyles: slashes, percent_r, mixed
|
||||
|
@ -334,7 +393,12 @@ Style/RegexpLiteral:
|
|||
Style/RescueModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5
|
||||
# Offense count: 114
|
||||
# Cop supports --auto-correct.
|
||||
Style/SafeNavigation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 7
|
||||
# Cop supports --auto-correct.
|
||||
Style/SelfAssignment:
|
||||
Enabled: false
|
||||
|
@ -351,7 +415,7 @@ Style/SingleLineBlockParams:
|
|||
Style/SingleLineMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 124
|
||||
# Offense count: 125
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: space, no_space
|
||||
|
@ -364,19 +428,19 @@ Style/SpaceBeforeBlockBraces:
|
|||
Style/SpaceBeforeFirstArg:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 141
|
||||
# Offense count: 145
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
|
||||
# SupportedStyles: space, no_space
|
||||
Style/SpaceInsideBlockBraces:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 96
|
||||
# Offense count: 99
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceInsideBrackets:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 62
|
||||
# Offense count: 65
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceInsideParens:
|
||||
Enabled: false
|
||||
|
@ -386,21 +450,21 @@ Style/SpaceInsideParens:
|
|||
Style/SpaceInsidePercentLiteralDelimiters:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 40
|
||||
# Offense count: 41
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: SupportedStyles.
|
||||
# SupportedStyles: use_perl_names, use_english_names
|
||||
Style/SpecialGlobalVars:
|
||||
EnforcedStyle: use_perl_names
|
||||
|
||||
# Offense count: 30
|
||||
# Offense count: 31
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: single_quotes, double_quotes
|
||||
Style/StringLiteralsInInterpolation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 32
|
||||
# Offense count: 33
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: IgnoredMethods.
|
||||
# IgnoredMethods: respond_to, define_method
|
||||
|
@ -414,7 +478,7 @@ Style/SymbolProc:
|
|||
Style/TernaryParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 24
|
||||
# Offense count: 29
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
|
||||
# SupportedStyles: comma, consistent_comma, no_comma
|
||||
|
|
|
@ -61,7 +61,7 @@ linters:
|
|||
|
||||
# Separate rule, function, and mixin declarations with empty lines.
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# Reports when you have an empty rule set.
|
||||
EmptyRule:
|
||||
|
@ -79,7 +79,7 @@ linters:
|
|||
|
||||
# HEX colors should use three-character values where possible.
|
||||
HexLength:
|
||||
enabled: true
|
||||
enabled: false
|
||||
|
||||
# HEX color values should use lower-case colors to differentiate between
|
||||
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
|
||||
|
@ -219,7 +219,7 @@ linters:
|
|||
# Property values, @extend, @include, and @import directives, and variable
|
||||
# declarations should always end with a semicolon.
|
||||
TrailingSemicolon:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# Reports lines containing trailing whitespace.
|
||||
TrailingWhitespace:
|
||||
|
|
|
@ -226,8 +226,7 @@ a feedback issue (if there isn't one already) and leave a comment asking for it
|
|||
to be marked as `Accepting merge requests`. Please include screenshots or
|
||||
wireframes if the feature will also change the UI.
|
||||
|
||||
Merge requests can be filed either at [GitLab.com][gitlab-mr-tracker] or at
|
||||
[github.com][github-mr-tracker].
|
||||
Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
|
||||
|
||||
If you are new to GitLab development (or web development in general), see the
|
||||
[I want to contribute!](#i-want-to-contribute) section to get you started with
|
||||
|
@ -246,10 +245,17 @@ tests are least likely to receive timely feedback. The workflow to make a merge
|
|||
request is as follows:
|
||||
|
||||
1. Fork the project into your personal space on GitLab.com
|
||||
1. Create a feature branch, branch away from `master`.
|
||||
1. Create a feature branch, branch away from `master`
|
||||
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
|
||||
1. Add your changes to the [CHANGELOG](CHANGELOG)
|
||||
1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide]
|
||||
1. Add your changes to the [CHANGELOG.md](CHANGELOG.md):
|
||||
1. If you are fixing a ~regression issue, you can add your entry to the next
|
||||
patch release (e.g. `8.12.5` if current version is `8.12.4`)
|
||||
1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if
|
||||
current version is `8.12.4`
|
||||
1. Please add your entry at a random place among the entries of the targeted
|
||||
release
|
||||
1. If you are writing documentation, make sure to follow the
|
||||
[documentation styleguide][doc-styleguide]
|
||||
1. If you have multiple commits please combine them into one commit by
|
||||
[squashing them][git-squash]
|
||||
1. Push the commit(s) to your fork
|
||||
|
@ -258,7 +264,7 @@ request is as follows:
|
|||
1. The MR description should give a motive for your change and the method you
|
||||
used to achieve it, see the [merge request description format]
|
||||
(#merge-request-description-format)
|
||||
1. If the MR changes the UI it should include before and after screenshots
|
||||
1. If the MR changes the UI it should include *Before* and *After* screenshots
|
||||
1. If the MR changes CSS classes please include the list of affected pages,
|
||||
`grep css-class ./app -R`
|
||||
1. Link any relevant [issues][ce-tracker] in the merge request description and
|
||||
|
@ -270,7 +276,9 @@ request is as follows:
|
|||
[shell command guidelines](doc/development/shell_commands.md)
|
||||
1. If your code creates new files on disk please read the
|
||||
[shared files guidelines](doc/development/shared_files.md).
|
||||
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
|
||||
1. When writing commit messages please follow
|
||||
[these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
[guidelines](http://chris.beams.io/posts/git-commit/).
|
||||
1. If your merge request adds one or more migrations, make sure to execute all
|
||||
migrations on a fresh database before the MR is reviewed. If the review leads
|
||||
to large changes in the MR, do this again once the review is complete.
|
||||
|
@ -305,23 +313,6 @@ Please ensure that your merge request meets the contribution acceptance criteria
|
|||
When having your code reviewed and when reviewing merge requests please take the
|
||||
[code review guidelines](doc/development/code_review.md) into account.
|
||||
|
||||
### Merge request description format
|
||||
|
||||
Please submit merge requests using the following template in the merge request
|
||||
description area. Copy-paste it to retain the markdown format.
|
||||
|
||||
```
|
||||
## What does this MR do?
|
||||
|
||||
## Are there points in the code the reviewer needs to double check?
|
||||
|
||||
## Why was this MR needed?
|
||||
|
||||
## What are the relevant issue numbers?
|
||||
|
||||
## Screenshots (if relevant)
|
||||
```
|
||||
|
||||
### Contribution acceptance criteria
|
||||
|
||||
1. The change is as small as possible
|
||||
|
@ -333,8 +324,8 @@ description area. Copy-paste it to retain the markdown format.
|
|||
aforementioned failing test
|
||||
1. Your MR initially contains a single commit (please use `git rebase -i` to
|
||||
squash commits)
|
||||
1. Your changes can merge without problems (if not please merge `master`, never
|
||||
rebase commits pushed to the remote server)
|
||||
1. Your changes can merge without problems (if not please rebase if you're the
|
||||
only one working on your feature branch, otherwise, merge `master`)
|
||||
1. Does not break any existing functionality
|
||||
1. Fixes one specific issue or implements one specific feature (do not combine
|
||||
things, send separate merge requests if needed)
|
||||
|
@ -352,7 +343,10 @@ description area. Copy-paste it to retain the markdown format.
|
|||
entire line to follow it. This prevents linting tools from generating warnings.
|
||||
- Don't touch neighbouring lines. As an exception, automatic mass
|
||||
refactoring modifications may leave style non-compliant.
|
||||
1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.), they should conform to our [Licensing guidelines][license-finder-doc]. See the instructions in that document for help if your MR fails the "license-finder" test with a "Dependencies that need approval" error.
|
||||
1. If the merge request adds any new libraries (gems, JavaScript libraries,
|
||||
etc.), they should conform to our [Licensing guidelines][license-finder-doc].
|
||||
See the instructions in that document for help if your MR fails the
|
||||
"license-finder" test with a "Dependencies that need approval" error.
|
||||
|
||||
## Changes for Stable Releases
|
||||
|
||||
|
@ -468,7 +462,6 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
|
||||
[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
|
||||
[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
|
||||
[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls
|
||||
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
|
||||
[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
|
||||
[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.6.1
|
||||
3.6.6
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.8.2
|
||||
0.8.5
|
||||
|
|
47
Gemfile
|
@ -6,10 +6,8 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
|||
# Responders respond_to and respond_with
|
||||
gem 'responders', '~> 2.0'
|
||||
|
||||
# Specify a sprockets version due to increased performance
|
||||
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
|
||||
gem 'sprockets', '~> 3.6.0'
|
||||
gem 'sprockets-es6'
|
||||
gem 'sprockets', '~> 3.7.0'
|
||||
gem 'sprockets-es6', '~> 0.9.2'
|
||||
|
||||
# Default values for AR models
|
||||
gem 'default_value_for', '~> 3.0.0'
|
||||
|
@ -19,7 +17,7 @@ gem 'mysql2', '~> 0.3.16', group: :mysql
|
|||
gem 'pg', '~> 0.18.2', group: :postgres
|
||||
|
||||
# Authentication libraries
|
||||
gem 'devise', '~> 4.0'
|
||||
gem 'devise', '~> 4.2'
|
||||
gem 'doorkeeper', '~> 4.2.0'
|
||||
gem 'omniauth', '~> 1.3.1'
|
||||
gem 'omniauth-auth0', '~> 1.4.1'
|
||||
|
@ -31,7 +29,7 @@ gem 'omniauth-github', '~> 1.1.1'
|
|||
gem 'omniauth-gitlab', '~> 1.0.0'
|
||||
gem 'omniauth-google-oauth2', '~> 0.4.1'
|
||||
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
|
||||
gem 'omniauth-saml', '~> 1.6.0'
|
||||
gem 'omniauth-saml', '~> 1.7.0'
|
||||
gem 'omniauth-shibboleth', '~> 1.2.0'
|
||||
gem 'omniauth-twitter', '~> 1.2.0'
|
||||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
|
@ -53,7 +51,7 @@ gem 'browser', '~> 2.2'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem 'gitlab_git', '~> 10.6.6'
|
||||
gem 'gitlab_git', '~> 10.6.8'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
|
@ -102,8 +100,8 @@ gem 'seed-fu', '~> 2.3.5'
|
|||
|
||||
# Markdown and HTML processing
|
||||
gem 'html-pipeline', '~> 1.11.0'
|
||||
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
|
||||
gem 'github-markup', '~> 1.4'
|
||||
gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie'
|
||||
gem 'gitlab-markup', '~> 1.5.0'
|
||||
gem 'redcarpet', '~> 3.3.3'
|
||||
gem 'RedCloth', '~> 4.3.2'
|
||||
gem 'rdoc', '~>3.6'
|
||||
|
@ -112,6 +110,7 @@ gem 'creole', '~> 0.5.0'
|
|||
gem 'wikicloth', '0.8.1'
|
||||
gem 'asciidoctor', '~> 1.5.2'
|
||||
gem 'rouge', '~> 2.0'
|
||||
gem 'truncato', '~> 0.7.8'
|
||||
|
||||
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
|
||||
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
|
||||
|
@ -122,8 +121,8 @@ gem 'diffy', '~> 3.0.3'
|
|||
|
||||
# Application server
|
||||
group :unicorn do
|
||||
gem 'unicorn', '~> 4.9.0'
|
||||
gem 'unicorn-worker-killer', '~> 0.4.2'
|
||||
gem 'unicorn', '~> 5.1.0'
|
||||
gem 'unicorn-worker-killer', '~> 0.4.4'
|
||||
end
|
||||
|
||||
# State machine
|
||||
|
@ -132,11 +131,10 @@ gem 'state_machines-activerecord', '~> 0.4.0'
|
|||
gem 'after_commit_queue', '~> 1.3.0'
|
||||
|
||||
# Issue tags
|
||||
gem 'acts-as-taggable-on', '~> 3.4'
|
||||
gem 'acts-as-taggable-on', '~> 4.0'
|
||||
|
||||
# Background jobs
|
||||
gem 'sinatra', '~> 1.4.4', require: false
|
||||
gem 'sidekiq', '~> 4.0'
|
||||
gem 'sidekiq', '~> 4.2'
|
||||
gem 'sidekiq-cron', '~> 0.4.0'
|
||||
gem 'redis-namespace', '~> 1.5.2'
|
||||
|
||||
|
@ -213,7 +211,7 @@ gem 'oj', '~> 2.17.4'
|
|||
gem 'chronic', '~> 0.10.2'
|
||||
gem 'chronic_duration', '~> 0.10.6'
|
||||
|
||||
gem 'sass-rails', '~> 5.0.0'
|
||||
gem 'sass-rails', '~> 5.0.6'
|
||||
gem 'coffee-rails', '~> 4.1.0'
|
||||
gem 'uglifier', '~> 2.7.2'
|
||||
gem 'turbolinks', '~> 2.5.0'
|
||||
|
@ -227,14 +225,14 @@ gem 'gon', '~> 6.1.0'
|
|||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||
gem 'jquery-rails', '~> 4.1.0'
|
||||
gem 'jquery-ui-rails', '~> 5.0.0'
|
||||
gem 'request_store', '~> 1.3.0'
|
||||
gem 'request_store', '~> 1.3'
|
||||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
gem 'base32', '~> 0.3.0'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven', '~> 1.1.0'
|
||||
gem 'sentry-raven', '~> 2.0.0'
|
||||
|
||||
gem 'premailer-rails', '~> 1.9.0'
|
||||
|
||||
|
@ -298,12 +296,11 @@ group :development, :test do
|
|||
gem 'spring-commands-spinach', '~> 1.1.0'
|
||||
gem 'spring-commands-teaspoon', '~> 0.0.2'
|
||||
|
||||
gem 'rubocop', '~> 0.42.0', require: false
|
||||
gem 'rubocop', '~> 0.43.0', require: false
|
||||
gem 'rubocop-rspec', '~> 1.5.0', require: false
|
||||
gem 'scss_lint', '~> 0.47.0', require: false
|
||||
gem 'haml_lint', '~> 0.18.2', require: false
|
||||
gem 'simplecov', '0.12.0', require: false
|
||||
gem 'flog', '~> 4.3.2', require: false
|
||||
gem 'flay', '~> 2.6.1', require: false
|
||||
gem 'bundler-audit', '~> 0.5.0', require: false
|
||||
|
||||
|
@ -311,6 +308,8 @@ group :development, :test do
|
|||
|
||||
gem 'license_finder', '~> 2.1.0', require: false
|
||||
gem 'knapsack', '~> 1.11.0'
|
||||
|
||||
gem 'activerecord_sane_schema_dumper', '0.2'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
@ -323,19 +322,15 @@ group :test do
|
|||
gem 'timecop', '~> 0.8.0'
|
||||
end
|
||||
|
||||
group :production do
|
||||
gem 'gitlab_meta', '7.0'
|
||||
end
|
||||
|
||||
gem 'newrelic_rpm', '~> 3.16'
|
||||
|
||||
gem 'octokit', '~> 4.3.0'
|
||||
|
||||
gem 'mail_room', '~> 0.8'
|
||||
gem 'mail_room', '~> 0.8.1'
|
||||
|
||||
gem 'email_reply_parser', '~> 0.5.8'
|
||||
|
||||
gem 'ruby-prof', '~> 0.15.9'
|
||||
gem 'ruby-prof', '~> 0.16.2'
|
||||
|
||||
## CI
|
||||
gem 'activerecord-session_store', '~> 1.0.0'
|
||||
|
@ -348,7 +343,7 @@ gem 'oauth2', '~> 1.2.0'
|
|||
gem 'paranoia', '~> 2.0'
|
||||
|
||||
# Health check
|
||||
gem 'health_check', '~> 2.1.0'
|
||||
gem 'health_check', '~> 2.2.0'
|
||||
|
||||
# System information
|
||||
gem 'vmstat', '~> 2.2'
|
||||
|
|
108
Gemfile.lock
|
@ -38,14 +38,16 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 1.5.2, < 3)
|
||||
railties (>= 4.0, < 5.1)
|
||||
activerecord_sane_schema_dumper (0.2)
|
||||
rails (>= 4, < 5)
|
||||
activesupport (4.2.7.1)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
acts-as-taggable-on (3.5.0)
|
||||
activerecord (>= 3.2, < 5)
|
||||
acts-as-taggable-on (4.0.0)
|
||||
activerecord (>= 4.0)
|
||||
addressable (2.3.8)
|
||||
after_commit_queue (1.3.0)
|
||||
activerecord (>= 3.0)
|
||||
|
@ -157,11 +159,15 @@ GEM
|
|||
database_cleaner (1.5.3)
|
||||
debug_inspector (0.0.2)
|
||||
debugger-ruby_core_source (1.3.8)
|
||||
deckar01-task_list (1.0.5)
|
||||
activesupport (~> 4.0)
|
||||
html-pipeline
|
||||
rack (~> 1.0)
|
||||
default_value_for (3.0.2)
|
||||
activerecord (>= 3.2.0, < 5.1)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (4.1.1)
|
||||
devise (4.2.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 5.1)
|
||||
|
@ -209,9 +215,6 @@ GEM
|
|||
flay (2.6.1)
|
||||
ruby_parser (~> 3.0)
|
||||
sexp_processor (~> 4.0)
|
||||
flog (4.3.2)
|
||||
ruby_parser (~> 3.1, > 3.1.0)
|
||||
sexp_processor (~> 4.4)
|
||||
flowdock (0.7.1)
|
||||
httparty (~> 0.7)
|
||||
multi_json
|
||||
|
@ -279,12 +282,12 @@ GEM
|
|||
diff-lcs (~> 1.1)
|
||||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_git (10.6.6)
|
||||
gitlab-markup (1.5.0)
|
||||
gitlab_git (10.6.8)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
rugged (~> 0.24.0)
|
||||
gitlab_meta (7.0)
|
||||
gitlab_omniauth-ldap (1.2.1)
|
||||
net-ldap (~> 0.9)
|
||||
omniauth (~> 1.0)
|
||||
|
@ -334,7 +337,7 @@ GEM
|
|||
thor
|
||||
tilt
|
||||
hashie (3.4.4)
|
||||
health_check (2.1.0)
|
||||
health_check (2.2.1)
|
||||
rails (>= 4.0)
|
||||
hipchat (1.5.2)
|
||||
httparty
|
||||
|
@ -399,7 +402,7 @@ GEM
|
|||
systemu (~> 2.6.2)
|
||||
mail (2.6.4)
|
||||
mime-types (>= 1.16, < 4)
|
||||
mail_room (0.8.0)
|
||||
mail_room (0.8.1)
|
||||
method_source (0.8.2)
|
||||
mime-types (2.99.3)
|
||||
mimemagic (0.3.0)
|
||||
|
@ -470,9 +473,9 @@ GEM
|
|||
omniauth-oauth2 (1.3.1)
|
||||
oauth2 (~> 1.0)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-saml (1.6.0)
|
||||
omniauth-saml (1.7.0)
|
||||
omniauth (~> 1.3)
|
||||
ruby-saml (~> 1.3)
|
||||
ruby-saml (~> 1.4)
|
||||
omniauth-shibboleth (1.2.1)
|
||||
omniauth (>= 1.0.0)
|
||||
omniauth-twitter (1.2.1)
|
||||
|
@ -487,7 +490,7 @@ GEM
|
|||
orm_adapter (0.5.0)
|
||||
paranoia (2.1.4)
|
||||
activerecord (~> 4.0)
|
||||
parser (2.3.1.2)
|
||||
parser (2.3.1.4)
|
||||
ast (~> 2.2)
|
||||
pg (0.18.4)
|
||||
pkg-config (1.1.7)
|
||||
|
@ -554,7 +557,7 @@ GEM
|
|||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.1.0)
|
||||
raindrops (0.15.0)
|
||||
raindrops (0.17.0)
|
||||
rake (10.5.0)
|
||||
rb-fsevent (0.9.6)
|
||||
rb-inotify (0.9.5)
|
||||
|
@ -588,7 +591,7 @@ GEM
|
|||
request_store (1.3.1)
|
||||
rerun (0.11.0)
|
||||
listen (~> 3.0)
|
||||
responders (2.1.1)
|
||||
responders (2.3.0)
|
||||
railties (>= 4.2.0, < 5.1)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
|
@ -620,7 +623,7 @@ GEM
|
|||
rspec-retry (0.4.5)
|
||||
rspec-core
|
||||
rspec-support (3.5.0)
|
||||
rubocop (0.42.0)
|
||||
rubocop (0.43.0)
|
||||
parser (>= 2.3.1.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
|
@ -630,9 +633,9 @@ GEM
|
|||
rubocop (>= 0.40.0)
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-prof (0.15.9)
|
||||
ruby-prof (0.16.2)
|
||||
ruby-progressbar (1.8.1)
|
||||
ruby-saml (1.3.0)
|
||||
ruby-saml (1.4.1)
|
||||
nokogiri (>= 1.5.10)
|
||||
ruby_parser (3.8.2)
|
||||
sexp_processor (~> 4.1)
|
||||
|
@ -645,7 +648,7 @@ GEM
|
|||
sanitize (2.1.0)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.4.22)
|
||||
sass-rails (5.0.5)
|
||||
sass-rails (5.0.6)
|
||||
railties (>= 4.0.0, < 6)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
|
@ -665,19 +668,19 @@ GEM
|
|||
activesupport (>= 3.1)
|
||||
select2-rails (3.5.9.3)
|
||||
thor (~> 0.14)
|
||||
sentry-raven (1.1.0)
|
||||
faraday (>= 0.7.6)
|
||||
sentry-raven (2.0.2)
|
||||
faraday (>= 0.7.6, < 0.10.x)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.7.0)
|
||||
sham_rack (1.3.6)
|
||||
rack
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (4.1.4)
|
||||
sidekiq (4.2.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (~> 1.5)
|
||||
redis (~> 3.2, >= 3.2.1)
|
||||
sinatra (>= 1.4.7)
|
||||
sidekiq-cron (0.4.0)
|
||||
redis-namespace (>= 1.5.2)
|
||||
rufus-scheduler (>= 2.0.24)
|
||||
|
@ -687,10 +690,6 @@ GEM
|
|||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
sinatra (1.4.7)
|
||||
rack (~> 1.5)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
slack-notifier (1.2.1)
|
||||
slop (3.6.0)
|
||||
spinach (0.8.10)
|
||||
|
@ -710,10 +709,10 @@ GEM
|
|||
spring (>= 0.9.1)
|
||||
spring-commands-teaspoon (0.0.2)
|
||||
spring (>= 0.9.1)
|
||||
sprockets (3.6.3)
|
||||
sprockets (3.7.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-es6 (0.9.0)
|
||||
sprockets-es6 (0.9.2)
|
||||
babel-source (>= 5.8.11)
|
||||
babel-transpiler
|
||||
sprockets (>= 3.0.0)
|
||||
|
@ -733,8 +732,6 @@ GEM
|
|||
ffi
|
||||
sysexits (1.2.0)
|
||||
systemu (2.6.5)
|
||||
task_list (1.0.2)
|
||||
html-pipeline
|
||||
teaspoon (1.1.5)
|
||||
railties (>= 3.2.5, < 6)
|
||||
teaspoon-jasmine (2.2.0)
|
||||
|
@ -751,6 +748,9 @@ GEM
|
|||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
truncato (0.7.8)
|
||||
htmlentities (~> 4.3.1)
|
||||
nokogiri (~> 1.6.1)
|
||||
turbolinks (2.5.3)
|
||||
coffee-rails
|
||||
tzinfo (1.2.2)
|
||||
|
@ -764,9 +764,8 @@ GEM
|
|||
unf_ext
|
||||
unf_ext (0.0.7.2)
|
||||
unicode-display_width (1.1.1)
|
||||
unicorn (4.9.0)
|
||||
unicorn (5.1.0)
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
raindrops (~> 0.7)
|
||||
unicorn-worker-killer (0.4.4)
|
||||
get_process_mem (~> 0)
|
||||
|
@ -809,7 +808,8 @@ DEPENDENCIES
|
|||
RedCloth (~> 4.3.2)
|
||||
ace-rails-ap (~> 4.1.0)
|
||||
activerecord-session_store (~> 1.0.0)
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
activerecord_sane_schema_dumper (= 0.2)
|
||||
acts-as-taggable-on (~> 4.0)
|
||||
addressable (~> 2.3.8)
|
||||
after_commit_queue (~> 1.3.0)
|
||||
akismet (~> 2.0)
|
||||
|
@ -840,8 +840,9 @@ DEPENDENCIES
|
|||
creole (~> 0.5.0)
|
||||
d3_rails (~> 3.5.0)
|
||||
database_cleaner (~> 1.5.0)
|
||||
deckar01-task_list (= 1.0.5)
|
||||
default_value_for (~> 3.0.0)
|
||||
devise (~> 4.0)
|
||||
devise (~> 4.2)
|
||||
devise-two-factor (~> 3.0.0)
|
||||
diffy (~> 3.0.3)
|
||||
doorkeeper (~> 4.2.0)
|
||||
|
@ -851,7 +852,6 @@ DEPENDENCIES
|
|||
factory_girl_rails (~> 4.6.0)
|
||||
ffaker (~> 2.0.0)
|
||||
flay (~> 2.6.1)
|
||||
flog (~> 4.3.2)
|
||||
fog-aws (~> 0.9)
|
||||
fog-azure (~> 0.0)
|
||||
fog-core (~> 1.40)
|
||||
|
@ -865,10 +865,9 @@ DEPENDENCIES
|
|||
gemnasium-gitlab-service (~> 0.2)
|
||||
gemojione (~> 3.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
github-markup (~> 1.4)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_git (~> 10.6.6)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab-markup (~> 1.5.0)
|
||||
gitlab_git (~> 10.6.8)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.2)
|
||||
|
@ -877,7 +876,7 @@ DEPENDENCIES
|
|||
grape-entity (~> 0.4.2)
|
||||
haml_lint (~> 0.18.2)
|
||||
hamlit (~> 2.6.1)
|
||||
health_check (~> 2.1.0)
|
||||
health_check (~> 2.2.0)
|
||||
hipchat (~> 1.5.0)
|
||||
html-pipeline (~> 1.11.0)
|
||||
httparty (~> 0.13.3)
|
||||
|
@ -894,7 +893,7 @@ DEPENDENCIES
|
|||
license_finder (~> 2.1.0)
|
||||
licensee (~> 8.0.0)
|
||||
loofah (~> 2.0.3)
|
||||
mail_room (~> 0.8)
|
||||
mail_room (~> 0.8.1)
|
||||
method_source (~> 0.8)
|
||||
minitest (~> 5.7.0)
|
||||
mousetrap-rails (~> 1.4.6)
|
||||
|
@ -916,7 +915,7 @@ DEPENDENCIES
|
|||
omniauth-gitlab (~> 1.0.0)
|
||||
omniauth-google-oauth2 (~> 0.4.1)
|
||||
omniauth-kerberos (~> 0.3.0)
|
||||
omniauth-saml (~> 1.6.0)
|
||||
omniauth-saml (~> 1.7.0)
|
||||
omniauth-shibboleth (~> 1.2.0)
|
||||
omniauth-twitter (~> 1.2.0)
|
||||
omniauth_crowd (~> 2.2.0)
|
||||
|
@ -939,31 +938,30 @@ DEPENDENCIES
|
|||
redis (~> 3.2)
|
||||
redis-namespace (~> 1.5.2)
|
||||
redis-rails (~> 4.0.0)
|
||||
request_store (~> 1.3.0)
|
||||
request_store (~> 1.3)
|
||||
rerun (~> 0.11.0)
|
||||
responders (~> 2.0)
|
||||
rouge (~> 2.0)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-rails (~> 3.5.0)
|
||||
rspec-retry (~> 0.4.5)
|
||||
rubocop (~> 0.42.0)
|
||||
rubocop (~> 0.43.0)
|
||||
rubocop-rspec (~> 1.5.0)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
ruby-prof (~> 0.15.9)
|
||||
ruby-prof (~> 0.16.2)
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 5.0.0)
|
||||
sass-rails (~> 5.0.6)
|
||||
scss_lint (~> 0.47.0)
|
||||
sdoc (~> 0.3.20)
|
||||
seed-fu (~> 2.3.5)
|
||||
select2-rails (~> 3.5.9)
|
||||
sentry-raven (~> 1.1.0)
|
||||
sentry-raven (~> 2.0.0)
|
||||
settingslogic (~> 2.0.9)
|
||||
sham_rack (~> 1.3.6)
|
||||
shoulda-matchers (~> 2.8.0)
|
||||
sidekiq (~> 4.0)
|
||||
sidekiq (~> 4.2)
|
||||
sidekiq-cron (~> 0.4.0)
|
||||
simplecov (= 0.12.0)
|
||||
sinatra (~> 1.4.4)
|
||||
slack-notifier (~> 1.2.0)
|
||||
spinach-rails (~> 0.2.1)
|
||||
spinach-rerun-reporter (~> 0.0.2)
|
||||
|
@ -971,23 +969,23 @@ DEPENDENCIES
|
|||
spring-commands-rspec (~> 1.0.4)
|
||||
spring-commands-spinach (~> 1.1.0)
|
||||
spring-commands-teaspoon (~> 0.0.2)
|
||||
sprockets (~> 3.6.0)
|
||||
sprockets-es6
|
||||
sprockets (~> 3.7.0)
|
||||
sprockets-es6 (~> 0.9.2)
|
||||
state_machines-activerecord (~> 0.4.0)
|
||||
sys-filesystem (~> 1.1.6)
|
||||
task_list (~> 1.0.2)
|
||||
teaspoon (~> 1.1.0)
|
||||
teaspoon-jasmine (~> 2.2.0)
|
||||
test_after_commit (~> 0.4.2)
|
||||
thin (~> 1.7.0)
|
||||
timecop (~> 0.8.0)
|
||||
truncato (~> 0.7.8)
|
||||
turbolinks (~> 2.5.0)
|
||||
u2f (~> 0.2.1)
|
||||
uglifier (~> 2.7.2)
|
||||
underscore-rails (~> 1.8.0)
|
||||
unf (~> 0.1.4)
|
||||
unicorn (~> 4.9.0)
|
||||
unicorn-worker-killer (~> 0.4.2)
|
||||
unicorn (~> 5.1.0)
|
||||
unicorn-worker-killer (~> 0.4.4)
|
||||
version_sorter (~> 2.1.0)
|
||||
virtus (~> 1.0.1)
|
||||
vmstat (~> 2.2)
|
||||
|
@ -996,4 +994,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.12.5
|
||||
1.13.2
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# GitLab
|
||||
|
||||
[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
|
||||
[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
|
||||
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
|
||||
[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
|
||||
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
|
||||
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
|
||||
|
||||
## Canonical source
|
||||
|
||||
The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
|
||||
The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
|
||||
|
||||
## Open source software to collaborate on code
|
||||
|
||||
|
|
2
VERSION
|
@ -1 +1 @@
|
|||
8.12.3
|
||||
8.13.3
|
||||
|
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif
Normal file
After Width: | Height: | Size: 3 KiB |
After Width: | Height: | Size: 663 B |
After Width: | Height: | Size: 369 B |
After Width: | Height: | Size: 278 B |
After Width: | Height: | Size: 1,013 B |
BIN
app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red.gif
Normal file
After Width: | Height: | Size: 660 B |
|
@ -1,115 +0,0 @@
|
|||
(function() {
|
||||
this.LabelManager = (function() {
|
||||
LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
|
||||
|
||||
function LabelManager(opts) {
|
||||
// Defaults
|
||||
var ref, ref1, ref2;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
this.togglePriorityButton = (ref = opts.togglePriorityButton) != null ? ref : $('.js-toggle-priority'), this.prioritizedLabels = (ref1 = opts.prioritizedLabels) != null ? ref1 : $('.js-prioritized-labels'), this.otherLabels = (ref2 = opts.otherLabels) != null ? ref2 : $('.js-other-labels');
|
||||
this.prioritizedLabels.sortable({
|
||||
items: 'li',
|
||||
placeholder: 'list-placeholder',
|
||||
axis: 'y',
|
||||
update: this.onPrioritySortUpdate.bind(this)
|
||||
});
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
LabelManager.prototype.bindEvents = function() {
|
||||
return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
|
||||
};
|
||||
|
||||
LabelManager.prototype.onTogglePriorityClick = function(e) {
|
||||
var $btn, $label, $tooltip, _this, action;
|
||||
e.preventDefault();
|
||||
_this = e.data;
|
||||
$btn = $(e.currentTarget);
|
||||
$label = $("#" + ($btn.data('domId')));
|
||||
action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
|
||||
// Make sure tooltip will hide
|
||||
$tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby')));
|
||||
$tooltip.tooltip('destroy');
|
||||
return _this.toggleLabelPriority($label, action);
|
||||
};
|
||||
|
||||
LabelManager.prototype.toggleLabelPriority = function($label, action, persistState) {
|
||||
var $from, $target, _this, url, xhr;
|
||||
if (persistState == null) {
|
||||
persistState = true;
|
||||
}
|
||||
_this = this;
|
||||
url = $label.find('.js-toggle-priority').data('url');
|
||||
$target = this.prioritizedLabels;
|
||||
$from = this.otherLabels;
|
||||
// Optimistic update
|
||||
if (action === 'remove') {
|
||||
$target = this.otherLabels;
|
||||
$from = this.prioritizedLabels;
|
||||
}
|
||||
if ($from.find('li').length === 1) {
|
||||
$from.find('.empty-message').removeClass('hidden');
|
||||
}
|
||||
if (!$target.find('li').length) {
|
||||
$target.find('.empty-message').addClass('hidden');
|
||||
}
|
||||
$label.detach().appendTo($target);
|
||||
// Return if we are not persisting state
|
||||
if (!persistState) {
|
||||
return;
|
||||
}
|
||||
if (action === 'remove') {
|
||||
xhr = $.ajax({
|
||||
url: url,
|
||||
type: 'DELETE'
|
||||
});
|
||||
// Restore empty message
|
||||
if (!$from.find('li').length) {
|
||||
$from.find('.empty-message').removeClass('hidden');
|
||||
}
|
||||
} else {
|
||||
xhr = this.savePrioritySort($label, action);
|
||||
}
|
||||
return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
|
||||
};
|
||||
|
||||
LabelManager.prototype.onPrioritySortUpdate = function() {
|
||||
var xhr;
|
||||
xhr = this.savePrioritySort();
|
||||
return xhr.fail(function() {
|
||||
return new Flash(this.errorMessage, 'alert');
|
||||
});
|
||||
};
|
||||
|
||||
LabelManager.prototype.savePrioritySort = function() {
|
||||
return $.post({
|
||||
url: this.prioritizedLabels.data('url'),
|
||||
data: {
|
||||
label_ids: this.getSortedLabelsIds()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
LabelManager.prototype.rollbackLabelPosition = function($label, originalAction) {
|
||||
var action;
|
||||
action = originalAction === 'remove' ? 'add' : 'remove';
|
||||
this.toggleLabelPriority($label, action, false);
|
||||
return new Flash(this.errorMessage, 'alert');
|
||||
};
|
||||
|
||||
LabelManager.prototype.getSortedLabelsIds = function() {
|
||||
var sortedIds;
|
||||
sortedIds = [];
|
||||
this.prioritizedLabels.find('li').each(function() {
|
||||
return sortedIds.push($(this).data('id'));
|
||||
});
|
||||
return sortedIds;
|
||||
};
|
||||
|
||||
return LabelManager;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
|
@ -21,16 +21,14 @@
|
|||
};
|
||||
|
||||
Activities.prototype.toggleFilter = function(sender) {
|
||||
var event_filters, filter;
|
||||
var filter = sender.attr("id").split("_")[0];
|
||||
|
||||
$('.event-filter .active').removeClass("active");
|
||||
event_filters = $.cookie("event_filter");
|
||||
filter = sender.attr("id").split("_")[0];
|
||||
$.cookie("event_filter", (event_filters !== filter ? filter : ""), {
|
||||
$.cookie("event_filter", filter, {
|
||||
path: gon.relative_url_root || '/'
|
||||
});
|
||||
if (event_filters !== filter) {
|
||||
return sender.closest('li').toggleClass("active");
|
||||
}
|
||||
|
||||
sender.closest('li').toggleClass("active");
|
||||
};
|
||||
|
||||
return Activities;
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
groupProjectsPath: "/api/:version/groups/:id/projects.json",
|
||||
projectsPath: "/api/:version/projects.json?simple=true",
|
||||
labelsPath: "/:namespace_path/:project_path/labels",
|
||||
licensePath: "/api/:version/licenses/:key",
|
||||
gitignorePath: "/api/:version/gitignores/:key",
|
||||
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
|
||||
licensePath: "/api/:version/templates/licenses/:key",
|
||||
gitignorePath: "/api/:version/templates/gitignores/:key",
|
||||
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
|
||||
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
|
||||
|
||||
group: function(group_id, callback) {
|
||||
var url = Api.buildUrl(Api.groupPath)
|
||||
.replace(':id', group_id);
|
||||
|
@ -23,12 +22,13 @@
|
|||
},
|
||||
// Return groups list. Filtered by query
|
||||
// Only active groups retrieved
|
||||
groups: function(query, skip_ldap, callback) {
|
||||
groups: function(query, skip_ldap, skip_groups, callback) {
|
||||
var url = Api.buildUrl(Api.groupsPath);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: {
|
||||
search: query,
|
||||
skip_groups: skip_groups,
|
||||
per_page: 20
|
||||
},
|
||||
dataType: "json"
|
||||
|
|
|
@ -247,7 +247,7 @@
|
|||
$this.toggleClass('active');
|
||||
var notesHolders = $this.closest('.diff-file').find('.notes_holder');
|
||||
if ($this.hasClass('active')) {
|
||||
notesHolders.show();
|
||||
notesHolders.show().find('.hide').show();
|
||||
} else {
|
||||
notesHolders.hide();
|
||||
}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
|
||||
/*= require blob/template_selector */
|
||||
|
||||
(function() {
|
||||
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
hasProp = {}.hasOwnProperty;
|
||||
|
||||
this.BlobCiYamlSelector = (function(superClass) {
|
||||
extend(BlobCiYamlSelector, superClass);
|
||||
|
||||
function BlobCiYamlSelector() {
|
||||
return BlobCiYamlSelector.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
|
||||
BlobCiYamlSelector.prototype.requestFile = function(query) {
|
||||
return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
|
||||
};
|
||||
|
||||
return BlobCiYamlSelector;
|
||||
|
||||
})(TemplateSelector);
|
||||
|
||||
this.BlobCiYamlSelectors = (function() {
|
||||
function BlobCiYamlSelectors(opts) {
|
||||
var ref;
|
||||
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitlab-ci-yml-selector'), this.editor = opts.editor;
|
||||
this.$dropdowns.each((function(_this) {
|
||||
return function(i, dropdown) {
|
||||
var $dropdown;
|
||||
$dropdown = $(dropdown);
|
||||
return new BlobCiYamlSelector({
|
||||
pattern: /(.gitlab-ci.yml)/,
|
||||
data: $dropdown.data('data'),
|
||||
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
|
||||
dropdown: $dropdown,
|
||||
editor: _this.editor
|
||||
});
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
return BlobCiYamlSelectors;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
40
app/assets/javascripts/blob/blob_ci_yaml.js.es6
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*= require blob/template_selector */
|
||||
((global) => {
|
||||
|
||||
class BlobCiYamlSelector extends gl.TemplateSelector {
|
||||
requestFile(query) {
|
||||
return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
|
||||
}
|
||||
|
||||
requestFileSuccess(file) {
|
||||
return super.requestFileSuccess(file);
|
||||
}
|
||||
}
|
||||
|
||||
global.BlobCiYamlSelector = BlobCiYamlSelector;
|
||||
|
||||
class BlobCiYamlSelectors {
|
||||
constructor({ editor, $dropdowns } = {}) {
|
||||
this.editor = editor;
|
||||
this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
|
||||
this.initSelectors();
|
||||
}
|
||||
|
||||
initSelectors() {
|
||||
const editor = this.editor;
|
||||
this.$dropdowns.each((i, dropdown) => {
|
||||
const $dropdown = $(dropdown);
|
||||
return new BlobCiYamlSelector({
|
||||
editor,
|
||||
pattern: /(.gitlab-ci.yml)/,
|
||||
data: $dropdown.data('data'),
|
||||
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
|
||||
dropdown: $dropdown
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
global.BlobCiYamlSelectors = BlobCiYamlSelectors;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -18,6 +18,6 @@
|
|||
|
||||
return BlobGitignoreSelector;
|
||||
|
||||
})(TemplateSelector);
|
||||
})(gl.TemplateSelector);
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -23,6 +23,6 @@
|
|||
|
||||
return BlobLicenseSelector;
|
||||
|
||||
})(TemplateSelector);
|
||||
})(gl.TemplateSelector);
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
(function() {
|
||||
this.BlobLicenseSelectors = (function() {
|
||||
function BlobLicenseSelectors(opts) {
|
||||
var ref;
|
||||
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-license-selector'), this.editor = opts.editor;
|
||||
this.$dropdowns.each((function(_this) {
|
||||
return function(i, dropdown) {
|
||||
var $dropdown;
|
||||
$dropdown = $(dropdown);
|
||||
return new BlobLicenseSelector({
|
||||
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
|
||||
data: $dropdown.data('data'),
|
||||
wrapper: $dropdown.closest('.js-license-selector-wrap'),
|
||||
dropdown: $dropdown,
|
||||
editor: _this.editor
|
||||
});
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
return BlobLicenseSelectors;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
21
app/assets/javascripts/blob/blob_license_selectors.js.es6
Normal file
|
@ -0,0 +1,21 @@
|
|||
((global) => {
|
||||
class BlobLicenseSelectors {
|
||||
constructor({ $dropdowns, editor }) {
|
||||
this.$dropdowns = $('.js-license-selector');
|
||||
this.editor = editor;
|
||||
this.$dropdowns.each((i, dropdown) => {
|
||||
const $dropdown = $(dropdown);
|
||||
return new BlobLicenseSelector({
|
||||
editor,
|
||||
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
|
||||
data: $dropdown.data('data'),
|
||||
wrapper: $dropdown.closest('.js-license-selector-wrap'),
|
||||
dropdown: $dropdown,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
global.BlobLicenseSelectors = BlobLicenseSelectors;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,100 +0,0 @@
|
|||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
this.TemplateSelector = (function() {
|
||||
function TemplateSelector(opts) {
|
||||
var ref;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
this.onClick = bind(this.onClick, this);
|
||||
this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name');
|
||||
this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
|
||||
this.buildDropdown();
|
||||
this.bindEvents();
|
||||
this.onFilenameUpdate();
|
||||
|
||||
this.autosizeUpdateEvent = document.createEvent('Event');
|
||||
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
|
||||
}
|
||||
|
||||
TemplateSelector.prototype.buildDropdown = function() {
|
||||
return this.dropdown.glDropdown({
|
||||
data: this.data,
|
||||
filterable: true,
|
||||
selectable: true,
|
||||
toggleLabel: this.toggleLabel,
|
||||
search: {
|
||||
fields: ['name']
|
||||
},
|
||||
clicked: this.onClick,
|
||||
text: function(item) {
|
||||
return item.name;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
TemplateSelector.prototype.bindEvents = function() {
|
||||
return this.$input.on('keyup blur', (function(_this) {
|
||||
return function(e) {
|
||||
return _this.onFilenameUpdate();
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
TemplateSelector.prototype.toggleLabel = function(item) {
|
||||
return item.name;
|
||||
};
|
||||
|
||||
TemplateSelector.prototype.onFilenameUpdate = function() {
|
||||
var filenameMatches;
|
||||
if (!this.$input.length) {
|
||||
return;
|
||||
}
|
||||
filenameMatches = this.pattern.test(this.$input.val().trim());
|
||||
if (!filenameMatches) {
|
||||
this.wrapper.addClass('hidden');
|
||||
return;
|
||||
}
|
||||
return this.wrapper.removeClass('hidden');
|
||||
};
|
||||
|
||||
TemplateSelector.prototype.onClick = function(item, el, e) {
|
||||
e.preventDefault();
|
||||
return this.requestFile(item);
|
||||
};
|
||||
|
||||
TemplateSelector.prototype.requestFile = function(item) {
|
||||
// This `requestFile` method is an abstract method that should
|
||||
// be added by all subclasses.
|
||||
};
|
||||
|
||||
// To be implemented on the extending class
|
||||
// e.g.
|
||||
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
|
||||
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
|
||||
this.editor.setValue(file.content, 1);
|
||||
if (!skipFocus) this.editor.focus();
|
||||
|
||||
if (this.editor instanceof jQuery) {
|
||||
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
|
||||
}
|
||||
};
|
||||
|
||||
TemplateSelector.prototype.startLoadingSpinner = function() {
|
||||
this.dropdownIcon
|
||||
.addClass('fa-spinner fa-spin')
|
||||
.removeClass('fa-chevron-down');
|
||||
};
|
||||
|
||||
TemplateSelector.prototype.stopLoadingSpinner = function() {
|
||||
this.dropdownIcon
|
||||
.addClass('fa-chevron-down')
|
||||
.removeClass('fa-spinner fa-spin');
|
||||
};
|
||||
|
||||
return TemplateSelector;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
97
app/assets/javascripts/blob/template_selector.js.es6
Normal file
|
@ -0,0 +1,97 @@
|
|||
((global) => {
|
||||
class TemplateSelector {
|
||||
constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.dropdown = dropdown;
|
||||
this.data = data;
|
||||
this.pattern = pattern;
|
||||
this.wrapper = wrapper;
|
||||
this.editor = editor;
|
||||
this.fileEndpoint = fileEndpoint;
|
||||
this.$input = $input || $('#file_name');
|
||||
this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
|
||||
this.buildDropdown();
|
||||
this.bindEvents();
|
||||
this.onFilenameUpdate();
|
||||
|
||||
this.autosizeUpdateEvent = document.createEvent('Event');
|
||||
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
|
||||
}
|
||||
|
||||
buildDropdown() {
|
||||
return this.dropdown.glDropdown({
|
||||
data: this.data,
|
||||
filterable: true,
|
||||
selectable: true,
|
||||
toggleLabel: this.toggleLabel,
|
||||
search: {
|
||||
fields: ['name']
|
||||
},
|
||||
clicked: this.onClick,
|
||||
text: function(item) {
|
||||
return item.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
|
||||
}
|
||||
|
||||
toggleLabel(item) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
onFilenameUpdate() {
|
||||
var filenameMatches;
|
||||
if (!this.$input.length) {
|
||||
return;
|
||||
}
|
||||
filenameMatches = this.pattern.test(this.$input.val().trim());
|
||||
if (!filenameMatches) {
|
||||
this.wrapper.addClass('hidden');
|
||||
return;
|
||||
}
|
||||
return this.wrapper.removeClass('hidden');
|
||||
}
|
||||
|
||||
onClick(item, el, e) {
|
||||
e.preventDefault();
|
||||
return this.requestFile(item);
|
||||
}
|
||||
|
||||
requestFile(item) {
|
||||
// This `requestFile` method is an abstract method that should
|
||||
// be added by all subclasses.
|
||||
}
|
||||
|
||||
// To be implemented on the extending class
|
||||
// e.g.
|
||||
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
|
||||
requestFileSuccess(file, { skipFocus } = {}) {
|
||||
const oldValue = this.editor.getValue();
|
||||
let newValue = file.content;
|
||||
|
||||
this.editor.setValue(newValue, 1);
|
||||
if (!skipFocus) this.editor.focus();
|
||||
|
||||
if (this.editor instanceof jQuery) {
|
||||
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
|
||||
}
|
||||
}
|
||||
|
||||
startLoadingSpinner() {
|
||||
this.dropdownIcon
|
||||
.addClass('fa-spinner fa-spin')
|
||||
.removeClass('fa-chevron-down');
|
||||
}
|
||||
|
||||
stopLoadingSpinner() {
|
||||
this.dropdownIcon
|
||||
.addClass('fa-chevron-down')
|
||||
.removeClass('fa-spinner fa-spin');
|
||||
}
|
||||
}
|
||||
|
||||
global.TemplateSelector = TemplateSelector;
|
||||
})(window.gl || ( window.gl = {}));
|
|
@ -22,13 +22,14 @@
|
|||
// submitted textarea
|
||||
})(this));
|
||||
this.initModePanesAndLinks();
|
||||
new BlobLicenseSelectors({
|
||||
this.initSoftWrap();
|
||||
new gl.BlobLicenseSelectors({
|
||||
editor: this.editor
|
||||
});
|
||||
new BlobGitignoreSelectors({
|
||||
editor: this.editor
|
||||
});
|
||||
new BlobCiYamlSelectors({
|
||||
new gl.BlobCiYamlSelectors({
|
||||
editor: this.editor
|
||||
});
|
||||
}
|
||||
|
@ -50,6 +51,7 @@
|
|||
this.$editModePanes.hide();
|
||||
currentPane.fadeIn(200);
|
||||
if (paneId === "#preview") {
|
||||
this.$toggleButton.hide();
|
||||
return $.post(currentLink.data("preview-url"), {
|
||||
content: this.editor.getValue()
|
||||
}, function(response) {
|
||||
|
@ -57,10 +59,23 @@
|
|||
return currentPane.syntaxHighlight();
|
||||
});
|
||||
} else {
|
||||
this.$toggleButton.show();
|
||||
return this.editor.focus();
|
||||
}
|
||||
};
|
||||
|
||||
EditBlob.prototype.initSoftWrap = function() {
|
||||
this.isSoftWrapped = false;
|
||||
this.$toggleButton = $('.soft-wrap-toggle');
|
||||
this.$toggleButton.on('click', this.toggleSoftWrap.bind(this));
|
||||
};
|
||||
|
||||
EditBlob.prototype.toggleSoftWrap = function(e) {
|
||||
this.isSoftWrapped = !this.isSoftWrapped;
|
||||
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
|
||||
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
|
||||
};
|
||||
|
||||
return EditBlob;
|
||||
|
||||
})();
|
||||
|
|
|
@ -28,12 +28,13 @@ $(() => {
|
|||
state: Store.state,
|
||||
loading: true,
|
||||
endpoint: $boardApp.dataset.endpoint,
|
||||
boardId: $boardApp.dataset.boardId,
|
||||
disabled: $boardApp.dataset.disabled === 'true',
|
||||
issueLinkBase: $boardApp.dataset.issueLinkBase
|
||||
},
|
||||
init: Store.create.bind(Store),
|
||||
created () {
|
||||
gl.boardService = new BoardService(this.endpoint);
|
||||
gl.boardService = new BoardService(this.endpoint, this.boardId);
|
||||
},
|
||||
ready () {
|
||||
Store.disabled = this.disabled;
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
filters: Store.state.filters
|
||||
filters: Store.state.filters,
|
||||
showIssueForm: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
@ -33,6 +34,11 @@
|
|||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showNewIssueForm() {
|
||||
this.showIssueForm = !this.showIssueForm;
|
||||
}
|
||||
},
|
||||
ready () {
|
||||
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||
disabled: this.disabled,
|
||||
|
|
|
@ -8,10 +8,8 @@
|
|||
data () {
|
||||
return {
|
||||
predefinedLabels: [
|
||||
new ListLabel({ title: 'Development', color: '#5CB85C' }),
|
||||
new ListLabel({ title: 'Testing', color: '#F0AD4E' }),
|
||||
new ListLabel({ title: 'Production', color: '#FF5F00' }),
|
||||
new ListLabel({ title: 'Ready', color: '#FF0000' })
|
||||
new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
|
||||
new ListLabel({ title: 'Doing', color: '#5CB85C' })
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//= require ./board_card
|
||||
//= require ./board_new_issue
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
@ -8,14 +9,16 @@
|
|||
|
||||
gl.issueBoards.BoardList = Vue.extend({
|
||||
components: {
|
||||
'board-card': gl.issueBoards.BoardCard
|
||||
'board-card': gl.issueBoards.BoardCard,
|
||||
'board-new-issue': gl.issueBoards.BoardNewIssue
|
||||
},
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
list: Object,
|
||||
issues: Array,
|
||||
loading: Boolean,
|
||||
issueLinkBase: String
|
||||
issueLinkBase: String,
|
||||
showIssueForm: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -73,7 +76,7 @@
|
|||
group: 'issues',
|
||||
sort: false,
|
||||
disabled: this.disabled,
|
||||
filter: '.board-list-count',
|
||||
filter: '.board-list-count, .is-disabled',
|
||||
onStart: (e) => {
|
||||
const card = this.$refs.issue[e.oldIndex];
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
||||
gl.issueBoards.BoardNewIssue = Vue.extend({
|
||||
props: {
|
||||
list: Object,
|
||||
showIssueForm: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
error: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
showIssueForm () {
|
||||
this.$els.input.focus();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit(e) {
|
||||
e.preventDefault();
|
||||
if (this.title.trim() === '') return;
|
||||
|
||||
this.error = false;
|
||||
|
||||
const labels = this.list.label ? [this.list.label] : [];
|
||||
const issue = new ListIssue({
|
||||
title: this.title,
|
||||
labels
|
||||
});
|
||||
|
||||
this.list.newIssue(issue)
|
||||
.then((data) => {
|
||||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
$(this.$els.submitButton).enable();
|
||||
})
|
||||
.catch(() => {
|
||||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
$(this.$els.submitButton).enable();
|
||||
|
||||
// Remove the issue
|
||||
this.list.removeIssue(issue);
|
||||
|
||||
// Show error message
|
||||
this.error = true;
|
||||
this.showIssueForm = true;
|
||||
});
|
||||
|
||||
this.cancel();
|
||||
},
|
||||
cancel() {
|
||||
this.showIssueForm = false;
|
||||
this.title = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -21,7 +21,7 @@
|
|||
fallbackClass: 'is-dragging',
|
||||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
filter: '.has-tooltip',
|
||||
filter: '.has-tooltip, .btn',
|
||||
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||
scrollSpeed: 20,
|
||||
|
|
|
@ -87,6 +87,17 @@ class List {
|
|||
});
|
||||
}
|
||||
|
||||
newIssue (issue) {
|
||||
this.addIssue(issue);
|
||||
this.issuesSize++;
|
||||
|
||||
return gl.boardService.newIssue(this.id, issue)
|
||||
.then((resp) => {
|
||||
const data = resp.json();
|
||||
issue.id = data.iid;
|
||||
});
|
||||
}
|
||||
|
||||
createIssues (data) {
|
||||
data.forEach((issueObj) => {
|
||||
this.addIssue(new ListIssue(issueObj));
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
class BoardService {
|
||||
constructor (root) {
|
||||
constructor (root, boardId) {
|
||||
Vue.http.options.root = root;
|
||||
|
||||
this.lists = Vue.resource(`${root}/lists{/id}`, {}, {
|
||||
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
|
||||
generate: {
|
||||
method: 'POST',
|
||||
url: `${root}/lists/generate.json`
|
||||
url: `${root}/${boardId}/lists/generate.json`
|
||||
}
|
||||
});
|
||||
this.issue = Vue.resource(`${root}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${root}/lists{/id}/issues`, {});
|
||||
this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {});
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
|
@ -58,4 +58,10 @@ class BoardService {
|
|||
to_list_id
|
||||
});
|
||||
}
|
||||
|
||||
newIssue (id, issue) {
|
||||
return this.issues.save({ id }, {
|
||||
issue
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,18 +15,17 @@
|
|||
this.hideSidebar = bind(this.hideSidebar, this);
|
||||
this.toggleSidebar = bind(this.toggleSidebar, this);
|
||||
this.updateDropdown = bind(this.updateDropdown, this);
|
||||
this.$document = $(document);
|
||||
clearInterval(Build.interval);
|
||||
// Init breakpoint checker
|
||||
this.bp = Breakpoints.get();
|
||||
$('.js-build-sidebar').niceScroll();
|
||||
this.initSidebar();
|
||||
|
||||
this.populateJobs(this.build_stage);
|
||||
this.updateStageDropdownText(this.build_stage);
|
||||
this.hideSidebar();
|
||||
|
||||
$(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
|
||||
$(window).off('resize.build').on('resize.build', this.hideSidebar);
|
||||
$(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
|
||||
this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
|
||||
$('#js-build-scroll > a').off('click').on('click', this.stepTrace);
|
||||
this.updateArtifactRemoveDate();
|
||||
if ($('#build-trace').length) {
|
||||
|
@ -62,6 +61,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
Build.prototype.initSidebar = function() {
|
||||
this.$sidebar = $('.js-build-sidebar');
|
||||
this.sidebarTranslationLimits = {
|
||||
min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
|
||||
}
|
||||
this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight();
|
||||
this.$sidebar.css({
|
||||
top: this.sidebarTranslationLimits.max
|
||||
});
|
||||
this.$sidebar.niceScroll();
|
||||
this.hideSidebar();
|
||||
this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
|
||||
this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this));
|
||||
};
|
||||
|
||||
Build.prototype.getInitialBuildTrace = function() {
|
||||
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']
|
||||
|
||||
|
@ -129,15 +143,23 @@
|
|||
|
||||
Build.prototype.toggleSidebar = function() {
|
||||
if (this.shouldHideSidebar()) {
|
||||
return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed');
|
||||
return this.$sidebar.toggleClass('right-sidebar-expanded right-sidebar-collapsed');
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.translateSidebar = function(e) {
|
||||
var newPosition = this.sidebarTranslationLimits.max - document.body.scrollTop;
|
||||
if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min;
|
||||
this.$sidebar.css({
|
||||
top: newPosition
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.hideSidebar = function() {
|
||||
if (this.shouldHideSidebar()) {
|
||||
return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
|
||||
return this.$sidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
|
||||
} else {
|
||||
return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
|
||||
return this.$sidebar.removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -146,7 +168,7 @@
|
|||
$date = $('.js-artifacts-remove');
|
||||
if ($date.length) {
|
||||
date = $date.text();
|
||||
return $date.text($.timefor(new Date(date.replace(/-/g, '/')), ' '));
|
||||
return $date.text($.timefor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
var $dropdown, selected;
|
||||
$dropdown = $(this);
|
||||
selected = $dropdown.data('selected');
|
||||
return $dropdown.glDropdown({
|
||||
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'),
|
||||
|
@ -23,8 +26,9 @@
|
|||
selectable: true,
|
||||
filterable: true,
|
||||
filterByText: true,
|
||||
fieldName: $dropdown.attr('name'),
|
||||
filterInput: 'input[type="text"]',
|
||||
toggleLabel: true,
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
filterInput: 'input[type="search"]',
|
||||
renderRow: function(ref) {
|
||||
var link;
|
||||
if (ref.header != null) {
|
||||
|
@ -41,6 +45,14 @@
|
|||
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');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -26,15 +26,15 @@
|
|||
};
|
||||
|
||||
showTooltip = function(target, title) {
|
||||
return $(target).tooltip({
|
||||
container: 'body',
|
||||
html: 'true',
|
||||
placement: 'auto bottom',
|
||||
title: title,
|
||||
trigger: 'manual'
|
||||
}).tooltip('show').one('mouseleave', function() {
|
||||
return $(this).tooltip('hide');
|
||||
});
|
||||
var $target = $(target);
|
||||
var originalTitle = $target.data('original-title');
|
||||
|
||||
$target
|
||||
.attr('title', 'Copied!')
|
||||
.tooltip('fixTitle')
|
||||
.tooltip('show')
|
||||
.attr('title', originalTitle)
|
||||
.tooltip('fixTitle');
|
||||
};
|
||||
|
||||
$(function() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//= require vue
|
||||
|
||||
((global) => {
|
||||
|
||||
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
|
||||
|
@ -34,7 +36,11 @@
|
|||
method: 'GET',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: { start_date: options.startDate }
|
||||
data: {
|
||||
cycle_analytics: {
|
||||
start_date: options.startDate
|
||||
}
|
||||
}
|
||||
}).done((data) => {
|
||||
this.decorateData(data);
|
||||
this.initDropdown();
|
|
@ -7,6 +7,9 @@
|
|||
function Diff() {
|
||||
$('.files .diff-file').singleFileDiff();
|
||||
this.filesCommentButton = $('.files .diff-file').filesCommentButton();
|
||||
if (this.diffViewType() === 'parallel') {
|
||||
$('.content-wrapper .container-fluid').removeClass('container-limited');
|
||||
}
|
||||
$(document).off('click', '.js-unfold');
|
||||
$(document).on('click', '.js-unfold', (function(_this) {
|
||||
return function(event) {
|
||||
|
@ -52,6 +55,10 @@
|
|||
})(this));
|
||||
}
|
||||
|
||||
Diff.prototype.diffViewType = function() {
|
||||
return $('.inline-parallel-buttons a.active').data('view-type');
|
||||
}
|
||||
|
||||
Diff.prototype.lineNumbers = function(line) {
|
||||
if (!line.children().length) {
|
||||
return [0, 0];
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
Dispatcher = (function() {
|
||||
function Dispatcher() {
|
||||
this.initSearch();
|
||||
this.initFieldErrors();
|
||||
this.initPageScripts();
|
||||
}
|
||||
|
||||
|
@ -20,13 +21,17 @@
|
|||
path = page.split(':');
|
||||
shortcut_handler = null;
|
||||
switch (page) {
|
||||
case 'sessions:new':
|
||||
new UsernameValidator();
|
||||
break;
|
||||
case 'projects:boards:show':
|
||||
case 'projects:boards:index':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:merge_requests:index':
|
||||
case 'projects:issues:index':
|
||||
Issuable.init();
|
||||
new IssuableBulkActions();
|
||||
new gl.IssuableBulkActions();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:issues:show':
|
||||
|
@ -40,12 +45,12 @@
|
|||
new Milestone();
|
||||
break;
|
||||
case 'dashboard:todos:index':
|
||||
new Todos();
|
||||
new gl.Todos();
|
||||
break;
|
||||
case 'projects:milestones:new':
|
||||
case 'projects:milestones:edit':
|
||||
new ZenMode();
|
||||
new DueDateSelect();
|
||||
new gl.DueDateSelectors();
|
||||
new GLForm($('.milestone-form'));
|
||||
break;
|
||||
case 'groups:milestones:new':
|
||||
|
@ -59,7 +64,9 @@
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.issue-form'));
|
||||
new IssuableForm($('.issue-form'));
|
||||
new IssuableTemplateSelectors();
|
||||
new LabelsSelect();
|
||||
new MilestoneSelect();
|
||||
new gl.IssuableTemplateSelectors();
|
||||
break;
|
||||
case 'projects:merge_requests:new':
|
||||
case 'projects:merge_requests:edit':
|
||||
|
@ -67,7 +74,9 @@
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.merge-request-form'));
|
||||
new IssuableForm($('.merge-request-form'));
|
||||
new IssuableTemplateSelectors();
|
||||
new LabelsSelect();
|
||||
new MilestoneSelect();
|
||||
new gl.IssuableTemplateSelectors();
|
||||
break;
|
||||
case 'projects:tags:new':
|
||||
new ZenMode();
|
||||
|
@ -92,9 +101,6 @@
|
|||
new ZenMode();
|
||||
new MergedButtons();
|
||||
break;
|
||||
case "projects:merge_requests:conflicts":
|
||||
window.mcui = new MergeConflictResolver()
|
||||
break;
|
||||
case 'projects:merge_requests:index':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
Issuable.init();
|
||||
|
@ -111,6 +117,9 @@
|
|||
new ZenMode();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:commit:builds':
|
||||
new gl.Pipelines();
|
||||
break;
|
||||
case 'projects:commits:show':
|
||||
case 'projects:activity':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
@ -122,6 +131,9 @@
|
|||
new TreeView();
|
||||
}
|
||||
break;
|
||||
case 'projects:pipelines:show':
|
||||
new gl.Pipelines();
|
||||
break;
|
||||
case 'groups:activity':
|
||||
new Activities();
|
||||
break;
|
||||
|
@ -132,12 +144,12 @@
|
|||
break;
|
||||
case 'groups:group_members:index':
|
||||
new gl.MemberExpirationDate();
|
||||
new GroupMembers();
|
||||
new gl.Members();
|
||||
new UsersSelect();
|
||||
break;
|
||||
case 'projects:project_members:index':
|
||||
new gl.MemberExpirationDate();
|
||||
new ProjectMembers();
|
||||
new gl.Members();
|
||||
new UsersSelect();
|
||||
break;
|
||||
case 'groups:new':
|
||||
|
@ -159,13 +171,15 @@
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
new ShortcutsBlob(true);
|
||||
break;
|
||||
case 'groups:labels:new':
|
||||
case 'groups:labels:edit':
|
||||
case 'projects:labels:new':
|
||||
case 'projects:labels:edit':
|
||||
new Labels();
|
||||
break;
|
||||
case 'projects:labels:index':
|
||||
if ($('.prioritized-labels').length) {
|
||||
new LabelManager();
|
||||
new gl.LabelManager();
|
||||
}
|
||||
break;
|
||||
case 'projects:network:show':
|
||||
|
@ -279,10 +293,16 @@
|
|||
Dispatcher.prototype.initSearch = function() {
|
||||
// Only when search form is present
|
||||
if ($('.search').length) {
|
||||
return new SearchAutocomplete();
|
||||
return new gl.SearchAutocomplete();
|
||||
}
|
||||
};
|
||||
|
||||
Dispatcher.prototype.initFieldErrors = function() {
|
||||
$('.show-gl-field-errors').each((i, form) => {
|
||||
new gl.GlFieldErrors(form);
|
||||
});
|
||||
};
|
||||
|
||||
return Dispatcher;
|
||||
|
||||
})();
|
|
@ -1,107 +0,0 @@
|
|||
(function() {
|
||||
this.DueDateSelect = (function() {
|
||||
function DueDateSelect() {
|
||||
var $datePicker, $dueDate, $loading;
|
||||
// Milestone edit/new form
|
||||
$datePicker = $('.datepicker');
|
||||
if ($datePicker.length) {
|
||||
$dueDate = $('#milestone_due_date');
|
||||
$datePicker.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
onSelect: function(dateText, inst) {
|
||||
return $dueDate.val(dateText);
|
||||
}
|
||||
}).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
|
||||
}
|
||||
$('.js-clear-due-date').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
return $.datepicker._clearDate($datePicker);
|
||||
});
|
||||
// Issuable sidebar
|
||||
$loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
|
||||
$('.js-due-date-select').each(function(i, dropdown) {
|
||||
var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
|
||||
$dropdown = $(dropdown);
|
||||
$dropdownParent = $dropdown.closest('.dropdown');
|
||||
$datePicker = $dropdownParent.find('.js-due-date-calendar');
|
||||
$block = $dropdown.closest('.block');
|
||||
$selectbox = $dropdown.closest('.selectbox');
|
||||
$value = $block.find('.value');
|
||||
$valueContent = $block.find('.value-content');
|
||||
$sidebarValue = $('.js-due-date-sidebar-value', $block);
|
||||
fieldName = $dropdown.data('field-name');
|
||||
abilityName = $dropdown.data('ability-name');
|
||||
issueUpdateURL = $dropdown.data('issue-update');
|
||||
$dropdown.glDropdown({
|
||||
hidden: function() {
|
||||
$selectbox.hide();
|
||||
return $value.css('display', '');
|
||||
}
|
||||
});
|
||||
addDueDate = function(isDropdown) {
|
||||
var data, date, mediumDate, value;
|
||||
// Create the post date
|
||||
value = $("input[name='" + fieldName + "']").val();
|
||||
if (value !== '') {
|
||||
date = new Date(value.replace(new RegExp('-', 'g'), ','));
|
||||
mediumDate = $.datepicker.formatDate('M d, yy', date);
|
||||
} else {
|
||||
mediumDate = 'No due date';
|
||||
}
|
||||
data = {};
|
||||
data[abilityName] = {};
|
||||
data[abilityName].due_date = value;
|
||||
return $.ajax({
|
||||
type: 'PUT',
|
||||
url: issueUpdateURL,
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
beforeSend: function() {
|
||||
var cssClass;
|
||||
$loading.fadeIn();
|
||||
if (isDropdown) {
|
||||
$dropdown.trigger('loading.gl.dropdown');
|
||||
$selectbox.hide();
|
||||
}
|
||||
$value.css('display', '');
|
||||
cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value';
|
||||
$valueContent.html("<span class='" + cssClass + "'>" + mediumDate + "</span>");
|
||||
$sidebarValue.html(mediumDate);
|
||||
if (value !== '') {
|
||||
return $('.js-remove-due-date-holder').removeClass('hidden');
|
||||
} else {
|
||||
return $('.js-remove-due-date-holder').addClass('hidden');
|
||||
}
|
||||
}
|
||||
}).done(function(data) {
|
||||
if (isDropdown) {
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$dropdown.dropdown('toggle');
|
||||
}
|
||||
return $loading.fadeOut();
|
||||
});
|
||||
};
|
||||
$block.on('click', '.js-remove-due-date', function(e) {
|
||||
e.preventDefault();
|
||||
$("input[name='" + fieldName + "']").val('');
|
||||
return addDueDate(false);
|
||||
});
|
||||
return $datePicker.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
defaultDate: $("input[name='" + fieldName + "']").val(),
|
||||
altField: "input[name='" + fieldName + "']",
|
||||
onSelect: function() {
|
||||
return addDueDate(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
$(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) {
|
||||
return e.stopImmediatePropagation();
|
||||
});
|
||||
}
|
||||
|
||||
return DueDateSelect;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
161
app/assets/javascripts/due_date_select.js.es6
Normal file
|
@ -0,0 +1,161 @@
|
|||
(function(global) {
|
||||
class DueDateSelect {
|
||||
constructor({ $dropdown, $loading } = {}) {
|
||||
const $dropdownParent = $dropdown.closest('.dropdown');
|
||||
const $block = $dropdown.closest('.block');
|
||||
this.$loading = $loading;
|
||||
this.$dropdown = $dropdown;
|
||||
this.$dropdownParent = $dropdownParent;
|
||||
this.$datePicker = $dropdownParent.find('.js-due-date-calendar');
|
||||
this.$block = $block;
|
||||
this.$selectbox = $dropdown.closest('.selectbox');
|
||||
this.$value = $block.find('.value');
|
||||
this.$valueContent = $block.find('.value-content');
|
||||
this.$sidebarValue = $('.js-due-date-sidebar-value', $block);
|
||||
this.fieldName = $dropdown.data('field-name'),
|
||||
this.abilityName = $dropdown.data('ability-name'),
|
||||
this.issueUpdateURL = $dropdown.data('issue-update')
|
||||
|
||||
this.rawSelectedDate = null;
|
||||
this.displayedDate = null;
|
||||
this.datePayload = null;
|
||||
|
||||
this.initGlDropdown();
|
||||
this.initRemoveDueDate();
|
||||
this.initDatePicker();
|
||||
this.initStopPropagation();
|
||||
}
|
||||
|
||||
initGlDropdown() {
|
||||
this.$dropdown.glDropdown({
|
||||
hidden: () => {
|
||||
this.$selectbox.hide();
|
||||
this.$value.css('display', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initDatePicker() {
|
||||
this.$datePicker.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
defaultDate: $("input[name='" + this.fieldName + "']").val(),
|
||||
altField: "input[name='" + this.fieldName + "']",
|
||||
onSelect: () => {
|
||||
return this.saveDueDate(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initRemoveDueDate() {
|
||||
this.$block.on('click', '.js-remove-due-date', (e) => {
|
||||
e.preventDefault();
|
||||
$("input[name='" + this.fieldName + "']").val('');
|
||||
return this.saveDueDate(false);
|
||||
});
|
||||
}
|
||||
|
||||
initStopPropagation() {
|
||||
$(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => {
|
||||
return e.stopImmediatePropagation();
|
||||
});
|
||||
}
|
||||
|
||||
saveDueDate(isDropdown) {
|
||||
this.parseSelectedDate();
|
||||
this.prepSelectedDate();
|
||||
this.submitSelectedDate(isDropdown);
|
||||
}
|
||||
|
||||
parseSelectedDate() {
|
||||
this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val();
|
||||
if (this.rawSelectedDate.length) {
|
||||
let dateObj = new Date(this.rawSelectedDate);
|
||||
this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj);
|
||||
} else {
|
||||
this.displayedDate = 'No due date';
|
||||
}
|
||||
}
|
||||
|
||||
prepSelectedDate() {
|
||||
const datePayload = {};
|
||||
datePayload[this.abilityName] = {};
|
||||
datePayload[this.abilityName].due_date = this.rawSelectedDate;
|
||||
this.datePayload = datePayload;
|
||||
}
|
||||
|
||||
submitSelectedDate(isDropdown) {
|
||||
return $.ajax({
|
||||
type: 'PUT',
|
||||
url: this.issueUpdateURL,
|
||||
data: this.datePayload,
|
||||
dataType: 'json',
|
||||
beforeSend: () => {
|
||||
const selectedDateValue = this.datePayload[this.abilityName].due_date;
|
||||
const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
|
||||
|
||||
this.$loading.fadeIn();
|
||||
|
||||
if (isDropdown) {
|
||||
this.$dropdown.trigger('loading.gl.dropdown');
|
||||
this.$selectbox.hide();
|
||||
}
|
||||
|
||||
this.$value.css('display', '');
|
||||
this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
|
||||
this.$sidebarValue.html(this.displayedDate);
|
||||
|
||||
return selectedDateValue.length ?
|
||||
$('.js-remove-due-date-holder').removeClass('hidden') :
|
||||
$('.js-remove-due-date-holder').addClass('hidden');
|
||||
|
||||
}
|
||||
}).done((data) => {
|
||||
if (isDropdown) {
|
||||
this.$dropdown.trigger('loaded.gl.dropdown');
|
||||
this.$dropdown.dropdown('toggle');
|
||||
}
|
||||
return this.$loading.fadeOut();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DueDateSelectors {
|
||||
constructor() {
|
||||
this.initMilestoneDueDate();
|
||||
this.initIssuableSelect();
|
||||
}
|
||||
|
||||
initMilestoneDueDate() {
|
||||
const $datePicker = $('.datepicker');
|
||||
|
||||
if ($datePicker.length) {
|
||||
const $dueDate = $('#milestone_due_date');
|
||||
$datePicker.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
onSelect: (dateText, inst) => {
|
||||
$dueDate.val(dateText);
|
||||
}
|
||||
}).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
|
||||
}
|
||||
$('.js-clear-due-date').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
$.datepicker._clearDate($datePicker);
|
||||
});
|
||||
}
|
||||
|
||||
initIssuableSelect() {
|
||||
const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
|
||||
|
||||
$('.js-due-date-select').each((i, dropdown) => {
|
||||
const $dropdown = $(dropdown);
|
||||
new DueDateSelect({
|
||||
$dropdown,
|
||||
$loading
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
global.DueDateSelectors = DueDateSelectors;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -52,37 +52,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
setup: function(input) {
|
||||
setup: _.debounce(function(input) {
|
||||
// Add GFM auto-completion to all input fields, that accept GFM input.
|
||||
this.input = input || $('.js-gfm-input');
|
||||
// destroy previous instances
|
||||
this.destroyAtWho();
|
||||
// set up instances
|
||||
this.setupAtWho();
|
||||
if (this.dataSource) {
|
||||
if (!this.dataLoading && !this.cachedData) {
|
||||
|
||||
if (this.dataSource && !this.dataLoading && !this.cachedData) {
|
||||
this.dataLoading = true;
|
||||
setTimeout((function(_this) {
|
||||
return function() {
|
||||
var fetch;
|
||||
fetch = _this.fetchData(_this.dataSource);
|
||||
return fetch.done(function(data) {
|
||||
_this.dataLoading = false;
|
||||
return _this.loadData(data);
|
||||
return this.fetchData(this.dataSource)
|
||||
.done((data) => {
|
||||
this.dataLoading = false;
|
||||
this.loadData(data);
|
||||
});
|
||||
};
|
||||
// We should wait until initializations are done
|
||||
// and only trigger the last .setup since
|
||||
// The previous .dataSource belongs to the previous issuable
|
||||
// and the last one will have the **proper** .dataSource property
|
||||
// TODO: Make this a singleton and turn off events when moving to another page
|
||||
})(this), 1000);
|
||||
}
|
||||
|
||||
if (this.cachedData != null) {
|
||||
return this.loadData(this.cachedData);
|
||||
}
|
||||
}
|
||||
},
|
||||
}, 1000),
|
||||
setupAtWho: function() {
|
||||
// Emoji
|
||||
this.input.atwho({
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
return function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return _this.input.val('').trigger('keyup').focus();
|
||||
return _this.input.val('').trigger('input').focus();
|
||||
};
|
||||
})(this));
|
||||
// Key events
|
||||
|
@ -37,28 +37,16 @@
|
|||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
.on('keyup', function(e) {
|
||||
var keyCode;
|
||||
keyCode = e.which;
|
||||
if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
|
||||
return;
|
||||
}
|
||||
.on('input', function() {
|
||||
if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
||||
$inputContainer.addClass(HAS_VALUE_CLASS);
|
||||
} else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
||||
$inputContainer.removeClass(HAS_VALUE_CLASS);
|
||||
}
|
||||
if (keyCode === 13 && !options.elIsInput) {
|
||||
return false;
|
||||
}
|
||||
// Only filter asynchronously only if option remote is set
|
||||
if (this.options.remote) {
|
||||
clearTimeout(timeout);
|
||||
return timeout = setTimeout(function() {
|
||||
var blurField = this.shouldBlur(keyCode);
|
||||
if (blurField && this.filterInputBlur) {
|
||||
this.input.blur();
|
||||
}
|
||||
return this.options.query(this.input.val(), function(data) {
|
||||
return this.options.callback(data);
|
||||
}.bind(this));
|
||||
|
@ -255,7 +243,7 @@
|
|||
_this.fullData = data;
|
||||
_this.parseData(_this.fullData);
|
||||
if (_this.options.filterable && _this.filter && _this.filter.input) {
|
||||
return _this.filter.input.trigger('keyup');
|
||||
return _this.filter.input.trigger('input');
|
||||
}
|
||||
};
|
||||
// Remote data
|
||||
|
@ -443,6 +431,7 @@
|
|||
var contentHtml;
|
||||
this.resetRows();
|
||||
this.addArrowKeyEvent();
|
||||
|
||||
if (this.options.setIndeterminateIds) {
|
||||
this.options.setIndeterminateIds.call(this);
|
||||
}
|
||||
|
@ -460,9 +449,21 @@
|
|||
if (this.options.filterable) {
|
||||
this.filterInput.focus();
|
||||
}
|
||||
|
||||
if (this.options.showMenuAbove) {
|
||||
this.positionMenuAbove();
|
||||
}
|
||||
|
||||
return this.dropdown.trigger('shown.gl.dropdown');
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.positionMenuAbove = function() {
|
||||
var $button = $(this.el);
|
||||
var $menu = this.dropdown.find('.dropdown-menu');
|
||||
|
||||
$menu.css('top', ($button.height() + $menu.height()) * -1);
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.hidden = function(e) {
|
||||
var $input;
|
||||
this.resetRows();
|
||||
|
@ -474,7 +475,7 @@
|
|||
// Triggering 'keyup' will re-render the dropdown which is not always required
|
||||
// specially if we want to keep the state of the dropdown needed for bulk-assignment
|
||||
if (!this.options.persistWhenHide) {
|
||||
$input.trigger("keyup");
|
||||
$input.trigger("input");
|
||||
}
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
$('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
|
||||
|
@ -487,14 +488,27 @@
|
|||
|
||||
// Render the full menu
|
||||
GitLabDropdown.prototype.renderMenu = function(html) {
|
||||
var menu_html;
|
||||
menu_html = "";
|
||||
if (this.options.renderMenu) {
|
||||
menu_html = this.options.renderMenu(html);
|
||||
return this.options.renderMenu(html);
|
||||
} else {
|
||||
menu_html = $('<ul />').append(html);
|
||||
var ul = document.createElement('ul');
|
||||
|
||||
for (var i = 0; i < html.length; i++) {
|
||||
var el = html[i];
|
||||
|
||||
if (el instanceof jQuery) {
|
||||
el = el.get(0);
|
||||
}
|
||||
|
||||
if (typeof el === 'string') {
|
||||
ul.innerHTML += el;
|
||||
} else {
|
||||
ul.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
return ul;
|
||||
}
|
||||
return menu_html;
|
||||
};
|
||||
|
||||
// Append the menu into the dropdown
|
||||
|
@ -508,7 +522,7 @@
|
|||
};
|
||||
|
||||
GitLabDropdown.prototype.renderItem = function(data, group, index) {
|
||||
var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value;
|
||||
var field, fieldName, html, selected, text, url, value;
|
||||
if (group == null) {
|
||||
group = false;
|
||||
}
|
||||
|
@ -516,18 +530,16 @@
|
|||
// Render the row
|
||||
index = false;
|
||||
}
|
||||
html = "";
|
||||
// Divider
|
||||
if (data === "divider") {
|
||||
return "<li class='divider'></li>";
|
||||
}
|
||||
// Separator is a full-width divider
|
||||
if (data === "separator") {
|
||||
return "<li class='separator'></li>";
|
||||
html = document.createElement('li');
|
||||
if (data === 'divider' || data === 'separator') {
|
||||
html.className = data;
|
||||
return html;
|
||||
}
|
||||
// Header
|
||||
if (data.header != null) {
|
||||
return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header });
|
||||
html.className = 'dropdown-header';
|
||||
html.innerHTML = data.header;
|
||||
return html;
|
||||
}
|
||||
if (this.options.renderRow) {
|
||||
// Call the render function
|
||||
|
@ -554,24 +566,25 @@
|
|||
} else {
|
||||
text = data.text != null ? data.text : '';
|
||||
}
|
||||
cssClass = "";
|
||||
if (selected) {
|
||||
cssClass = "is-active";
|
||||
}
|
||||
if (this.highlight) {
|
||||
text = this.highlightTextMatches(text, this.filterInput.val());
|
||||
}
|
||||
if (group) {
|
||||
groupAttrs = 'data-group=' + group + ' data-index=' + index;
|
||||
} else {
|
||||
groupAttrs = '';
|
||||
// Create the list item & the link
|
||||
var link = document.createElement('a');
|
||||
|
||||
link.href = url;
|
||||
link.innerHTML = text;
|
||||
|
||||
if (selected) {
|
||||
link.className = 'is-active';
|
||||
}
|
||||
html = _.template('<li><a href="<%- url %>" <%- groupAttrs %> class="<%- cssClass %>"><%= text %></a></li>')({
|
||||
url: url,
|
||||
groupAttrs: groupAttrs,
|
||||
cssClass: cssClass,
|
||||
text: text
|
||||
});
|
||||
|
||||
if (group) {
|
||||
link.dataset.group = group;
|
||||
link.dataset.index = index;
|
||||
}
|
||||
|
||||
html.appendChild(link);
|
||||
}
|
||||
return html;
|
||||
};
|
||||
|
@ -725,6 +738,7 @@
|
|||
return false;
|
||||
}
|
||||
if (currentKeyCode === 13 && currentIndex !== -1) {
|
||||
e.preventDefault();
|
||||
_this.selectRowAtIndex();
|
||||
}
|
||||
};
|
||||
|
|
167
app/assets/javascripts/gl_field_errors.js.es6
Normal file
|
@ -0,0 +1,167 @@
|
|||
((global) => {
|
||||
/*
|
||||
* This class overrides the browser's validation error bubbles, displaying custom
|
||||
* error messages for invalid fields instead. To begin validating any form, add the
|
||||
* class `show-gl-field-errors` to the form element, and ensure error messages are
|
||||
* declared in each inputs' title attribute.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <form class='show-gl-field-errors'>
|
||||
* <input type='text' name='username' title='Username is required.'/>
|
||||
*</form>
|
||||
*
|
||||
* */
|
||||
|
||||
const errorMessageClass = 'gl-field-error';
|
||||
const inputErrorClass = 'gl-field-error-outline';
|
||||
|
||||
class GlFieldError {
|
||||
constructor({ input, formErrors }) {
|
||||
this.inputElement = $(input);
|
||||
this.inputDomElement = this.inputElement.get(0);
|
||||
this.form = formErrors;
|
||||
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
|
||||
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
|
||||
|
||||
this.state = {
|
||||
valid: false,
|
||||
empty: true
|
||||
};
|
||||
|
||||
this.initFieldValidation();
|
||||
}
|
||||
|
||||
initFieldValidation() {
|
||||
// hidden when injected into DOM
|
||||
this.inputElement.after(this.fieldErrorElement);
|
||||
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
|
||||
this.scopedSiblings = this.safelySelectSiblings();
|
||||
}
|
||||
|
||||
safelySelectSiblings() {
|
||||
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
|
||||
const ignoreSelector = '.validation-ignore';
|
||||
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
|
||||
const parentContainer = this.inputElement.parent('.form-group');
|
||||
|
||||
// Only select siblings when they're scoped within a form-group with one input
|
||||
const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
|
||||
|
||||
return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
|
||||
}
|
||||
|
||||
renderValidity() {
|
||||
this.renderClear();
|
||||
|
||||
if (this.state.valid) {
|
||||
return this.renderValid();
|
||||
}
|
||||
|
||||
if (this.state.empty) {
|
||||
return this.renderEmpty();
|
||||
}
|
||||
|
||||
if (!this.state.valid) {
|
||||
return this.renderInvalid();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handleInvalidSubmit(event) {
|
||||
event.preventDefault();
|
||||
const currentValue = this.accessCurrentValue();
|
||||
this.state.valid = false;
|
||||
this.state.empty = currentValue === '';
|
||||
|
||||
this.renderValidity();
|
||||
this.form.focusOnFirstInvalid.apply(this.form);
|
||||
// For UX, wait til after first invalid submission to check each keyup
|
||||
this.inputElement.off('keyup.field_validator')
|
||||
.on('keyup.field_validator', this.updateValidity.bind(this));
|
||||
|
||||
}
|
||||
|
||||
/* Get or set current input value */
|
||||
accessCurrentValue(newVal) {
|
||||
return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
|
||||
}
|
||||
|
||||
getInputValidity() {
|
||||
return this.inputDomElement.validity.valid;
|
||||
}
|
||||
|
||||
updateValidity() {
|
||||
const inputVal = this.accessCurrentValue();
|
||||
this.state.empty = !inputVal.length;
|
||||
this.state.valid = this.getInputValidity();
|
||||
this.renderValidity();
|
||||
}
|
||||
|
||||
renderValid() {
|
||||
return this.renderClear();
|
||||
}
|
||||
|
||||
renderEmpty() {
|
||||
return this.renderInvalid();
|
||||
}
|
||||
|
||||
renderInvalid() {
|
||||
this.inputElement.addClass(inputErrorClass);
|
||||
this.scopedSiblings.hide();
|
||||
return this.fieldErrorElement.show();
|
||||
}
|
||||
|
||||
renderClear() {
|
||||
const inputVal = this.accessCurrentValue();
|
||||
if (!inputVal.split(' ').length) {
|
||||
const trimmedInput = inputVal.trim();
|
||||
this.accessCurrentValue(trimmedInput);
|
||||
}
|
||||
this.inputElement.removeClass(inputErrorClass);
|
||||
this.scopedSiblings.hide();
|
||||
this.fieldErrorElement.hide();
|
||||
}
|
||||
}
|
||||
|
||||
const customValidationFlag = 'no-gl-field-errors';
|
||||
|
||||
class GlFieldErrors {
|
||||
constructor(form) {
|
||||
this.form = $(form);
|
||||
this.state = {
|
||||
inputs: [],
|
||||
valid: false
|
||||
};
|
||||
this.initValidators();
|
||||
}
|
||||
|
||||
initValidators () {
|
||||
// select all non-hidden inputs in form
|
||||
this.state.inputs = this.form.find(':input:not([type=hidden])').toArray()
|
||||
.filter((input) => !input.classList.contains(customValidationFlag))
|
||||
.map((input) => new GlFieldError({ input, formErrors: this }));
|
||||
|
||||
this.form.on('submit', this.catchInvalidFormSubmit);
|
||||
}
|
||||
|
||||
/* Neccessary to prevent intercept and override invalid form submit
|
||||
* because Safari & iOS quietly allow form submission when form is invalid
|
||||
* and prevents disabling of invalid submit button by application.js */
|
||||
|
||||
catchInvalidFormSubmit (event) {
|
||||
if (!event.currentTarget.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
focusOnFirstInvalid () {
|
||||
const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0];
|
||||
firstInvalid.inputElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
global.GlFieldErrors = GlFieldErrors;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,13 +0,0 @@
|
|||
(function() {
|
||||
this.GroupMembers = (function() {
|
||||
function GroupMembers() {
|
||||
$('li.group_member').bind('ajax:success', function() {
|
||||
return $(this).fadeOut();
|
||||
});
|
||||
}
|
||||
|
||||
return GroupMembers;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
|
@ -5,14 +5,15 @@
|
|||
function GroupsSelect() {
|
||||
$('.ajax-groups-select').each((function(_this) {
|
||||
return function(i, select) {
|
||||
var skip_ldap;
|
||||
var skip_ldap, skip_groups;
|
||||
skip_ldap = $(select).hasClass('skip_ldap');
|
||||
skip_groups = $(select).data('skip-groups') || [];
|
||||
return $(select).select2({
|
||||
placeholder: "Search for a group",
|
||||
multiple: $(select).hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
query: function(query) {
|
||||
return Api.groups(query.term, skip_ldap, function(groups) {
|
||||
return Api.groups(query.term, skip_ldap, skip_groups, function(groups) {
|
||||
var data;
|
||||
data = {
|
||||
results: groups
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
}).remove();
|
||||
// Submit the form to get new data
|
||||
Issuable.filterResults($('.filter-form'));
|
||||
return $('.js-label-select').trigger('update.label');
|
||||
});
|
||||
},
|
||||
filterResults: (function(_this) {
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
(function() {
|
||||
this.IssuableBulkActions = (function() {
|
||||
function IssuableBulkActions(opts) {
|
||||
// Set defaults
|
||||
var ref, ref1, ref2;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li');
|
||||
// Save instance
|
||||
((global) => {
|
||||
|
||||
class IssuableBulkActions {
|
||||
constructor({ container, form, issues } = {}) {
|
||||
this.container = container || $('.content'),
|
||||
this.form = form || this.getElement('.bulk-update');
|
||||
this.issues = issues || this.getElement('.issues-list .issue');
|
||||
this.form.data('bulkActions', this);
|
||||
this.willUpdateLabels = false;
|
||||
this.bindEvents();
|
||||
|
@ -15,53 +12,46 @@
|
|||
Issuable.initChecks();
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.getElement = function(selector) {
|
||||
getElement(selector) {
|
||||
return this.container.find(selector);
|
||||
};
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.bindEvents = function() {
|
||||
bindEvents() {
|
||||
return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
|
||||
};
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.onFormSubmit = function(e) {
|
||||
onFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
return this.submit();
|
||||
};
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.submit = function() {
|
||||
var _this, xhr;
|
||||
_this = this;
|
||||
xhr = $.ajax({
|
||||
submit() {
|
||||
const _this = this;
|
||||
const xhr = $.ajax({
|
||||
url: this.form.attr('action'),
|
||||
method: this.form.attr('method'),
|
||||
dataType: 'JSON',
|
||||
data: this.getFormDataAsObject()
|
||||
});
|
||||
xhr.done(function(response, status, xhr) {
|
||||
return location.reload();
|
||||
});
|
||||
xhr.fail(function() {
|
||||
return new Flash("Issue update failed");
|
||||
});
|
||||
xhr.done(() => window.location.reload());
|
||||
xhr.fail(() => new Flash("Issue update failed"));
|
||||
return xhr.always(this.onFormSubmitAlways.bind(this));
|
||||
};
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.onFormSubmitAlways = function() {
|
||||
onFormSubmitAlways() {
|
||||
return this.form.find('[type="submit"]').enable();
|
||||
};
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.getSelectedIssues = function() {
|
||||
getSelectedIssues() {
|
||||
return this.issues.has('.selected_issue:checked');
|
||||
};
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.getLabelsFromSelection = function() {
|
||||
var labels;
|
||||
labels = [];
|
||||
getLabelsFromSelection() {
|
||||
const labels = [];
|
||||
this.getSelectedIssues().map(function() {
|
||||
var _labels;
|
||||
_labels = $(this).data('labels');
|
||||
if (_labels) {
|
||||
return _labels.map(function(labelId) {
|
||||
const labelsData = $(this).data('labels');
|
||||
if (labelsData) {
|
||||
return labelsData.map(function(labelId) {
|
||||
if (labels.indexOf(labelId) === -1) {
|
||||
return labels.push(labelId);
|
||||
}
|
||||
|
@ -69,7 +59,7 @@
|
|||
}
|
||||
});
|
||||
return labels;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -77,25 +67,21 @@
|
|||
* @return {Array} Label IDs
|
||||
*/
|
||||
|
||||
IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() {
|
||||
var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result;
|
||||
result = [];
|
||||
labelsToKeep = [];
|
||||
ref = this.getElement('.labels-filter .is-indeterminate');
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
el = ref[i];
|
||||
labelsToKeep.push($(el).data('labelId'));
|
||||
}
|
||||
ref1 = this.getLabelsFromSelection();
|
||||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||||
id = ref1[j];
|
||||
// Only the ones that we are not going to keep
|
||||
getUnmarkedIndeterminedLabels() {
|
||||
const result = [];
|
||||
const labelsToKeep = [];
|
||||
|
||||
this.getElement('.labels-filter .is-indeterminate')
|
||||
.each((i, el) => labelsToKeep.push($(el).data('labelId')));
|
||||
|
||||
this.getLabelsFromSelection().forEach((id) => {
|
||||
if (labelsToKeep.indexOf(id) === -1) {
|
||||
result.push(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -103,9 +89,8 @@
|
|||
* Returns key/value pairs from form data
|
||||
*/
|
||||
|
||||
IssuableBulkActions.prototype.getFormDataAsObject = function() {
|
||||
var formData;
|
||||
formData = {
|
||||
getFormDataAsObject() {
|
||||
const formData = {
|
||||
update: {
|
||||
state_event: this.form.find('input[name="update[state_event]"]').val(),
|
||||
assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
|
||||
|
@ -125,19 +110,18 @@
|
|||
});
|
||||
}
|
||||
return formData;
|
||||
};
|
||||
}
|
||||
|
||||
IssuableBulkActions.prototype.getLabelsToApply = function() {
|
||||
var $labels, labelIds;
|
||||
labelIds = [];
|
||||
$labels = this.form.find('.labels-filter input[name="update[label_ids][]"]');
|
||||
getLabelsToApply() {
|
||||
const labelIds = [];
|
||||
const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]');
|
||||
$labels.each(function(k, label) {
|
||||
if (label) {
|
||||
return labelIds.push(parseInt($(label).val()));
|
||||
}
|
||||
});
|
||||
return labelIds;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -145,11 +129,10 @@
|
|||
* @return {Array} Array of labels IDs
|
||||
*/
|
||||
|
||||
IssuableBulkActions.prototype.getLabelsToRemove = function() {
|
||||
var indeterminatedLabels, labelsToApply, result;
|
||||
result = [];
|
||||
indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
|
||||
labelsToApply = this.getLabelsToApply();
|
||||
getLabelsToRemove() {
|
||||
const result = [];
|
||||
const indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
|
||||
const labelsToApply = this.getLabelsToApply();
|
||||
indeterminatedLabels.map(function(id) {
|
||||
// We need to exclude label IDs that will be applied
|
||||
// By not doing this will cause issues from selection to not add labels at all
|
||||
|
@ -158,10 +141,9 @@
|
|||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return IssuableBulkActions;
|
||||
global.IssuableBulkActions = IssuableBulkActions;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
||||
})(window.gl || (window.gl = {}));
|
106
app/assets/javascripts/label_manager.js.es6
Normal file
|
@ -0,0 +1,106 @@
|
|||
((global) => {
|
||||
|
||||
class LabelManager {
|
||||
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
|
||||
this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority');
|
||||
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
|
||||
this.otherLabels = otherLabels || $('.js-other-labels');
|
||||
this.errorMessage = 'Unable to update label prioritization at this time';
|
||||
this.prioritizedLabels.sortable({
|
||||
items: 'li',
|
||||
placeholder: 'list-placeholder',
|
||||
axis: 'y',
|
||||
update: this.onPrioritySortUpdate.bind(this)
|
||||
});
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
|
||||
}
|
||||
|
||||
onTogglePriorityClick(e) {
|
||||
e.preventDefault();
|
||||
const _this = e.data;
|
||||
const $btn = $(e.currentTarget);
|
||||
const $label = $(`#${$btn.data('domId')}`);
|
||||
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
|
||||
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
|
||||
$tooltip.tooltip('destroy');
|
||||
return _this.toggleLabelPriority($label, action);
|
||||
}
|
||||
|
||||
toggleLabelPriority($label, action, persistState) {
|
||||
if (persistState == null) {
|
||||
persistState = true;
|
||||
}
|
||||
let xhr;
|
||||
const _this = this;
|
||||
const url = $label.find('.js-toggle-priority').data('url');
|
||||
let $target = this.prioritizedLabels;
|
||||
let $from = this.otherLabels;
|
||||
if (action === 'remove') {
|
||||
$target = this.otherLabels;
|
||||
$from = this.prioritizedLabels;
|
||||
}
|
||||
if ($from.find('li').length === 1) {
|
||||
$from.find('.empty-message').removeClass('hidden');
|
||||
}
|
||||
if (!$target.find('li').length) {
|
||||
$target.find('.empty-message').addClass('hidden');
|
||||
}
|
||||
$label.detach().appendTo($target);
|
||||
// Return if we are not persisting state
|
||||
if (!persistState) {
|
||||
return;
|
||||
}
|
||||
if (action === 'remove') {
|
||||
xhr = $.ajax({
|
||||
url,
|
||||
type: 'DELETE'
|
||||
});
|
||||
// Restore empty message
|
||||
if (!$from.find('li').length) {
|
||||
$from.find('.empty-message').removeClass('hidden');
|
||||
}
|
||||
} else {
|
||||
xhr = this.savePrioritySort($label, action);
|
||||
}
|
||||
return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
|
||||
}
|
||||
|
||||
onPrioritySortUpdate() {
|
||||
const xhr = this.savePrioritySort();
|
||||
return xhr.fail(function() {
|
||||
return new Flash(this.errorMessage, 'alert');
|
||||
});
|
||||
}
|
||||
|
||||
savePrioritySort() {
|
||||
return $.post({
|
||||
url: this.prioritizedLabels.data('url'),
|
||||
data: {
|
||||
label_ids: this.getSortedLabelsIds()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rollbackLabelPosition($label, originalAction) {
|
||||
const action = originalAction === 'remove' ? 'add' : 'remove';
|
||||
this.toggleLabelPriority($label, action, false);
|
||||
return new Flash(this.errorMessage, 'alert');
|
||||
}
|
||||
|
||||
getSortedLabelsIds() {
|
||||
const sortedIds = [];
|
||||
this.prioritizedLabels.find('li').each(function() {
|
||||
sortedIds.push($(this).data('id'));
|
||||
});
|
||||
return sortedIds;
|
||||
}
|
||||
}
|
||||
|
||||
gl.LabelManager = LabelManager;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
||||
|
|
@ -4,8 +4,9 @@
|
|||
var _this;
|
||||
_this = this;
|
||||
$('.js-label-select').each(function(i, dropdown) {
|
||||
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected;
|
||||
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove;
|
||||
$dropdown = $(dropdown);
|
||||
$toggleText = $dropdown.find('.dropdown-toggle-text');
|
||||
namespacePath = $dropdown.data('namespace-path');
|
||||
projectPath = $dropdown.data('project-path');
|
||||
labelUrl = $dropdown.data('labels');
|
||||
|
@ -16,6 +17,7 @@
|
|||
}
|
||||
showNo = $dropdown.data('show-no');
|
||||
showAny = $dropdown.data('show-any');
|
||||
showMenuAbove = $dropdown.data('showMenuAbove');
|
||||
defaultLabel = $dropdown.data('default-label');
|
||||
abilityName = $dropdown.data('ability-name');
|
||||
$selectbox = $dropdown.closest('.selectbox');
|
||||
|
@ -25,6 +27,9 @@
|
|||
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
|
||||
$value = $block.find('.value');
|
||||
$loading = $block.find('.block-loading').fadeOut();
|
||||
fieldName = $dropdown.data('field-name');
|
||||
useId = $dropdown.is('.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown');
|
||||
propertyName = useId ? 'id' : 'title';
|
||||
initialSelected = $selectbox
|
||||
.find('input[name="' + $dropdown.data('field-name') + '"]')
|
||||
.map(function () {
|
||||
|
@ -46,7 +51,7 @@
|
|||
|
||||
saveLabelData = function() {
|
||||
var data, selected;
|
||||
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() {
|
||||
selected = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "']").map(function() {
|
||||
return this.value;
|
||||
}).get();
|
||||
|
||||
|
@ -76,7 +81,8 @@
|
|||
if (data.labels.length) {
|
||||
template = labelHTMLTemplate(data);
|
||||
labelCount = data.labels.length;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
template = labelNoneHTMLTemplate;
|
||||
}
|
||||
$value.removeAttr('style').html(template);
|
||||
|
@ -93,7 +99,8 @@
|
|||
}
|
||||
|
||||
labelTooltipTitle = labelTitles.join(', ');
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
labelTooltipTitle = '';
|
||||
$sidebarLabelTooltip.tooltip('destroy');
|
||||
}
|
||||
|
@ -115,6 +122,7 @@
|
|||
});
|
||||
};
|
||||
return $dropdown.glDropdown({
|
||||
showMenuAbove: showMenuAbove,
|
||||
data: function(term, callback) {
|
||||
return $.ajax({
|
||||
url: labelUrl
|
||||
|
@ -134,23 +142,29 @@
|
|||
};
|
||||
}).value();
|
||||
if ($dropdown.hasClass('js-extra-options')) {
|
||||
var extraData = [];
|
||||
if (showNo) {
|
||||
data.unshift({
|
||||
extraData.unshift({
|
||||
id: 0,
|
||||
title: 'No Label'
|
||||
});
|
||||
}
|
||||
if (showAny) {
|
||||
data.unshift({
|
||||
extraData.unshift({
|
||||
isAny: true,
|
||||
title: 'Any Label'
|
||||
});
|
||||
}
|
||||
if (data.length > 2) {
|
||||
data.splice(2, 0, 'divider');
|
||||
if (extraData.length) {
|
||||
extraData.push('divider');
|
||||
data = extraData.concat(data);
|
||||
}
|
||||
}
|
||||
return callback(data);
|
||||
|
||||
callback(data);
|
||||
if (showMenuAbove) {
|
||||
$dropdown.data('glDropdown').positionMenuAbove();
|
||||
}
|
||||
});
|
||||
},
|
||||
renderRow: function(label, instance) {
|
||||
|
@ -158,7 +172,7 @@
|
|||
$li = $('<li>');
|
||||
$a = $('<a href="#">');
|
||||
selectedClass = [];
|
||||
removesAll = label.id === 0 || (label.id == null);
|
||||
removesAll = label.id <= 0 || (label.id == null);
|
||||
if ($dropdown.hasClass('js-filter-bulk-update')) {
|
||||
indeterminate = instance.indeterminateIds;
|
||||
active = instance.activeIds;
|
||||
|
@ -195,14 +209,16 @@
|
|||
return color + " " + percentFirst + "%," + color + " " + percentSecond + "% ";
|
||||
}).join(',');
|
||||
color = "linear-gradient(" + color + ")";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (label.color != null) {
|
||||
color = label.color[0];
|
||||
}
|
||||
}
|
||||
if (color) {
|
||||
colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
colorEl = '';
|
||||
}
|
||||
// We need to identify which items are actually labels
|
||||
|
@ -220,30 +236,46 @@
|
|||
},
|
||||
selectable: true,
|
||||
filterable: true,
|
||||
selected: $dropdown.data('selected') || [],
|
||||
toggleLabel: function(selected, el) {
|
||||
var selected_labels;
|
||||
selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active');
|
||||
if (selected && (selected.title != null)) {
|
||||
if (selected_labels.length > 1) {
|
||||
return selected.title + " +" + (selected_labels.length - 1) + " more";
|
||||
} else {
|
||||
return selected.title;
|
||||
var isSelected = el !== null ? el.hasClass('is-active') : false;
|
||||
var title = selected.title;
|
||||
var selectedLabels = this.selected;
|
||||
|
||||
if (selected.id === 0) {
|
||||
this.selected = [];
|
||||
return 'No Label';
|
||||
}
|
||||
} else if (!selected && selected_labels.length !== 0) {
|
||||
if (selected_labels.length > 1) {
|
||||
return ($(selected_labels[0]).text()) + " +" + (selected_labels.length - 1) + " more";
|
||||
} else if (selected_labels.length === 1) {
|
||||
return $(selected_labels).text();
|
||||
else if (isSelected) {
|
||||
this.selected.push(title);
|
||||
}
|
||||
} else {
|
||||
else {
|
||||
var index = this.selected.indexOf(title);
|
||||
this.selected.splice(index, 1);
|
||||
}
|
||||
|
||||
if (selectedLabels.length === 1) {
|
||||
return selectedLabels;
|
||||
}
|
||||
else if (selectedLabels.length) {
|
||||
return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more";
|
||||
}
|
||||
else {
|
||||
return defaultLabel;
|
||||
}
|
||||
},
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
id: function(label) {
|
||||
if (label.id <= 0) return label.title;
|
||||
|
||||
if ($dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
return label.id;
|
||||
}
|
||||
|
||||
if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) {
|
||||
return label.title;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return label.id;
|
||||
}
|
||||
},
|
||||
|
@ -255,16 +287,23 @@
|
|||
$selectbox.hide();
|
||||
// display:block overrides the hide-collapse rule
|
||||
$value.removeAttr('style');
|
||||
if (page === 'projects:boards:show') {
|
||||
|
||||
if ($dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('html').hasClass('issue-boards-page')) {
|
||||
return;
|
||||
}
|
||||
if ($dropdown.hasClass('js-multiselect')) {
|
||||
if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']");
|
||||
Issuable.filterResults($dropdown.closest('form'));
|
||||
} else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
}
|
||||
else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
$dropdown.closest('form').submit();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (!$dropdown.hasClass('js-filter-bulk-update')) {
|
||||
saveLabelData();
|
||||
}
|
||||
|
@ -281,21 +320,31 @@
|
|||
clicked: function(label, $el, e) {
|
||||
var isIssueIndex, isMRIndex, page;
|
||||
_this.enableBulkLabelDropdown();
|
||||
if ($dropdown.hasClass('js-filter-bulk-update')) {
|
||||
|
||||
if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) {
|
||||
$dropdown.parent()
|
||||
.find('.dropdown-clear-active')
|
||||
.removeClass('is-active')
|
||||
}
|
||||
|
||||
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
page = $('body').data('page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = page === 'projects:merge_requests:index';
|
||||
if (page === 'projects:boards:show') {
|
||||
if ($('html').hasClass('issue-boards-page')) {
|
||||
if (label.isAny) {
|
||||
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
|
||||
} else if (label.title) {
|
||||
}
|
||||
else if ($el.hasClass('is-active')) {
|
||||
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
var filters = gl.issueBoards.BoardsStore.state.filters['label_name'];
|
||||
filters = filters.filter(function (label) {
|
||||
return label !== $el.text().trim();
|
||||
filters = filters.filter(function (filteredLabel) {
|
||||
return filteredLabel !== label.title;
|
||||
});
|
||||
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters;
|
||||
}
|
||||
|
@ -303,17 +352,21 @@
|
|||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
e.preventDefault();
|
||||
return;
|
||||
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
}
|
||||
else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
if (!$dropdown.hasClass('js-multiselect')) {
|
||||
selectedLabel = label.title;
|
||||
return Issuable.filterResults($dropdown.closest('form'));
|
||||
}
|
||||
} else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
}
|
||||
else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
return $dropdown.closest('form').submit();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if ($dropdown.hasClass('js-multiselect')) {
|
||||
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return saveLabelData();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,11 @@
|
|||
gl.utils.getPagePath = function() {
|
||||
return $('body').data('page').split(':')[0];
|
||||
};
|
||||
gl.utils.parseUrl = function (url) {
|
||||
var parser = document.createElement('a');
|
||||
parser.href = url;
|
||||
return parser;
|
||||
};
|
||||
return jQuery.timefor = function(time, suffix, expiredLabel) {
|
||||
var suffixFromNow, timefor;
|
||||
if (!time) {
|
||||
|
|
|
@ -14,14 +14,18 @@
|
|||
inputs.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
minDate: 1,
|
||||
onSelect: toggleClearInput
|
||||
onSelect: function () {
|
||||
$(this).trigger('change');
|
||||
toggleClearInput.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
inputs.next('.js-clear-input').on('click', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
|
||||
input.datepicker('setDate', null);
|
||||
input.datepicker('setDate', null)
|
||||
.trigger('change');
|
||||
toggleClearInput.call(input);
|
||||
});
|
||||
|
||||
|
|
36
app/assets/javascripts/members.js.es6
Normal file
|
@ -0,0 +1,36 @@
|
|||
((w) => {
|
||||
w.gl = w.gl || {};
|
||||
|
||||
class Members {
|
||||
constructor() {
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
|
||||
$('.js-member-update-control').off('change').on('change', this.formSubmit);
|
||||
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
|
||||
}
|
||||
|
||||
removeRow(e) {
|
||||
const $target = $(e.target);
|
||||
|
||||
if ($target.hasClass('btn-remove')) {
|
||||
$target.closest('.member')
|
||||
.fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formSubmit() {
|
||||
$(this).closest('form').trigger("submit.rails").end().disable();
|
||||
}
|
||||
|
||||
formSuccess() {
|
||||
$(this).find('.js-member-update-control').enable();
|
||||
}
|
||||
}
|
||||
|
||||
gl.Members = Members;
|
||||
})(window);
|
|
@ -1,341 +0,0 @@
|
|||
const HEAD_HEADER_TEXT = 'HEAD//our changes';
|
||||
const ORIGIN_HEADER_TEXT = 'origin//their changes';
|
||||
const HEAD_BUTTON_TITLE = 'Use ours';
|
||||
const ORIGIN_BUTTON_TITLE = 'Use theirs';
|
||||
|
||||
|
||||
class MergeConflictDataProvider {
|
||||
|
||||
getInitialData() {
|
||||
const diffViewType = $.cookie('diff_view');
|
||||
|
||||
return {
|
||||
isLoading : true,
|
||||
hasError : false,
|
||||
isParallel : diffViewType === 'parallel',
|
||||
diffViewType : diffViewType,
|
||||
isSubmitting : false,
|
||||
conflictsData : {},
|
||||
resolutionData : {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
decorateData(vueInstance, data) {
|
||||
this.vueInstance = vueInstance;
|
||||
|
||||
if (data.type === 'error') {
|
||||
vueInstance.hasError = true;
|
||||
data.errorMessage = data.message;
|
||||
}
|
||||
else {
|
||||
data.shortCommitSha = data.commit_sha.slice(0, 7);
|
||||
data.commitMessage = data.commit_message;
|
||||
|
||||
this.setParallelLines(data);
|
||||
this.setInlineLines(data);
|
||||
this.updateResolutionsData(data);
|
||||
}
|
||||
|
||||
vueInstance.conflictsData = data;
|
||||
vueInstance.isSubmitting = false;
|
||||
|
||||
const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict';
|
||||
vueInstance.conflictsData.conflictsText = conflictsText;
|
||||
}
|
||||
|
||||
|
||||
updateResolutionsData(data) {
|
||||
const vi = this.vueInstance;
|
||||
|
||||
data.files.forEach( (file) => {
|
||||
file.sections.forEach( (section) => {
|
||||
if (section.conflict) {
|
||||
vi.$set(`resolutionData['${section.id}']`, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
setParallelLines(data) {
|
||||
data.files.forEach( (file) => {
|
||||
file.filePath = this.getFilePath(file);
|
||||
file.iconClass = `fa-${file.blob_icon}`;
|
||||
file.blobPath = file.blob_path;
|
||||
file.parallelLines = [];
|
||||
const linesObj = { left: [], right: [] };
|
||||
|
||||
file.sections.forEach( (section) => {
|
||||
const { conflict, lines, id } = section;
|
||||
|
||||
if (conflict) {
|
||||
linesObj.left.push(this.getOriginHeaderLine(id));
|
||||
linesObj.right.push(this.getHeadHeaderLine(id));
|
||||
}
|
||||
|
||||
lines.forEach( (line) => {
|
||||
const { type } = line;
|
||||
|
||||
if (conflict) {
|
||||
if (type === 'old') {
|
||||
linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
|
||||
}
|
||||
else if (type === 'new') {
|
||||
linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const lineType = type || 'context';
|
||||
|
||||
linesObj.left.push (this.getLineForParallelView(line, id, lineType));
|
||||
linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
|
||||
}
|
||||
});
|
||||
|
||||
this.checkLineLengths(linesObj);
|
||||
});
|
||||
|
||||
for (let i = 0, len = linesObj.left.length; i < len; i++) {
|
||||
file.parallelLines.push([
|
||||
linesObj.right[i],
|
||||
linesObj.left[i]
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
checkLineLengths(linesObj) {
|
||||
let { left, right } = linesObj;
|
||||
|
||||
if (left.length !== right.length) {
|
||||
if (left.length > right.length) {
|
||||
const diff = left.length - right.length;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
right.push({ lineType: 'emptyLine', richText: '' });
|
||||
}
|
||||
}
|
||||
else {
|
||||
const diff = right.length - left.length;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
left.push({ lineType: 'emptyLine', richText: '' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setInlineLines(data) {
|
||||
data.files.forEach( (file) => {
|
||||
file.iconClass = `fa-${file.blob_icon}`;
|
||||
file.blobPath = file.blob_path;
|
||||
file.filePath = this.getFilePath(file);
|
||||
file.inlineLines = []
|
||||
|
||||
file.sections.forEach( (section) => {
|
||||
let currentLineType = 'new';
|
||||
const { conflict, lines, id } = section;
|
||||
|
||||
if (conflict) {
|
||||
file.inlineLines.push(this.getHeadHeaderLine(id));
|
||||
}
|
||||
|
||||
lines.forEach( (line) => {
|
||||
const { type } = line;
|
||||
|
||||
if ((type === 'new' || type === 'old') && currentLineType !== type) {
|
||||
currentLineType = type;
|
||||
file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
|
||||
}
|
||||
|
||||
this.decorateLineForInlineView(line, id, conflict);
|
||||
file.inlineLines.push(line);
|
||||
})
|
||||
|
||||
if (conflict) {
|
||||
file.inlineLines.push(this.getOriginHeaderLine(id));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
handleSelected(sectionId, selection) {
|
||||
const vi = this.vueInstance;
|
||||
|
||||
vi.resolutionData[sectionId] = selection;
|
||||
vi.conflictsData.files.forEach( (file) => {
|
||||
file.inlineLines.forEach( (line) => {
|
||||
if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
|
||||
this.markLine(line, selection);
|
||||
}
|
||||
});
|
||||
|
||||
file.parallelLines.forEach( (lines) => {
|
||||
const left = lines[0];
|
||||
const right = lines[1];
|
||||
const hasSameId = right.id === sectionId || left.id === sectionId;
|
||||
const isLeftMatch = left.hasConflict || left.isHeader;
|
||||
const isRightMatch = right.hasConflict || right.isHeader;
|
||||
|
||||
if (hasSameId && (isLeftMatch || isRightMatch)) {
|
||||
this.markLine(left, selection);
|
||||
this.markLine(right, selection);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateViewType(newType) {
|
||||
const vi = this.vueInstance;
|
||||
|
||||
if (newType === vi.diffView || !(newType === 'parallel' || newType === 'inline')) {
|
||||
return;
|
||||
}
|
||||
|
||||
vi.diffView = newType;
|
||||
vi.isParallel = newType === 'parallel';
|
||||
$.cookie('diff_view', newType); // TODO: Make sure that cookie path added.
|
||||
$('.content-wrapper .container-fluid').toggleClass('container-limited');
|
||||
}
|
||||
|
||||
|
||||
markLine(line, selection) {
|
||||
if (selection === 'head' && line.isHead) {
|
||||
line.isSelected = true;
|
||||
line.isUnselected = false;
|
||||
}
|
||||
else if (selection === 'origin' && line.isOrigin) {
|
||||
line.isSelected = true;
|
||||
line.isUnselected = false;
|
||||
}
|
||||
else {
|
||||
line.isSelected = false;
|
||||
line.isUnselected = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getConflictsCount() {
|
||||
return Object.keys(this.vueInstance.resolutionData).length;
|
||||
}
|
||||
|
||||
|
||||
getResolvedCount() {
|
||||
let count = 0;
|
||||
const data = this.vueInstance.resolutionData;
|
||||
|
||||
for (const id in data) {
|
||||
const resolution = data[id];
|
||||
if (resolution) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
isReadyToCommit() {
|
||||
const { conflictsData, isSubmitting } = this.vueInstance
|
||||
const allResolved = this.getConflictsCount() === this.getResolvedCount();
|
||||
const hasCommitMessage = $.trim(conflictsData.commitMessage).length;
|
||||
|
||||
return !isSubmitting && hasCommitMessage && allResolved;
|
||||
}
|
||||
|
||||
|
||||
getCommitButtonText() {
|
||||
const initial = 'Commit conflict resolution';
|
||||
const inProgress = 'Committing...';
|
||||
const vue = this.vueInstance;
|
||||
|
||||
return vue ? vue.isSubmitting ? inProgress : initial : initial;
|
||||
}
|
||||
|
||||
|
||||
decorateLineForInlineView(line, id, conflict) {
|
||||
const { type } = line;
|
||||
line.id = id;
|
||||
line.hasConflict = conflict;
|
||||
line.isHead = type === 'new';
|
||||
line.isOrigin = type === 'old';
|
||||
line.hasMatch = type === 'match';
|
||||
line.richText = line.rich_text;
|
||||
line.isSelected = false;
|
||||
line.isUnselected = false;
|
||||
}
|
||||
|
||||
getLineForParallelView(line, id, lineType, isHead) {
|
||||
const { old_line, new_line, rich_text } = line;
|
||||
const hasConflict = lineType === 'conflict';
|
||||
|
||||
return {
|
||||
id,
|
||||
lineType,
|
||||
hasConflict,
|
||||
isHead : hasConflict && isHead,
|
||||
isOrigin : hasConflict && !isHead,
|
||||
hasMatch : lineType === 'match',
|
||||
lineNumber : isHead ? new_line : old_line,
|
||||
section : isHead ? 'head' : 'origin',
|
||||
richText : rich_text,
|
||||
isSelected : false,
|
||||
isUnselected : false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getHeadHeaderLine(id) {
|
||||
return {
|
||||
id : id,
|
||||
richText : HEAD_HEADER_TEXT,
|
||||
buttonTitle : HEAD_BUTTON_TITLE,
|
||||
type : 'new',
|
||||
section : 'head',
|
||||
isHeader : true,
|
||||
isHead : true,
|
||||
isSelected : false,
|
||||
isUnselected: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getOriginHeaderLine(id) {
|
||||
return {
|
||||
id : id,
|
||||
richText : ORIGIN_HEADER_TEXT,
|
||||
buttonTitle : ORIGIN_BUTTON_TITLE,
|
||||
type : 'old',
|
||||
section : 'origin',
|
||||
isHeader : true,
|
||||
isOrigin : true,
|
||||
isSelected : false,
|
||||
isUnselected: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handleFailedRequest(vueInstance, data) {
|
||||
vueInstance.hasError = true;
|
||||
vueInstance.conflictsData.errorMessage = 'Something went wrong!';
|
||||
}
|
||||
|
||||
|
||||
getCommitData() {
|
||||
return {
|
||||
commit_message: this.vueInstance.conflictsData.commitMessage,
|
||||
sections: this.vueInstance.resolutionData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getFilePath(file) {
|
||||
const { old_path, new_path } = file;
|
||||
return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
//= require vue
|
||||
|
||||
class MergeConflictResolver {
|
||||
|
||||
constructor() {
|
||||
this.dataProvider = new MergeConflictDataProvider()
|
||||
this.initVue()
|
||||
}
|
||||
|
||||
|
||||
initVue() {
|
||||
const that = this;
|
||||
this.vue = new Vue({
|
||||
el : '#conflicts',
|
||||
name : 'MergeConflictResolver',
|
||||
data : this.dataProvider.getInitialData(),
|
||||
created : this.fetchData(),
|
||||
computed : this.setComputedProperties(),
|
||||
methods : {
|
||||
handleSelected(sectionId, selection) {
|
||||
that.dataProvider.handleSelected(sectionId, selection);
|
||||
},
|
||||
handleViewTypeChange(newType) {
|
||||
that.dataProvider.updateViewType(newType);
|
||||
},
|
||||
commit() {
|
||||
that.commit();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
setComputedProperties() {
|
||||
const dp = this.dataProvider;
|
||||
|
||||
return {
|
||||
conflictsCount() { return dp.getConflictsCount() },
|
||||
resolvedCount() { return dp.getResolvedCount() },
|
||||
readyToCommit() { return dp.isReadyToCommit() },
|
||||
commitButtonText() { return dp.getCommitButtonText() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fetchData() {
|
||||
const dp = this.dataProvider;
|
||||
|
||||
$.get($('#conflicts').data('conflictsPath'))
|
||||
.done((data) => {
|
||||
dp.decorateData(this.vue, data);
|
||||
})
|
||||
.error((data) => {
|
||||
dp.handleFailedRequest(this.vue, data);
|
||||
})
|
||||
.always(() => {
|
||||
this.vue.isLoading = false;
|
||||
|
||||
this.vue.$nextTick(() => {
|
||||
$('#conflicts .js-syntax-highlight').syntaxHighlight();
|
||||
});
|
||||
|
||||
if (this.vue.diffViewType === 'parallel') {
|
||||
$('.content-wrapper .container-fluid').removeClass('container-limited');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
commit() {
|
||||
this.vue.isSubmitting = true;
|
||||
|
||||
$.post($('#conflicts').data('resolveConflictsPath'), this.dataProvider.getCommitData())
|
||||
.done((data) => {
|
||||
window.location.href = data.redirect_to;
|
||||
})
|
||||
.error(() => {
|
||||
this.vue.isSubmitting = false;
|
||||
new Flash('Something went wrong!');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
((global) => {
|
||||
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
global.mergeConflicts.diffFileEditor = Vue.extend({
|
||||
props: {
|
||||
file: Object,
|
||||
onCancelDiscardConfirmation: Function,
|
||||
onAcceptDiscardConfirmation: Function
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
saved: false,
|
||||
loading: false,
|
||||
fileLoaded: false,
|
||||
originalContent: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
classObject() {
|
||||
return {
|
||||
'saved': this.saved,
|
||||
'is-loading': this.loading
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
['file.showEditor'](val) {
|
||||
this.resetEditorContent();
|
||||
|
||||
if (!val || this.fileLoaded || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadEditor();
|
||||
}
|
||||
},
|
||||
ready() {
|
||||
if (this.file.loadEditor) {
|
||||
this.loadEditor();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadEditor() {
|
||||
this.loading = true;
|
||||
|
||||
$.get(this.file.content_path)
|
||||
.done((file) => {
|
||||
let content = this.$el.querySelector('pre');
|
||||
let fileContent = document.createTextNode(file.content);
|
||||
|
||||
content.textContent = fileContent.textContent;
|
||||
|
||||
this.originalContent = file.content;
|
||||
this.fileLoaded = true;
|
||||
this.editor = ace.edit(content);
|
||||
this.editor.$blockScrolling = Infinity; // Turn off annoying warning
|
||||
this.editor.getSession().setMode(`ace/mode/${file.blob_ace_mode}`);
|
||||
this.editor.on('change', () => {
|
||||
this.saveDiffResolution();
|
||||
});
|
||||
this.saveDiffResolution();
|
||||
})
|
||||
.fail(() => {
|
||||
new Flash('Failed to load the file, please try again.');
|
||||
})
|
||||
.always(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
saveDiffResolution() {
|
||||
this.saved = true;
|
||||
|
||||
// This probably be better placed in the data provider
|
||||
this.file.content = this.editor.getValue();
|
||||
this.file.resolveEditChanged = this.file.content !== this.originalContent;
|
||||
this.file.promptDiscardConfirmation = false;
|
||||
},
|
||||
resetEditorContent() {
|
||||
if (this.fileLoaded) {
|
||||
this.editor.setValue(this.originalContent, -1);
|
||||
}
|
||||
},
|
||||
cancelDiscardConfirmation(file) {
|
||||
this.onCancelDiscardConfirmation(file);
|
||||
},
|
||||
acceptDiscardConfirmation(file) {
|
||||
this.onAcceptDiscardConfirmation(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,12 @@
|
|||
((global) => {
|
||||
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
global.mergeConflicts.inlineConflictLines = Vue.extend({
|
||||
props: {
|
||||
file: Object
|
||||
},
|
||||
mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
|
||||
});
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,14 @@
|
|||
((global) => {
|
||||
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
global.mergeConflicts.parallelConflictLine = Vue.extend({
|
||||
props: {
|
||||
file: Object,
|
||||
line: Object
|
||||
},
|
||||
mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
|
||||
template: '#parallel-conflict-line'
|
||||
});
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,15 @@
|
|||
((global) => {
|
||||
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
global.mergeConflicts.parallelConflictLines = Vue.extend({
|
||||
props: {
|
||||
file: Object
|
||||
},
|
||||
mixins: [global.mergeConflicts.utils],
|
||||
components: {
|
||||
'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine
|
||||
}
|
||||
});
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,30 @@
|
|||
((global) => {
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
class mergeConflictsService {
|
||||
constructor(options) {
|
||||
this.conflictsPath = options.conflictsPath;
|
||||
this.resolveConflictsPath = options.resolveConflictsPath;
|
||||
}
|
||||
|
||||
fetchConflictsData() {
|
||||
return $.ajax({
|
||||
dataType: 'json',
|
||||
url: this.conflictsPath
|
||||
});
|
||||
}
|
||||
|
||||
submitResolveConflicts(data) {
|
||||
return $.ajax({
|
||||
url: this.resolveConflictsPath,
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
method: 'POST'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
global.mergeConflicts.mergeConflictsService = mergeConflictsService;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,437 @@
|
|||
((global) => {
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
const diffViewType = $.cookie('diff_view');
|
||||
const HEAD_HEADER_TEXT = 'HEAD//our changes';
|
||||
const ORIGIN_HEADER_TEXT = 'origin//their changes';
|
||||
const HEAD_BUTTON_TITLE = 'Use ours';
|
||||
const ORIGIN_BUTTON_TITLE = 'Use theirs';
|
||||
const INTERACTIVE_RESOLVE_MODE = 'interactive';
|
||||
const EDIT_RESOLVE_MODE = 'edit';
|
||||
const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
|
||||
const VIEW_TYPES = {
|
||||
INLINE: 'inline',
|
||||
PARALLEL: 'parallel'
|
||||
};
|
||||
const CONFLICT_TYPES = {
|
||||
TEXT: 'text',
|
||||
TEXT_EDITOR: 'text-editor'
|
||||
};
|
||||
|
||||
global.mergeConflicts.mergeConflictsStore = {
|
||||
state: {
|
||||
isLoading: true,
|
||||
hasError: false,
|
||||
isSubmitting: false,
|
||||
isParallel: diffViewType === VIEW_TYPES.PARALLEL,
|
||||
diffViewType: diffViewType,
|
||||
conflictsData: {}
|
||||
},
|
||||
|
||||
setConflictsData(data) {
|
||||
this.decorateFiles(data.files);
|
||||
|
||||
this.state.conflictsData = {
|
||||
files: data.files,
|
||||
commitMessage: data.commit_message,
|
||||
sourceBranch: data.source_branch,
|
||||
targetBranch: data.target_branch,
|
||||
commitMessage: data.commit_message,
|
||||
shortCommitSha: data.commit_sha.slice(0, 7),
|
||||
};
|
||||
},
|
||||
|
||||
decorateFiles(files) {
|
||||
files.forEach((file) => {
|
||||
file.content = '';
|
||||
file.resolutionData = {};
|
||||
file.promptDiscardConfirmation = false;
|
||||
file.resolveMode = DEFAULT_RESOLVE_MODE;
|
||||
file.filePath = this.getFilePath(file);
|
||||
file.iconClass = `fa-${file.blob_icon}`;
|
||||
file.blobPath = file.blob_path;
|
||||
|
||||
if (file.type === CONFLICT_TYPES.TEXT) {
|
||||
file.showEditor = false;
|
||||
file.loadEditor = false;
|
||||
|
||||
this.setInlineLine(file);
|
||||
this.setParallelLine(file);
|
||||
} else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
|
||||
file.showEditor = true;
|
||||
file.loadEditor = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setInlineLine(file) {
|
||||
file.inlineLines = [];
|
||||
|
||||
file.sections.forEach((section) => {
|
||||
let currentLineType = 'new';
|
||||
const { conflict, lines, id } = section;
|
||||
|
||||
if (conflict) {
|
||||
file.inlineLines.push(this.getHeadHeaderLine(id));
|
||||
}
|
||||
|
||||
lines.forEach((line) => {
|
||||
const { type } = line;
|
||||
|
||||
if ((type === 'new' || type === 'old') && currentLineType !== type) {
|
||||
currentLineType = type;
|
||||
file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
|
||||
}
|
||||
|
||||
this.decorateLineForInlineView(line, id, conflict);
|
||||
file.inlineLines.push(line);
|
||||
})
|
||||
|
||||
if (conflict) {
|
||||
file.inlineLines.push(this.getOriginHeaderLine(id));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setParallelLine(file) {
|
||||
file.parallelLines = [];
|
||||
const linesObj = { left: [], right: [] };
|
||||
|
||||
file.sections.forEach((section) => {
|
||||
const { conflict, lines, id } = section;
|
||||
|
||||
if (conflict) {
|
||||
linesObj.left.push(this.getOriginHeaderLine(id));
|
||||
linesObj.right.push(this.getHeadHeaderLine(id));
|
||||
}
|
||||
|
||||
lines.forEach((line) => {
|
||||
const { type } = line;
|
||||
|
||||
if (conflict) {
|
||||
if (type === 'old') {
|
||||
linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
|
||||
} else if (type === 'new') {
|
||||
linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
|
||||
}
|
||||
} else {
|
||||
const lineType = type || 'context';
|
||||
|
||||
linesObj.left.push (this.getLineForParallelView(line, id, lineType));
|
||||
linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
|
||||
}
|
||||
});
|
||||
|
||||
this.checkLineLengths(linesObj);
|
||||
});
|
||||
|
||||
for (let i = 0, len = linesObj.left.length; i < len; i++) {
|
||||
file.parallelLines.push([
|
||||
linesObj.right[i],
|
||||
linesObj.left[i]
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
setLoadingState(state) {
|
||||
this.state.isLoading = state;
|
||||
},
|
||||
|
||||
setErrorState(state) {
|
||||
this.state.hasError = state;
|
||||
},
|
||||
|
||||
setFailedRequest(message) {
|
||||
this.state.hasError = true;
|
||||
this.state.conflictsData.errorMessage = message;
|
||||
},
|
||||
|
||||
getConflictsCount() {
|
||||
if (!this.state.conflictsData.files.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const files = this.state.conflictsData.files;
|
||||
let count = 0;
|
||||
|
||||
files.forEach((file) => {
|
||||
if (file.type === CONFLICT_TYPES.TEXT) {
|
||||
file.sections.forEach((section) => {
|
||||
if (section.conflict) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
getConflictsCountText() {
|
||||
const count = this.getConflictsCount();
|
||||
const text = count ? 'conflicts' : 'conflict';
|
||||
|
||||
return `${count} ${text}`;
|
||||
},
|
||||
|
||||
setViewType(viewType) {
|
||||
this.state.diffView = viewType;
|
||||
this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;
|
||||
|
||||
$.cookie('diff_view', viewType, {
|
||||
path: gon.relative_url_root || '/'
|
||||
});
|
||||
},
|
||||
|
||||
getHeadHeaderLine(id) {
|
||||
return {
|
||||
id: id,
|
||||
richText: HEAD_HEADER_TEXT,
|
||||
buttonTitle: HEAD_BUTTON_TITLE,
|
||||
type: 'new',
|
||||
section: 'head',
|
||||
isHeader: true,
|
||||
isHead: true,
|
||||
isSelected: false,
|
||||
isUnselected: false
|
||||
};
|
||||
},
|
||||
|
||||
decorateLineForInlineView(line, id, conflict) {
|
||||
const { type } = line;
|
||||
line.id = id;
|
||||
line.hasConflict = conflict;
|
||||
line.isHead = type === 'new';
|
||||
line.isOrigin = type === 'old';
|
||||
line.hasMatch = type === 'match';
|
||||
line.richText = line.rich_text;
|
||||
line.isSelected = false;
|
||||
line.isUnselected = false;
|
||||
},
|
||||
|
||||
getLineForParallelView(line, id, lineType, isHead) {
|
||||
const { old_line, new_line, rich_text } = line;
|
||||
const hasConflict = lineType === 'conflict';
|
||||
|
||||
return {
|
||||
id,
|
||||
lineType,
|
||||
hasConflict,
|
||||
isHead: hasConflict && isHead,
|
||||
isOrigin: hasConflict && !isHead,
|
||||
hasMatch: lineType === 'match',
|
||||
lineNumber: isHead ? new_line : old_line,
|
||||
section: isHead ? 'head' : 'origin',
|
||||
richText: rich_text,
|
||||
isSelected: false,
|
||||
isUnselected: false
|
||||
};
|
||||
},
|
||||
|
||||
getOriginHeaderLine(id) {
|
||||
return {
|
||||
id: id,
|
||||
richText: ORIGIN_HEADER_TEXT,
|
||||
buttonTitle: ORIGIN_BUTTON_TITLE,
|
||||
type: 'old',
|
||||
section: 'origin',
|
||||
isHeader: true,
|
||||
isOrigin: true,
|
||||
isSelected: false,
|
||||
isUnselected: false
|
||||
};
|
||||
},
|
||||
|
||||
getFilePath(file) {
|
||||
const { old_path, new_path } = file;
|
||||
return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
|
||||
},
|
||||
|
||||
checkLineLengths(linesObj) {
|
||||
let { left, right } = linesObj;
|
||||
|
||||
if (left.length !== right.length) {
|
||||
if (left.length > right.length) {
|
||||
const diff = left.length - right.length;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
right.push({ lineType: 'emptyLine', richText: '' });
|
||||
}
|
||||
} else {
|
||||
const diff = right.length - left.length;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
left.push({ lineType: 'emptyLine', richText: '' });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setPromptConfirmationState(file, state) {
|
||||
file.promptDiscardConfirmation = state;
|
||||
},
|
||||
|
||||
setFileResolveMode(file, mode) {
|
||||
if (mode === INTERACTIVE_RESOLVE_MODE) {
|
||||
file.showEditor = false;
|
||||
} else if (mode === EDIT_RESOLVE_MODE) {
|
||||
// Restore Interactive mode when switching to Edit mode
|
||||
file.showEditor = true;
|
||||
file.loadEditor = true;
|
||||
file.resolutionData = {};
|
||||
|
||||
this.restoreFileLinesState(file);
|
||||
}
|
||||
|
||||
file.resolveMode = mode;
|
||||
},
|
||||
|
||||
restoreFileLinesState(file) {
|
||||
file.inlineLines.forEach((line) => {
|
||||
if (line.hasConflict || line.isHeader) {
|
||||
line.isSelected = false;
|
||||
line.isUnselected = false;
|
||||
}
|
||||
});
|
||||
|
||||
file.parallelLines.forEach((lines) => {
|
||||
const left = lines[0];
|
||||
const right = lines[1];
|
||||
const isLeftMatch = left.hasConflict || left.isHeader;
|
||||
const isRightMatch = right.hasConflict || right.isHeader;
|
||||
|
||||
if (isLeftMatch || isRightMatch) {
|
||||
left.isSelected = false;
|
||||
left.isUnselected = false;
|
||||
right.isSelected = false;
|
||||
right.isUnselected = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
isReadyToCommit() {
|
||||
const files = this.state.conflictsData.files;
|
||||
const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length;
|
||||
let unresolved = 0;
|
||||
|
||||
for (let i = 0, l = files.length; i < l; i++) {
|
||||
let file = files[i];
|
||||
|
||||
if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
|
||||
let numberConflicts = 0;
|
||||
let resolvedConflicts = Object.keys(file.resolutionData).length
|
||||
|
||||
// We only check for conflicts type 'text'
|
||||
// since conflicts `text_editor` can´t be resolved in interactive mode
|
||||
if (file.type === CONFLICT_TYPES.TEXT) {
|
||||
for (let j = 0, k = file.sections.length; j < k; j++) {
|
||||
if (file.sections[j].conflict) {
|
||||
numberConflicts++;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedConflicts !== numberConflicts) {
|
||||
unresolved++;
|
||||
}
|
||||
}
|
||||
} else if (file.resolveMode === EDIT_RESOLVE_MODE) {
|
||||
|
||||
// Unlikely to happen since switching to Edit mode saves content automatically.
|
||||
// Checking anyway in case the save strategy changes in the future
|
||||
if (!file.content) {
|
||||
unresolved++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !this.state.isSubmitting && hasCommitMessage && !unresolved;
|
||||
},
|
||||
|
||||
getCommitButtonText() {
|
||||
const initial = 'Commit conflict resolution';
|
||||
const inProgress = 'Committing...';
|
||||
|
||||
return this.state ? this.state.isSubmitting ? inProgress : initial : initial;
|
||||
},
|
||||
|
||||
getCommitData() {
|
||||
let commitData = {};
|
||||
|
||||
commitData = {
|
||||
commit_message: this.state.conflictsData.commitMessage,
|
||||
files: []
|
||||
};
|
||||
|
||||
this.state.conflictsData.files.forEach((file) => {
|
||||
let addFile;
|
||||
|
||||
addFile = {
|
||||
old_path: file.old_path,
|
||||
new_path: file.new_path
|
||||
};
|
||||
|
||||
if (file.type === CONFLICT_TYPES.TEXT) {
|
||||
|
||||
// Submit only one data for type of editing
|
||||
if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
|
||||
addFile.sections = file.resolutionData;
|
||||
} else if (file.resolveMode === EDIT_RESOLVE_MODE) {
|
||||
addFile.content = file.content;
|
||||
}
|
||||
} else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
|
||||
addFile.content = file.content;
|
||||
}
|
||||
|
||||
commitData.files.push(addFile);
|
||||
});
|
||||
|
||||
return commitData;
|
||||
},
|
||||
|
||||
handleSelected(file, sectionId, selection) {
|
||||
Vue.set(file.resolutionData, sectionId, selection);
|
||||
|
||||
file.inlineLines.forEach((line) => {
|
||||
if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
|
||||
this.markLine(line, selection);
|
||||
}
|
||||
});
|
||||
|
||||
file.parallelLines.forEach((lines) => {
|
||||
const left = lines[0];
|
||||
const right = lines[1];
|
||||
const hasSameId = right.id === sectionId || left.id === sectionId;
|
||||
const isLeftMatch = left.hasConflict || left.isHeader;
|
||||
const isRightMatch = right.hasConflict || right.isHeader;
|
||||
|
||||
if (hasSameId && (isLeftMatch || isRightMatch)) {
|
||||
this.markLine(left, selection);
|
||||
this.markLine(right, selection);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
markLine(line, selection) {
|
||||
if (selection === 'head' && line.isHead) {
|
||||
line.isSelected = true;
|
||||
line.isUnselected = false;
|
||||
} else if (selection === 'origin' && line.isOrigin) {
|
||||
line.isSelected = true;
|
||||
line.isUnselected = false;
|
||||
} else {
|
||||
line.isSelected = false;
|
||||
line.isUnselected = true;
|
||||
}
|
||||
},
|
||||
|
||||
setSubmitState(state) {
|
||||
this.state.isSubmitting = state;
|
||||
},
|
||||
|
||||
fileTextTypePresent() {
|
||||
return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
|
||||
}
|
||||
};
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,89 @@
|
|||
//= require vue
|
||||
//= require ./merge_conflict_store
|
||||
//= require ./merge_conflict_service
|
||||
//= require ./mixins/line_conflict_utils
|
||||
//= require ./mixins/line_conflict_actions
|
||||
//= require ./components/diff_file_editor
|
||||
//= require ./components/inline_conflict_lines
|
||||
//= require ./components/parallel_conflict_line
|
||||
//= require ./components/parallel_conflict_lines
|
||||
|
||||
$(() => {
|
||||
const INTERACTIVE_RESOLVE_MODE = 'interactive';
|
||||
const conflictsEl = document.querySelector('#conflicts');
|
||||
const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore;
|
||||
const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({
|
||||
conflictsPath: conflictsEl.dataset.conflictsPath,
|
||||
resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath
|
||||
});
|
||||
|
||||
gl.MergeConflictsResolverApp = new Vue({
|
||||
el: '#conflicts',
|
||||
data: mergeConflictsStore.state,
|
||||
components: {
|
||||
'diff-file-editor': gl.mergeConflicts.diffFileEditor,
|
||||
'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines,
|
||||
'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines
|
||||
},
|
||||
computed: {
|
||||
conflictsCountText() { return mergeConflictsStore.getConflictsCountText() },
|
||||
readyToCommit() { return mergeConflictsStore.isReadyToCommit() },
|
||||
commitButtonText() { return mergeConflictsStore.getCommitButtonText() },
|
||||
showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() }
|
||||
},
|
||||
created() {
|
||||
mergeConflictsService
|
||||
.fetchConflictsData()
|
||||
.done((data) => {
|
||||
if (data.type === 'error') {
|
||||
mergeConflictsStore.setFailedRequest(data.message);
|
||||
} else {
|
||||
mergeConflictsStore.setConflictsData(data);
|
||||
}
|
||||
})
|
||||
.error(() => {
|
||||
mergeConflictsStore.setFailedRequest();
|
||||
})
|
||||
.always(() => {
|
||||
mergeConflictsStore.setLoadingState(false);
|
||||
|
||||
this.$nextTick(() => {
|
||||
$(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight();
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
handleViewTypeChange(viewType) {
|
||||
mergeConflictsStore.setViewType(viewType);
|
||||
},
|
||||
onClickResolveModeButton(file, mode) {
|
||||
if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) {
|
||||
mergeConflictsStore.setPromptConfirmationState(file, true);
|
||||
return;
|
||||
}
|
||||
|
||||
mergeConflictsStore.setFileResolveMode(file, mode);
|
||||
},
|
||||
acceptDiscardConfirmation(file) {
|
||||
mergeConflictsStore.setPromptConfirmationState(file, false);
|
||||
mergeConflictsStore.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE);
|
||||
},
|
||||
cancelDiscardConfirmation(file) {
|
||||
mergeConflictsStore.setPromptConfirmationState(file, false);
|
||||
},
|
||||
commit() {
|
||||
mergeConflictsStore.setSubmitState(true);
|
||||
|
||||
mergeConflictsService
|
||||
.submitResolveConflicts(mergeConflictsStore.getCommitData())
|
||||
.done((data) => {
|
||||
window.location.href = data.redirect_to;
|
||||
})
|
||||
.error(() => {
|
||||
mergeConflictsStore.setSubmitState(false);
|
||||
new Flash('Failed to save merge conflicts resolutions. Please try again!');
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
((global) => {
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
global.mergeConflicts.actions = {
|
||||
methods: {
|
||||
handleSelected(file, sectionId, selection) {
|
||||
gl.mergeConflicts.mergeConflictsStore.handleSelected(file, sectionId, selection);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,18 @@
|
|||
((global) => {
|
||||
global.mergeConflicts = global.mergeConflicts || {};
|
||||
|
||||
global.mergeConflicts.utils = {
|
||||
methods: {
|
||||
lineCssClass(line) {
|
||||
return {
|
||||
'head': line.isHead,
|
||||
'origin': line.isOrigin,
|
||||
'match': line.hasMatch,
|
||||
'selected': line.isSelected,
|
||||
'unselected': line.isUnselected
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -36,13 +36,10 @@
|
|||
};
|
||||
|
||||
MergeRequest.prototype.initTabs = function() {
|
||||
if (this.opts.action !== 'new') {
|
||||
// `MergeRequests#new` has no tab-persisting or lazy-loading behavior
|
||||
window.mrTabs = new MergeRequestTabs(this.opts);
|
||||
} else {
|
||||
// Show the first tab (Commits)
|
||||
return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show');
|
||||
if (window.mrTabs) {
|
||||
window.mrTabs.unbindEvents();
|
||||
}
|
||||
window.mrTabs = new MergeRequestTabs(this.opts);
|
||||
};
|
||||
|
||||
MergeRequest.prototype.showAllCommits = function() {
|
||||
|
|
|
@ -56,9 +56,14 @@
|
|||
|
||||
MergeRequestTabs.prototype.commitsLoaded = false;
|
||||
|
||||
MergeRequestTabs.prototype.fixedLayoutPref = null;
|
||||
|
||||
function MergeRequestTabs(opts) {
|
||||
this.opts = opts != null ? opts : {};
|
||||
this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true;
|
||||
|
||||
this.buildsLoaded = this.opts.buildsLoaded || false;
|
||||
|
||||
this.setCurrentAction = bind(this.setCurrentAction, this);
|
||||
this.tabShown = bind(this.tabShown, this);
|
||||
this.showTab = bind(this.showTab, this);
|
||||
|
@ -66,11 +71,17 @@
|
|||
this._location = location;
|
||||
this.bindEvents();
|
||||
this.activateTab(this.opts.action);
|
||||
this.initAffix();
|
||||
}
|
||||
|
||||
MergeRequestTabs.prototype.bindEvents = function() {
|
||||
$(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown);
|
||||
return $(document).on('click', '.js-show-tab', this.showTab);
|
||||
$(document).on('click', '.js-show-tab', this.showTab);
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.unbindEvents = function() {
|
||||
$(document).off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown);
|
||||
$(document).off('click', '.js-show-tab', this.showTab);
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.showTab = function(event) {
|
||||
|
@ -85,11 +96,15 @@
|
|||
if (action === 'commits') {
|
||||
this.loadCommits($target.attr('href'));
|
||||
this.expandView();
|
||||
} else if (action === 'diffs') {
|
||||
this.resetViewContainer();
|
||||
} else if (this.isDiffAction(action)) {
|
||||
this.loadDiff($target.attr('href'));
|
||||
if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') {
|
||||
this.shrinkView();
|
||||
}
|
||||
if (this.diffViewType() === 'parallel') {
|
||||
this.expandViewContainer();
|
||||
}
|
||||
navBarHeight = $('.navbar-gitlab').outerHeight();
|
||||
$.scrollTo(".merge-request-details .merge-request-tabs", {
|
||||
offset: -navBarHeight
|
||||
|
@ -97,11 +112,14 @@
|
|||
} else if (action === 'builds') {
|
||||
this.loadBuilds($target.attr('href'));
|
||||
this.expandView();
|
||||
this.resetViewContainer();
|
||||
} else if (action === 'pipelines') {
|
||||
this.loadPipelines($target.attr('href'));
|
||||
this.expandView();
|
||||
this.resetViewContainer();
|
||||
} else {
|
||||
this.expandView();
|
||||
this.resetViewContainer();
|
||||
}
|
||||
if (this.opts.setUrl) {
|
||||
this.setCurrentAction(action);
|
||||
|
@ -126,7 +144,7 @@
|
|||
if (action === 'show') {
|
||||
action = 'notes';
|
||||
}
|
||||
return $(".merge-request-tabs a[data-action='" + action + "']").tab('show');
|
||||
$(".merge-request-tabs a[data-action='" + action + "']").tab('show').trigger('shown.bs.tab');
|
||||
};
|
||||
|
||||
// Replaces the current Merge Request-specific action in the URL with a new one
|
||||
|
@ -156,8 +174,9 @@
|
|||
action = 'notes';
|
||||
}
|
||||
this.currentAction = action;
|
||||
// Remove a trailing '/commits' or '/diffs'
|
||||
new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, '');
|
||||
// Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs'
|
||||
new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, '');
|
||||
|
||||
// Append the new action if we're on a tab other than 'notes'
|
||||
if (action !== 'notes') {
|
||||
new_state += "/" + action;
|
||||
|
@ -196,8 +215,13 @@
|
|||
if (this.diffsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We extract pathname for the current Changes tab anchor href
|
||||
// some pages like MergeRequestsController#new has query parameters on that anchor
|
||||
var url = gl.utils.parseUrl(source);
|
||||
|
||||
return this._get({
|
||||
url: (source + ".json") + this._location.search,
|
||||
url: (url.pathname + ".json") + this._location.search,
|
||||
success: (function(_this) {
|
||||
return function(data) {
|
||||
$('#diffs').html(data.html);
|
||||
|
@ -209,7 +233,7 @@
|
|||
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
|
||||
$('#diffs .js-syntax-highlight').syntaxHighlight();
|
||||
$('#diffs .diff-file').singleFileDiff();
|
||||
if (_this.diffViewType() === 'parallel') {
|
||||
if (_this.diffViewType() === 'parallel' && (_this.isDiffAction(_this.currentAction)) ) {
|
||||
_this.expandViewContainer();
|
||||
}
|
||||
_this.diffsLoaded = true;
|
||||
|
@ -258,6 +282,7 @@
|
|||
document.querySelector("div#builds").innerHTML = data.html;
|
||||
gl.utils.localTimeAgo($('.js-timeago', 'div#builds'));
|
||||
_this.buildsLoaded = true;
|
||||
if (!this.pipelines) this.pipelines = new gl.Pipelines();
|
||||
return _this.scrollToElement("#builds");
|
||||
};
|
||||
})(this)
|
||||
|
@ -308,11 +333,25 @@
|
|||
|
||||
MergeRequestTabs.prototype.diffViewType = function() {
|
||||
return $('.inline-parallel-buttons a.active').data('view-type');
|
||||
// Returns diff view type
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.isDiffAction = function(action) {
|
||||
return action === 'diffs' || action === 'new/diffs'
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.expandViewContainer = function() {
|
||||
return $('.container-fluid').removeClass('container-limited');
|
||||
var $wrapper = $('.content-wrapper .container-fluid');
|
||||
if (this.fixedLayoutPref === null) {
|
||||
this.fixedLayoutPref = $wrapper.hasClass('container-limited');
|
||||
}
|
||||
$wrapper.removeClass('container-limited');
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.resetViewContainer = function() {
|
||||
if (this.fixedLayoutPref !== null) {
|
||||
$('.content-wrapper .container-fluid')
|
||||
.toggleClass('container-limited', this.fixedLayoutPref);
|
||||
}
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.shrinkView = function() {
|
||||
|
@ -343,6 +382,43 @@
|
|||
// Only when sidebar is collapsed
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.initAffix = function () {
|
||||
var $tabs = $('.js-tabs-affix');
|
||||
|
||||
// Screen space on small screens is usually very sparse
|
||||
// So we dont affix the tabs on these
|
||||
if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
|
||||
|
||||
var $diffTabs = $('#diff-notes-app'),
|
||||
$fixedNav = $('.navbar-fixed-top'),
|
||||
$layoutNav = $('.layout-nav');
|
||||
|
||||
$tabs.off('affix.bs.affix affix-top.bs.affix')
|
||||
.affix({
|
||||
offset: {
|
||||
top: function () {
|
||||
var tabsTop = $diffTabs.offset().top - $tabs.height();
|
||||
tabsTop = tabsTop - ($fixedNav.height() + $layoutNav.height());
|
||||
|
||||
return tabsTop;
|
||||
}
|
||||
}
|
||||
}).on('affix.bs.affix', function () {
|
||||
$diffTabs.css({
|
||||
marginTop: $tabs.height()
|
||||
});
|
||||
}).on('affix-top.bs.affix', function () {
|
||||
$diffTabs.css({
|
||||
marginTop: ''
|
||||
});
|
||||
});
|
||||
|
||||
// Fix bug when reloading the page already scrolling
|
||||
if ($tabs.hasClass('affix')) {
|
||||
$tabs.trigger('affix.bs.affix');
|
||||
}
|
||||
};
|
||||
|
||||
return MergeRequestTabs;
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,7 +1,32 @@
|
|||
(function() {
|
||||
((global) => {
|
||||
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
this.MergeRequestWidget = (function() {
|
||||
const DEPLOYMENT_TEMPLATE = `<div class="mr-widget-heading" id="<%- id %>">
|
||||
<div class="ci_widget ci-success">
|
||||
<%= ci_success_icon %>
|
||||
<span>
|
||||
Deployed to
|
||||
<a href="<%- url %>" target="_blank" class="environment">
|
||||
<%- name %>
|
||||
</a>
|
||||
<span class="js-environment-timeago" data-toggle="tooltip" data-placement="top" data-title="<%- deployed_at_formatted %>">
|
||||
<%- deployed_at %>
|
||||
</span>
|
||||
<a class="js-environment-link" href="<%- external_url %>" target="_blank">
|
||||
<i class="fa fa-external-link"></i>
|
||||
View on <%- external_url_formatted %>
|
||||
</a>
|
||||
</span>
|
||||
<span class="stop-env-container js-stop-env-link">
|
||||
<a href="<%- stop_url %>" class="close-evn-link" data-method="post" rel="nofollow" data-confirm="Are you sure you want to stop this environment?">
|
||||
<i class="fa fa-stop-circle-o"/>
|
||||
Stop environment
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
global.MergeRequestWidget = (function() {
|
||||
function MergeRequestWidget(opts) {
|
||||
// Initialize MergeRequestWidget behavior
|
||||
//
|
||||
|
@ -10,17 +35,23 @@
|
|||
// ci_status_url - String, URL to use to check CI status
|
||||
//
|
||||
this.opts = opts;
|
||||
this.$widgetBody = $('.mr-widget-body');
|
||||
$('#modal_merge_info').modal({
|
||||
show: false
|
||||
});
|
||||
this.firstCICheck = true;
|
||||
this.readyForCICheck = false;
|
||||
this.readyForCIEnvironmentCheck = false;
|
||||
this.cancel = false;
|
||||
clearInterval(this.fetchBuildStatusInterval);
|
||||
clearInterval(this.fetchBuildEnvironmentStatusInterval);
|
||||
this.clearEventListeners();
|
||||
this.addEventListeners();
|
||||
this.getCIStatus(false);
|
||||
this.getCIEnvironmentsStatus();
|
||||
this.retrieveSuccessIcon();
|
||||
this.pollCIStatus();
|
||||
this.pollCIEnvironmentsStatus();
|
||||
notifyPermissions();
|
||||
}
|
||||
|
||||
|
@ -41,6 +72,7 @@
|
|||
page = $('body').data('page').split(':').last();
|
||||
if (allowedPages.indexOf(page) < 0) {
|
||||
clearInterval(_this.fetchBuildStatusInterval);
|
||||
clearInterval(_this.fetchBuildEnvironmentStatusInterval);
|
||||
_this.cancelPolling();
|
||||
return _this.clearEventListeners();
|
||||
}
|
||||
|
@ -48,6 +80,12 @@
|
|||
})(this));
|
||||
};
|
||||
|
||||
MergeRequestWidget.prototype.retrieveSuccessIcon = function() {
|
||||
const $ciSuccessIcon = $('.js-success-icon');
|
||||
this.$ciSuccessIcon = $ciSuccessIcon.html();
|
||||
$ciSuccessIcon.remove();
|
||||
}
|
||||
|
||||
MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
|
||||
if (deleteSourceBranch == null) {
|
||||
deleteSourceBranch = false;
|
||||
|
@ -62,7 +100,7 @@
|
|||
urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : '';
|
||||
return window.location.href = window.location.pathname + urlSuffix;
|
||||
} else if (data.merge_error) {
|
||||
return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>");
|
||||
return this.$widgetBody.html("<h4>" + data.merge_error + "</h4>");
|
||||
} else {
|
||||
callback = function() {
|
||||
return merge_request_widget.mergeInProgress(deleteSourceBranch);
|
||||
|
@ -118,6 +156,7 @@
|
|||
if (data.status === '') {
|
||||
return;
|
||||
}
|
||||
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
|
||||
if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
|
||||
_this.opts.ci_status = data.status;
|
||||
_this.showCIStatus(data.status);
|
||||
|
@ -150,6 +189,46 @@
|
|||
})(this));
|
||||
};
|
||||
|
||||
MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() {
|
||||
this.fetchBuildEnvironmentStatusInterval = setInterval(() => {
|
||||
if (!this.readyForCIEnvironmentCheck) return;
|
||||
this.getCIEnvironmentsStatus();
|
||||
this.readyForCIEnvironmentCheck = false;
|
||||
}, 300000);
|
||||
};
|
||||
|
||||
MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() {
|
||||
$.getJSON(this.opts.ci_environments_status_url, (environments) => {
|
||||
if (this.cancel) return;
|
||||
this.readyForCIEnvironmentCheck = true;
|
||||
if (environments && environments.length) this.renderEnvironments(environments);
|
||||
});
|
||||
};
|
||||
|
||||
MergeRequestWidget.prototype.renderEnvironments = function(environments) {
|
||||
for (let i = 0; i < environments.length; i++) {
|
||||
const environment = environments[i];
|
||||
if ($(`.mr-state-widget #${ environment.id }`).length) return;
|
||||
const $template = $(DEPLOYMENT_TEMPLATE);
|
||||
if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
|
||||
|
||||
if (!environment.stop_url) {
|
||||
$('.js-stop-env-link', $template).remove();
|
||||
}
|
||||
|
||||
if (environment.deployed_at && environment.deployed_at_formatted) {
|
||||
environment.deployed_at = $.timeago(environment.deployed_at) + '.';
|
||||
} else {
|
||||
$('.js-environment-timeago', $template).remove();
|
||||
environment.name += '.';
|
||||
}
|
||||
environment.ci_success_icon = this.$ciSuccessIcon;
|
||||
const templateString = _.unescape($template[0].outerHTML);
|
||||
const template = _.template(templateString)(environment)
|
||||
this.$widgetBody.before(template);
|
||||
}
|
||||
};
|
||||
|
||||
MergeRequestWidget.prototype.showCIStatus = function(state) {
|
||||
var allowed_states;
|
||||
if (state == null) {
|
||||
|
@ -190,4 +269,4 @@
|
|||
|
||||
})();
|
||||
|
||||
}).call(this);
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -7,7 +7,7 @@
|
|||
this.currentProject = JSON.parse(currentProject);
|
||||
}
|
||||
$('.js-milestone-select').each(function(i, dropdown) {
|
||||
var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId;
|
||||
var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove;
|
||||
$dropdown = $(dropdown);
|
||||
projectId = $dropdown.data('project-id');
|
||||
milestonesUrl = $dropdown.data('milestones');
|
||||
|
@ -15,6 +15,7 @@
|
|||
selectedMilestone = $dropdown.data('selected');
|
||||
showNo = $dropdown.data('show-no');
|
||||
showAny = $dropdown.data('show-any');
|
||||
showMenuAbove = $dropdown.data('showMenuAbove');
|
||||
showUpcoming = $dropdown.data('show-upcoming');
|
||||
useId = $dropdown.data('use-id');
|
||||
defaultLabel = $dropdown.data('default-label');
|
||||
|
@ -31,12 +32,12 @@
|
|||
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
|
||||
}
|
||||
return $dropdown.glDropdown({
|
||||
showMenuAbove: showMenuAbove,
|
||||
data: function(term, callback) {
|
||||
return $.ajax({
|
||||
url: milestonesUrl
|
||||
}).done(function(data) {
|
||||
var extraOptions;
|
||||
extraOptions = [];
|
||||
var extraOptions = [];
|
||||
if (showAny) {
|
||||
extraOptions.push({
|
||||
id: 0,
|
||||
|
@ -58,10 +59,14 @@
|
|||
title: 'Upcoming'
|
||||
});
|
||||
}
|
||||
if (extraOptions.length > 2) {
|
||||
if (extraOptions.length) {
|
||||
extraOptions.push('divider');
|
||||
}
|
||||
return callback(extraOptions.concat(data));
|
||||
|
||||
callback(extraOptions.concat(data));
|
||||
if (showMenuAbove) {
|
||||
$dropdown.data('glDropdown').positionMenuAbove();
|
||||
}
|
||||
});
|
||||
},
|
||||
filterable: true,
|
||||
|
@ -69,19 +74,20 @@
|
|||
fields: ['title']
|
||||
},
|
||||
selectable: true,
|
||||
toggleLabel: function(selected) {
|
||||
if (selected && 'id' in selected) {
|
||||
toggleLabel: function(selected, el, e) {
|
||||
if (selected && 'id' in selected && $(el).hasClass('is-active')) {
|
||||
return selected.title;
|
||||
} else {
|
||||
return defaultLabel;
|
||||
}
|
||||
},
|
||||
defaultLabel: defaultLabel,
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
text: function(milestone) {
|
||||
return _.escape(milestone.title);
|
||||
},
|
||||
id: function(milestone) {
|
||||
if (!useId) {
|
||||
if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
|
||||
return milestone.name;
|
||||
} else {
|
||||
return milestone.id;
|
||||
|
@ -100,10 +106,11 @@
|
|||
page = $('body').data('page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = (page === page && page === 'projects:merge_requests:index');
|
||||
if ($dropdown.hasClass('js-filter-bulk-update')) {
|
||||
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (page === 'projects:boards:show') {
|
||||
if ($('html').hasClass('issue-boards-page')) {
|
||||
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name;
|
||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
e.preventDefault();
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
(function() {
|
||||
function toggleGraph() {
|
||||
const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
|
||||
const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
|
||||
const $btnText = $(this).find('.toggle-btn-text');
|
||||
|
||||
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
|
||||
|
||||
const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
|
||||
|
||||
graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
|
||||
}
|
||||
|
||||
$(document).on('click', '.toggle-pipeline-btn', toggleGraph);
|
||||
})();
|
40
app/assets/javascripts/pipelines.js.es6
Normal file
|
@ -0,0 +1,40 @@
|
|||
((global) => {
|
||||
|
||||
class Pipelines {
|
||||
constructor() {
|
||||
$(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph);
|
||||
this.addMarginToBuildColumns();
|
||||
}
|
||||
|
||||
toggleGraph() {
|
||||
const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
|
||||
const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
|
||||
const $btnText = $(this).find('.toggle-btn-text');
|
||||
const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
|
||||
|
||||
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
|
||||
|
||||
|
||||
graphCollapsed ? $btnText.text('Hide') : $btnText.text('Expand')
|
||||
}
|
||||
|
||||
addMarginToBuildColumns() {
|
||||
const $secondChildBuildNode = $('.build:nth-child(2)');
|
||||
if ($secondChildBuildNode.length) {
|
||||
const $firstChildBuildNode = $secondChildBuildNode.prev('.build');
|
||||
const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column');
|
||||
const $previousColumn = $multiBuildColumn.prev('.stage-column');
|
||||
$multiBuildColumn.addClass('left-margin');
|
||||
$firstChildBuildNode.addClass('left-connector');
|
||||
$previousColumn.each(function() {
|
||||
$this = $(this);
|
||||
if ($('.build', $this).length === 1) $this.addClass('no-margin');
|
||||
});
|
||||
}
|
||||
$('.pipeline-graph').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
global.Pipelines = Pipelines;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,47 +1,45 @@
|
|||
(function() {
|
||||
var GitLabCrop,
|
||||
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
GitLabCrop = (function() {
|
||||
var FILENAMEREGEX;
|
||||
((global) => {
|
||||
|
||||
// Matches everything but the file name
|
||||
FILENAMEREGEX = /^.*[\\\/]/;
|
||||
const FILENAMEREGEX = /^.*[\\\/]/;
|
||||
|
||||
function GitLabCrop(input, opts) {
|
||||
var ref, ref1, ref2, ref3, ref4;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
this.onUploadImageBtnClick = bind(this.onUploadImageBtnClick, this);
|
||||
this.onModalHide = bind(this.onModalHide, this);
|
||||
this.onModalShow = bind(this.onModalShow, this);
|
||||
this.onPickImageClick = bind(this.onPickImageClick, this);
|
||||
class GitLabCrop {
|
||||
constructor(input, { filename, previewImage, modalCrop, pickImageEl, uploadImageBtn, modalCropImg,
|
||||
exportWidth = 200, exportHeight = 200, cropBoxWidth = 200, cropBoxHeight = 200 } = {}) {
|
||||
|
||||
this.onUploadImageBtnClick = this.onUploadImageBtnClick.bind(this);
|
||||
this.onModalHide = this.onModalHide.bind(this);
|
||||
this.onModalShow = this.onModalShow.bind(this);
|
||||
this.onPickImageClick = this.onPickImageClick.bind(this);
|
||||
this.fileInput = $(input);
|
||||
// We should rename to avoid spec to fail
|
||||
// Form will submit the proper input filed with a file using FormData
|
||||
this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger");
|
||||
// Set defaults
|
||||
this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg;
|
||||
// Required params
|
||||
// Ensure needed elements are jquery objects
|
||||
// If selector is provided we will convert them to a jQuery Object
|
||||
this.filename = this.getElement(this.filename);
|
||||
this.previewImage = this.getElement(this.previewImage);
|
||||
this.pickImageEl = this.getElement(this.pickImageEl);
|
||||
// Modal elements usually are outside the @form element
|
||||
this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop;
|
||||
this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn;
|
||||
this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
|
||||
this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `this.fileInput.attr('id')-trigger`);
|
||||
this.exportWidth = exportWidth;
|
||||
this.exportHeight = exportHeight;
|
||||
this.cropBoxWidth = cropBoxWidth;
|
||||
this.cropBoxHeight = cropBoxHeight;
|
||||
this.form = this.fileInput.parents('form');
|
||||
this.filename = filename;
|
||||
this.previewImage = previewImage;
|
||||
this.modalCrop = modalCrop;
|
||||
this.pickImageEl = pickImageEl;
|
||||
this.uploadImageBtn = uploadImageBtn;
|
||||
this.modalCropImg = modalCropImg;
|
||||
this.filename = this.getElement(filename);
|
||||
this.previewImage = this.getElement(previewImage);
|
||||
this.pickImageEl = this.getElement(pickImageEl);
|
||||
this.modalCrop = _.isString(modalCrop) ? $(modalCrop) : modalCrop;
|
||||
this.uploadImageBtn = _.isString(uploadImageBtn) ? $(uploadImageBtn) : uploadImageBtn;
|
||||
this.modalCropImg = _.isString(modalCropImg) ? $(modalCropImg) : modalCropImg;
|
||||
this.cropActionsBtn = this.modalCrop.find('[data-method]');
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.getElement = function(selector) {
|
||||
getElement(selector) {
|
||||
return $(selector, this.form);
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.bindEvents = function() {
|
||||
bindEvents() {
|
||||
var _this;
|
||||
_this = this;
|
||||
this.fileInput.on('change', function(e) {
|
||||
|
@ -57,13 +55,13 @@
|
|||
return _this.onActionBtnClick(btn);
|
||||
});
|
||||
return this.croppedImageBlob = null;
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.onPickImageClick = function() {
|
||||
onPickImageClick() {
|
||||
return this.fileInput.trigger('click');
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.onModalShow = function() {
|
||||
onModalShow() {
|
||||
var _this;
|
||||
_this = this;
|
||||
return this.modalCropImg.cropper({
|
||||
|
@ -95,44 +93,44 @@
|
|||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.onModalHide = function() {
|
||||
onModalHide() {
|
||||
return this.modalCropImg.attr('src', '').cropper('destroy');
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image
|
||||
e.preventDefault(); // Destroy cropper instance
|
||||
onUploadImageBtnClick(e) {
|
||||
e.preventDefault();
|
||||
this.setBlob();
|
||||
this.setPreview();
|
||||
this.modalCrop.modal('hide');
|
||||
return this.fileInput.val('');
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.onActionBtnClick = function(btn) {
|
||||
onActionBtnClick(btn) {
|
||||
var data, result;
|
||||
data = $(btn).data();
|
||||
if (this.modalCropImg.data('cropper') && data.method) {
|
||||
return result = this.modalCropImg.cropper(data.method, data.option);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.onFileInputChange = function(e, input) {
|
||||
onFileInputChange(e, input) {
|
||||
return this.readFile(input);
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.readFile = function(input) {
|
||||
readFile(input) {
|
||||
var _this, reader;
|
||||
_this = this;
|
||||
reader = new FileReader;
|
||||
reader.onload = function() {
|
||||
reader.onload = () => {
|
||||
_this.modalCropImg.attr('src', reader.result);
|
||||
return _this.modalCrop.modal('show');
|
||||
};
|
||||
return reader.readAsDataURL(input.files[0]);
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.dataURLtoBlob = function(dataURL) {
|
||||
dataURLtoBlob(dataURL) {
|
||||
var array, binary, i, k, len, v;
|
||||
binary = atob(dataURL.split(',')[1]);
|
||||
array = [];
|
||||
|
@ -143,35 +141,32 @@
|
|||
return new Blob([new Uint8Array(array)], {
|
||||
type: 'image/png'
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.setPreview = function() {
|
||||
setPreview() {
|
||||
var filename;
|
||||
this.previewImage.attr('src', this.dataURL);
|
||||
filename = this.fileInput.val().replace(FILENAMEREGEX, '');
|
||||
return this.filename.text(filename);
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.setBlob = function() {
|
||||
setBlob() {
|
||||
this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', {
|
||||
width: 200,
|
||||
height: 200
|
||||
}).toDataURL('image/png');
|
||||
return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL);
|
||||
};
|
||||
}
|
||||
|
||||
GitLabCrop.prototype.getBlob = function() {
|
||||
getBlob() {
|
||||
return this.croppedImageBlob;
|
||||
};
|
||||
|
||||
return GitLabCrop;
|
||||
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
$.fn.glCrop = function(opts) {
|
||||
return this.each(function() {
|
||||
return $(this).data('glcrop', new GitLabCrop(this, opts));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,106 +0,0 @@
|
|||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
this.Profile = (function() {
|
||||
function Profile(opts) {
|
||||
var cropOpts, ref;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
this.onSubmitForm = bind(this.onSubmitForm, this);
|
||||
this.form = (ref = opts.form) != null ? ref : $('.edit-user');
|
||||
$('.js-preferences-form').on('change.preference', 'input[type=radio]', function() {
|
||||
return $(this).parents('form').submit();
|
||||
// Automatically submit the Preferences form when any of its radio buttons change
|
||||
});
|
||||
$('#user_notification_email').on('change', function() {
|
||||
return $(this).parents('form').submit();
|
||||
// Automatically submit email form when it changes
|
||||
});
|
||||
$('.update-username').on('ajax:before', function() {
|
||||
$('.loading-username').show();
|
||||
$(this).find('.update-success').hide();
|
||||
return $(this).find('.update-failed').hide();
|
||||
});
|
||||
$('.update-username').on('ajax:complete', function() {
|
||||
$('.loading-username').hide();
|
||||
$(this).find('.btn-save').enable();
|
||||
return $(this).find('.loading-gif').hide();
|
||||
});
|
||||
$('.update-notifications').on('ajax:success', function(e, data) {
|
||||
if (data.saved) {
|
||||
return new Flash("Notification settings saved", "notice");
|
||||
} else {
|
||||
return new Flash("Failed to save new settings", "alert");
|
||||
}
|
||||
});
|
||||
this.bindEvents();
|
||||
cropOpts = {
|
||||
filename: '.js-avatar-filename',
|
||||
previewImage: '.avatar-image .avatar',
|
||||
modalCrop: '.modal-profile-crop',
|
||||
pickImageEl: '.js-choose-user-avatar-button',
|
||||
uploadImageBtn: '.js-upload-user-avatar',
|
||||
modalCropImg: '.modal-profile-crop-image'
|
||||
};
|
||||
this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop');
|
||||
}
|
||||
|
||||
Profile.prototype.bindEvents = function() {
|
||||
return this.form.on('submit', this.onSubmitForm);
|
||||
};
|
||||
|
||||
Profile.prototype.onSubmitForm = function(e) {
|
||||
e.preventDefault();
|
||||
return this.saveForm();
|
||||
};
|
||||
|
||||
Profile.prototype.saveForm = function() {
|
||||
var avatarBlob, formData, self;
|
||||
self = this;
|
||||
formData = new FormData(this.form[0]);
|
||||
avatarBlob = this.avatarGlCrop.getBlob();
|
||||
if (avatarBlob != null) {
|
||||
formData.append('user[avatar]', avatarBlob, 'avatar.png');
|
||||
}
|
||||
return $.ajax({
|
||||
url: this.form.attr('action'),
|
||||
type: this.form.attr('method'),
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
return new Flash(response.message, 'notice');
|
||||
},
|
||||
error: function(jqXHR) {
|
||||
return new Flash(jqXHR.responseJSON.message, 'alert');
|
||||
},
|
||||
complete: function() {
|
||||
window.scrollTo(0, 0);
|
||||
// Enable submit button after requests ends
|
||||
return self.form.find(':input[disabled]').enable();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return Profile;
|
||||
|
||||
})();
|
||||
|
||||
$(function() {
|
||||
$(document).on('focusout.ssh_key', '#key_key', function() {
|
||||
var $title, comment;
|
||||
$title = $('#key_title');
|
||||
comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
|
||||
if (comment && comment.length > 1 && $title.val() === '') {
|
||||
return $title.val(comment[1]).change();
|
||||
}
|
||||
// Extract the SSH Key title from its comment
|
||||
});
|
||||
if (gl.utils.getPagePath() === 'profiles') {
|
||||
return new Profile();
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
100
app/assets/javascripts/profile/profile.js.es6
Normal file
|
@ -0,0 +1,100 @@
|
|||
((global) => {
|
||||
|
||||
class Profile {
|
||||
constructor({ form } = {}) {
|
||||
this.onSubmitForm = this.onSubmitForm.bind(this);
|
||||
this.form = form || $('.edit-user');
|
||||
this.bindEvents();
|
||||
this.initAvatarGlCrop();
|
||||
}
|
||||
|
||||
initAvatarGlCrop() {
|
||||
const cropOpts = {
|
||||
filename: '.js-avatar-filename',
|
||||
previewImage: '.avatar-image .avatar',
|
||||
modalCrop: '.modal-profile-crop',
|
||||
pickImageEl: '.js-choose-user-avatar-button',
|
||||
uploadImageBtn: '.js-upload-user-avatar',
|
||||
modalCropImg: '.modal-profile-crop-image'
|
||||
};
|
||||
this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
|
||||
$('#user_notification_email').on('change', this.submitForm);
|
||||
$('.update-username').on('ajax:before', this.beforeUpdateUsername);
|
||||
$('.update-username').on('ajax:complete', this.afterUpdateUsername);
|
||||
$('.update-notifications').on('ajax:success', this.onUpdateNotifs);
|
||||
this.form.on('submit', this.onSubmitForm);
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
return $(this).parents('form').submit();
|
||||
}
|
||||
|
||||
onSubmitForm(e) {
|
||||
e.preventDefault();
|
||||
return this.saveForm();
|
||||
}
|
||||
|
||||
beforeUpdateUsername() {
|
||||
$('.loading-username').show();
|
||||
$(this).find('.update-success').hide();
|
||||
return $(this).find('.update-failed').hide();
|
||||
}
|
||||
|
||||
afterUpdateUsername() {
|
||||
$('.loading-username').hide();
|
||||
$(this).find('.btn-save').enable();
|
||||
return $(this).find('.loading-gif').hide();
|
||||
}
|
||||
|
||||
onUpdateNotifs(e, data) {
|
||||
return data.saved ?
|
||||
new Flash("Notification settings saved", "notice") :
|
||||
new Flash("Failed to save new settings", "alert");
|
||||
}
|
||||
|
||||
saveForm() {
|
||||
const self = this;
|
||||
const formData = new FormData(this.form[0]);
|
||||
const avatarBlob = this.avatarGlCrop.getBlob();
|
||||
|
||||
if (avatarBlob != null) {
|
||||
formData.append('user[avatar]', avatarBlob, 'avatar.png');
|
||||
}
|
||||
|
||||
return $.ajax({
|
||||
url: this.form.attr('action'),
|
||||
type: this.form.attr('method'),
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: response => new Flash(response.message, 'notice'),
|
||||
error: jqXHR => new Flash(jqXHR.responseJSON.message, 'alert'),
|
||||
complete: () => {
|
||||
window.scrollTo(0, 0);
|
||||
// Enable submit button after requests ends
|
||||
return self.form.find(':input[disabled]').enable();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$(document).on('focusout.ssh_key', '#key_key', function() {
|
||||
const $title = $('#key_title');
|
||||
const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
|
||||
if (comment && comment.length > 1 && $title.val() === '') {
|
||||
return $title.val(comment[1]).change();
|
||||
}
|
||||
// Extract the SSH Key title from its comment
|
||||
});
|
||||
if (global.utils.getPagePath() === 'profiles') {
|
||||
return new Profile();
|
||||
}
|
||||
});
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -82,7 +82,7 @@
|
|||
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));
|
||||
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', ref);
|
||||
return $('<li />').append(link);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -36,16 +36,6 @@
|
|||
}
|
||||
};
|
||||
})(this));
|
||||
return this.element.find(".tree-content-holder .tree-table").on("click", function(event) {
|
||||
var path;
|
||||
if (event.target.nodeName !== "A") {
|
||||
path = this.element.find(".tree-item-file-name a", this).attr("href");
|
||||
if (path) {
|
||||
return location.href = path;
|
||||
}
|
||||
}
|
||||
});
|
||||
// init event
|
||||
};
|
||||
|
||||
ProjectFindFile.prototype.findFile = function() {
|
||||
|
@ -121,11 +111,12 @@
|
|||
// make tbody row html
|
||||
ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
|
||||
var $tr;
|
||||
$tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>");
|
||||
$tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
|
||||
if (matches) {
|
||||
$tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
|
||||
} else {
|
||||
$tr.find("a").attr("href", blobItemUrl).text(filePath);
|
||||
$tr.find("a").attr("href", blobItemUrl);
|
||||
$tr.find(".str-truncated").text(filePath);
|
||||
}
|
||||
return $tr;
|
||||
};
|
||||
|
@ -165,10 +156,10 @@
|
|||
};
|
||||
|
||||
ProjectFindFile.prototype.goToBlob = function() {
|
||||
var path;
|
||||
path = this.element.find(".tree-item.selected .tree-item-file-name a").attr("href");
|
||||
if (path) {
|
||||
return location.href = path;
|
||||
var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
|
||||
|
||||
if ($link.length) {
|
||||
$link.get(0).click();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
(function() {
|
||||
this.ProjectMembers = (function() {
|
||||
function ProjectMembers() {
|
||||
$('li.project_member').bind('ajax:success', function() {
|
||||
return $(this).fadeOut();
|
||||
});
|
||||
}
|
||||
return ProjectMembers;
|
||||
})();
|
||||
}).call(this);
|
|
@ -5,6 +5,7 @@
|
|||
function ProjectNew() {
|
||||
this.toggleSettings = bind(this.toggleSettings, this);
|
||||
this.$selects = $('.features select');
|
||||
this.$repoSelects = this.$selects.filter('.js-repo-select');
|
||||
|
||||
$('.project-edit-container').on('ajax:before', (function(_this) {
|
||||
return function() {
|
||||
|
@ -14,6 +15,7 @@
|
|||
})(this));
|
||||
this.toggleSettings();
|
||||
this.toggleSettingsOnclick();
|
||||
this.toggleRepoVisibility();
|
||||
}
|
||||
|
||||
ProjectNew.prototype.toggleSettings = function() {
|
||||
|
@ -41,6 +43,38 @@
|
|||
}
|
||||
};
|
||||
|
||||
ProjectNew.prototype.toggleRepoVisibility = function () {
|
||||
var $repoAccessLevel = $('.js-repo-access-level select');
|
||||
|
||||
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
|
||||
.nextAll()
|
||||
.hide();
|
||||
|
||||
$repoAccessLevel.off('change')
|
||||
.on('change', function () {
|
||||
var selectedVal = parseInt($repoAccessLevel.val());
|
||||
|
||||
this.$repoSelects.each(function () {
|
||||
var $this = $(this),
|
||||
repoSelectVal = parseInt($this.val());
|
||||
|
||||
$this.find('option').show();
|
||||
|
||||
if (selectedVal < repoSelectVal) {
|
||||
$this.val(selectedVal);
|
||||
}
|
||||
|
||||
$this.find("option[value='" + selectedVal + "']").nextAll().hide();
|
||||
});
|
||||
|
||||
if (selectedVal) {
|
||||
this.$repoSelects.removeClass('disabled');
|
||||
} else {
|
||||
this.$repoSelects.addClass('disabled');
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
return ProjectNew;
|
||||
|
||||
})();
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
data = groups.concat(projects);
|
||||
return finalCallback(data);
|
||||
};
|
||||
return Api.groups(term, false, groupsCallback);
|
||||
return Api.groups(term, false, false, groupsCallback);
|
||||
};
|
||||
} else {
|
||||
projectsCallback = finalCallback;
|
||||
|
@ -72,7 +72,7 @@
|
|||
data = groups.concat(projects);
|
||||
return finalCallback(data);
|
||||
};
|
||||
return Api.groups(query.term, false, groupsCallback);
|
||||
return Api.groups(query.term, false, false, groupsCallback);
|
||||
};
|
||||
} else {
|
||||
projectsCallback = finalCallback;
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
dataType: 'json',
|
||||
data: {
|
||||
_method: 'PATCH',
|
||||
id: this.$wrap.data('banchId'),
|
||||
protected_branch: {
|
||||
merge_access_levels_attributes: [{
|
||||
id: this.$allowedToMergeDropdown.data('access-level-id'),
|
|
@ -0,0 +1 @@
|
|||
/*= require_tree . */
|
|
@ -10,7 +10,7 @@
|
|||
filterable: true,
|
||||
fieldName: 'group_id',
|
||||
data: function(term, callback) {
|
||||
return Api.groups(term, null, function(data) {
|
||||
return Api.groups(term, false, false, function(data) {
|
||||
data.unshift({
|
||||
name: 'Any'
|
||||
});
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
((global) => {
|
||||
|
||||
this.SearchAutocomplete = (function() {
|
||||
var KEYCODE;
|
||||
|
||||
KEYCODE = {
|
||||
const KEYCODE = {
|
||||
ESCAPE: 27,
|
||||
BACKSPACE: 8,
|
||||
ENTER: 13,
|
||||
|
@ -12,19 +8,14 @@
|
|||
DOWN: 40
|
||||
};
|
||||
|
||||
function SearchAutocomplete(opts) {
|
||||
var ref, ref1, ref2, ref3, ref4;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
this.onSearchInputBlur = bind(this.onSearchInputBlur, this);
|
||||
this.onClearInputClick = bind(this.onClearInputClick, this);
|
||||
this.onSearchInputFocus = bind(this.onSearchInputFocus, this);
|
||||
this.onSearchInputClick = bind(this.onSearchInputClick, this);
|
||||
this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
|
||||
this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
|
||||
this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
|
||||
// Dropdown Element
|
||||
class SearchAutocomplete {
|
||||
constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
|
||||
this.bindEventContext();
|
||||
this.wrap = wrap || $('.search');
|
||||
this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
|
||||
this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path');
|
||||
this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
|
||||
this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
|
||||
this.dropdown = this.wrap.find('.dropdown');
|
||||
this.dropdownContent = this.dropdown.find('.dropdown-content');
|
||||
this.locationBadgeEl = this.getElement('.location-badge');
|
||||
|
@ -46,19 +37,27 @@
|
|||
}
|
||||
|
||||
// Finds an element inside wrapper element
|
||||
SearchAutocomplete.prototype.getElement = function(selector) {
|
||||
bindEventContext() {
|
||||
this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
|
||||
this.onClearInputClick = this.onClearInputClick.bind(this);
|
||||
this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
|
||||
this.onSearchInputClick = this.onSearchInputClick.bind(this);
|
||||
this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
|
||||
this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
|
||||
}
|
||||
getElement(selector) {
|
||||
return this.wrap.find(selector);
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.saveOriginalState = function() {
|
||||
saveOriginalState() {
|
||||
return this.originalState = this.serializeState();
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.saveTextLength = function() {
|
||||
saveTextLength() {
|
||||
return this.lastTextLength = this.searchInput.val().length;
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.createAutocomplete = function() {
|
||||
createAutocomplete() {
|
||||
return this.searchInput.glDropdown({
|
||||
filterInputBlur: false,
|
||||
filterable: true,
|
||||
|
@ -73,9 +72,9 @@
|
|||
selectable: true,
|
||||
clicked: this.onClick.bind(this)
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.getData = function(term, callback) {
|
||||
getData(term, callback) {
|
||||
var _this, contents, jqXHR;
|
||||
_this = this;
|
||||
if (!term) {
|
||||
|
@ -138,9 +137,9 @@
|
|||
}).always(function() {
|
||||
return _this.loadingSuggestions = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.getCategoryContents = function() {
|
||||
getCategoryContents() {
|
||||
var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils;
|
||||
userId = gon.current_user_id;
|
||||
utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
|
||||
|
@ -173,9 +172,9 @@
|
|||
items.splice(0, 1);
|
||||
}
|
||||
return items;
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.serializeState = function() {
|
||||
serializeState() {
|
||||
return {
|
||||
// Search Criteria
|
||||
search_project_id: this.projectInputEl.val(),
|
||||
|
@ -186,9 +185,9 @@
|
|||
// Location badge
|
||||
_location: this.locationBadgeEl.text()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.bindEvents = function() {
|
||||
bindEvents() {
|
||||
this.searchInput.on('keydown', this.onSearchInputKeyDown);
|
||||
this.searchInput.on('keyup', this.onSearchInputKeyUp);
|
||||
this.searchInput.on('click', this.onSearchInputClick);
|
||||
|
@ -200,9 +199,9 @@
|
|||
return _this.searchInput.focus();
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.enableAutocomplete = function() {
|
||||
enableAutocomplete() {
|
||||
var _this;
|
||||
// No need to enable anything if user is not logged in
|
||||
if (!gon.current_user_id) {
|
||||
|
@ -216,12 +215,12 @@
|
|||
}
|
||||
};
|
||||
|
||||
SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
|
||||
// Saves last length of the entered text
|
||||
onSearchInputKeyDown() {
|
||||
return this.saveTextLength();
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) {
|
||||
onSearchInputKeyUp(e) {
|
||||
switch (e.keyCode) {
|
||||
case KEYCODE.BACKSPACE:
|
||||
// when trying to remove the location badge
|
||||
|
@ -259,54 +258,53 @@
|
|||
}
|
||||
}
|
||||
this.wrap.toggleClass('has-value', !!e.target.value);
|
||||
};
|
||||
}
|
||||
|
||||
// Avoid falsy value to be returned
|
||||
SearchAutocomplete.prototype.onSearchInputClick = function(e) {
|
||||
// Prevents closing the dropdown menu
|
||||
onSearchInputClick(e) {
|
||||
return e.stopImmediatePropagation();
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.onSearchInputFocus = function() {
|
||||
onSearchInputFocus() {
|
||||
this.isFocused = true;
|
||||
this.wrap.addClass('search-active');
|
||||
if (this.getValue() === '') {
|
||||
return this.getData();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.getValue = function() {
|
||||
getValue() {
|
||||
return this.searchInput.val();
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.onClearInputClick = function(e) {
|
||||
onClearInputClick(e) {
|
||||
e.preventDefault();
|
||||
return this.searchInput.val('').focus();
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.onSearchInputBlur = function(e) {
|
||||
onSearchInputBlur(e) {
|
||||
this.isFocused = false;
|
||||
this.wrap.removeClass('search-active');
|
||||
// If input is blank then restore state
|
||||
if (this.searchInput.val() === '') {
|
||||
return this.restoreOriginalState();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.addLocationBadge = function(item) {
|
||||
addLocationBadge(item) {
|
||||
var badgeText, category, value;
|
||||
category = item.category != null ? item.category + ": " : '';
|
||||
value = item.value != null ? item.value : '';
|
||||
badgeText = "" + category + value;
|
||||
this.locationBadgeEl.text(badgeText).show();
|
||||
return this.wrap.addClass('has-location-badge');
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.hasLocationBadge = function() {
|
||||
hasLocationBadge() {
|
||||
return this.wrap.is('.has-location-badge');
|
||||
};
|
||||
|
||||
SearchAutocomplete.prototype.restoreOriginalState = function() {
|
||||
restoreOriginalState() {
|
||||
var i, input, inputs, len;
|
||||
inputs = Object.keys(this.originalState);
|
||||
for (i = 0, len = inputs.length; i < len; i++) {
|
||||
|
@ -320,13 +318,13 @@
|
|||
value: this.originalState._location
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.badgePresent = function() {
|
||||
badgePresent() {
|
||||
return this.locationBadgeEl.length;
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.resetSearchState = function() {
|
||||
resetSearchState() {
|
||||
var i, input, inputs, len, results;
|
||||
inputs = Object.keys(this.originalState);
|
||||
results = [];
|
||||
|
@ -339,30 +337,30 @@
|
|||
results.push(this.getElement("#" + input).val(''));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.removeLocationBadge = function() {
|
||||
removeLocationBadge() {
|
||||
this.locationBadgeEl.hide();
|
||||
this.resetSearchState();
|
||||
this.wrap.removeClass('has-location-badge');
|
||||
return this.disableAutocomplete();
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.disableAutocomplete = function() {
|
||||
disableAutocomplete() {
|
||||
if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
|
||||
this.searchInput.addClass('disabled');
|
||||
this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
|
||||
this.restoreMenu();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SearchAutocomplete.prototype.restoreMenu = function() {
|
||||
restoreMenu() {
|
||||
var html;
|
||||
html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>";
|
||||
return this.dropdownContent.html(html);
|
||||
};
|
||||
|
||||
SearchAutocomplete.prototype.onClick = function(item, $el, e) {
|
||||
onClick(item, $el, e) {
|
||||
if (location.pathname.indexOf(item.url) !== -1) {
|
||||
e.preventDefault();
|
||||
if (!this.badgePresent) {
|
||||
|
@ -385,8 +383,45 @@
|
|||
}
|
||||
};
|
||||
|
||||
return SearchAutocomplete;
|
||||
}
|
||||
|
||||
})();
|
||||
global.SearchAutocomplete = SearchAutocomplete;
|
||||
|
||||
}).call(this);
|
||||
$(function() {
|
||||
var $projectOptionsDataEl = $('.js-search-project-options');
|
||||
var $groupOptionsDataEl = $('.js-search-group-options');
|
||||
var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
|
||||
|
||||
if ($projectOptionsDataEl.length) {
|
||||
gl.projectOptions = gl.projectOptions || {};
|
||||
|
||||
var projectPath = $projectOptionsDataEl.data('project-path');
|
||||
|
||||
gl.projectOptions[projectPath] = {
|
||||
name: $projectOptionsDataEl.data('name'),
|
||||
issuesPath: $projectOptionsDataEl.data('issues-path'),
|
||||
mrPath: $projectOptionsDataEl.data('mr-path')
|
||||
};
|
||||
}
|
||||
|
||||
if ($groupOptionsDataEl.length) {
|
||||
gl.groupOptions = gl.groupOptions || {};
|
||||
|
||||
var groupPath = $groupOptionsDataEl.data('group-path');
|
||||
|
||||
gl.groupOptions[groupPath] = {
|
||||
name: $groupOptionsDataEl.data('name'),
|
||||
issuesPath: $groupOptionsDataEl.data('issues-path'),
|
||||
mrPath: $groupOptionsDataEl.data('mr-path')
|
||||
};
|
||||
}
|
||||
|
||||
if ($dashboardOptionsDataEl.length) {
|
||||
gl.dashboardOptions = {
|
||||
issuesPath: $dashboardOptionsDataEl.data('issues-path'),
|
||||
mrPath: $dashboardOptionsDataEl.data('mr-path')
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
})(window.gl || (window.gl = {}));
|