New upstream version 8.11.3+dfsg
1
.gitignore
vendored
|
@ -30,6 +30,7 @@
|
||||||
/config/secrets.yml
|
/config/secrets.yml
|
||||||
/config/sidekiq.yml
|
/config/sidekiq.yml
|
||||||
/coverage/*
|
/coverage/*
|
||||||
|
/coverage-javascript/
|
||||||
/db/*.sqlite3
|
/db/*.sqlite3
|
||||||
/db/*.sqlite3-journal
|
/db/*.sqlite3-journal
|
||||||
/db/data.yml
|
/db/data.yml
|
||||||
|
|
144
.gitlab-ci.yml
|
@ -1,7 +1,7 @@
|
||||||
image: "ruby:2.1"
|
image: "ruby:2.3.1"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
key: "ruby21"
|
key: "ruby-231"
|
||||||
paths:
|
paths:
|
||||||
- vendor/apt
|
- vendor/apt
|
||||||
- vendor/ruby
|
- vendor/ruby
|
||||||
|
@ -15,6 +15,7 @@ variables:
|
||||||
USE_DB: "true"
|
USE_DB: "true"
|
||||||
USE_BUNDLE_INSTALL: "true"
|
USE_BUNDLE_INSTALL: "true"
|
||||||
GIT_DEPTH: "20"
|
GIT_DEPTH: "20"
|
||||||
|
PHANTOMJS_VERSION: "2.1.1"
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- source ./scripts/prepare_build.sh
|
- source ./scripts/prepare_build.sh
|
||||||
|
@ -28,6 +29,7 @@ stages:
|
||||||
- prepare
|
- prepare
|
||||||
- test
|
- test
|
||||||
- post-test
|
- post-test
|
||||||
|
- pages
|
||||||
|
|
||||||
# Prepare and merge knapsack tests
|
# Prepare and merge knapsack tests
|
||||||
.knapsack-state: &knapsack-state
|
.knapsack-state: &knapsack-state
|
||||||
|
@ -40,6 +42,7 @@ stages:
|
||||||
paths:
|
paths:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
artifacts:
|
artifacts:
|
||||||
|
expire_in: 31d
|
||||||
paths:
|
paths:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
|
|
||||||
|
@ -81,8 +84,10 @@ update-knapsack:
|
||||||
- cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
|
- cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
|
||||||
- knapsack rspec
|
- knapsack rspec
|
||||||
artifacts:
|
artifacts:
|
||||||
|
expire_in: 31d
|
||||||
paths:
|
paths:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
|
- coverage/
|
||||||
|
|
||||||
.spinach-knapsack: &spinach-knapsack
|
.spinach-knapsack: &spinach-knapsack
|
||||||
stage: test
|
stage: test
|
||||||
|
@ -97,8 +102,10 @@ update-knapsack:
|
||||||
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
|
- 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:
|
artifacts:
|
||||||
|
expire_in: 31d
|
||||||
paths:
|
paths:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
|
- coverage/
|
||||||
|
|
||||||
rspec 0 20: *rspec-knapsack
|
rspec 0 20: *rspec-knapsack
|
||||||
rspec 1 20: *rspec-knapsack
|
rspec 1 20: *rspec-knapsack
|
||||||
|
@ -132,68 +139,68 @@ spinach 7 10: *spinach-knapsack
|
||||||
spinach 8 10: *spinach-knapsack
|
spinach 8 10: *spinach-knapsack
|
||||||
spinach 9 10: *spinach-knapsack
|
spinach 9 10: *spinach-knapsack
|
||||||
|
|
||||||
# Execute all testing suites against Ruby 2.3
|
# Execute all testing suites against Ruby 2.1
|
||||||
.ruby-23: &ruby-23
|
.ruby-21: &ruby-21
|
||||||
image: "ruby:2.3"
|
image: "ruby:2.1"
|
||||||
<<: *use-db
|
<<: *use-db
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
cache:
|
cache:
|
||||||
key: "ruby-23"
|
key: "ruby21"
|
||||||
paths:
|
paths:
|
||||||
- vendor/apt
|
- vendor/apt
|
||||||
- vendor/ruby
|
- vendor/ruby
|
||||||
|
|
||||||
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
|
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
|
||||||
<<: *rspec-knapsack
|
<<: *rspec-knapsack
|
||||||
<<: *ruby-23
|
<<: *ruby-21
|
||||||
|
|
||||||
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23
|
.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
|
||||||
<<: *spinach-knapsack
|
<<: *spinach-knapsack
|
||||||
<<: *ruby-23
|
<<: *ruby-21
|
||||||
|
|
||||||
rspec 0 20 ruby23: *rspec-knapsack-ruby23
|
rspec 0 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 1 20 ruby23: *rspec-knapsack-ruby23
|
rspec 1 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 2 20 ruby23: *rspec-knapsack-ruby23
|
rspec 2 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 3 20 ruby23: *rspec-knapsack-ruby23
|
rspec 3 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 4 20 ruby23: *rspec-knapsack-ruby23
|
rspec 4 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 5 20 ruby23: *rspec-knapsack-ruby23
|
rspec 5 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 6 20 ruby23: *rspec-knapsack-ruby23
|
rspec 6 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 7 20 ruby23: *rspec-knapsack-ruby23
|
rspec 7 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 8 20 ruby23: *rspec-knapsack-ruby23
|
rspec 8 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 9 20 ruby23: *rspec-knapsack-ruby23
|
rspec 9 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 10 20 ruby23: *rspec-knapsack-ruby23
|
rspec 10 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 11 20 ruby23: *rspec-knapsack-ruby23
|
rspec 11 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 12 20 ruby23: *rspec-knapsack-ruby23
|
rspec 12 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 13 20 ruby23: *rspec-knapsack-ruby23
|
rspec 13 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 14 20 ruby23: *rspec-knapsack-ruby23
|
rspec 14 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 15 20 ruby23: *rspec-knapsack-ruby23
|
rspec 15 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 16 20 ruby23: *rspec-knapsack-ruby23
|
rspec 16 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 17 20 ruby23: *rspec-knapsack-ruby23
|
rspec 17 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 18 20 ruby23: *rspec-knapsack-ruby23
|
rspec 18 20 ruby21: *rspec-knapsack-ruby21
|
||||||
rspec 19 20 ruby23: *rspec-knapsack-ruby23
|
rspec 19 20 ruby21: *rspec-knapsack-ruby21
|
||||||
|
|
||||||
spinach 0 10 ruby23: *spinach-knapsack-ruby23
|
spinach 0 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 1 10 ruby23: *spinach-knapsack-ruby23
|
spinach 1 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 2 10 ruby23: *spinach-knapsack-ruby23
|
spinach 2 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 3 10 ruby23: *spinach-knapsack-ruby23
|
spinach 3 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 4 10 ruby23: *spinach-knapsack-ruby23
|
spinach 4 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 5 10 ruby23: *spinach-knapsack-ruby23
|
spinach 5 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 6 10 ruby23: *spinach-knapsack-ruby23
|
spinach 6 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 7 10 ruby23: *spinach-knapsack-ruby23
|
spinach 7 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 8 10 ruby23: *spinach-knapsack-ruby23
|
spinach 8 10 ruby21: *spinach-knapsack-ruby21
|
||||||
spinach 9 10 ruby23: *spinach-knapsack-ruby23
|
spinach 9 10 ruby21: *spinach-knapsack-ruby21
|
||||||
|
|
||||||
# Other generic tests
|
# Other generic tests
|
||||||
|
|
||||||
.static-analyses-variables: &static-analyses-variables
|
.ruby-static-analysis: &ruby-static-analysis
|
||||||
variables:
|
variables:
|
||||||
SIMPLECOV: "false"
|
SIMPLECOV: "false"
|
||||||
USE_DB: "false"
|
USE_DB: "false"
|
||||||
USE_BUNDLE_INSTALL: "true"
|
USE_BUNDLE_INSTALL: "true"
|
||||||
|
|
||||||
.exec: &exec
|
.exec: &exec
|
||||||
<<: *static-analyses-variables
|
<<: *ruby-static-analysis
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- bundle exec $CI_BUILD_NAME
|
- bundle exec $CI_BUILD_NAME
|
||||||
|
@ -216,20 +223,54 @@ teaspoon:
|
||||||
stage: test
|
stage: test
|
||||||
<<: *use-db
|
<<: *use-db
|
||||||
script:
|
script:
|
||||||
|
- curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
|
||||||
|
- apt-get install --assume-yes nodejs
|
||||||
|
- npm install --global istanbul
|
||||||
- teaspoon
|
- teaspoon
|
||||||
|
artifacts:
|
||||||
|
name: coverage-javascript
|
||||||
|
expire_in: 31d
|
||||||
|
paths:
|
||||||
|
- coverage-javascript/default/
|
||||||
|
|
||||||
|
lint-doc:
|
||||||
|
stage: test
|
||||||
|
image: "phusion/baseimage:latest"
|
||||||
|
before_script: []
|
||||||
|
script:
|
||||||
|
- scripts/lint-doc.sh
|
||||||
|
|
||||||
bundler:audit:
|
bundler:audit:
|
||||||
stage: test
|
stage: test
|
||||||
<<: *static-analyses-variables
|
<<: *ruby-static-analysis
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
script:
|
script:
|
||||||
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
|
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
stage: post-test
|
||||||
|
services: []
|
||||||
|
variables:
|
||||||
|
USE_DB: "false"
|
||||||
|
USE_BUNDLE_INSTALL: "true"
|
||||||
|
script:
|
||||||
|
- bundle exec scripts/merge-simplecov
|
||||||
|
artifacts:
|
||||||
|
name: coverage
|
||||||
|
expire_in: 31d
|
||||||
|
paths:
|
||||||
|
- coverage/index.html
|
||||||
|
- coverage/assets/
|
||||||
|
|
||||||
|
|
||||||
# Notify slack in the end
|
# Notify slack in the end
|
||||||
|
|
||||||
notify:slack:
|
notify:slack:
|
||||||
stage: post-test
|
stage: post-test
|
||||||
|
variables:
|
||||||
|
USE_DB: "false"
|
||||||
|
USE_BUNDLE_INSTALL: "false"
|
||||||
script:
|
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>"
|
- ./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>"
|
||||||
when: on_failure
|
when: on_failure
|
||||||
|
@ -238,3 +279,20 @@ notify:slack:
|
||||||
- tags@gitlab-org/gitlab-ce
|
- tags@gitlab-org/gitlab-ce
|
||||||
- master@gitlab-org/gitlab-ee
|
- master@gitlab-org/gitlab-ee
|
||||||
- tags@gitlab-org/gitlab-ee
|
- tags@gitlab-org/gitlab-ee
|
||||||
|
|
||||||
|
pages:
|
||||||
|
before_script: []
|
||||||
|
stage: pages
|
||||||
|
dependencies:
|
||||||
|
- coverage
|
||||||
|
- teaspoon
|
||||||
|
script:
|
||||||
|
- mv public/ .public/
|
||||||
|
- mkdir public/
|
||||||
|
- mv coverage public/coverage-ruby
|
||||||
|
- mv coverage-javascript/default/ public/coverage-javascript/
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- public
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
35
.mailmap
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#
|
||||||
|
# This list is used by git-shortlog to make contributions from the
|
||||||
|
# same person appearing to be so.
|
||||||
|
#
|
||||||
|
|
||||||
|
Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@archlinux.gr>
|
||||||
|
Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@users.noreply.github.com>
|
||||||
|
Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dmitriy.zaporozhets@gmail.com>
|
||||||
|
Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dzaporozhets@sphereconsultinginc.com>
|
||||||
|
Douwe Maan <douwe@gitlab.com> <douwe@selenight.nl>
|
||||||
|
Douwe Maan <douwe@gitlab.com> <me@douwe.me>
|
||||||
|
Grzegorz Bizon <grzegorz@gitlab.com> <grzegorz.bizon@ntsn.pl>
|
||||||
|
Grzegorz Bizon <grzegorz@gitlab.com> <grzesiek.bizon@gmail.com>
|
||||||
|
Jacob Vosmaer <jacob@gitlab.com> <contact@jacobvosmaer.nl>
|
||||||
|
Jacob Vosmaer <jacob@gitlab.com> Jacob Vosmaer (GitLab) <jacob@gitlab.com>
|
||||||
|
Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MacBook-Pro.local>
|
||||||
|
Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MBP.fios-router.home>
|
||||||
|
Jacob Schatz <jschatz@gitlab.com> <jschatz1@gmail.com>
|
||||||
|
James Lopez <james@jameslopez.es> <james@gitlab.com>
|
||||||
|
James Lopez <james@jameslopez.es> <james.lopez@vodafone.com>
|
||||||
|
Kamil Trzciński <kamil@gitlab.com> <ayufan@ayufan.eu>
|
||||||
|
Marin Jankovski <maxlazio@gmail.com> <marin@gitlab.com>
|
||||||
|
Phil Hughes <me@iamphill.com> <theephil@gmail.com>
|
||||||
|
Rémy Coutable <remy@rymai.me> <remy@gitlab.com>
|
||||||
|
Robert Schilling <rschilling@student.tugraz.at> <Razer6@users.noreply.github.com>
|
||||||
|
Robert Schilling <rschilling@student.tugraz.at> <schilling.ro@gmail.com>
|
||||||
|
Robert Speicher <robert@gitlab.com> <rspeicher@gmail.com>
|
||||||
|
Stan Hu <stanhu@gmail.com> <stanhu@alum.mit.edu>
|
||||||
|
Stan Hu <stanhu@gmail.com> <stanhu@packetzoom.com>
|
||||||
|
Stan Hu <stanhu@gmail.com> <stanhu@users.noreply.github.com>
|
||||||
|
Stan Hu <stanhu@gmail.com> stanhu <stanhu@gmail.com>
|
||||||
|
Sytse Sijbrandij <sytse@gitlab.com> <sytse+admin@gitlab.com>
|
||||||
|
Sytse Sijbrandij <sytse@gitlab.com> <sytse@dosire.com>
|
||||||
|
Sytse Sijbrandij <sytse@gitlab.com> <sytses@gmail.com>
|
||||||
|
Sytse Sijbrandij <sytse@gitlab.com> dosire <sytse@gitlab.com>
|
25
.rubocop.yml
|
@ -149,19 +149,19 @@ Style/EmptyLinesAroundAccessModifier:
|
||||||
|
|
||||||
# Keeps track of empty lines around block bodies.
|
# Keeps track of empty lines around block bodies.
|
||||||
Style/EmptyLinesAroundBlockBody:
|
Style/EmptyLinesAroundBlockBody:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
|
|
||||||
# Keeps track of empty lines around class bodies.
|
# Keeps track of empty lines around class bodies.
|
||||||
Style/EmptyLinesAroundClassBody:
|
Style/EmptyLinesAroundClassBody:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
|
|
||||||
# Keeps track of empty lines around module bodies.
|
# Keeps track of empty lines around module bodies.
|
||||||
Style/EmptyLinesAroundModuleBody:
|
Style/EmptyLinesAroundModuleBody:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
|
|
||||||
# Keeps track of empty lines around method bodies.
|
# Keeps track of empty lines around method bodies.
|
||||||
Style/EmptyLinesAroundMethodBody:
|
Style/EmptyLinesAroundMethodBody:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
|
|
||||||
# Avoid the use of END blocks.
|
# Avoid the use of END blocks.
|
||||||
Style/EndBlock:
|
Style/EndBlock:
|
||||||
|
@ -291,6 +291,10 @@ Style/MultilineMethodDefinitionBraceLayout:
|
||||||
Style/MultilineOperationIndentation:
|
Style/MultilineOperationIndentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
|
||||||
|
Style/MultilineTernaryOperator:
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
# Favor unless over if for negative conditions (or control flow or).
|
# Favor unless over if for negative conditions (or control flow or).
|
||||||
Style/NegatedIf:
|
Style/NegatedIf:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
@ -369,6 +373,10 @@ Style/SpaceAfterNot:
|
||||||
Style/SpaceAfterSemicolon:
|
Style/SpaceAfterSemicolon:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
|
# Use space around equals in parameter default
|
||||||
|
Style/SpaceAroundEqualsInParameterDefault:
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
# Use a space around keywords if appropriate.
|
# Use a space around keywords if appropriate.
|
||||||
Style/SpaceAroundKeyword:
|
Style/SpaceAroundKeyword:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
@ -506,6 +514,15 @@ Metrics/PerceivedComplexity:
|
||||||
|
|
||||||
#################### Lint ################################
|
#################### Lint ################################
|
||||||
|
|
||||||
|
# Checks for useless access modifiers.
|
||||||
|
Lint/UselessAccessModifier:
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
|
# Checks for attempts to use `private` or `protected` to set the visibility
|
||||||
|
# of a class method, which does not work.
|
||||||
|
Lint/IneffectiveAccessModifier:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
# Checks for ambiguous operators in the first argument of a method invocation
|
# Checks for ambiguous operators in the first argument of a method invocation
|
||||||
# without parentheses.
|
# without parentheses.
|
||||||
Lint/AmbiguousOperator:
|
Lint/AmbiguousOperator:
|
||||||
|
|
|
@ -19,10 +19,6 @@ Lint/AssignmentInCondition:
|
||||||
Lint/HandleExceptions:
|
Lint/HandleExceptions:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Offense count: 21
|
|
||||||
Lint/IneffectiveAccessModifier:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Offense count: 2
|
# Offense count: 2
|
||||||
Lint/Loop:
|
Lint/Loop:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
@ -48,10 +44,6 @@ Lint/UnusedBlockArgument:
|
||||||
Lint/UnusedMethodArgument:
|
Lint/UnusedMethodArgument:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Offense count: 11
|
|
||||||
Lint/UselessAccessModifier:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Offense count: 12
|
# Offense count: 12
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
Performance/PushSplat:
|
Performance/PushSplat:
|
||||||
|
@ -226,10 +218,6 @@ Style/LineEndConcatenation:
|
||||||
Style/MethodCallParentheses:
|
Style/MethodCallParentheses:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Offense count: 3
|
|
||||||
Style/MultilineTernaryOperator:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Offense count: 62
|
# Offense count: 62
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
Style/MutableConstant:
|
Style/MutableConstant:
|
||||||
|
@ -351,13 +339,6 @@ Style/SingleLineBlockParams:
|
||||||
Style/SingleLineMethods:
|
Style/SingleLineMethods:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Offense count: 14
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
||||||
# SupportedStyles: space, no_space
|
|
||||||
Style/SpaceAroundEqualsInParameterDefault:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Offense count: 119
|
# Offense count: 119
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.1.8
|
2.3.1
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
# .simplecov
|
|
||||||
SimpleCov.start 'rails' do
|
|
||||||
merge_timeout 3600
|
|
||||||
end
|
|
184
CHANGELOG
|
@ -1,5 +1,183 @@
|
||||||
Please view this file on the master branch, on stable branches it's out of date.
|
Please view this file on the master branch, on stable branches it's out of date.
|
||||||
|
|
||||||
|
v 8.11.3
|
||||||
|
- Don't show resolve conflicts link before MR status is updated
|
||||||
|
- Fix IE11 fork button bug !598
|
||||||
|
- Don't prevent viewing the MR when git refs for conflicts can't be found on disk
|
||||||
|
- Allow system info page to handle case where info is unavailable
|
||||||
|
- Fix external issue tracker "Issues" link leading to 404s
|
||||||
|
- Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters
|
||||||
|
- Label list shows all issues (opened or closed) with that label
|
||||||
|
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
|
||||||
|
|
||||||
|
v 8.11.2
|
||||||
|
- Show "Create Merge Request" widget for push events to fork projects on the source project. !5978
|
||||||
|
- Use gitlab-workhorse 0.7.11 !5983
|
||||||
|
- Does not halt the GitHub import process when an error occurs. !5763
|
||||||
|
- Fix file links on project page when default view is Files !5933
|
||||||
|
- Fixed enter key in search input not working !5888
|
||||||
|
|
||||||
|
v 8.11.1
|
||||||
|
- Pulled due to packaging error.
|
||||||
|
|
||||||
|
v 8.11.0
|
||||||
|
- Use test coverage value from the latest successful pipeline in badge. !5862
|
||||||
|
- Add test coverage report badge. !5708
|
||||||
|
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
|
||||||
|
- Add Koding (online IDE) integration
|
||||||
|
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
|
||||||
|
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
|
||||||
|
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
|
||||||
|
- Fix adding line comments on the initial commit to a repo !5900
|
||||||
|
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
|
||||||
|
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
|
||||||
|
- Update to Ruby 2.3.1. !4948
|
||||||
|
- Add Issues Board !5548
|
||||||
|
- Allow resolving merge conflicts in the UI !5479
|
||||||
|
- Improve diff performance by eliminating redundant checks for text blobs
|
||||||
|
- Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi)
|
||||||
|
- Convert switch icon into icon font (ClemMakesApps)
|
||||||
|
- API: Endpoints for enabling and disabling deploy keys
|
||||||
|
- API: List access requests, request access, approve, and deny access requests to a project or a group. !4833
|
||||||
|
- Use long options for curl examples in documentation !5703 (winniehell)
|
||||||
|
- Added tooltip listing label names to the labels value in the collapsed issuable sidebar
|
||||||
|
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
|
||||||
|
- GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository
|
||||||
|
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
|
||||||
|
- Allow naming U2F devices !5833
|
||||||
|
- Ignore URLs starting with // in Markdown links !5677 (winniehell)
|
||||||
|
- Fix CI status icon link underline (ClemMakesApps)
|
||||||
|
- The Repository class is now instrumented
|
||||||
|
- Fix commit mention font inconsistency (ClemMakesApps)
|
||||||
|
- Do not escape URI when extracting path !5878 (winniehell)
|
||||||
|
- Fix filter label tooltip HTML rendering (ClemMakesApps)
|
||||||
|
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive
|
||||||
|
- Expand commit message width in repo view (ClemMakesApps)
|
||||||
|
- Cache highlighted diff lines for merge requests
|
||||||
|
- Pre-create all builds for a Pipeline when the new Pipeline is created !5295
|
||||||
|
- Allow merge request diff notes and discussions to be explicitly marked as resolved
|
||||||
|
- API: Add deployment endpoints
|
||||||
|
- API: Add Play endpoint on Builds
|
||||||
|
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
|
||||||
|
- Show wall clock time when showing a pipeline. !5734
|
||||||
|
- Show member roles to all users on members page
|
||||||
|
- Project.visible_to_user is instrumented again
|
||||||
|
- Fix awardable button mutuality loading spinners (ClemMakesApps)
|
||||||
|
- Sort todos by date and priority
|
||||||
|
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
|
||||||
|
- Optimize maximum user access level lookup in loading of notes
|
||||||
|
- Send notification emails to users newly mentioned in issue and MR edits !5800
|
||||||
|
- Add "No one can push" as an option for protected branches. !5081
|
||||||
|
- Improve performance of AutolinkFilter#text_parse by using XPath
|
||||||
|
- Add experimental Redis Sentinel support !1877
|
||||||
|
- Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB
|
||||||
|
- Fix branches page dropdown sort initial state (ClemMakesApps)
|
||||||
|
- Environments have an url to link to
|
||||||
|
- Various redundant database indexes have been removed
|
||||||
|
- Update `timeago` plugin to use multiple string/locale settings
|
||||||
|
- Remove unused images (ClemMakesApps)
|
||||||
|
- Get issue and merge request description templates from repositories
|
||||||
|
- Add hover state to todos !5361 (winniehell)
|
||||||
|
- Fix icon alignment of star and fork buttons !5451 (winniehell)
|
||||||
|
- Enforce 2FA restrictions on API authentication endpoints !5820
|
||||||
|
- Limit git rev-list output count to one in forced push check
|
||||||
|
- Show deployment status on merge requests with external URLs
|
||||||
|
- Clean up unused routes (Josef Strzibny)
|
||||||
|
- Fix issue on empty project to allow developers to only push to protected branches if given permission
|
||||||
|
- API: Add enpoints for pipelines
|
||||||
|
- Add green outline to New Branch button. !5447 (winniehell)
|
||||||
|
- Optimize generating of cache keys for issues and notes
|
||||||
|
- Fix repository push email formatting in Outlook
|
||||||
|
- Improve performance of syntax highlighting Markdown code blocks
|
||||||
|
- Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects
|
||||||
|
- Remove delay when hitting "Reply..." button on page with a lot of discussions
|
||||||
|
- Retrieve rendered HTML from cache in one request
|
||||||
|
- Fix renaming repository when name contains invalid chararacters under project settings
|
||||||
|
- Upgrade Grape from 0.13.0 to 0.15.0. !4601
|
||||||
|
- Trigram indexes for the "ci_runners" table have been removed to speed up UPDATE queries
|
||||||
|
- Fix devise deprecation warnings.
|
||||||
|
- Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764
|
||||||
|
- Update version_sorter and use new interface for faster tag sorting
|
||||||
|
- Optimize checking if a user has read access to a list of issues !5370
|
||||||
|
- Store all DB secrets in secrets.yml, under descriptive names !5274
|
||||||
|
- Fix syntax highlighting in file editor
|
||||||
|
- Support slash commands in issue and merge request descriptions as well as comments. !5021
|
||||||
|
- Nokogiri's various parsing methods are now instrumented
|
||||||
|
- Add archived badge to project list !5798
|
||||||
|
- Add simple identifier to public SSH keys (muteor)
|
||||||
|
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
|
||||||
|
- Fix filter input alignment (ClemMakesApps)
|
||||||
|
- Include old revision in merge request update hooks (Ben Boeckel)
|
||||||
|
- Add build event color in HipChat messages (David Eisner)
|
||||||
|
- Make fork counter always clickable. !5463 (winniehell)
|
||||||
|
- Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon)
|
||||||
|
- Gitlab::Highlight is now instrumented
|
||||||
|
- All created issues, API or WebUI, can be submitted to Akismet for spam check !5333
|
||||||
|
- Allow users to import cross-repository pull requests from GitHub
|
||||||
|
- The overhead of instrumented method calls has been reduced
|
||||||
|
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
|
||||||
|
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
|
||||||
|
- Add pipeline events hook
|
||||||
|
- Bump gitlab_git to speedup DiffCollection iterations
|
||||||
|
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
|
||||||
|
- Make branches sortable without push permission !5462 (winniehell)
|
||||||
|
- Check for Ci::Build artifacts at database level on pipeline partial
|
||||||
|
- Convert image diff background image to CSS (ClemMakesApps)
|
||||||
|
- Remove unnecessary index_projects_on_builds_enabled index from the projects table
|
||||||
|
- Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
|
||||||
|
- Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
|
||||||
|
- Fix search for notes which belongs to deleted objects
|
||||||
|
- Allow Akismet to be trained by submitting issues as spam or ham !5538
|
||||||
|
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
|
||||||
|
- Allow branch names ending with .json for graph and network page !5579 (winniehell)
|
||||||
|
- Add the `sprockets-es6` gem
|
||||||
|
- Improve OAuth2 client documentation (muteor)
|
||||||
|
- Fix diff comments inverted toggle bug (ClemMakesApps)
|
||||||
|
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
|
||||||
|
- Profile requests when a header is passed
|
||||||
|
- Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab.
|
||||||
|
- Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
|
||||||
|
- Add commit stats in commit api. !5517 (dixpac)
|
||||||
|
- Add CI configuration button on project page
|
||||||
|
- Fix merge request new view not changing code view rendering style
|
||||||
|
- edit_blob_link will use blob passed onto the options parameter
|
||||||
|
- Make error pages responsive (Takuya Noguchi)
|
||||||
|
- The performance of the project dropdown used for moving issues has been improved
|
||||||
|
- Fix skip_repo parameter being ignored when destroying a namespace
|
||||||
|
- Add all builds into stage/job dropdowns on builds page
|
||||||
|
- Change requests_profiles resource constraint to catch virtually any file
|
||||||
|
- Bump gitlab_git to lazy load compare commits
|
||||||
|
- Reduce number of queries made for merge_requests/:id/diffs
|
||||||
|
- Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski)
|
||||||
|
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
|
||||||
|
- Fix bug where destroying a namespace would not always destroy projects
|
||||||
|
- Fix RequestProfiler::Middleware error when code is reloaded in development
|
||||||
|
- Allow horizontal scrolling of code blocks in issue body
|
||||||
|
- Catch what warden might throw when profiling requests to re-throw it
|
||||||
|
- Avoid commit lookup on diff_helper passing existing local variable to the helper method
|
||||||
|
- Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac)
|
||||||
|
- Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker
|
||||||
|
- Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
|
||||||
|
- Adds support for pending invitation project members importing projects
|
||||||
|
- Add pipeline visualization/graph on pipeline page
|
||||||
|
- Update devise initializer to turn on changed password notification emails. !5648 (tombell)
|
||||||
|
- Avoid to show the original password field when password is automatically set. !5712 (duduribeiro)
|
||||||
|
- Fix importing GitLab projects with an invalid MR source project
|
||||||
|
- Sort folders with submodules in Files view !5521
|
||||||
|
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
|
||||||
|
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
|
||||||
|
- Add pipelines tab to merge requests
|
||||||
|
- Fix notification_service argument error of declined invitation emails
|
||||||
|
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter
|
||||||
|
- Speed up todos queries by limiting the projects set we join with
|
||||||
|
- Ensure file editing in UI does not overwrite commited changes without warning user
|
||||||
|
- Eliminate unneeded calls to Repository#blob_at when listing commits with no path
|
||||||
|
- Update gitlab_git gem to 10.4.7
|
||||||
|
- Simplify SQL queries of marking a todo as done
|
||||||
|
|
||||||
|
v 8.10.6 (unreleased)
|
||||||
|
- Fix import/export configuration missing some included attributes
|
||||||
|
|
||||||
v 8.10.5
|
v 8.10.5
|
||||||
- Add a data migration to fix some missing timestamps in the members table. !5670
|
- Add a data migration to fix some missing timestamps in the members table. !5670
|
||||||
- Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706
|
- Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706
|
||||||
|
@ -20,6 +198,7 @@ v 8.10.3
|
||||||
- Fix importer for GitHub Pull Requests when a branch was removed. !5573
|
- Fix importer for GitHub Pull Requests when a branch was removed. !5573
|
||||||
- Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584
|
- Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584
|
||||||
- Trim extra displayed carriage returns in diffs and files with CRLFs. !5588
|
- Trim extra displayed carriage returns in diffs and files with CRLFs. !5588
|
||||||
|
- Fix label already exist error message in the right sidebar.
|
||||||
|
|
||||||
v 8.10.2
|
v 8.10.2
|
||||||
- User can now search branches by name. !5144
|
- User can now search branches by name. !5144
|
||||||
|
@ -98,6 +277,9 @@ v 8.10.0
|
||||||
- Fix check for New Branch button on Issue page. !4630 (winniehell)
|
- Fix check for New Branch button on Issue page. !4630 (winniehell)
|
||||||
- Fix GFM autocomplete not working on wiki pages
|
- Fix GFM autocomplete not working on wiki pages
|
||||||
- Fixed enter key not triggering click on first row when searching in a dropdown
|
- Fixed enter key not triggering click on first row when searching in a dropdown
|
||||||
|
- Updated dropdowns in issuable form to use new GitLab dropdown style
|
||||||
|
- Make images fit to the size of the viewport !4810
|
||||||
|
- Fix check for New Branch button on Issue page !4630 (winniehell)
|
||||||
- Fix MR-auto-close text added to description. !4836
|
- Fix MR-auto-close text added to description. !4836
|
||||||
- Support U2F devices in Firefox. !5177
|
- Support U2F devices in Firefox. !5177
|
||||||
- Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
|
- Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
|
||||||
|
@ -115,6 +297,7 @@ v 8.10.0
|
||||||
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020
|
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020
|
||||||
- Updated project header design
|
- Updated project header design
|
||||||
- Issuable collapsed assignee tooltip is now the users name
|
- Issuable collapsed assignee tooltip is now the users name
|
||||||
|
- Fix compare view not changing code view rendering style
|
||||||
- Exclude email check from the standard health check
|
- Exclude email check from the standard health check
|
||||||
- Updated layout for Projects, Groups, Users on Admin area. !4424
|
- Updated layout for Projects, Groups, Users on Admin area. !4424
|
||||||
- Fix changing issue state columns in milestone view
|
- Fix changing issue state columns in milestone view
|
||||||
|
@ -159,6 +342,7 @@ v 8.10.0
|
||||||
- Fix new snippet style bug (elliotec)
|
- Fix new snippet style bug (elliotec)
|
||||||
- Instrument Rinku usage
|
- Instrument Rinku usage
|
||||||
- Be explicit to define merge request discussion variables
|
- Be explicit to define merge request discussion variables
|
||||||
|
- Use cache for todos counter calling TodoService
|
||||||
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
|
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
|
||||||
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
|
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
|
||||||
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
|
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
|
||||||
|
|
|
@ -41,6 +41,8 @@ abbreviation.
|
||||||
If you have read this guide and want to know how the GitLab [core team]
|
If you have read this guide and want to know how the GitLab [core team]
|
||||||
operates please see [the GitLab contributing process](PROCESS.md).
|
operates please see [the GitLab contributing process](PROCESS.md).
|
||||||
|
|
||||||
|
- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
|
||||||
|
|
||||||
## Contributor license agreement
|
## Contributor license agreement
|
||||||
|
|
||||||
By submitting code as an individual you agree to the
|
By submitting code as an individual you agree to the
|
||||||
|
@ -334,6 +336,10 @@ request is as follows:
|
||||||
1. If your code creates new files on disk please read the
|
1. If your code creates new files on disk please read the
|
||||||
[shared files guidelines](doc/development/shared_files.md).
|
[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.
|
||||||
|
1. For more complex migrations, write tests.
|
||||||
|
|
||||||
The **official merge window** is in the beginning of the month from the 1st to
|
The **official merge window** is in the beginning of the month from the 1st to
|
||||||
the 7th day of the month. This is the best time to submit an MR and get
|
the 7th day of the month. This is the best time to submit an MR and get
|
||||||
|
@ -459,8 +465,10 @@ merge request:
|
||||||
- multi-line method chaining style **Option B**: dot `.` on previous line
|
- multi-line method chaining style **Option B**: dot `.` on previous line
|
||||||
- string literal quoting style **Option A**: single quoted by default
|
- string literal quoting style **Option A**: single quoted by default
|
||||||
1. [Rails](https://github.com/bbatsov/rails-style-guide)
|
1. [Rails](https://github.com/bbatsov/rails-style-guide)
|
||||||
|
1. [Newlines styleguide][newlines-styleguide]
|
||||||
1. [Testing](doc/development/testing.md)
|
1. [Testing](doc/development/testing.md)
|
||||||
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
|
1. [JavaScript (ES6)](https://github.com/airbnb/javascript)
|
||||||
|
1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5)
|
||||||
1. [SCSS styleguide][scss-styleguide]
|
1. [SCSS styleguide][scss-styleguide]
|
||||||
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
|
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
|
||||||
contributors to enhance security
|
contributors to enhance security
|
||||||
|
@ -530,6 +538,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
||||||
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
|
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
|
||||||
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
|
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
|
||||||
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
|
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
|
||||||
|
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
|
||||||
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
|
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
|
||||||
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
|
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
|
||||||
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
|
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
3.2.1
|
3.4.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.7.8
|
0.7.11
|
||||||
|
|
36
Gemfile
|
@ -1,6 +1,6 @@
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'rails', '4.2.7'
|
gem 'rails', '4.2.7.1'
|
||||||
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
||||||
|
|
||||||
# Responders respond_to and respond_with
|
# Responders respond_to and respond_with
|
||||||
|
@ -9,6 +9,7 @@ gem 'responders', '~> 2.0'
|
||||||
# Specify a sprockets version due to increased performance
|
# Specify a sprockets version due to increased performance
|
||||||
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
|
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
|
||||||
gem 'sprockets', '~> 3.6.0'
|
gem 'sprockets', '~> 3.6.0'
|
||||||
|
gem 'sprockets-es6'
|
||||||
|
|
||||||
# Default values for AR models
|
# Default values for AR models
|
||||||
gem 'default_value_for', '~> 3.0.0'
|
gem 'default_value_for', '~> 3.0.0'
|
||||||
|
@ -19,7 +20,7 @@ gem 'pg', '~> 0.18.2', group: :postgres
|
||||||
|
|
||||||
# Authentication libraries
|
# Authentication libraries
|
||||||
gem 'devise', '~> 4.0'
|
gem 'devise', '~> 4.0'
|
||||||
gem 'doorkeeper', '~> 4.0'
|
gem 'doorkeeper', '~> 4.2.0'
|
||||||
gem 'omniauth', '~> 1.3.1'
|
gem 'omniauth', '~> 1.3.1'
|
||||||
gem 'omniauth-auth0', '~> 1.4.1'
|
gem 'omniauth-auth0', '~> 1.4.1'
|
||||||
gem 'omniauth-azure-oauth2', '~> 0.0.6'
|
gem 'omniauth-azure-oauth2', '~> 0.0.6'
|
||||||
|
@ -52,7 +53,7 @@ gem 'browser', '~> 2.2'
|
||||||
|
|
||||||
# Extracting information from a git repository
|
# Extracting information from a git repository
|
||||||
# Provide access to Gitlab::Git library
|
# Provide access to Gitlab::Git library
|
||||||
gem 'gitlab_git', '~> 10.3.2'
|
gem 'gitlab_git', '~> 10.4.7'
|
||||||
|
|
||||||
# LDAP Auth
|
# LDAP Auth
|
||||||
# GitLab fork with several improvements to original library. For full list of changes
|
# GitLab fork with several improvements to original library. For full list of changes
|
||||||
|
@ -68,7 +69,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
|
||||||
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
|
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
|
||||||
|
|
||||||
# API
|
# API
|
||||||
gem 'grape', '~> 0.13.0'
|
gem 'grape', '~> 0.15.0'
|
||||||
gem 'grape-entity', '~> 0.4.2'
|
gem 'grape-entity', '~> 0.4.2'
|
||||||
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
|
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
|
||||||
gem 'kaminari', '~> 0.17.0'
|
gem 'kaminari', '~> 0.17.0'
|
||||||
|
|
||||||
# HAML
|
# HAML
|
||||||
gem 'hamlit', '~> 2.5'
|
gem 'hamlit', '~> 2.6.1'
|
||||||
|
|
||||||
# Files attachments
|
# Files attachments
|
||||||
gem 'carrierwave', '~> 0.10.0'
|
gem 'carrierwave', '~> 0.10.0'
|
||||||
|
@ -153,7 +154,7 @@ gem 'settingslogic', '~> 2.0.9'
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
|
|
||||||
gem 'version_sorter', '~> 2.0.0'
|
gem 'version_sorter', '~> 2.1.0'
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
gem 'redis-rails', '~> 4.0.0'
|
gem 'redis-rails', '~> 4.0.0'
|
||||||
|
@ -162,9 +163,6 @@ gem 'redis-rails', '~> 4.0.0'
|
||||||
gem 'redis', '~> 3.2'
|
gem 'redis', '~> 3.2'
|
||||||
gem 'connection_pool', '~> 2.0'
|
gem 'connection_pool', '~> 2.0'
|
||||||
|
|
||||||
# Campfire integration
|
|
||||||
gem 'tinder', '~> 1.10.0'
|
|
||||||
|
|
||||||
# HipChat integration
|
# HipChat integration
|
||||||
gem 'hipchat', '~> 1.5.0'
|
gem 'hipchat', '~> 1.5.0'
|
||||||
|
|
||||||
|
@ -203,7 +201,7 @@ gem 'licensee', '~> 8.0.0'
|
||||||
gem 'rack-attack', '~> 4.3.1'
|
gem 'rack-attack', '~> 4.3.1'
|
||||||
|
|
||||||
# Ace editor
|
# Ace editor
|
||||||
gem 'ace-rails-ap', '~> 4.0.2'
|
gem 'ace-rails-ap', '~> 4.1.0'
|
||||||
|
|
||||||
# Keyboard shortcuts
|
# Keyboard shortcuts
|
||||||
gem 'mousetrap-rails', '~> 1.4.6'
|
gem 'mousetrap-rails', '~> 1.4.6'
|
||||||
|
@ -211,7 +209,8 @@ gem 'mousetrap-rails', '~> 1.4.6'
|
||||||
# Detect and convert string character encoding
|
# Detect and convert string character encoding
|
||||||
gem 'charlock_holmes', '~> 0.7.3'
|
gem 'charlock_holmes', '~> 0.7.3'
|
||||||
|
|
||||||
# Parse duration
|
# Parse time & duration
|
||||||
|
gem 'chronic', '~> 0.10.2'
|
||||||
gem 'chronic_duration', '~> 0.10.6'
|
gem 'chronic_duration', '~> 0.10.6'
|
||||||
|
|
||||||
gem 'sass-rails', '~> 5.0.0'
|
gem 'sass-rails', '~> 5.0.0'
|
||||||
|
@ -224,7 +223,7 @@ gem 'addressable', '~> 2.3.8'
|
||||||
gem 'bootstrap-sass', '~> 3.3.0'
|
gem 'bootstrap-sass', '~> 3.3.0'
|
||||||
gem 'font-awesome-rails', '~> 4.6.1'
|
gem 'font-awesome-rails', '~> 4.6.1'
|
||||||
gem 'gemojione', '~> 3.0'
|
gem 'gemojione', '~> 3.0'
|
||||||
gem 'gon', '~> 6.0.1'
|
gem 'gon', '~> 6.1.0'
|
||||||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||||
gem 'jquery-rails', '~> 4.1.0'
|
gem 'jquery-rails', '~> 4.1.0'
|
||||||
gem 'jquery-ui-rails', '~> 5.0.0'
|
gem 'jquery-ui-rails', '~> 5.0.0'
|
||||||
|
@ -252,7 +251,7 @@ group :development do
|
||||||
|
|
||||||
gem 'letter_opener_web', '~> 1.3.0'
|
gem 'letter_opener_web', '~> 1.3.0'
|
||||||
gem 'rerun', '~> 0.11.0'
|
gem 'rerun', '~> 0.11.0'
|
||||||
gem 'bullet', '~> 5.0.0', require: false
|
gem 'bullet', '~> 5.2.0', require: false
|
||||||
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
|
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
|
||||||
gem 'web-console', '~> 2.0'
|
gem 'web-console', '~> 2.0'
|
||||||
|
|
||||||
|
@ -274,7 +273,7 @@ group :development, :test do
|
||||||
gem 'awesome_print', '~> 1.2.0', require: false
|
gem 'awesome_print', '~> 1.2.0', require: false
|
||||||
gem 'fuubar', '~> 2.0.0'
|
gem 'fuubar', '~> 2.0.0'
|
||||||
|
|
||||||
gem 'database_cleaner', '~> 1.4.0'
|
gem 'database_cleaner', '~> 1.5.0'
|
||||||
gem 'factory_girl_rails', '~> 4.6.0'
|
gem 'factory_girl_rails', '~> 4.6.0'
|
||||||
gem 'rspec-rails', '~> 3.5.0'
|
gem 'rspec-rails', '~> 3.5.0'
|
||||||
gem 'rspec-retry', '~> 0.4.5'
|
gem 'rspec-retry', '~> 0.4.5'
|
||||||
|
@ -302,7 +301,7 @@ group :development, :test do
|
||||||
gem 'rubocop', '~> 0.41.2', require: false
|
gem 'rubocop', '~> 0.41.2', require: false
|
||||||
gem 'rubocop-rspec', '~> 1.5.0', require: false
|
gem 'rubocop-rspec', '~> 1.5.0', require: false
|
||||||
gem 'scss_lint', '~> 0.47.0', require: false
|
gem 'scss_lint', '~> 0.47.0', require: false
|
||||||
gem 'simplecov', '~> 0.11.0', require: false
|
gem 'simplecov', '0.12.0', require: false
|
||||||
gem 'flog', '~> 4.3.2', require: false
|
gem 'flog', '~> 4.3.2', require: false
|
||||||
gem 'flay', '~> 2.6.1', require: false
|
gem 'flay', '~> 2.6.1', require: false
|
||||||
gem 'bundler-audit', '~> 0.5.0', require: false
|
gem 'bundler-audit', '~> 0.5.0', require: false
|
||||||
|
@ -316,6 +315,7 @@ end
|
||||||
group :test do
|
group :test do
|
||||||
gem 'shoulda-matchers', '~> 2.8.0', require: false
|
gem 'shoulda-matchers', '~> 2.8.0', require: false
|
||||||
gem 'email_spec', '~> 1.6.0'
|
gem 'email_spec', '~> 1.6.0'
|
||||||
|
gem 'json-schema', '~> 2.6.2'
|
||||||
gem 'webmock', '~> 1.21.0'
|
gem 'webmock', '~> 1.21.0'
|
||||||
gem 'test_after_commit', '~> 0.4.2'
|
gem 'test_after_commit', '~> 0.4.2'
|
||||||
gem 'sham_rack', '~> 1.3.6'
|
gem 'sham_rack', '~> 1.3.6'
|
||||||
|
@ -325,7 +325,7 @@ group :production do
|
||||||
gem 'gitlab_meta', '7.0'
|
gem 'gitlab_meta', '7.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'newrelic_rpm', '~> 3.14'
|
gem 'newrelic_rpm', '~> 3.16'
|
||||||
|
|
||||||
gem 'octokit', '~> 4.3.0'
|
gem 'octokit', '~> 4.3.0'
|
||||||
|
|
||||||
|
@ -333,6 +333,8 @@ gem 'mail_room', '~> 0.8'
|
||||||
|
|
||||||
gem 'email_reply_parser', '~> 0.5.8'
|
gem 'email_reply_parser', '~> 0.5.8'
|
||||||
|
|
||||||
|
gem 'ruby-prof', '~> 0.15.9'
|
||||||
|
|
||||||
## CI
|
## CI
|
||||||
gem 'activerecord-session_store', '~> 1.0.0'
|
gem 'activerecord-session_store', '~> 1.0.0'
|
||||||
gem 'nested_form', '~> 0.3.2'
|
gem 'nested_form', '~> 0.3.2'
|
||||||
|
@ -347,5 +349,5 @@ gem 'paranoia', '~> 2.0'
|
||||||
gem 'health_check', '~> 2.1.0'
|
gem 'health_check', '~> 2.1.0'
|
||||||
|
|
||||||
# System information
|
# System information
|
||||||
gem 'vmstat', '~> 2.1.1'
|
gem 'vmstat', '~> 2.2'
|
||||||
gem 'sys-filesystem', '~> 1.1.6'
|
gem 'sys-filesystem', '~> 1.1.6'
|
||||||
|
|
160
Gemfile.lock
|
@ -2,35 +2,35 @@ GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
RedCloth (4.3.2)
|
RedCloth (4.3.2)
|
||||||
ace-rails-ap (4.0.2)
|
ace-rails-ap (4.1.0)
|
||||||
actionmailer (4.2.7)
|
actionmailer (4.2.7.1)
|
||||||
actionpack (= 4.2.7)
|
actionpack (= 4.2.7.1)
|
||||||
actionview (= 4.2.7)
|
actionview (= 4.2.7.1)
|
||||||
activejob (= 4.2.7)
|
activejob (= 4.2.7.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
actionpack (4.2.7)
|
actionpack (4.2.7.1)
|
||||||
actionview (= 4.2.7)
|
actionview (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
rack (~> 1.6)
|
rack (~> 1.6)
|
||||||
rack-test (~> 0.6.2)
|
rack-test (~> 0.6.2)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
actionview (4.2.7)
|
actionview (4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubis (~> 2.7.0)
|
erubis (~> 2.7.0)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
activejob (4.2.7)
|
activejob (4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
globalid (>= 0.3.0)
|
globalid (>= 0.3.0)
|
||||||
activemodel (4.2.7)
|
activemodel (4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
activerecord (4.2.7)
|
activerecord (4.2.7.1)
|
||||||
activemodel (= 4.2.7)
|
activemodel (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
arel (~> 6.0)
|
arel (~> 6.0)
|
||||||
activerecord-session_store (1.0.0)
|
activerecord-session_store (1.0.0)
|
||||||
actionpack (>= 4.0, < 5.1)
|
actionpack (>= 4.0, < 5.1)
|
||||||
|
@ -38,7 +38,7 @@ GEM
|
||||||
multi_json (~> 1.11, >= 1.11.2)
|
multi_json (~> 1.11, >= 1.11.2)
|
||||||
rack (>= 1.5.2, < 3)
|
rack (>= 1.5.2, < 3)
|
||||||
railties (>= 4.0, < 5.1)
|
railties (>= 4.0, < 5.1)
|
||||||
activesupport (4.2.7)
|
activesupport (4.2.7.1)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
json (~> 1.7, >= 1.7.7)
|
json (~> 1.7, >= 1.7.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
|
@ -59,7 +59,7 @@ GEM
|
||||||
oauth2 (~> 1.0)
|
oauth2 (~> 1.0)
|
||||||
asciidoctor (1.5.3)
|
asciidoctor (1.5.3)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
attr_encrypted (3.0.1)
|
attr_encrypted (3.0.3)
|
||||||
encryptor (~> 3.0.0)
|
encryptor (~> 3.0.0)
|
||||||
attr_required (1.0.0)
|
attr_required (1.0.0)
|
||||||
autoprefixer-rails (6.2.3)
|
autoprefixer-rails (6.2.3)
|
||||||
|
@ -85,6 +85,10 @@ GEM
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
faraday_middleware (~> 0.10)
|
faraday_middleware (~> 0.10)
|
||||||
nokogiri (~> 1.6)
|
nokogiri (~> 1.6)
|
||||||
|
babel-source (5.8.35)
|
||||||
|
babel-transpiler (0.7.0)
|
||||||
|
babel-source (>= 4.0, < 6)
|
||||||
|
execjs (~> 2.0)
|
||||||
babosa (1.0.2)
|
babosa (1.0.2)
|
||||||
base32 (0.3.2)
|
base32 (0.3.2)
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
|
@ -100,9 +104,9 @@ GEM
|
||||||
brakeman (3.3.2)
|
brakeman (3.3.2)
|
||||||
browser (2.2.0)
|
browser (2.2.0)
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
bullet (5.0.0)
|
bullet (5.2.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.9.0)
|
uniform_notifier (~> 1.10.0)
|
||||||
bundler-audit (0.5.0)
|
bundler-audit (0.5.0)
|
||||||
bundler (~> 1.2)
|
bundler (~> 1.2)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
|
@ -124,6 +128,7 @@ GEM
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
cause (0.1)
|
cause (0.1)
|
||||||
charlock_holmes (0.7.3)
|
charlock_holmes (0.7.3)
|
||||||
|
chronic (0.10.2)
|
||||||
chronic_duration (0.10.6)
|
chronic_duration (0.10.6)
|
||||||
numerizer (~> 0.1.1)
|
numerizer (~> 0.1.1)
|
||||||
chunky_png (1.3.5)
|
chunky_png (1.3.5)
|
||||||
|
@ -149,11 +154,11 @@ GEM
|
||||||
d3_rails (3.5.11)
|
d3_rails (3.5.11)
|
||||||
railties (>= 3.1.0)
|
railties (>= 3.1.0)
|
||||||
daemons (1.2.3)
|
daemons (1.2.3)
|
||||||
database_cleaner (1.4.1)
|
database_cleaner (1.5.3)
|
||||||
debug_inspector (0.0.2)
|
debug_inspector (0.0.2)
|
||||||
debugger-ruby_core_source (1.3.8)
|
debugger-ruby_core_source (1.3.8)
|
||||||
default_value_for (3.0.1)
|
default_value_for (3.0.2)
|
||||||
activerecord (>= 3.2.0, < 5.0)
|
activerecord (>= 3.2.0, < 5.1)
|
||||||
descendants_tracker (0.0.4)
|
descendants_tracker (0.0.4)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
devise (4.1.1)
|
devise (4.1.1)
|
||||||
|
@ -171,7 +176,7 @@ GEM
|
||||||
diff-lcs (1.2.5)
|
diff-lcs (1.2.5)
|
||||||
diffy (3.0.7)
|
diffy (3.0.7)
|
||||||
docile (1.1.5)
|
docile (1.1.5)
|
||||||
doorkeeper (4.0.0)
|
doorkeeper (4.2.0)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
dropzonejs-rails (0.7.2)
|
dropzonejs-rails (0.7.2)
|
||||||
rails (> 3.1)
|
rails (> 3.1)
|
||||||
|
@ -274,7 +279,7 @@ GEM
|
||||||
diff-lcs (~> 1.1)
|
diff-lcs (~> 1.1)
|
||||||
mime-types (>= 1.16, < 3)
|
mime-types (>= 1.16, < 3)
|
||||||
posix-spawn (~> 0.3)
|
posix-spawn (~> 0.3)
|
||||||
gitlab_git (10.3.2)
|
gitlab_git (10.4.7)
|
||||||
activesupport (~> 4.0)
|
activesupport (~> 4.0)
|
||||||
charlock_holmes (~> 0.7.3)
|
charlock_holmes (~> 0.7.3)
|
||||||
github-linguist (~> 4.7.0)
|
github-linguist (~> 4.7.0)
|
||||||
|
@ -285,7 +290,7 @@ GEM
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.0)
|
||||||
pyu-ruby-sasl (~> 0.0.3.1)
|
pyu-ruby-sasl (~> 0.0.3.1)
|
||||||
rubyntlm (~> 0.3)
|
rubyntlm (~> 0.3)
|
||||||
globalid (0.3.6)
|
globalid (0.3.7)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
gollum-grit_adapter (1.0.1)
|
gollum-grit_adapter (1.0.1)
|
||||||
gitlab-grit (~> 2.7, >= 2.7.1)
|
gitlab-grit (~> 2.7, >= 2.7.1)
|
||||||
|
@ -299,12 +304,12 @@ GEM
|
||||||
gollum-rugged_adapter (0.4.2)
|
gollum-rugged_adapter (0.4.2)
|
||||||
mime-types (>= 1.15)
|
mime-types (>= 1.15)
|
||||||
rugged (~> 0.24.0, >= 0.21.3)
|
rugged (~> 0.24.0, >= 0.21.3)
|
||||||
gon (6.0.1)
|
gon (6.1.0)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
json
|
json
|
||||||
multi_json
|
multi_json
|
||||||
request_store (>= 1.0)
|
request_store (>= 1.0)
|
||||||
grape (0.13.0)
|
grape (0.15.0)
|
||||||
activesupport
|
activesupport
|
||||||
builder
|
builder
|
||||||
hashie (>= 2.1.0)
|
hashie (>= 2.1.0)
|
||||||
|
@ -317,7 +322,7 @@ GEM
|
||||||
grape-entity (0.4.8)
|
grape-entity (0.4.8)
|
||||||
activesupport
|
activesupport
|
||||||
multi_json (>= 1.3.2)
|
multi_json (>= 1.3.2)
|
||||||
hamlit (2.5.0)
|
hamlit (2.6.1)
|
||||||
temple (~> 0.7.6)
|
temple (~> 0.7.6)
|
||||||
thor
|
thor
|
||||||
tilt
|
tilt
|
||||||
|
@ -331,11 +336,10 @@ GEM
|
||||||
activesupport (>= 2)
|
activesupport (>= 2)
|
||||||
nokogiri (~> 1.4)
|
nokogiri (~> 1.4)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http_parser.rb (0.5.3)
|
|
||||||
httparty (0.13.7)
|
httparty (0.13.7)
|
||||||
json (~> 1.8)
|
json (~> 1.8)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
httpclient (2.7.0.1)
|
httpclient (2.8.2)
|
||||||
i18n (0.7.0)
|
i18n (0.7.0)
|
||||||
ice_nine (0.11.1)
|
ice_nine (0.11.1)
|
||||||
influxdb (0.2.3)
|
influxdb (0.2.3)
|
||||||
|
@ -353,6 +357,8 @@ GEM
|
||||||
jquery-ui-rails (5.0.5)
|
jquery-ui-rails (5.0.5)
|
||||||
railties (>= 3.2.16)
|
railties (>= 3.2.16)
|
||||||
json (1.8.3)
|
json (1.8.3)
|
||||||
|
json-schema (2.6.2)
|
||||||
|
addressable (~> 2.3.8)
|
||||||
jwt (1.5.4)
|
jwt (1.5.4)
|
||||||
kaminari (0.17.0)
|
kaminari (0.17.0)
|
||||||
actionpack (>= 3.0.0)
|
actionpack (>= 3.0.0)
|
||||||
|
@ -400,7 +406,7 @@ GEM
|
||||||
nested_form (0.3.2)
|
nested_form (0.3.2)
|
||||||
net-ldap (0.12.1)
|
net-ldap (0.12.1)
|
||||||
net-ssh (3.0.1)
|
net-ssh (3.0.1)
|
||||||
newrelic_rpm (3.14.1.311)
|
newrelic_rpm (3.16.0.318)
|
||||||
nokogiri (1.6.8)
|
nokogiri (1.6.8)
|
||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.1.0)
|
||||||
pkg-config (~> 1.1.7)
|
pkg-config (~> 1.1.7)
|
||||||
|
@ -505,7 +511,7 @@ GEM
|
||||||
rack-cors (0.4.0)
|
rack-cors (0.4.0)
|
||||||
rack-mount (0.8.3)
|
rack-mount (0.8.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rack-oauth2 (1.2.1)
|
rack-oauth2 (1.2.3)
|
||||||
activesupport (>= 2.3)
|
activesupport (>= 2.3)
|
||||||
attr_required (>= 0.0.5)
|
attr_required (>= 0.0.5)
|
||||||
httpclient (>= 2.4)
|
httpclient (>= 2.4)
|
||||||
|
@ -515,16 +521,16 @@ GEM
|
||||||
rack
|
rack
|
||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rails (4.2.7)
|
rails (4.2.7.1)
|
||||||
actionmailer (= 4.2.7)
|
actionmailer (= 4.2.7.1)
|
||||||
actionpack (= 4.2.7)
|
actionpack (= 4.2.7.1)
|
||||||
actionview (= 4.2.7)
|
actionview (= 4.2.7.1)
|
||||||
activejob (= 4.2.7)
|
activejob (= 4.2.7.1)
|
||||||
activemodel (= 4.2.7)
|
activemodel (= 4.2.7.1)
|
||||||
activerecord (= 4.2.7)
|
activerecord (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
bundler (>= 1.3.0, < 2.0)
|
bundler (>= 1.3.0, < 2.0)
|
||||||
railties (= 4.2.7)
|
railties (= 4.2.7.1)
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
rails-deprecated_sanitizer (1.0.3)
|
rails-deprecated_sanitizer (1.0.3)
|
||||||
activesupport (>= 4.2.0.alpha)
|
activesupport (>= 4.2.0.alpha)
|
||||||
|
@ -534,9 +540,9 @@ GEM
|
||||||
rails-deprecated_sanitizer (>= 1.0.1)
|
rails-deprecated_sanitizer (>= 1.0.1)
|
||||||
rails-html-sanitizer (1.0.3)
|
rails-html-sanitizer (1.0.3)
|
||||||
loofah (~> 2.0)
|
loofah (~> 2.0)
|
||||||
railties (4.2.7)
|
railties (4.2.7.1)
|
||||||
actionpack (= 4.2.7)
|
actionpack (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.1.0)
|
rainbow (2.1.0)
|
||||||
|
@ -571,7 +577,7 @@ GEM
|
||||||
redis-store (~> 1.1.0)
|
redis-store (~> 1.1.0)
|
||||||
redis-store (1.1.7)
|
redis-store (1.1.7)
|
||||||
redis (>= 2.2)
|
redis (>= 2.2)
|
||||||
request_store (1.3.0)
|
request_store (1.3.1)
|
||||||
rerun (0.11.0)
|
rerun (0.11.0)
|
||||||
listen (~> 3.0)
|
listen (~> 3.0)
|
||||||
responders (2.1.1)
|
responders (2.1.1)
|
||||||
|
@ -616,6 +622,7 @@ GEM
|
||||||
rubocop (>= 0.40.0)
|
rubocop (>= 0.40.0)
|
||||||
ruby-fogbugz (0.2.1)
|
ruby-fogbugz (0.2.1)
|
||||||
crack (~> 0.4)
|
crack (~> 0.4)
|
||||||
|
ruby-prof (0.15.9)
|
||||||
ruby-progressbar (1.8.1)
|
ruby-progressbar (1.8.1)
|
||||||
ruby-saml (1.3.0)
|
ruby-saml (1.3.0)
|
||||||
nokogiri (>= 1.5.10)
|
nokogiri (>= 1.5.10)
|
||||||
|
@ -667,10 +674,9 @@ GEM
|
||||||
redis-namespace (>= 1.5.2)
|
redis-namespace (>= 1.5.2)
|
||||||
rufus-scheduler (>= 2.0.24)
|
rufus-scheduler (>= 2.0.24)
|
||||||
sidekiq (>= 4.0.0)
|
sidekiq (>= 4.0.0)
|
||||||
simple_oauth (0.1.9)
|
simplecov (0.12.0)
|
||||||
simplecov (0.11.2)
|
|
||||||
docile (~> 1.1.0)
|
docile (~> 1.1.0)
|
||||||
json (~> 1.8)
|
json (>= 1.8, < 3)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.0)
|
simplecov-html (0.10.0)
|
||||||
sinatra (1.4.7)
|
sinatra (1.4.7)
|
||||||
|
@ -700,6 +706,10 @@ GEM
|
||||||
sprockets (3.6.3)
|
sprockets (3.6.3)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
|
sprockets-es6 (0.9.0)
|
||||||
|
babel-source (>= 5.8.11)
|
||||||
|
babel-transpiler
|
||||||
|
sprockets (>= 3.0.0)
|
||||||
sprockets-rails (3.1.1)
|
sprockets-rails (3.1.1)
|
||||||
actionpack (>= 4.0)
|
actionpack (>= 4.0)
|
||||||
activesupport (>= 4.0)
|
activesupport (>= 4.0)
|
||||||
|
@ -733,21 +743,8 @@ GEM
|
||||||
tilt (2.0.5)
|
tilt (2.0.5)
|
||||||
timecop (0.8.1)
|
timecop (0.8.1)
|
||||||
timfel-krb5-auth (0.8.3)
|
timfel-krb5-auth (0.8.3)
|
||||||
tinder (1.10.1)
|
|
||||||
eventmachine (~> 1.0)
|
|
||||||
faraday (~> 0.9.0)
|
|
||||||
faraday_middleware (~> 0.9)
|
|
||||||
hashie (>= 1.0)
|
|
||||||
json (~> 1.8.0)
|
|
||||||
mime-types
|
|
||||||
multi_json (~> 1.7)
|
|
||||||
twitter-stream (~> 0.1)
|
|
||||||
turbolinks (2.5.3)
|
turbolinks (2.5.3)
|
||||||
coffee-rails
|
coffee-rails
|
||||||
twitter-stream (0.1.16)
|
|
||||||
eventmachine (>= 0.12.8)
|
|
||||||
http_parser.rb (~> 0.5.1)
|
|
||||||
simple_oauth (~> 0.1.4)
|
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.2)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
u2f (0.2.1)
|
u2f (0.2.1)
|
||||||
|
@ -766,16 +763,16 @@ GEM
|
||||||
unicorn-worker-killer (0.4.4)
|
unicorn-worker-killer (0.4.4)
|
||||||
get_process_mem (~> 0)
|
get_process_mem (~> 0)
|
||||||
unicorn (>= 4, < 6)
|
unicorn (>= 4, < 6)
|
||||||
uniform_notifier (1.9.0)
|
uniform_notifier (1.10.0)
|
||||||
uuid (2.3.8)
|
uuid (2.3.8)
|
||||||
macaddr (~> 1.0)
|
macaddr (~> 1.0)
|
||||||
version_sorter (2.0.0)
|
version_sorter (2.1.0)
|
||||||
virtus (1.0.5)
|
virtus (1.0.5)
|
||||||
axiom-types (~> 0.1)
|
axiom-types (~> 0.1)
|
||||||
coercible (~> 1.0)
|
coercible (~> 1.0)
|
||||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||||
equalizer (~> 0.0, >= 0.0.9)
|
equalizer (~> 0.0, >= 0.0.9)
|
||||||
vmstat (2.1.1)
|
vmstat (2.2.0)
|
||||||
warden (1.2.6)
|
warden (1.2.6)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
web-console (2.3.0)
|
web-console (2.3.0)
|
||||||
|
@ -802,7 +799,7 @@ PLATFORMS
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
RedCloth (~> 4.3.2)
|
RedCloth (~> 4.3.2)
|
||||||
ace-rails-ap (~> 4.0.2)
|
ace-rails-ap (~> 4.1.0)
|
||||||
activerecord-session_store (~> 1.0.0)
|
activerecord-session_store (~> 1.0.0)
|
||||||
acts-as-taggable-on (~> 3.4)
|
acts-as-taggable-on (~> 3.4)
|
||||||
addressable (~> 2.3.8)
|
addressable (~> 2.3.8)
|
||||||
|
@ -821,24 +818,25 @@ DEPENDENCIES
|
||||||
bootstrap-sass (~> 3.3.0)
|
bootstrap-sass (~> 3.3.0)
|
||||||
brakeman (~> 3.3.0)
|
brakeman (~> 3.3.0)
|
||||||
browser (~> 2.2)
|
browser (~> 2.2)
|
||||||
bullet (~> 5.0.0)
|
bullet (~> 5.2.0)
|
||||||
bundler-audit (~> 0.5.0)
|
bundler-audit (~> 0.5.0)
|
||||||
byebug (~> 8.2.1)
|
byebug (~> 8.2.1)
|
||||||
capybara (~> 2.6.2)
|
capybara (~> 2.6.2)
|
||||||
capybara-screenshot (~> 1.0.0)
|
capybara-screenshot (~> 1.0.0)
|
||||||
carrierwave (~> 0.10.0)
|
carrierwave (~> 0.10.0)
|
||||||
charlock_holmes (~> 0.7.3)
|
charlock_holmes (~> 0.7.3)
|
||||||
|
chronic (~> 0.10.2)
|
||||||
chronic_duration (~> 0.10.6)
|
chronic_duration (~> 0.10.6)
|
||||||
coffee-rails (~> 4.1.0)
|
coffee-rails (~> 4.1.0)
|
||||||
connection_pool (~> 2.0)
|
connection_pool (~> 2.0)
|
||||||
creole (~> 0.5.0)
|
creole (~> 0.5.0)
|
||||||
d3_rails (~> 3.5.0)
|
d3_rails (~> 3.5.0)
|
||||||
database_cleaner (~> 1.4.0)
|
database_cleaner (~> 1.5.0)
|
||||||
default_value_for (~> 3.0.0)
|
default_value_for (~> 3.0.0)
|
||||||
devise (~> 4.0)
|
devise (~> 4.0)
|
||||||
devise-two-factor (~> 3.0.0)
|
devise-two-factor (~> 3.0.0)
|
||||||
diffy (~> 3.0.3)
|
diffy (~> 3.0.3)
|
||||||
doorkeeper (~> 4.0)
|
doorkeeper (~> 4.2.0)
|
||||||
dropzonejs-rails (~> 0.7.1)
|
dropzonejs-rails (~> 0.7.1)
|
||||||
email_reply_parser (~> 0.5.8)
|
email_reply_parser (~> 0.5.8)
|
||||||
email_spec (~> 1.6.0)
|
email_spec (~> 1.6.0)
|
||||||
|
@ -861,15 +859,15 @@ DEPENDENCIES
|
||||||
github-linguist (~> 4.7.0)
|
github-linguist (~> 4.7.0)
|
||||||
github-markup (~> 1.4)
|
github-markup (~> 1.4)
|
||||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||||
gitlab_git (~> 10.3.2)
|
gitlab_git (~> 10.4.7)
|
||||||
gitlab_meta (= 7.0)
|
gitlab_meta (= 7.0)
|
||||||
gitlab_omniauth-ldap (~> 1.2.1)
|
gitlab_omniauth-ldap (~> 1.2.1)
|
||||||
gollum-lib (~> 4.2)
|
gollum-lib (~> 4.2)
|
||||||
gollum-rugged_adapter (~> 0.4.2)
|
gollum-rugged_adapter (~> 0.4.2)
|
||||||
gon (~> 6.0.1)
|
gon (~> 6.1.0)
|
||||||
grape (~> 0.13.0)
|
grape (~> 0.15.0)
|
||||||
grape-entity (~> 0.4.2)
|
grape-entity (~> 0.4.2)
|
||||||
hamlit (~> 2.5)
|
hamlit (~> 2.6.1)
|
||||||
health_check (~> 2.1.0)
|
health_check (~> 2.1.0)
|
||||||
hipchat (~> 1.5.0)
|
hipchat (~> 1.5.0)
|
||||||
html-pipeline (~> 1.11.0)
|
html-pipeline (~> 1.11.0)
|
||||||
|
@ -879,6 +877,7 @@ DEPENDENCIES
|
||||||
jquery-rails (~> 4.1.0)
|
jquery-rails (~> 4.1.0)
|
||||||
jquery-turbolinks (~> 2.1.0)
|
jquery-turbolinks (~> 2.1.0)
|
||||||
jquery-ui-rails (~> 5.0.0)
|
jquery-ui-rails (~> 5.0.0)
|
||||||
|
json-schema (~> 2.6.2)
|
||||||
jwt
|
jwt
|
||||||
kaminari (~> 0.17.0)
|
kaminari (~> 0.17.0)
|
||||||
knapsack (~> 1.11.0)
|
knapsack (~> 1.11.0)
|
||||||
|
@ -893,7 +892,7 @@ DEPENDENCIES
|
||||||
mysql2 (~> 0.3.16)
|
mysql2 (~> 0.3.16)
|
||||||
nested_form (~> 0.3.2)
|
nested_form (~> 0.3.2)
|
||||||
net-ssh (~> 3.0.1)
|
net-ssh (~> 3.0.1)
|
||||||
newrelic_rpm (~> 3.14)
|
newrelic_rpm (~> 3.16)
|
||||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||||
oauth2 (~> 1.2.0)
|
oauth2 (~> 1.2.0)
|
||||||
octokit (~> 4.3.0)
|
octokit (~> 4.3.0)
|
||||||
|
@ -920,7 +919,7 @@ DEPENDENCIES
|
||||||
rack-attack (~> 4.3.1)
|
rack-attack (~> 4.3.1)
|
||||||
rack-cors (~> 0.4.0)
|
rack-cors (~> 0.4.0)
|
||||||
rack-oauth2 (~> 1.2.1)
|
rack-oauth2 (~> 1.2.1)
|
||||||
rails (= 4.2.7)
|
rails (= 4.2.7.1)
|
||||||
rails-deprecated_sanitizer (~> 1.0.3)
|
rails-deprecated_sanitizer (~> 1.0.3)
|
||||||
rainbow (~> 2.1.0)
|
rainbow (~> 2.1.0)
|
||||||
rblineprof (~> 0.3.6)
|
rblineprof (~> 0.3.6)
|
||||||
|
@ -940,6 +939,7 @@ DEPENDENCIES
|
||||||
rubocop (~> 0.41.2)
|
rubocop (~> 0.41.2)
|
||||||
rubocop-rspec (~> 1.5.0)
|
rubocop-rspec (~> 1.5.0)
|
||||||
ruby-fogbugz (~> 0.2.1)
|
ruby-fogbugz (~> 0.2.1)
|
||||||
|
ruby-prof (~> 0.15.9)
|
||||||
sanitize (~> 2.0)
|
sanitize (~> 2.0)
|
||||||
sass-rails (~> 5.0.0)
|
sass-rails (~> 5.0.0)
|
||||||
scss_lint (~> 0.47.0)
|
scss_lint (~> 0.47.0)
|
||||||
|
@ -952,7 +952,7 @@ DEPENDENCIES
|
||||||
shoulda-matchers (~> 2.8.0)
|
shoulda-matchers (~> 2.8.0)
|
||||||
sidekiq (~> 4.0)
|
sidekiq (~> 4.0)
|
||||||
sidekiq-cron (~> 0.4.0)
|
sidekiq-cron (~> 0.4.0)
|
||||||
simplecov (~> 0.11.0)
|
simplecov (= 0.12.0)
|
||||||
sinatra (~> 1.4.4)
|
sinatra (~> 1.4.4)
|
||||||
six (~> 0.2.0)
|
six (~> 0.2.0)
|
||||||
slack-notifier (~> 1.2.0)
|
slack-notifier (~> 1.2.0)
|
||||||
|
@ -963,6 +963,7 @@ DEPENDENCIES
|
||||||
spring-commands-spinach (~> 1.1.0)
|
spring-commands-spinach (~> 1.1.0)
|
||||||
spring-commands-teaspoon (~> 0.0.2)
|
spring-commands-teaspoon (~> 0.0.2)
|
||||||
sprockets (~> 3.6.0)
|
sprockets (~> 3.6.0)
|
||||||
|
sprockets-es6
|
||||||
state_machines-activerecord (~> 0.4.0)
|
state_machines-activerecord (~> 0.4.0)
|
||||||
sys-filesystem (~> 1.1.6)
|
sys-filesystem (~> 1.1.6)
|
||||||
task_list (~> 1.0.2)
|
task_list (~> 1.0.2)
|
||||||
|
@ -970,7 +971,6 @@ DEPENDENCIES
|
||||||
teaspoon-jasmine (~> 2.2.0)
|
teaspoon-jasmine (~> 2.2.0)
|
||||||
test_after_commit (~> 0.4.2)
|
test_after_commit (~> 0.4.2)
|
||||||
thin (~> 1.7.0)
|
thin (~> 1.7.0)
|
||||||
tinder (~> 1.10.0)
|
|
||||||
turbolinks (~> 2.5.0)
|
turbolinks (~> 2.5.0)
|
||||||
u2f (~> 0.2.1)
|
u2f (~> 0.2.1)
|
||||||
uglifier (~> 2.7.2)
|
uglifier (~> 2.7.2)
|
||||||
|
@ -978,9 +978,9 @@ DEPENDENCIES
|
||||||
unf (~> 0.1.4)
|
unf (~> 0.1.4)
|
||||||
unicorn (~> 4.9.0)
|
unicorn (~> 4.9.0)
|
||||||
unicorn-worker-killer (~> 0.4.2)
|
unicorn-worker-killer (~> 0.4.2)
|
||||||
version_sorter (~> 2.0.0)
|
version_sorter (~> 2.1.0)
|
||||||
virtus (~> 1.0.1)
|
virtus (~> 1.0.1)
|
||||||
vmstat (~> 2.1.1)
|
vmstat (~> 2.2)
|
||||||
web-console (~> 2.0)
|
web-console (~> 2.0)
|
||||||
webmock (~> 1.21.0)
|
webmock (~> 1.21.0)
|
||||||
wikicloth (= 0.8.1)
|
wikicloth (= 0.8.1)
|
||||||
|
|
|
@ -8,6 +8,8 @@ treatment, etc.). And so that maintainers know what to expect from contributors
|
||||||
(use the latest version, ensure that the issue is addressed, friendly treatment,
|
(use the latest version, ensure that the issue is addressed, friendly treatment,
|
||||||
etc.).
|
etc.).
|
||||||
|
|
||||||
|
- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
|
||||||
|
|
||||||
## Common actions
|
## Common actions
|
||||||
|
|
||||||
### Issue team
|
### Issue team
|
||||||
|
|
2
VERSION
|
@ -1 +1 @@
|
||||||
8.10.5
|
8.11.3
|
||||||
|
|
Before Width: | Height: | Size: 90 B |
Before Width: | Height: | Size: 167 B |
Before Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 418 B |
Before Width: | Height: | Size: 222 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 5.7 KiB |
8
app/assets/images/koding-logo.svg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14">
|
||||||
|
<g fill="#d6d7d9">
|
||||||
|
<path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/>
|
||||||
|
<ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/>
|
||||||
|
<ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 231 B |
Before Width: | Height: | Size: 49 B |
110
app/assets/javascripts/LabelManager.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
(function() {
|
||||||
|
this.LabelManager = (function() {
|
||||||
|
LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
|
||||||
|
|
||||||
|
function LabelManager(opts) {
|
||||||
|
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';
|
||||||
|
$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;
|
||||||
|
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);
|
||||||
|
if (!persistState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === 'remove') {
|
||||||
|
xhr = $.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'DELETE'
|
||||||
|
});
|
||||||
|
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);
|
|
@ -1,92 +0,0 @@
|
||||||
class @LabelManager
|
|
||||||
errorMessage: 'Unable to update label prioritization at this time'
|
|
||||||
|
|
||||||
constructor: (opts = {}) ->
|
|
||||||
# Defaults
|
|
||||||
{
|
|
||||||
@togglePriorityButton = $('.js-toggle-priority')
|
|
||||||
@prioritizedLabels = $('.js-prioritized-labels')
|
|
||||||
@otherLabels = $('.js-other-labels')
|
|
||||||
} = opts
|
|
||||||
|
|
||||||
@prioritizedLabels.sortable(
|
|
||||||
items: 'li'
|
|
||||||
placeholder: 'list-placeholder'
|
|
||||||
axis: 'y'
|
|
||||||
update: @onPrioritySortUpdate.bind(@)
|
|
||||||
)
|
|
||||||
|
|
||||||
@bindEvents()
|
|
||||||
|
|
||||||
bindEvents: ->
|
|
||||||
@togglePriorityButton.on 'click', @, @onTogglePriorityClick
|
|
||||||
|
|
||||||
onTogglePriorityClick: (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
_this = e.data
|
|
||||||
$btn = $(e.currentTarget)
|
|
||||||
$label = $("##{$btn.data('domId')}")
|
|
||||||
action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
|
|
||||||
|
|
||||||
# Make sure tooltip will hide
|
|
||||||
$tooltip = $ "##{$btn.find('.has-tooltip:visible').attr('aria-describedby')}"
|
|
||||||
$tooltip.tooltip 'destroy'
|
|
||||||
|
|
||||||
_this.toggleLabelPriority($label, action)
|
|
||||||
|
|
||||||
toggleLabelPriority: ($label, action, persistState = true) ->
|
|
||||||
_this = @
|
|
||||||
url = $label.find('.js-toggle-priority').data 'url'
|
|
||||||
|
|
||||||
$target = @prioritizedLabels
|
|
||||||
$from = @otherLabels
|
|
||||||
|
|
||||||
# Optimistic update
|
|
||||||
if action is 'remove'
|
|
||||||
$target = @otherLabels
|
|
||||||
$from = @prioritizedLabels
|
|
||||||
|
|
||||||
if $from.find('li').length is 1
|
|
||||||
$from.find('.empty-message').removeClass('hidden')
|
|
||||||
|
|
||||||
if not $target.find('li').length
|
|
||||||
$target.find('.empty-message').addClass('hidden')
|
|
||||||
|
|
||||||
$label.detach().appendTo($target)
|
|
||||||
|
|
||||||
# Return if we are not persisting state
|
|
||||||
return unless persistState
|
|
||||||
|
|
||||||
if action is 'remove'
|
|
||||||
xhr = $.ajax url: url, type: 'DELETE'
|
|
||||||
|
|
||||||
# Restore empty message
|
|
||||||
$from.find('.empty-message').removeClass('hidden') unless $from.find('li').length
|
|
||||||
else
|
|
||||||
xhr = @savePrioritySort($label, action)
|
|
||||||
|
|
||||||
xhr.fail @rollbackLabelPosition.bind(@, $label, action)
|
|
||||||
|
|
||||||
onPrioritySortUpdate: ->
|
|
||||||
xhr = @savePrioritySort()
|
|
||||||
|
|
||||||
xhr.fail ->
|
|
||||||
new Flash(@errorMessage, 'alert')
|
|
||||||
|
|
||||||
savePrioritySort: () ->
|
|
||||||
$.post
|
|
||||||
url: @prioritizedLabels.data('url')
|
|
||||||
data:
|
|
||||||
label_ids: @getSortedLabelsIds()
|
|
||||||
|
|
||||||
rollbackLabelPosition: ($label, originalAction)->
|
|
||||||
action = if originalAction is 'remove' then 'add' else 'remove'
|
|
||||||
@toggleLabelPriority($label, action, false)
|
|
||||||
|
|
||||||
new Flash(@errorMessage, 'alert')
|
|
||||||
|
|
||||||
getSortedLabelsIds: ->
|
|
||||||
sortedIds = []
|
|
||||||
@prioritizedLabels.find('li').each ->
|
|
||||||
sortedIds.push $(@).data 'id'
|
|
||||||
sortedIds
|
|
40
app/assets/javascripts/activities.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
(function() {
|
||||||
|
this.Activities = (function() {
|
||||||
|
function Activities() {
|
||||||
|
Pager.init(20, true, false, this.updateTooltips);
|
||||||
|
$(".event-filter-link").on("click", (function(_this) {
|
||||||
|
return function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
_this.toggleFilter($(event.currentTarget));
|
||||||
|
return _this.reloadActivities();
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
Activities.prototype.updateTooltips = function() {
|
||||||
|
return gl.utils.localTimeAgo($('.js-timeago', '#activity'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Activities.prototype.reloadActivities = function() {
|
||||||
|
$(".content_list").html('');
|
||||||
|
return Pager.init(20, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
Activities.prototype.toggleFilter = function(sender) {
|
||||||
|
var event_filters, filter;
|
||||||
|
$('.event-filter .active').removeClass("active");
|
||||||
|
event_filters = $.cookie("event_filter");
|
||||||
|
filter = sender.attr("id").split("_")[0];
|
||||||
|
$.cookie("event_filter", (event_filters !== filter ? filter : ""), {
|
||||||
|
path: '/'
|
||||||
|
});
|
||||||
|
if (event_filters !== filter) {
|
||||||
|
return sender.closest('li').toggleClass("active");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Activities;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,24 +0,0 @@
|
||||||
class @Activities
|
|
||||||
constructor: ->
|
|
||||||
Pager.init 20, true, false, @updateTooltips
|
|
||||||
$(".event-filter-link").on "click", (event) =>
|
|
||||||
event.preventDefault()
|
|
||||||
@toggleFilter($(event.currentTarget))
|
|
||||||
@reloadActivities()
|
|
||||||
|
|
||||||
updateTooltips: ->
|
|
||||||
gl.utils.localTimeAgo($('.js-timeago', '#activity'))
|
|
||||||
|
|
||||||
reloadActivities: ->
|
|
||||||
$(".content_list").html ''
|
|
||||||
Pager.init 20, true
|
|
||||||
|
|
||||||
|
|
||||||
toggleFilter: (sender) ->
|
|
||||||
$('.event-filter .active').removeClass "active"
|
|
||||||
event_filters = $.cookie("event_filter")
|
|
||||||
filter = sender.attr("id").split("_")[0]
|
|
||||||
$.cookie "event_filter", (if event_filters isnt filter then filter else ""), { path: '/' }
|
|
||||||
|
|
||||||
if event_filters isnt filter
|
|
||||||
sender.closest('li').toggleClass "active"
|
|
64
app/assets/javascripts/admin.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
(function() {
|
||||||
|
this.Admin = (function() {
|
||||||
|
function Admin() {
|
||||||
|
var modal, showBlacklistType;
|
||||||
|
$('input#user_force_random_password').on('change', function(elem) {
|
||||||
|
var elems;
|
||||||
|
elems = $('#user_password, #user_password_confirmation');
|
||||||
|
if ($(this).attr('checked')) {
|
||||||
|
return elems.val('').attr('disabled', true);
|
||||||
|
} else {
|
||||||
|
return elems.removeAttr('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('body').on('click', '.js-toggle-colors-link', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
return $('.js-toggle-colors-container').toggle();
|
||||||
|
});
|
||||||
|
$('.log-tabs a').click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
return $(this).tab('show');
|
||||||
|
});
|
||||||
|
$('.log-bottom').click(function(e) {
|
||||||
|
var visible_log;
|
||||||
|
e.preventDefault();
|
||||||
|
visible_log = $(".file-content:visible");
|
||||||
|
return visible_log.animate({
|
||||||
|
scrollTop: visible_log.find('ol').height()
|
||||||
|
}, "fast");
|
||||||
|
});
|
||||||
|
modal = $('.change-owner-holder');
|
||||||
|
$('.change-owner-link').bind("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).hide();
|
||||||
|
return modal.show();
|
||||||
|
});
|
||||||
|
$('.change-owner-cancel-link').bind("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
modal.hide();
|
||||||
|
return $('.change-owner-link').show();
|
||||||
|
});
|
||||||
|
$('li.project_member').bind('ajax:success', function() {
|
||||||
|
return Turbolinks.visit(location.href);
|
||||||
|
});
|
||||||
|
$('li.group_member').bind('ajax:success', function() {
|
||||||
|
return Turbolinks.visit(location.href);
|
||||||
|
});
|
||||||
|
showBlacklistType = function() {
|
||||||
|
if ($("input[name='blacklist_type']:checked").val() === 'file') {
|
||||||
|
$('.blacklist-file').show();
|
||||||
|
return $('.blacklist-raw').hide();
|
||||||
|
} else {
|
||||||
|
$('.blacklist-file').hide();
|
||||||
|
return $('.blacklist-raw').show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$("input[name='blacklist_type']").click(showBlacklistType);
|
||||||
|
showBlacklistType();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Admin;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,51 +0,0 @@
|
||||||
class @Admin
|
|
||||||
constructor: ->
|
|
||||||
$('input#user_force_random_password').on 'change', (elem) ->
|
|
||||||
elems = $('#user_password, #user_password_confirmation')
|
|
||||||
|
|
||||||
if $(@).attr 'checked'
|
|
||||||
elems.val('').attr 'disabled', true
|
|
||||||
else
|
|
||||||
elems.removeAttr 'disabled'
|
|
||||||
|
|
||||||
$('body').on 'click', '.js-toggle-colors-link', (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
$('.js-toggle-colors-container').toggle()
|
|
||||||
|
|
||||||
$('.log-tabs a').click (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
$(this).tab('show')
|
|
||||||
|
|
||||||
$('.log-bottom').click (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
visible_log = $(".file-content:visible")
|
|
||||||
visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast")
|
|
||||||
|
|
||||||
modal = $('.change-owner-holder')
|
|
||||||
|
|
||||||
$('.change-owner-link').bind "click", (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
$(this).hide()
|
|
||||||
modal.show()
|
|
||||||
|
|
||||||
$('.change-owner-cancel-link').bind "click", (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
modal.hide()
|
|
||||||
$('.change-owner-link').show()
|
|
||||||
|
|
||||||
$('li.project_member').bind 'ajax:success', ->
|
|
||||||
Turbolinks.visit(location.href)
|
|
||||||
|
|
||||||
$('li.group_member').bind 'ajax:success', ->
|
|
||||||
Turbolinks.visit(location.href)
|
|
||||||
|
|
||||||
showBlacklistType = ->
|
|
||||||
if $("input[name='blacklist_type']:checked").val() == 'file'
|
|
||||||
$('.blacklist-file').show()
|
|
||||||
$('.blacklist-raw').hide()
|
|
||||||
else
|
|
||||||
$('.blacklist-file').hide()
|
|
||||||
$('.blacklist-raw').show()
|
|
||||||
|
|
||||||
$("input[name='blacklist_type']").click showBlacklistType
|
|
||||||
showBlacklistType()
|
|
145
app/assets/javascripts/api.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
(function() {
|
||||||
|
this.Api = {
|
||||||
|
groupsPath: "/api/:version/groups.json",
|
||||||
|
groupPath: "/api/:version/groups/:id.json",
|
||||||
|
namespacesPath: "/api/:version/namespaces.json",
|
||||||
|
groupProjectsPath: "/api/:version/groups/:id/projects.json",
|
||||||
|
projectsPath: "/api/:version/projects.json?simple=true",
|
||||||
|
labelsPath: "/api/:version/projects/:id/labels",
|
||||||
|
licensePath: "/api/:version/licenses/:key",
|
||||||
|
gitignorePath: "/api/:version/gitignores/:key",
|
||||||
|
gitlabCiYmlPath: "/api/:version/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);
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
data: {
|
||||||
|
private_token: gon.api_token
|
||||||
|
},
|
||||||
|
dataType: "json"
|
||||||
|
}).done(function(group) {
|
||||||
|
return callback(group);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
groups: function(query, skip_ldap, callback) {
|
||||||
|
var url = Api.buildUrl(Api.groupsPath);
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
data: {
|
||||||
|
private_token: gon.api_token,
|
||||||
|
search: query,
|
||||||
|
per_page: 20
|
||||||
|
},
|
||||||
|
dataType: "json"
|
||||||
|
}).done(function(groups) {
|
||||||
|
return callback(groups);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
namespaces: function(query, callback) {
|
||||||
|
var url = Api.buildUrl(Api.namespacesPath);
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
data: {
|
||||||
|
private_token: gon.api_token,
|
||||||
|
search: query,
|
||||||
|
per_page: 20
|
||||||
|
},
|
||||||
|
dataType: "json"
|
||||||
|
}).done(function(namespaces) {
|
||||||
|
return callback(namespaces);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
projects: function(query, order, callback) {
|
||||||
|
var url = Api.buildUrl(Api.projectsPath);
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
data: {
|
||||||
|
private_token: gon.api_token,
|
||||||
|
search: query,
|
||||||
|
order_by: order,
|
||||||
|
per_page: 20
|
||||||
|
},
|
||||||
|
dataType: "json"
|
||||||
|
}).done(function(projects) {
|
||||||
|
return callback(projects);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
newLabel: function(project_id, data, callback) {
|
||||||
|
var url = Api.buildUrl(Api.labelsPath)
|
||||||
|
.replace(':id', project_id);
|
||||||
|
data.private_token = gon.api_token;
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
type: "POST",
|
||||||
|
data: data,
|
||||||
|
dataType: "json"
|
||||||
|
}).done(function(label) {
|
||||||
|
return callback(label);
|
||||||
|
}).error(function(message) {
|
||||||
|
return callback(message.responseJSON);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
groupProjects: function(group_id, query, callback) {
|
||||||
|
var url = Api.buildUrl(Api.groupProjectsPath)
|
||||||
|
.replace(':id', group_id);
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
data: {
|
||||||
|
private_token: gon.api_token,
|
||||||
|
search: query,
|
||||||
|
per_page: 20
|
||||||
|
},
|
||||||
|
dataType: "json"
|
||||||
|
}).done(function(projects) {
|
||||||
|
return callback(projects);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
licenseText: function(key, data, callback) {
|
||||||
|
var url = Api.buildUrl(Api.licensePath)
|
||||||
|
.replace(':key', key);
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
data: data
|
||||||
|
}).done(function(license) {
|
||||||
|
return callback(license);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
gitignoreText: function(key, callback) {
|
||||||
|
var url = Api.buildUrl(Api.gitignorePath)
|
||||||
|
.replace(':key', key);
|
||||||
|
return $.get(url, function(gitignore) {
|
||||||
|
return callback(gitignore);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
gitlabCiYml: function(key, callback) {
|
||||||
|
var url = Api.buildUrl(Api.gitlabCiYmlPath)
|
||||||
|
.replace(':key', key);
|
||||||
|
return $.get(url, function(file) {
|
||||||
|
return callback(file);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
issueTemplate: function(namespacePath, projectPath, key, type, callback) {
|
||||||
|
var url = Api.buildUrl(Api.issuableTemplatePath)
|
||||||
|
.replace(':key', key)
|
||||||
|
.replace(':type', type)
|
||||||
|
.replace(':project_path', projectPath)
|
||||||
|
.replace(':namespace_path', namespacePath);
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
dataType: 'json'
|
||||||
|
}).done(function(file) {
|
||||||
|
callback(null, file);
|
||||||
|
}).error(callback);
|
||||||
|
},
|
||||||
|
buildUrl: function(url) {
|
||||||
|
if (gon.relative_url_root != null) {
|
||||||
|
url = gon.relative_url_root + url;
|
||||||
|
}
|
||||||
|
return url.replace(':version', gon.api_version);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,122 +0,0 @@
|
||||||
@Api =
|
|
||||||
groupsPath: "/api/:version/groups.json"
|
|
||||||
groupPath: "/api/:version/groups/:id.json"
|
|
||||||
namespacesPath: "/api/:version/namespaces.json"
|
|
||||||
groupProjectsPath: "/api/:version/groups/:id/projects.json"
|
|
||||||
projectsPath: "/api/:version/projects.json?simple=true"
|
|
||||||
labelsPath: "/api/:version/projects/:id/labels"
|
|
||||||
licensePath: "/api/:version/licenses/:key"
|
|
||||||
gitignorePath: "/api/:version/gitignores/:key"
|
|
||||||
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
|
|
||||||
|
|
||||||
group: (group_id, callback) ->
|
|
||||||
url = Api.buildUrl(Api.groupPath)
|
|
||||||
url = url.replace(':id', group_id)
|
|
||||||
|
|
||||||
$.ajax(
|
|
||||||
url: url
|
|
||||||
data:
|
|
||||||
private_token: gon.api_token
|
|
||||||
dataType: "json"
|
|
||||||
).done (group) ->
|
|
||||||
callback(group)
|
|
||||||
|
|
||||||
# Return groups list. Filtered by query
|
|
||||||
# Only active groups retrieved
|
|
||||||
groups: (query, skip_ldap, callback) ->
|
|
||||||
url = Api.buildUrl(Api.groupsPath)
|
|
||||||
|
|
||||||
$.ajax(
|
|
||||||
url: url
|
|
||||||
data:
|
|
||||||
private_token: gon.api_token
|
|
||||||
search: query
|
|
||||||
per_page: 20
|
|
||||||
dataType: "json"
|
|
||||||
).done (groups) ->
|
|
||||||
callback(groups)
|
|
||||||
|
|
||||||
# Return namespaces list. Filtered by query
|
|
||||||
namespaces: (query, callback) ->
|
|
||||||
url = Api.buildUrl(Api.namespacesPath)
|
|
||||||
|
|
||||||
$.ajax(
|
|
||||||
url: url
|
|
||||||
data:
|
|
||||||
private_token: gon.api_token
|
|
||||||
search: query
|
|
||||||
per_page: 20
|
|
||||||
dataType: "json"
|
|
||||||
).done (namespaces) ->
|
|
||||||
callback(namespaces)
|
|
||||||
|
|
||||||
# Return projects list. Filtered by query
|
|
||||||
projects: (query, order, callback) ->
|
|
||||||
url = Api.buildUrl(Api.projectsPath)
|
|
||||||
|
|
||||||
$.ajax(
|
|
||||||
url: url
|
|
||||||
data:
|
|
||||||
private_token: gon.api_token
|
|
||||||
search: query
|
|
||||||
order_by: order
|
|
||||||
per_page: 20
|
|
||||||
dataType: "json"
|
|
||||||
).done (projects) ->
|
|
||||||
callback(projects)
|
|
||||||
|
|
||||||
newLabel: (project_id, data, callback) ->
|
|
||||||
url = Api.buildUrl(Api.labelsPath)
|
|
||||||
url = url.replace(':id', project_id)
|
|
||||||
|
|
||||||
data.private_token = gon.api_token
|
|
||||||
$.ajax(
|
|
||||||
url: url
|
|
||||||
type: "POST"
|
|
||||||
data: data
|
|
||||||
dataType: "json"
|
|
||||||
).done (label) ->
|
|
||||||
callback(label)
|
|
||||||
.error (message) ->
|
|
||||||
callback(message.responseJSON)
|
|
||||||
|
|
||||||
# Return group projects list. Filtered by query
|
|
||||||
groupProjects: (group_id, query, callback) ->
|
|
||||||
url = Api.buildUrl(Api.groupProjectsPath)
|
|
||||||
url = url.replace(':id', group_id)
|
|
||||||
|
|
||||||
$.ajax(
|
|
||||||
url: url
|
|
||||||
data:
|
|
||||||
private_token: gon.api_token
|
|
||||||
search: query
|
|
||||||
per_page: 20
|
|
||||||
dataType: "json"
|
|
||||||
).done (projects) ->
|
|
||||||
callback(projects)
|
|
||||||
|
|
||||||
# Return text for a specific license
|
|
||||||
licenseText: (key, data, callback) ->
|
|
||||||
url = Api.buildUrl(Api.licensePath).replace(':key', key)
|
|
||||||
|
|
||||||
$.ajax(
|
|
||||||
url: url
|
|
||||||
data: data
|
|
||||||
).done (license) ->
|
|
||||||
callback(license)
|
|
||||||
|
|
||||||
gitignoreText: (key, callback) ->
|
|
||||||
url = Api.buildUrl(Api.gitignorePath).replace(':key', key)
|
|
||||||
|
|
||||||
$.get url, (gitignore) ->
|
|
||||||
callback(gitignore)
|
|
||||||
|
|
||||||
gitlabCiYml: (key, callback) ->
|
|
||||||
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
|
|
||||||
|
|
||||||
$.get url, (file) ->
|
|
||||||
callback(file)
|
|
||||||
|
|
||||||
buildUrl: (url) ->
|
|
||||||
url = gon.relative_url_root + url if gon.relative_url_root?
|
|
||||||
return url.replace(':version', gon.api_version)
|
|
327
app/assets/javascripts/application.js
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
/*= require jquery2 */
|
||||||
|
/*= require jquery-ui/autocomplete */
|
||||||
|
/*= require jquery-ui/datepicker */
|
||||||
|
/*= require jquery-ui/draggable */
|
||||||
|
/*= require jquery-ui/effect-highlight */
|
||||||
|
/*= require jquery-ui/sortable */
|
||||||
|
/*= require jquery_ujs */
|
||||||
|
/*= require jquery.cookie */
|
||||||
|
/*= require jquery.endless-scroll */
|
||||||
|
/*= require jquery.highlight */
|
||||||
|
/*= require jquery.waitforimages */
|
||||||
|
/*= require jquery.atwho */
|
||||||
|
/*= require jquery.scrollTo */
|
||||||
|
/*= require jquery.turbolinks */
|
||||||
|
/*= require turbolinks */
|
||||||
|
/*= require autosave */
|
||||||
|
/*= require bootstrap/affix */
|
||||||
|
/*= require bootstrap/alert */
|
||||||
|
/*= require bootstrap/button */
|
||||||
|
/*= require bootstrap/collapse */
|
||||||
|
/*= require bootstrap/dropdown */
|
||||||
|
/*= require bootstrap/modal */
|
||||||
|
/*= require bootstrap/scrollspy */
|
||||||
|
/*= require bootstrap/tab */
|
||||||
|
/*= require bootstrap/transition */
|
||||||
|
/*= require bootstrap/tooltip */
|
||||||
|
/*= require bootstrap/popover */
|
||||||
|
/*= require select2 */
|
||||||
|
/*= require underscore */
|
||||||
|
/*= require dropzone */
|
||||||
|
/*= require mousetrap */
|
||||||
|
/*= require mousetrap/pause */
|
||||||
|
/*= require shortcuts */
|
||||||
|
/*= require shortcuts_navigation */
|
||||||
|
/*= require shortcuts_dashboard_navigation */
|
||||||
|
/*= require shortcuts_issuable */
|
||||||
|
/*= require shortcuts_network */
|
||||||
|
/*= require jquery.nicescroll */
|
||||||
|
/*= require date.format */
|
||||||
|
/*= require_directory ./behaviors */
|
||||||
|
/*= require_directory ./blob */
|
||||||
|
/*= require_directory ./templates */
|
||||||
|
/*= require_directory ./commit */
|
||||||
|
/*= require_directory ./extensions */
|
||||||
|
/*= require_directory ./lib/utils */
|
||||||
|
/*= require_directory ./u2f */
|
||||||
|
/*= require_directory . */
|
||||||
|
/*= require fuzzaldrin-plus */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
window.slugify = function(text) {
|
||||||
|
return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.ajaxGet = function(url) {
|
||||||
|
return $.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: url,
|
||||||
|
dataType: "script"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.split = function(val) {
|
||||||
|
return val.split(/,\s*/);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.extractLast = function(term) {
|
||||||
|
return split(term).pop();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.rstrip = function(val) {
|
||||||
|
if (val) {
|
||||||
|
return val.replace(/\s+$/, '');
|
||||||
|
} else {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.disableButtonIfEmptyField = function(field_selector, button_selector) {
|
||||||
|
var closest_submit, field;
|
||||||
|
field = $(field_selector);
|
||||||
|
closest_submit = field.closest('form').find(button_selector);
|
||||||
|
if (rstrip(field.val()) === "") {
|
||||||
|
closest_submit.disable();
|
||||||
|
}
|
||||||
|
return field.on('input', function() {
|
||||||
|
if (rstrip($(this).val()) === "") {
|
||||||
|
return closest_submit.disable();
|
||||||
|
} else {
|
||||||
|
return closest_submit.enable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
|
||||||
|
var closest_submit, updateButtons;
|
||||||
|
closest_submit = form.find(button_selector);
|
||||||
|
updateButtons = function() {
|
||||||
|
var filled;
|
||||||
|
filled = true;
|
||||||
|
form.find('input').filter(form_selector).each(function() {
|
||||||
|
return filled = rstrip($(this).val()) !== "" || !$(this).attr('required');
|
||||||
|
});
|
||||||
|
if (filled) {
|
||||||
|
return closest_submit.enable();
|
||||||
|
} else {
|
||||||
|
return closest_submit.disable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateButtons();
|
||||||
|
return form.keyup(updateButtons);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.sanitize = function(str) {
|
||||||
|
return str.replace(/<(?:.|\n)*?>/gm, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.unbindEvents = function() {
|
||||||
|
return $(document).off('scroll');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.shiftWindow = function() {
|
||||||
|
return scrollBy(0, -100);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("page:fetch", unbindEvents);
|
||||||
|
|
||||||
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
if (location.hash) {
|
||||||
|
return setTimeout(shiftWindow, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
var $body, $document, $sidebarGutterToggle, $window, bootstrapBreakpoint, checkInitialSidebarSize, fitSidebarForSize, flash;
|
||||||
|
$document = $(document);
|
||||||
|
$window = $(window);
|
||||||
|
$body = $('body');
|
||||||
|
gl.utils.preventDisabledButtons();
|
||||||
|
bootstrapBreakpoint = bp.getBreakpointSize();
|
||||||
|
$(".nav-sidebar").niceScroll({
|
||||||
|
cursoropacitymax: '0.4',
|
||||||
|
cursorcolor: '#FFF',
|
||||||
|
cursorborder: "1px solid #FFF"
|
||||||
|
});
|
||||||
|
$(".js-select-on-focus").on("focusin", function() {
|
||||||
|
return $(this).select().one('mouseup', function(e) {
|
||||||
|
return e.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('.remove-row').bind('ajax:success', function() {
|
||||||
|
return $(this).closest('li').fadeOut();
|
||||||
|
});
|
||||||
|
$('.js-remove-tr').bind('ajax:before', function() {
|
||||||
|
return $(this).hide();
|
||||||
|
});
|
||||||
|
$('.js-remove-tr').bind('ajax:success', function() {
|
||||||
|
return $(this).closest('tr').fadeOut();
|
||||||
|
});
|
||||||
|
$('select.select2').select2({
|
||||||
|
width: 'resolve',
|
||||||
|
dropdownAutoWidth: true
|
||||||
|
});
|
||||||
|
$('.js-select2').bind('select2-close', function() {
|
||||||
|
return setTimeout((function() {
|
||||||
|
$('.select2-container-active').removeClass('select2-container-active');
|
||||||
|
return $(':focus').blur();
|
||||||
|
}), 1);
|
||||||
|
});
|
||||||
|
$body.tooltip({
|
||||||
|
selector: '.has-tooltip, [data-toggle="tooltip"]',
|
||||||
|
placement: function(_, el) {
|
||||||
|
var $el;
|
||||||
|
$el = $(el);
|
||||||
|
return $el.data('placement') || 'bottom';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('.trigger-submit').on('change', function() {
|
||||||
|
return $(this).parents('form').submit();
|
||||||
|
});
|
||||||
|
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
|
||||||
|
if ((flash = $(".flash-container")).length > 0) {
|
||||||
|
flash.click(function() {
|
||||||
|
return $(this).fadeOut();
|
||||||
|
});
|
||||||
|
flash.show();
|
||||||
|
}
|
||||||
|
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) {
|
||||||
|
var buttons;
|
||||||
|
buttons = $('[type="submit"]', this);
|
||||||
|
switch (e.type) {
|
||||||
|
case 'ajax:beforeSend':
|
||||||
|
case 'submit':
|
||||||
|
return buttons.disable();
|
||||||
|
default:
|
||||||
|
return buttons.enable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$(document).ajaxError(function(e, xhrObj, xhrSetting, xhrErrorText) {
|
||||||
|
var ref;
|
||||||
|
if (xhrObj.status === 401) {
|
||||||
|
return new Flash('You need to be logged in.', 'alert');
|
||||||
|
} else if ((ref = xhrObj.status) === 404 || ref === 500) {
|
||||||
|
return new Flash('Something went wrong on our end.', 'alert');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('.account-box').hover(function() {
|
||||||
|
return $(this).toggleClass('hover');
|
||||||
|
});
|
||||||
|
$document.on('click', '.diff-content .js-show-suppressed-diff', function() {
|
||||||
|
var $container;
|
||||||
|
$container = $(this).parent();
|
||||||
|
$container.next('table').show();
|
||||||
|
return $container.remove();
|
||||||
|
});
|
||||||
|
$('.navbar-toggle').on('click', function() {
|
||||||
|
$('.header-content .title').toggle();
|
||||||
|
$('.header-content .header-logo').toggle();
|
||||||
|
$('.header-content .navbar-collapse').toggle();
|
||||||
|
return $('.navbar-toggle').toggleClass('active');
|
||||||
|
});
|
||||||
|
$body.on("click", ".js-toggle-diff-comments", function(e) {
|
||||||
|
var $this = $(this);
|
||||||
|
$this.toggleClass('active');
|
||||||
|
var notesHolders = $this.closest('.diff-file').find('.notes_holder');
|
||||||
|
if ($this.hasClass('active')) {
|
||||||
|
notesHolders.show();
|
||||||
|
} else {
|
||||||
|
notesHolders.hide();
|
||||||
|
}
|
||||||
|
return e.preventDefault();
|
||||||
|
});
|
||||||
|
$document.off("click", '.js-confirm-danger');
|
||||||
|
$document.on("click", '.js-confirm-danger', function(e) {
|
||||||
|
var btn, form, text;
|
||||||
|
e.preventDefault();
|
||||||
|
btn = $(e.target);
|
||||||
|
text = btn.data("confirm-danger-message");
|
||||||
|
form = btn.closest("form");
|
||||||
|
return new ConfirmDangerModal(form, text);
|
||||||
|
});
|
||||||
|
$document.on('click', 'button', function() {
|
||||||
|
return $(this).blur();
|
||||||
|
});
|
||||||
|
$('input[type="search"]').each(function() {
|
||||||
|
var $this;
|
||||||
|
$this = $(this);
|
||||||
|
$this.attr('value', $this.val());
|
||||||
|
});
|
||||||
|
$document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function(e) {
|
||||||
|
var $this;
|
||||||
|
$this = $(this);
|
||||||
|
return $this.attr('value', $this.val());
|
||||||
|
});
|
||||||
|
$sidebarGutterToggle = $('.js-sidebar-toggle');
|
||||||
|
$document.off('breakpoint:change').on('breakpoint:change', function(e, breakpoint) {
|
||||||
|
var $gutterIcon;
|
||||||
|
if (breakpoint === 'sm' || breakpoint === 'xs') {
|
||||||
|
$gutterIcon = $sidebarGutterToggle.find('i');
|
||||||
|
if ($gutterIcon.hasClass('fa-angle-double-right')) {
|
||||||
|
return $sidebarGutterToggle.trigger('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fitSidebarForSize = function() {
|
||||||
|
var oldBootstrapBreakpoint;
|
||||||
|
oldBootstrapBreakpoint = bootstrapBreakpoint;
|
||||||
|
bootstrapBreakpoint = bp.getBreakpointSize();
|
||||||
|
if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
|
||||||
|
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkInitialSidebarSize = function() {
|
||||||
|
bootstrapBreakpoint = bp.getBreakpointSize();
|
||||||
|
if (bootstrapBreakpoint === "xs" || "sm") {
|
||||||
|
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$window.off("resize.app").on("resize.app", function(e) {
|
||||||
|
return fitSidebarForSize();
|
||||||
|
});
|
||||||
|
gl.awardsHandler = new AwardsHandler();
|
||||||
|
checkInitialSidebarSize();
|
||||||
|
new Aside();
|
||||||
|
if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
|
||||||
|
$.cookie('pin_nav', 'false', {
|
||||||
|
path: '/',
|
||||||
|
expires: 365 * 10
|
||||||
|
});
|
||||||
|
$('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
|
||||||
|
$('.navbar-fixed-top').removeClass('header-pinned-nav');
|
||||||
|
}
|
||||||
|
$document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
|
||||||
|
var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
|
||||||
|
e.preventDefault();
|
||||||
|
$pinBtn = $(e.currentTarget);
|
||||||
|
$page = $('.page-with-sidebar');
|
||||||
|
$topNav = $('.navbar-fixed-top');
|
||||||
|
$tooltip = $("#" + ($pinBtn.attr('aria-describedby')));
|
||||||
|
doPinNav = !$page.is('.page-sidebar-pinned');
|
||||||
|
tooltipText = 'Pin navigation';
|
||||||
|
$(this).toggleClass('is-active');
|
||||||
|
if (doPinNav) {
|
||||||
|
$page.addClass('page-sidebar-pinned');
|
||||||
|
$topNav.addClass('header-pinned-nav');
|
||||||
|
} else {
|
||||||
|
$tooltip.remove();
|
||||||
|
$page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
|
||||||
|
$topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
|
||||||
|
}
|
||||||
|
$.cookie('pin_nav', doPinNav, {
|
||||||
|
path: '/',
|
||||||
|
expires: 365 * 10
|
||||||
|
});
|
||||||
|
if ($.cookie('pin_nav') === 'true' || doPinNav) {
|
||||||
|
tooltipText = 'Unpin navigation';
|
||||||
|
}
|
||||||
|
$tooltip.find('.tooltip-inner').text(tooltipText);
|
||||||
|
return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Custom time ago
|
||||||
|
gl.utils.shortTimeAgo($('.js-short-timeago'));
|
||||||
|
});
|
||||||
|
}).call(this);
|
|
@ -1,310 +0,0 @@
|
||||||
# This is a manifest file that'll be compiled into including all the files listed below.
|
|
||||||
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
|
||||||
# be included in the compiled file accessible from http://example.com/assets/application.js
|
|
||||||
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
|
||||||
# the compiled file.
|
|
||||||
#
|
|
||||||
#= require jquery2
|
|
||||||
#= require jquery-ui/autocomplete
|
|
||||||
#= require jquery-ui/datepicker
|
|
||||||
#= require jquery-ui/draggable
|
|
||||||
#= require jquery-ui/effect-highlight
|
|
||||||
#= require jquery-ui/sortable
|
|
||||||
#= require jquery_ujs
|
|
||||||
#= require jquery.cookie
|
|
||||||
#= require jquery.endless-scroll
|
|
||||||
#= require jquery.highlight
|
|
||||||
#= require jquery.waitforimages
|
|
||||||
#= require jquery.atwho
|
|
||||||
#= require jquery.scrollTo
|
|
||||||
#= require jquery.turbolinks
|
|
||||||
#= require turbolinks
|
|
||||||
#= require autosave
|
|
||||||
#= require bootstrap/affix
|
|
||||||
#= require bootstrap/alert
|
|
||||||
#= require bootstrap/button
|
|
||||||
#= require bootstrap/collapse
|
|
||||||
#= require bootstrap/dropdown
|
|
||||||
#= require bootstrap/modal
|
|
||||||
#= require bootstrap/scrollspy
|
|
||||||
#= require bootstrap/tab
|
|
||||||
#= require bootstrap/transition
|
|
||||||
#= require bootstrap/tooltip
|
|
||||||
#= require bootstrap/popover
|
|
||||||
#= require select2
|
|
||||||
#= require ace/ace
|
|
||||||
#= require ace/ext-searchbox
|
|
||||||
#= require underscore
|
|
||||||
#= require dropzone
|
|
||||||
#= require mousetrap
|
|
||||||
#= require mousetrap/pause
|
|
||||||
#= require shortcuts
|
|
||||||
#= require shortcuts_navigation
|
|
||||||
#= require shortcuts_dashboard_navigation
|
|
||||||
#= require shortcuts_issuable
|
|
||||||
#= require shortcuts_network
|
|
||||||
#= require jquery.nicescroll
|
|
||||||
#= require date.format
|
|
||||||
#= require_directory ./behaviors
|
|
||||||
#= require_directory ./blob
|
|
||||||
#= require_directory ./commit
|
|
||||||
#= require_directory ./extensions
|
|
||||||
#= require_directory ./lib/utils
|
|
||||||
#= require_directory ./u2f
|
|
||||||
#= require_directory .
|
|
||||||
#= require fuzzaldrin-plus
|
|
||||||
|
|
||||||
window.slugify = (text) ->
|
|
||||||
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
|
|
||||||
|
|
||||||
window.ajaxGet = (url) ->
|
|
||||||
$.ajax({type: "GET", url: url, dataType: "script"})
|
|
||||||
|
|
||||||
window.split = (val) ->
|
|
||||||
return val.split( /,\s*/ )
|
|
||||||
|
|
||||||
window.extractLast = (term) ->
|
|
||||||
return split( term ).pop()
|
|
||||||
|
|
||||||
window.rstrip = (val) ->
|
|
||||||
return if val then val.replace(/\s+$/, '') else val
|
|
||||||
|
|
||||||
# Disable button if text field is empty
|
|
||||||
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
|
|
||||||
field = $(field_selector)
|
|
||||||
closest_submit = field.closest('form').find(button_selector)
|
|
||||||
|
|
||||||
closest_submit.disable() if rstrip(field.val()) is ""
|
|
||||||
|
|
||||||
field.on 'input', ->
|
|
||||||
if rstrip($(@).val()) is ""
|
|
||||||
closest_submit.disable()
|
|
||||||
else
|
|
||||||
closest_submit.enable()
|
|
||||||
|
|
||||||
# Disable button if any input field with given selector is empty
|
|
||||||
window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
|
|
||||||
closest_submit = form.find(button_selector)
|
|
||||||
updateButtons = ->
|
|
||||||
filled = true
|
|
||||||
form.find('input').filter(form_selector).each ->
|
|
||||||
filled = rstrip($(this).val()) != "" || !$(this).attr('required')
|
|
||||||
|
|
||||||
if filled
|
|
||||||
closest_submit.enable()
|
|
||||||
else
|
|
||||||
closest_submit.disable()
|
|
||||||
|
|
||||||
updateButtons()
|
|
||||||
form.keyup(updateButtons)
|
|
||||||
|
|
||||||
window.sanitize = (str) ->
|
|
||||||
return str.replace(/<(?:.|\n)*?>/gm, '')
|
|
||||||
|
|
||||||
window.unbindEvents = ->
|
|
||||||
$(document).off('scroll')
|
|
||||||
|
|
||||||
window.shiftWindow = ->
|
|
||||||
scrollBy 0, -100
|
|
||||||
|
|
||||||
document.addEventListener("page:fetch", unbindEvents)
|
|
||||||
|
|
||||||
window.addEventListener "hashchange", shiftWindow
|
|
||||||
|
|
||||||
window.onload = ->
|
|
||||||
# Scroll the window to avoid the topnav bar
|
|
||||||
# https://github.com/twitter/bootstrap/issues/1768
|
|
||||||
if location.hash
|
|
||||||
setTimeout shiftWindow, 100
|
|
||||||
|
|
||||||
$ ->
|
|
||||||
|
|
||||||
$document = $(document)
|
|
||||||
$window = $(window)
|
|
||||||
$body = $('body')
|
|
||||||
|
|
||||||
gl.utils.preventDisabledButtons()
|
|
||||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
|
||||||
|
|
||||||
$(".nav-sidebar").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
|
|
||||||
|
|
||||||
# Click a .js-select-on-focus field, select the contents
|
|
||||||
$(".js-select-on-focus").on "focusin", ->
|
|
||||||
# Prevent a mouseup event from deselecting the input
|
|
||||||
$(this).select().one 'mouseup', (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
$('.remove-row').bind 'ajax:success', ->
|
|
||||||
$(this).closest('li').fadeOut()
|
|
||||||
|
|
||||||
$('.js-remove-tr').bind 'ajax:before', ->
|
|
||||||
$(this).hide()
|
|
||||||
|
|
||||||
$('.js-remove-tr').bind 'ajax:success', ->
|
|
||||||
$(this).closest('tr').fadeOut()
|
|
||||||
|
|
||||||
# Initialize select2 selects
|
|
||||||
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
|
|
||||||
|
|
||||||
# Close select2 on escape
|
|
||||||
$('.js-select2').bind 'select2-close', ->
|
|
||||||
setTimeout ( ->
|
|
||||||
$('.select2-container-active').removeClass('select2-container-active')
|
|
||||||
$(':focus').blur()
|
|
||||||
), 1
|
|
||||||
|
|
||||||
# Initialize tooltips
|
|
||||||
$body.tooltip(
|
|
||||||
selector: '.has-tooltip, [data-toggle="tooltip"]'
|
|
||||||
placement: (_, el) ->
|
|
||||||
$el = $(el)
|
|
||||||
$el.data('placement') || 'bottom'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Form submitter
|
|
||||||
$('.trigger-submit').on 'change', ->
|
|
||||||
$(@).parents('form').submit()
|
|
||||||
|
|
||||||
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
|
|
||||||
|
|
||||||
# Flash
|
|
||||||
if (flash = $(".flash-container")).length > 0
|
|
||||||
flash.click -> $(@).fadeOut()
|
|
||||||
flash.show()
|
|
||||||
|
|
||||||
# Disable form buttons while a form is submitting
|
|
||||||
$body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
|
|
||||||
buttons = $('[type="submit"]', @)
|
|
||||||
|
|
||||||
switch e.type
|
|
||||||
when 'ajax:beforeSend', 'submit'
|
|
||||||
buttons.disable()
|
|
||||||
else
|
|
||||||
buttons.enable()
|
|
||||||
|
|
||||||
$(document).ajaxError (e, xhrObj, xhrSetting, xhrErrorText) ->
|
|
||||||
|
|
||||||
if xhrObj.status is 401
|
|
||||||
new Flash 'You need to be logged in.', 'alert'
|
|
||||||
|
|
||||||
else if xhrObj.status in [ 404, 500 ]
|
|
||||||
new Flash 'Something went wrong on our end.', 'alert'
|
|
||||||
|
|
||||||
|
|
||||||
# Show/Hide the profile menu when hovering the account box
|
|
||||||
$('.account-box').hover -> $(@).toggleClass('hover')
|
|
||||||
|
|
||||||
# Commit show suppressed diff
|
|
||||||
$document.on 'click', '.diff-content .js-show-suppressed-diff', ->
|
|
||||||
$container = $(@).parent()
|
|
||||||
$container.next('table').show()
|
|
||||||
$container.remove()
|
|
||||||
|
|
||||||
$('.navbar-toggle').on 'click', ->
|
|
||||||
$('.header-content .title').toggle()
|
|
||||||
$('.header-content .header-logo').toggle()
|
|
||||||
$('.header-content .navbar-collapse').toggle()
|
|
||||||
$('.navbar-toggle').toggleClass('active')
|
|
||||||
|
|
||||||
# Show/hide comments on diff
|
|
||||||
$body.on "click", ".js-toggle-diff-comments", (e) ->
|
|
||||||
$(@).toggleClass('active')
|
|
||||||
$(@).closest(".diff-file").find(".notes_holder").toggle()
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
$document.off "click", '.js-confirm-danger'
|
|
||||||
$document.on "click", '.js-confirm-danger', (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
btn = $(e.target)
|
|
||||||
text = btn.data("confirm-danger-message")
|
|
||||||
form = btn.closest("form")
|
|
||||||
new ConfirmDangerModal(form, text)
|
|
||||||
|
|
||||||
|
|
||||||
$document.on 'click', 'button', ->
|
|
||||||
$(this).blur()
|
|
||||||
|
|
||||||
$('input[type="search"]').each ->
|
|
||||||
$this = $(this)
|
|
||||||
$this.attr 'value', $this.val()
|
|
||||||
return
|
|
||||||
|
|
||||||
$document
|
|
||||||
.off 'keyup', 'input[type="search"]'
|
|
||||||
.on 'keyup', 'input[type="search"]' , (e) ->
|
|
||||||
$this = $(this)
|
|
||||||
$this.attr 'value', $this.val()
|
|
||||||
|
|
||||||
$sidebarGutterToggle = $('.js-sidebar-toggle')
|
|
||||||
|
|
||||||
$document
|
|
||||||
.off 'breakpoint:change'
|
|
||||||
.on 'breakpoint:change', (e, breakpoint) ->
|
|
||||||
if breakpoint is 'sm' or breakpoint is 'xs'
|
|
||||||
$gutterIcon = $sidebarGutterToggle.find('i')
|
|
||||||
if $gutterIcon.hasClass('fa-angle-double-right')
|
|
||||||
$sidebarGutterToggle.trigger('click')
|
|
||||||
|
|
||||||
fitSidebarForSize = ->
|
|
||||||
oldBootstrapBreakpoint = bootstrapBreakpoint
|
|
||||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
|
||||||
if bootstrapBreakpoint != oldBootstrapBreakpoint
|
|
||||||
$document.trigger('breakpoint:change', [bootstrapBreakpoint])
|
|
||||||
|
|
||||||
checkInitialSidebarSize = ->
|
|
||||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
|
||||||
if bootstrapBreakpoint is "xs" or "sm"
|
|
||||||
$document.trigger('breakpoint:change', [bootstrapBreakpoint])
|
|
||||||
|
|
||||||
$window
|
|
||||||
.off "resize.app"
|
|
||||||
.on "resize.app", (e) ->
|
|
||||||
fitSidebarForSize()
|
|
||||||
|
|
||||||
gl.awardsHandler = new AwardsHandler()
|
|
||||||
checkInitialSidebarSize()
|
|
||||||
new Aside()
|
|
||||||
|
|
||||||
# Sidenav pinning
|
|
||||||
if $window.width() < 1024 and $.cookie('pin_nav') is 'true'
|
|
||||||
$.cookie('pin_nav', 'false', { path: '/', expires: 365 * 10 })
|
|
||||||
$('.page-with-sidebar')
|
|
||||||
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
|
|
||||||
.removeClass('page-sidebar-pinned')
|
|
||||||
$('.navbar-fixed-top').removeClass('header-pinned-nav')
|
|
||||||
|
|
||||||
$document
|
|
||||||
.off 'click', '.js-nav-pin'
|
|
||||||
.on 'click', '.js-nav-pin', (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
$pinBtn = $(e.currentTarget)
|
|
||||||
$page = $ '.page-with-sidebar'
|
|
||||||
$topNav = $ '.navbar-fixed-top'
|
|
||||||
$tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
|
|
||||||
doPinNav = not $page.is('.page-sidebar-pinned')
|
|
||||||
tooltipText = 'Pin navigation'
|
|
||||||
|
|
||||||
$(this).toggleClass 'is-active'
|
|
||||||
|
|
||||||
if doPinNav
|
|
||||||
$page.addClass('page-sidebar-pinned')
|
|
||||||
$topNav.addClass('header-pinned-nav')
|
|
||||||
else
|
|
||||||
$tooltip.remove() # Remove it immediately when collapsing the sidebar
|
|
||||||
$page.removeClass('page-sidebar-pinned')
|
|
||||||
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
|
|
||||||
$topNav.removeClass('header-pinned-nav')
|
|
||||||
.toggleClass('header-collapsed header-expanded')
|
|
||||||
|
|
||||||
# Save settings
|
|
||||||
$.cookie 'pin_nav', doPinNav, { path: '/', expires: 365 * 10 }
|
|
||||||
|
|
||||||
if $.cookie('pin_nav') is 'true' or doPinNav
|
|
||||||
tooltipText = 'Unpin navigation'
|
|
||||||
|
|
||||||
# Update tooltip text immediately
|
|
||||||
$tooltip.find('.tooltip-inner').text(tooltipText)
|
|
||||||
|
|
||||||
# Persist tooltip title
|
|
||||||
$pinBtn.attr('title', tooltipText).tooltip('fixTitle')
|
|
26
app/assets/javascripts/aside.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
(function() {
|
||||||
|
this.Aside = (function() {
|
||||||
|
function Aside() {
|
||||||
|
$(document).off("click", "a.show-aside");
|
||||||
|
$(document).on("click", 'a.show-aside', function(e) {
|
||||||
|
var btn, icon;
|
||||||
|
e.preventDefault();
|
||||||
|
btn = $(e.currentTarget);
|
||||||
|
icon = btn.find('i');
|
||||||
|
if (icon.hasClass('fa-angle-left')) {
|
||||||
|
btn.parent().find('section').hide();
|
||||||
|
btn.parent().find('aside').fadeIn();
|
||||||
|
return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
|
||||||
|
} else {
|
||||||
|
btn.parent().find('aside').hide();
|
||||||
|
btn.parent().find('section').fadeIn();
|
||||||
|
return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Aside;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,16 +0,0 @@
|
||||||
class @Aside
|
|
||||||
constructor: ->
|
|
||||||
$(document).off "click", "a.show-aside"
|
|
||||||
$(document).on "click", 'a.show-aside', (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
btn = $(e.currentTarget)
|
|
||||||
icon = btn.find('i')
|
|
||||||
|
|
||||||
if icon.hasClass('fa-angle-left')
|
|
||||||
btn.parent().find('section').hide()
|
|
||||||
btn.parent().find('aside').fadeIn()
|
|
||||||
icon.removeClass('fa-angle-left').addClass('fa-angle-right')
|
|
||||||
else
|
|
||||||
btn.parent().find('aside').hide()
|
|
||||||
btn.parent().find('section').fadeIn()
|
|
||||||
icon.removeClass('fa-angle-right').addClass('fa-angle-left')
|
|
63
app/assets/javascripts/autosave.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
(function() {
|
||||||
|
this.Autosave = (function() {
|
||||||
|
function Autosave(field, key) {
|
||||||
|
this.field = field;
|
||||||
|
if (key.join != null) {
|
||||||
|
key = key.join("/");
|
||||||
|
}
|
||||||
|
this.key = "autosave/" + key;
|
||||||
|
this.field.data("autosave", this);
|
||||||
|
this.restore();
|
||||||
|
this.field.on("input", (function(_this) {
|
||||||
|
return function() {
|
||||||
|
return _this.save();
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
Autosave.prototype.restore = function() {
|
||||||
|
var e, error, text;
|
||||||
|
if (window.localStorage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
text = window.localStorage.getItem(this.key);
|
||||||
|
} catch (error) {
|
||||||
|
e = error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((text != null ? text.length : void 0) > 0) {
|
||||||
|
this.field.val(text);
|
||||||
|
}
|
||||||
|
return this.field.trigger("input");
|
||||||
|
};
|
||||||
|
|
||||||
|
Autosave.prototype.save = function() {
|
||||||
|
var text;
|
||||||
|
if (window.localStorage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
text = this.field.val();
|
||||||
|
if ((text != null ? text.length : void 0) > 0) {
|
||||||
|
try {
|
||||||
|
return window.localStorage.setItem(this.key, text);
|
||||||
|
} catch (undefined) {}
|
||||||
|
} else {
|
||||||
|
return this.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Autosave.prototype.reset = function() {
|
||||||
|
if (window.localStorage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return window.localStorage.removeItem(this.key);
|
||||||
|
} catch (undefined) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Autosave;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,39 +0,0 @@
|
||||||
class @Autosave
|
|
||||||
constructor: (field, key) ->
|
|
||||||
@field = field
|
|
||||||
|
|
||||||
key = key.join("/") if key.join?
|
|
||||||
@key = "autosave/#{key}"
|
|
||||||
|
|
||||||
@field.data "autosave", this
|
|
||||||
|
|
||||||
@restore()
|
|
||||||
|
|
||||||
@field.on "input", => @save()
|
|
||||||
|
|
||||||
restore: ->
|
|
||||||
return unless window.localStorage?
|
|
||||||
|
|
||||||
try
|
|
||||||
text = window.localStorage.getItem @key
|
|
||||||
catch e
|
|
||||||
return
|
|
||||||
|
|
||||||
@field.val text if text?.length > 0
|
|
||||||
@field.trigger "input"
|
|
||||||
|
|
||||||
save: ->
|
|
||||||
return unless window.localStorage?
|
|
||||||
|
|
||||||
text = @field.val()
|
|
||||||
if text?.length > 0
|
|
||||||
try
|
|
||||||
window.localStorage.setItem @key, text
|
|
||||||
else
|
|
||||||
@reset()
|
|
||||||
|
|
||||||
reset: ->
|
|
||||||
return unless window.localStorage?
|
|
||||||
|
|
||||||
try
|
|
||||||
window.localStorage.removeItem @key
|
|
|
@ -1,372 +0,0 @@
|
||||||
class @AwardsHandler
|
|
||||||
|
|
||||||
constructor: ->
|
|
||||||
|
|
||||||
@aliases = gl.emojiAliases()
|
|
||||||
|
|
||||||
$(document)
|
|
||||||
.off 'click', '.js-add-award'
|
|
||||||
.on 'click', '.js-add-award', (e) =>
|
|
||||||
e.stopPropagation()
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
@showEmojiMenu $(e.currentTarget)
|
|
||||||
|
|
||||||
$('html').on 'click', (e) ->
|
|
||||||
$target = $ e.target
|
|
||||||
|
|
||||||
unless $target.closest('.emoji-menu-content').length
|
|
||||||
$('.js-awards-block.current').removeClass 'current'
|
|
||||||
|
|
||||||
unless $target.closest('.emoji-menu').length
|
|
||||||
if $('.emoji-menu').is(':visible')
|
|
||||||
$('.js-add-award.is-active').removeClass 'is-active'
|
|
||||||
$('.emoji-menu').removeClass 'is-visible'
|
|
||||||
|
|
||||||
$(document)
|
|
||||||
.off 'click', '.js-emoji-btn'
|
|
||||||
.on 'click', '.js-emoji-btn', (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
$target = $ e.currentTarget
|
|
||||||
emoji = $target.find('.icon').data 'emoji'
|
|
||||||
|
|
||||||
$target.closest('.js-awards-block').addClass 'current'
|
|
||||||
@addAward @getVotesBlock(), @getAwardUrl(), emoji
|
|
||||||
|
|
||||||
|
|
||||||
showEmojiMenu: ($addBtn) ->
|
|
||||||
|
|
||||||
$menu = $ '.emoji-menu'
|
|
||||||
|
|
||||||
if $addBtn.hasClass 'js-note-emoji'
|
|
||||||
$addBtn.closest('.note').find('.js-awards-block').addClass 'current'
|
|
||||||
else
|
|
||||||
$addBtn.closest('.js-awards-block').addClass 'current'
|
|
||||||
|
|
||||||
if $menu.length
|
|
||||||
$holder = $addBtn.closest('.js-award-holder')
|
|
||||||
|
|
||||||
if $menu.is '.is-visible'
|
|
||||||
$addBtn.removeClass 'is-active'
|
|
||||||
$menu.removeClass 'is-visible'
|
|
||||||
$('#emoji_search').blur()
|
|
||||||
else
|
|
||||||
$addBtn.addClass 'is-active'
|
|
||||||
@positionMenu($menu, $addBtn)
|
|
||||||
|
|
||||||
$menu.addClass 'is-visible'
|
|
||||||
$('#emoji_search').focus()
|
|
||||||
else
|
|
||||||
$addBtn.addClass 'is-loading is-active'
|
|
||||||
url = @getAwardMenuUrl()
|
|
||||||
|
|
||||||
@createEmojiMenu url, =>
|
|
||||||
$addBtn.removeClass 'is-loading'
|
|
||||||
$menu = $('.emoji-menu')
|
|
||||||
@positionMenu($menu, $addBtn)
|
|
||||||
@renderFrequentlyUsedBlock() unless @frequentEmojiBlockRendered
|
|
||||||
|
|
||||||
setTimeout =>
|
|
||||||
$menu.addClass 'is-visible'
|
|
||||||
$('#emoji_search').focus()
|
|
||||||
@setupSearch()
|
|
||||||
, 200
|
|
||||||
|
|
||||||
|
|
||||||
createEmojiMenu: (awardMenuUrl, callback) ->
|
|
||||||
|
|
||||||
$.get awardMenuUrl, (response) ->
|
|
||||||
$('body').append response
|
|
||||||
callback()
|
|
||||||
|
|
||||||
|
|
||||||
positionMenu: ($menu, $addBtn) ->
|
|
||||||
|
|
||||||
position = $addBtn.data('position')
|
|
||||||
|
|
||||||
# The menu could potentially be off-screen or in a hidden overflow element
|
|
||||||
# So we position the element absolute in the body
|
|
||||||
css =
|
|
||||||
top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px"
|
|
||||||
|
|
||||||
if position? and position is 'right'
|
|
||||||
css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px"
|
|
||||||
$menu.addClass 'is-aligned-right'
|
|
||||||
else
|
|
||||||
css.left = "#{$addBtn.offset().left}px"
|
|
||||||
$menu.removeClass 'is-aligned-right'
|
|
||||||
|
|
||||||
$menu.css(css)
|
|
||||||
|
|
||||||
|
|
||||||
addAward: (votesBlock, awardUrl, emoji, checkMutuality = true, callback) ->
|
|
||||||
|
|
||||||
emoji = @normilizeEmojiName emoji
|
|
||||||
|
|
||||||
@postEmoji awardUrl, emoji, =>
|
|
||||||
@addAwardToEmojiBar votesBlock, emoji, checkMutuality
|
|
||||||
callback?()
|
|
||||||
|
|
||||||
$('.emoji-menu').removeClass 'is-visible'
|
|
||||||
|
|
||||||
|
|
||||||
addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = true) ->
|
|
||||||
|
|
||||||
@checkMutuality votesBlock, emoji if checkForMutuality
|
|
||||||
@addEmojiToFrequentlyUsedList emoji
|
|
||||||
|
|
||||||
emoji = @normilizeEmojiName emoji
|
|
||||||
$emojiButton = @findEmojiIcon(votesBlock, emoji).parent()
|
|
||||||
|
|
||||||
if $emojiButton.length > 0
|
|
||||||
if @isActive $emojiButton
|
|
||||||
@decrementCounter $emojiButton, emoji
|
|
||||||
else
|
|
||||||
counter = $emojiButton.find '.js-counter'
|
|
||||||
counter.text parseInt(counter.text()) + 1
|
|
||||||
$emojiButton.addClass 'active'
|
|
||||||
@addMeToUserList votesBlock, emoji
|
|
||||||
@animateEmoji $emojiButton
|
|
||||||
else
|
|
||||||
votesBlock.removeClass 'hidden'
|
|
||||||
@createEmoji votesBlock, emoji
|
|
||||||
|
|
||||||
|
|
||||||
getVotesBlock: ->
|
|
||||||
|
|
||||||
currentBlock = $ '.js-awards-block.current'
|
|
||||||
return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0
|
|
||||||
|
|
||||||
|
|
||||||
getAwardUrl: -> return @getVotesBlock().data 'award-url'
|
|
||||||
|
|
||||||
|
|
||||||
checkMutuality: (votesBlock, emoji) ->
|
|
||||||
|
|
||||||
awardUrl = @getAwardUrl()
|
|
||||||
|
|
||||||
if emoji in [ 'thumbsup', 'thumbsdown' ]
|
|
||||||
mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
|
|
||||||
$emojiButton = votesBlock.find("[data-emoji=#{mutualVote}]").parent()
|
|
||||||
isAlreadyVoted = $emojiButton.hasClass 'active'
|
|
||||||
|
|
||||||
if isAlreadyVoted
|
|
||||||
@showEmojiLoader $emojiButton
|
|
||||||
@addAward votesBlock, awardUrl, mutualVote, false, ->
|
|
||||||
$emojiButton.removeClass 'is-loading'
|
|
||||||
|
|
||||||
|
|
||||||
showEmojiLoader: ($emojiButton) ->
|
|
||||||
|
|
||||||
$loader = $emojiButton.find '.fa-spinner'
|
|
||||||
|
|
||||||
unless $loader.length
|
|
||||||
$emojiButton.append '<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>'
|
|
||||||
|
|
||||||
$emojiButton.addClass 'is-loading'
|
|
||||||
|
|
||||||
|
|
||||||
isActive: ($emojiButton) -> $emojiButton.hasClass 'active'
|
|
||||||
|
|
||||||
|
|
||||||
decrementCounter: ($emojiButton, emoji) ->
|
|
||||||
|
|
||||||
counter = $ '.js-counter', $emojiButton
|
|
||||||
counterNumber = parseInt counter.text(), 10
|
|
||||||
|
|
||||||
if counterNumber > 1
|
|
||||||
counter.text counterNumber - 1
|
|
||||||
@removeMeFromUserList $emojiButton, emoji
|
|
||||||
else if emoji is 'thumbsup' or emoji is 'thumbsdown'
|
|
||||||
$emojiButton.tooltip 'destroy'
|
|
||||||
counter.text '0'
|
|
||||||
@removeMeFromUserList $emojiButton, emoji
|
|
||||||
@removeEmoji $emojiButton if $emojiButton.parents('.note').length
|
|
||||||
else
|
|
||||||
@removeEmoji $emojiButton
|
|
||||||
|
|
||||||
$emojiButton.removeClass 'active'
|
|
||||||
|
|
||||||
|
|
||||||
removeEmoji: ($emojiButton) ->
|
|
||||||
|
|
||||||
$emojiButton.tooltip('destroy')
|
|
||||||
$emojiButton.remove()
|
|
||||||
|
|
||||||
$votesBlock = @getVotesBlock()
|
|
||||||
|
|
||||||
if $votesBlock.find('.js-emoji-btn').length is 0
|
|
||||||
$votesBlock.addClass 'hidden'
|
|
||||||
|
|
||||||
|
|
||||||
getAwardTooltip: ($awardBlock) ->
|
|
||||||
|
|
||||||
return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''
|
|
||||||
|
|
||||||
|
|
||||||
removeMeFromUserList: ($emojiButton, emoji) ->
|
|
||||||
|
|
||||||
awardBlock = $emojiButton
|
|
||||||
originalTitle = @getAwardTooltip awardBlock
|
|
||||||
|
|
||||||
authors = originalTitle.split ', '
|
|
||||||
authors.splice authors.indexOf('me'), 1
|
|
||||||
|
|
||||||
newAuthors = authors.join ', '
|
|
||||||
|
|
||||||
awardBlock
|
|
||||||
.closest '.js-emoji-btn'
|
|
||||||
.removeData 'original-title'
|
|
||||||
.attr 'data-original-title', newAuthors
|
|
||||||
|
|
||||||
@resetTooltip awardBlock
|
|
||||||
|
|
||||||
|
|
||||||
addMeToUserList: (votesBlock, emoji) ->
|
|
||||||
|
|
||||||
awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
|
|
||||||
origTitle = @getAwardTooltip awardBlock
|
|
||||||
users = []
|
|
||||||
|
|
||||||
if origTitle
|
|
||||||
users = origTitle.trim().split ', '
|
|
||||||
|
|
||||||
users.push 'me'
|
|
||||||
awardBlock.attr 'title', users.join ', '
|
|
||||||
|
|
||||||
@resetTooltip awardBlock
|
|
||||||
|
|
||||||
|
|
||||||
resetTooltip: (award) ->
|
|
||||||
|
|
||||||
award.tooltip 'destroy'
|
|
||||||
|
|
||||||
# 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
|
|
||||||
cb = -> award.tooltip()
|
|
||||||
setTimeout cb, 200
|
|
||||||
|
|
||||||
|
|
||||||
createEmoji_: (votesBlock, emoji) ->
|
|
||||||
|
|
||||||
emojiCssClass = @resolveNameToCssClass emoji
|
|
||||||
buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
|
|
||||||
<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>
|
|
||||||
<span class='award-control-text js-counter'>1</span>
|
|
||||||
</button>"
|
|
||||||
|
|
||||||
$emojiButton = $ buttonHtml
|
|
||||||
$emojiButton
|
|
||||||
.insertBefore votesBlock.find '.js-award-holder'
|
|
||||||
.find '.emoji-icon'
|
|
||||||
.data 'emoji', emoji
|
|
||||||
|
|
||||||
@animateEmoji $emojiButton
|
|
||||||
$('.award-control').tooltip()
|
|
||||||
votesBlock.removeClass 'current'
|
|
||||||
|
|
||||||
|
|
||||||
animateEmoji: ($emoji) ->
|
|
||||||
|
|
||||||
className = 'pulse animated'
|
|
||||||
|
|
||||||
$emoji.addClass className
|
|
||||||
setTimeout (-> $emoji.removeClass className), 321
|
|
||||||
|
|
||||||
|
|
||||||
createEmoji: (votesBlock, emoji) ->
|
|
||||||
|
|
||||||
if $('.emoji-menu').length
|
|
||||||
return @createEmoji_ votesBlock, emoji
|
|
||||||
|
|
||||||
@createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
|
|
||||||
|
|
||||||
|
|
||||||
getAwardMenuUrl: -> return gon.award_menu_url
|
|
||||||
|
|
||||||
|
|
||||||
resolveNameToCssClass: (emoji) ->
|
|
||||||
|
|
||||||
emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"
|
|
||||||
|
|
||||||
if emojiIcon.length > 0
|
|
||||||
unicodeName = emojiIcon.data 'unicode-name'
|
|
||||||
else
|
|
||||||
# Find by alias
|
|
||||||
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data 'unicode-name'
|
|
||||||
|
|
||||||
return "emoji-#{unicodeName}"
|
|
||||||
|
|
||||||
|
|
||||||
postEmoji: (awardUrl, emoji, callback) ->
|
|
||||||
|
|
||||||
$.post awardUrl, { name: emoji }, (data) ->
|
|
||||||
callback() if data.ok
|
|
||||||
|
|
||||||
|
|
||||||
findEmojiIcon: (votesBlock, emoji) ->
|
|
||||||
|
|
||||||
return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"
|
|
||||||
|
|
||||||
|
|
||||||
scrollToAwards: ->
|
|
||||||
|
|
||||||
options = scrollTop: $('.awards').offset().top - 110
|
|
||||||
$('body, html').animate options, 200
|
|
||||||
|
|
||||||
|
|
||||||
normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji
|
|
||||||
|
|
||||||
|
|
||||||
addEmojiToFrequentlyUsedList: (emoji) ->
|
|
||||||
|
|
||||||
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
|
|
||||||
frequentlyUsedEmojis.push emoji
|
|
||||||
$.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }
|
|
||||||
|
|
||||||
|
|
||||||
getFrequentlyUsedEmojis: ->
|
|
||||||
|
|
||||||
frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
|
|
||||||
return _.compact _.uniq frequentlyUsedEmojis
|
|
||||||
|
|
||||||
|
|
||||||
renderFrequentlyUsedBlock: ->
|
|
||||||
|
|
||||||
if $.cookie 'frequently_used_emojis'
|
|
||||||
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
|
|
||||||
|
|
||||||
ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>")
|
|
||||||
|
|
||||||
for emoji in frequentlyUsedEmojis
|
|
||||||
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
|
|
||||||
|
|
||||||
$('.emoji-menu-content')
|
|
||||||
.prepend(ul)
|
|
||||||
.prepend($('<h5>').text('Frequently used'))
|
|
||||||
|
|
||||||
@frequentEmojiBlockRendered = true
|
|
||||||
|
|
||||||
|
|
||||||
setupSearch: ->
|
|
||||||
|
|
||||||
$('input.emoji-search').on 'keyup', (ev) =>
|
|
||||||
term = $(ev.target).val()
|
|
||||||
|
|
||||||
# Clean previous search results
|
|
||||||
$('ul.emoji-menu-search, h5.emoji-search').remove()
|
|
||||||
|
|
||||||
if term
|
|
||||||
# Generate a search result block
|
|
||||||
h5 = $('<h5>').text('Search results')
|
|
||||||
found_emojis = @searchEmojis(term).show()
|
|
||||||
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis)
|
|
||||||
$('.emoji-menu-content ul, .emoji-menu-content h5').hide()
|
|
||||||
$('.emoji-menu-content').append(h5).append(ul)
|
|
||||||
else
|
|
||||||
$('.emoji-menu-content').children().show()
|
|
||||||
|
|
||||||
|
|
||||||
searchEmojis: (term) ->
|
|
||||||
|
|
||||||
$(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='#{term}']").closest('li').clone()
|
|
368
app/assets/javascripts/awards_handler.js
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
(function() {
|
||||||
|
this.AwardsHandler = (function() {
|
||||||
|
function AwardsHandler() {
|
||||||
|
this.aliases = gl.emojiAliases();
|
||||||
|
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
|
||||||
|
return function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
return _this.showEmojiMenu($(e.currentTarget));
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
$('html').on('click', function(e) {
|
||||||
|
var $target;
|
||||||
|
$target = $(e.target);
|
||||||
|
if (!$target.closest('.emoji-menu-content').length) {
|
||||||
|
$('.js-awards-block.current').removeClass('current');
|
||||||
|
}
|
||||||
|
if (!$target.closest('.emoji-menu').length) {
|
||||||
|
if ($('.emoji-menu').is(':visible')) {
|
||||||
|
$('.js-add-award.is-active').removeClass('is-active');
|
||||||
|
return $('.emoji-menu').removeClass('is-visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$(document).off('click', '.js-emoji-btn').on('click', '.js-emoji-btn', (function(_this) {
|
||||||
|
return function(e) {
|
||||||
|
var $target, emoji;
|
||||||
|
e.preventDefault();
|
||||||
|
$target = $(e.currentTarget);
|
||||||
|
emoji = $target.find('.icon').data('emoji');
|
||||||
|
$target.closest('.js-awards-block').addClass('current');
|
||||||
|
return _this.addAward(_this.getVotesBlock(), _this.getAwardUrl(), emoji);
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
AwardsHandler.prototype.showEmojiMenu = function($addBtn) {
|
||||||
|
var $holder, $menu, url;
|
||||||
|
$menu = $('.emoji-menu');
|
||||||
|
if ($addBtn.hasClass('js-note-emoji')) {
|
||||||
|
$addBtn.closest('.note').find('.js-awards-block').addClass('current');
|
||||||
|
} else {
|
||||||
|
$addBtn.closest('.js-awards-block').addClass('current');
|
||||||
|
}
|
||||||
|
if ($menu.length) {
|
||||||
|
$holder = $addBtn.closest('.js-award-holder');
|
||||||
|
if ($menu.is('.is-visible')) {
|
||||||
|
$addBtn.removeClass('is-active');
|
||||||
|
$menu.removeClass('is-visible');
|
||||||
|
return $('#emoji_search').blur();
|
||||||
|
} else {
|
||||||
|
$addBtn.addClass('is-active');
|
||||||
|
this.positionMenu($menu, $addBtn);
|
||||||
|
$menu.addClass('is-visible');
|
||||||
|
return $('#emoji_search').focus();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$addBtn.addClass('is-loading is-active');
|
||||||
|
url = this.getAwardMenuUrl();
|
||||||
|
return this.createEmojiMenu(url, (function(_this) {
|
||||||
|
return function() {
|
||||||
|
$addBtn.removeClass('is-loading');
|
||||||
|
$menu = $('.emoji-menu');
|
||||||
|
_this.positionMenu($menu, $addBtn);
|
||||||
|
if (!_this.frequentEmojiBlockRendered) {
|
||||||
|
_this.renderFrequentlyUsedBlock();
|
||||||
|
}
|
||||||
|
return setTimeout(function() {
|
||||||
|
$menu.addClass('is-visible');
|
||||||
|
$('#emoji_search').focus();
|
||||||
|
return _this.setupSearch();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.createEmojiMenu = function(awardMenuUrl, callback) {
|
||||||
|
return $.get(awardMenuUrl, function(response) {
|
||||||
|
$('body').append(response);
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.positionMenu = function($menu, $addBtn) {
|
||||||
|
var css, position;
|
||||||
|
position = $addBtn.data('position');
|
||||||
|
css = {
|
||||||
|
top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
|
||||||
|
};
|
||||||
|
if ((position != null) && position === 'right') {
|
||||||
|
css.left = (($addBtn.offset().left - $menu.outerWidth()) + 20) + "px";
|
||||||
|
$menu.addClass('is-aligned-right');
|
||||||
|
} else {
|
||||||
|
css.left = ($addBtn.offset().left) + "px";
|
||||||
|
$menu.removeClass('is-aligned-right');
|
||||||
|
}
|
||||||
|
return $menu.css(css);
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.addAward = function(votesBlock, awardUrl, emoji, checkMutuality, callback) {
|
||||||
|
if (checkMutuality == null) {
|
||||||
|
checkMutuality = true;
|
||||||
|
}
|
||||||
|
emoji = this.normilizeEmojiName(emoji);
|
||||||
|
this.postEmoji(awardUrl, emoji, (function(_this) {
|
||||||
|
return function() {
|
||||||
|
_this.addAwardToEmojiBar(votesBlock, emoji, checkMutuality);
|
||||||
|
return typeof callback === "function" ? callback() : void 0;
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
return $('.emoji-menu').removeClass('is-visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.addAwardToEmojiBar = function(votesBlock, emoji, checkForMutuality) {
|
||||||
|
var $emojiButton, counter;
|
||||||
|
if (checkForMutuality == null) {
|
||||||
|
checkForMutuality = true;
|
||||||
|
}
|
||||||
|
if (checkForMutuality) {
|
||||||
|
this.checkMutuality(votesBlock, emoji);
|
||||||
|
}
|
||||||
|
this.addEmojiToFrequentlyUsedList(emoji);
|
||||||
|
emoji = this.normilizeEmojiName(emoji);
|
||||||
|
$emojiButton = this.findEmojiIcon(votesBlock, emoji).parent();
|
||||||
|
if ($emojiButton.length > 0) {
|
||||||
|
if (this.isActive($emojiButton)) {
|
||||||
|
return this.decrementCounter($emojiButton, emoji);
|
||||||
|
} else {
|
||||||
|
counter = $emojiButton.find('.js-counter');
|
||||||
|
counter.text(parseInt(counter.text()) + 1);
|
||||||
|
$emojiButton.addClass('active');
|
||||||
|
this.addMeToUserList(votesBlock, emoji);
|
||||||
|
return this.animateEmoji($emojiButton);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
votesBlock.removeClass('hidden');
|
||||||
|
return this.createEmoji(votesBlock, emoji);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.getVotesBlock = function() {
|
||||||
|
var currentBlock;
|
||||||
|
currentBlock = $('.js-awards-block.current');
|
||||||
|
if (currentBlock.length) {
|
||||||
|
return currentBlock;
|
||||||
|
} else {
|
||||||
|
return $('.js-awards-block').eq(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.getAwardUrl = function() {
|
||||||
|
return this.getVotesBlock().data('award-url');
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.checkMutuality = function(votesBlock, emoji) {
|
||||||
|
var $emojiButton, awardUrl, isAlreadyVoted, mutualVote;
|
||||||
|
awardUrl = this.getAwardUrl();
|
||||||
|
if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
|
||||||
|
mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
|
||||||
|
$emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent();
|
||||||
|
isAlreadyVoted = $emojiButton.hasClass('active');
|
||||||
|
if (isAlreadyVoted) {
|
||||||
|
this.addAward(votesBlock, awardUrl, mutualVote, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.isActive = function($emojiButton) {
|
||||||
|
return $emojiButton.hasClass('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.decrementCounter = function($emojiButton, emoji) {
|
||||||
|
var counter, counterNumber;
|
||||||
|
counter = $('.js-counter', $emojiButton);
|
||||||
|
counterNumber = parseInt(counter.text(), 10);
|
||||||
|
if (counterNumber > 1) {
|
||||||
|
counter.text(counterNumber - 1);
|
||||||
|
this.removeMeFromUserList($emojiButton, emoji);
|
||||||
|
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
|
||||||
|
$emojiButton.tooltip('destroy');
|
||||||
|
counter.text('0');
|
||||||
|
this.removeMeFromUserList($emojiButton, emoji);
|
||||||
|
if ($emojiButton.parents('.note').length) {
|
||||||
|
this.removeEmoji($emojiButton);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.removeEmoji($emojiButton);
|
||||||
|
}
|
||||||
|
return $emojiButton.removeClass('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.removeEmoji = function($emojiButton) {
|
||||||
|
var $votesBlock;
|
||||||
|
$emojiButton.tooltip('destroy');
|
||||||
|
$emojiButton.remove();
|
||||||
|
$votesBlock = this.getVotesBlock();
|
||||||
|
if ($votesBlock.find('.js-emoji-btn').length === 0) {
|
||||||
|
return $votesBlock.addClass('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.getAwardTooltip = function($awardBlock) {
|
||||||
|
return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.removeMeFromUserList = function($emojiButton, emoji) {
|
||||||
|
var authors, awardBlock, newAuthors, originalTitle;
|
||||||
|
awardBlock = $emojiButton;
|
||||||
|
originalTitle = this.getAwardTooltip(awardBlock);
|
||||||
|
authors = originalTitle.split(', ');
|
||||||
|
authors.splice(authors.indexOf('me'), 1);
|
||||||
|
newAuthors = authors.join(', ');
|
||||||
|
awardBlock.closest('.js-emoji-btn').removeData('original-title').attr('data-original-title', newAuthors);
|
||||||
|
return this.resetTooltip(awardBlock);
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.addMeToUserList = function(votesBlock, emoji) {
|
||||||
|
var awardBlock, origTitle, users;
|
||||||
|
awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
|
||||||
|
origTitle = this.getAwardTooltip(awardBlock);
|
||||||
|
users = [];
|
||||||
|
if (origTitle) {
|
||||||
|
users = origTitle.trim().split(', ');
|
||||||
|
}
|
||||||
|
users.push('me');
|
||||||
|
awardBlock.attr('title', users.join(', '));
|
||||||
|
return this.resetTooltip(awardBlock);
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.resetTooltip = function(award) {
|
||||||
|
var cb;
|
||||||
|
award.tooltip('destroy');
|
||||||
|
cb = function() {
|
||||||
|
return award.tooltip();
|
||||||
|
};
|
||||||
|
return setTimeout(cb, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.createEmoji_ = function(votesBlock, emoji) {
|
||||||
|
var $emojiButton, buttonHtml, emojiCssClass;
|
||||||
|
emojiCssClass = this.resolveNameToCssClass(emoji);
|
||||||
|
buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'> <div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div> <span class='award-control-text js-counter'>1</span> </button>";
|
||||||
|
$emojiButton = $(buttonHtml);
|
||||||
|
$emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('emoji', emoji);
|
||||||
|
this.animateEmoji($emojiButton);
|
||||||
|
$('.award-control').tooltip();
|
||||||
|
return votesBlock.removeClass('current');
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.animateEmoji = function($emoji) {
|
||||||
|
var className;
|
||||||
|
className = 'pulse animated';
|
||||||
|
$emoji.addClass(className);
|
||||||
|
return setTimeout((function() {
|
||||||
|
return $emoji.removeClass(className);
|
||||||
|
}), 321);
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
|
||||||
|
if ($('.emoji-menu').length) {
|
||||||
|
return this.createEmoji_(votesBlock, emoji);
|
||||||
|
}
|
||||||
|
return this.createEmojiMenu(this.getAwardMenuUrl(), (function(_this) {
|
||||||
|
return function() {
|
||||||
|
return _this.createEmoji_(votesBlock, emoji);
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.getAwardMenuUrl = function() {
|
||||||
|
return gon.award_menu_url;
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.resolveNameToCssClass = function(emoji) {
|
||||||
|
var emojiIcon, unicodeName;
|
||||||
|
emojiIcon = $(".emoji-menu-content [data-emoji='" + emoji + "']");
|
||||||
|
if (emojiIcon.length > 0) {
|
||||||
|
unicodeName = emojiIcon.data('unicode-name');
|
||||||
|
} else {
|
||||||
|
unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
|
||||||
|
}
|
||||||
|
return "emoji-" + unicodeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.postEmoji = function(awardUrl, emoji, callback) {
|
||||||
|
return $.post(awardUrl, {
|
||||||
|
name: emoji
|
||||||
|
}, function(data) {
|
||||||
|
if (data.ok) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.findEmojiIcon = function(votesBlock, emoji) {
|
||||||
|
return votesBlock.find(".js-emoji-btn [data-emoji='" + emoji + "']");
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.scrollToAwards = function() {
|
||||||
|
var options;
|
||||||
|
options = {
|
||||||
|
scrollTop: $('.awards').offset().top - 110
|
||||||
|
};
|
||||||
|
return $('body, html').animate(options, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.normilizeEmojiName = function(emoji) {
|
||||||
|
return this.aliases[emoji] || emoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.addEmojiToFrequentlyUsedList = function(emoji) {
|
||||||
|
var frequentlyUsedEmojis;
|
||||||
|
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
|
||||||
|
frequentlyUsedEmojis.push(emoji);
|
||||||
|
return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), {
|
||||||
|
expires: 365
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.getFrequentlyUsedEmojis = function() {
|
||||||
|
var frequentlyUsedEmojis;
|
||||||
|
frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',');
|
||||||
|
return _.compact(_.uniq(frequentlyUsedEmojis));
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.renderFrequentlyUsedBlock = function() {
|
||||||
|
var emoji, frequentlyUsedEmojis, i, len, ul;
|
||||||
|
if ($.cookie('frequently_used_emojis')) {
|
||||||
|
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
|
||||||
|
ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>");
|
||||||
|
for (i = 0, len = frequentlyUsedEmojis.length; i < len; i++) {
|
||||||
|
emoji = frequentlyUsedEmojis[i];
|
||||||
|
$(".emoji-menu-content [data-emoji='" + emoji + "']").closest('li').clone().appendTo(ul);
|
||||||
|
}
|
||||||
|
$('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used'));
|
||||||
|
}
|
||||||
|
return this.frequentEmojiBlockRendered = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.setupSearch = function() {
|
||||||
|
return $('input.emoji-search').on('keyup', (function(_this) {
|
||||||
|
return function(ev) {
|
||||||
|
var found_emojis, h5, term, ul;
|
||||||
|
term = $(ev.target).val();
|
||||||
|
$('ul.emoji-menu-search, h5.emoji-search').remove();
|
||||||
|
if (term) {
|
||||||
|
h5 = $('<h5>').text('Search results');
|
||||||
|
found_emojis = _this.searchEmojis(term).show();
|
||||||
|
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
|
||||||
|
$('.emoji-menu-content ul, .emoji-menu-content h5').hide();
|
||||||
|
return $('.emoji-menu-content').append(h5).append(ul);
|
||||||
|
} else {
|
||||||
|
return $('.emoji-menu-content').children().show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
AwardsHandler.prototype.searchEmojis = function(term) {
|
||||||
|
return $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='" + term + "']").closest('li').clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
return AwardsHandler;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
30
app/assets/javascripts/behaviors/autosize.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
/*= require jquery.ba-resize */
|
||||||
|
|
||||||
|
|
||||||
|
/*= require autosize */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
$(function() {
|
||||||
|
var $fields;
|
||||||
|
$fields = $('.js-autosize');
|
||||||
|
$fields.on('autosize:resized', function() {
|
||||||
|
var $field;
|
||||||
|
$field = $(this);
|
||||||
|
return $field.data('height', $field.outerHeight());
|
||||||
|
});
|
||||||
|
$fields.on('resize.autosize', function() {
|
||||||
|
var $field;
|
||||||
|
$field = $(this);
|
||||||
|
if ($field.data('height') !== $field.outerHeight()) {
|
||||||
|
$field.data('height', $field.outerHeight());
|
||||||
|
autosize.destroy($field);
|
||||||
|
return $field.css('max-height', window.outerHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
autosize($fields);
|
||||||
|
autosize.update($fields);
|
||||||
|
return $fields.css('resize', 'vertical');
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,22 +0,0 @@
|
||||||
#= require jquery.ba-resize
|
|
||||||
#= require autosize
|
|
||||||
|
|
||||||
$ ->
|
|
||||||
$fields = $('.js-autosize')
|
|
||||||
|
|
||||||
$fields.on 'autosize:resized', ->
|
|
||||||
$field = $(@)
|
|
||||||
$field.data('height', $field.outerHeight())
|
|
||||||
|
|
||||||
$fields.on 'resize.autosize', ->
|
|
||||||
$field = $(@)
|
|
||||||
|
|
||||||
if $field.data('height') != $field.outerHeight()
|
|
||||||
$field.data('height', $field.outerHeight())
|
|
||||||
autosize.destroy($field)
|
|
||||||
$field.css('max-height', window.outerHeight)
|
|
||||||
|
|
||||||
autosize($fields)
|
|
||||||
autosize.update($fields)
|
|
||||||
|
|
||||||
$fields.css('resize', 'vertical')
|
|
|
@ -1,15 +0,0 @@
|
||||||
$ ->
|
|
||||||
$("body").on "click", ".js-details-target", ->
|
|
||||||
container = $(@).closest(".js-details-container")
|
|
||||||
container.toggleClass("open")
|
|
||||||
|
|
||||||
# Show details content. Hides link after click.
|
|
||||||
#
|
|
||||||
# %div
|
|
||||||
# %a.js-details-expand
|
|
||||||
# %div.js-details-content
|
|
||||||
#
|
|
||||||
$("body").on "click", ".js-details-expand", (e) ->
|
|
||||||
$(@).next('.js-details-content').removeClass("hide")
|
|
||||||
$(@).hide()
|
|
||||||
e.preventDefault()
|
|
15
app/assets/javascripts/behaviors/details_behavior.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
(function() {
|
||||||
|
$(function() {
|
||||||
|
$("body").on("click", ".js-details-target", function() {
|
||||||
|
var container;
|
||||||
|
container = $(this).closest(".js-details-container");
|
||||||
|
return container.toggleClass("open");
|
||||||
|
});
|
||||||
|
return $("body").on("click", ".js-details-expand", function(e) {
|
||||||
|
$(this).next('.js-details-content').removeClass("hide");
|
||||||
|
$(this).hide();
|
||||||
|
return e.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
58
app/assets/javascripts/behaviors/quick_submit.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
/*= require extensions/jquery */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var isMac, keyCodeIs;
|
||||||
|
|
||||||
|
isMac = function() {
|
||||||
|
return navigator.userAgent.match(/Macintosh/);
|
||||||
|
};
|
||||||
|
|
||||||
|
keyCodeIs = function(e, keyCode) {
|
||||||
|
if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return e.keyCode === keyCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
|
||||||
|
var $form, $submit_button;
|
||||||
|
if (!keyCodeIs(e, 13)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!((e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
$form = $(e.target).closest('form');
|
||||||
|
$submit_button = $form.find('input[type=submit], button[type=submit]');
|
||||||
|
if ($submit_button.attr('disabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$submit_button.disable();
|
||||||
|
return $form.submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
|
||||||
|
var $this, title;
|
||||||
|
if (!keyCodeIs(e, 9)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isMac()) {
|
||||||
|
title = "You can also press ⌘-Enter";
|
||||||
|
} else {
|
||||||
|
title = "You can also press Ctrl-Enter";
|
||||||
|
}
|
||||||
|
$this = $(this);
|
||||||
|
return $this.tooltip({
|
||||||
|
container: 'body',
|
||||||
|
html: 'true',
|
||||||
|
placement: 'auto top',
|
||||||
|
title: title,
|
||||||
|
trigger: 'manual'
|
||||||
|
}).tooltip('show').one('blur', function() {
|
||||||
|
return $this.tooltip('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,56 +0,0 @@
|
||||||
# Quick Submit behavior
|
|
||||||
#
|
|
||||||
# When a child field of a form with a `js-quick-submit` class receives a
|
|
||||||
# "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
|
|
||||||
# is submitted.
|
|
||||||
#
|
|
||||||
#= require extensions/jquery
|
|
||||||
#
|
|
||||||
# ### Example Markup
|
|
||||||
#
|
|
||||||
# <form action="/foo" class="js-quick-submit">
|
|
||||||
# <input type="text" />
|
|
||||||
# <textarea></textarea>
|
|
||||||
# <input type="submit" value="Submit" />
|
|
||||||
# </form>
|
|
||||||
#
|
|
||||||
isMac = ->
|
|
||||||
navigator.userAgent.match(/Macintosh/)
|
|
||||||
|
|
||||||
keyCodeIs = (e, keyCode) ->
|
|
||||||
return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat
|
|
||||||
return e.keyCode == keyCode
|
|
||||||
|
|
||||||
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
|
|
||||||
return unless keyCodeIs(e, 13) # Enter
|
|
||||||
|
|
||||||
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
$form = $(e.target).closest('form')
|
|
||||||
$submit_button = $form.find('input[type=submit], button[type=submit]')
|
|
||||||
|
|
||||||
return if $submit_button.attr('disabled')
|
|
||||||
|
|
||||||
$submit_button.disable()
|
|
||||||
$form.submit()
|
|
||||||
|
|
||||||
# If the user tabs to a submit button on a `js-quick-submit` form, display a
|
|
||||||
# tooltip to let them know they could've used the hotkey
|
|
||||||
$(document).on 'keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', (e) ->
|
|
||||||
return unless keyCodeIs(e, 9) # Tab
|
|
||||||
|
|
||||||
if isMac()
|
|
||||||
title = "You can also press ⌘-Enter"
|
|
||||||
else
|
|
||||||
title = "You can also press Ctrl-Enter"
|
|
||||||
|
|
||||||
$this = $(@)
|
|
||||||
$this.tooltip(
|
|
||||||
container: 'body'
|
|
||||||
html: 'true'
|
|
||||||
placement: 'auto top'
|
|
||||||
title: title
|
|
||||||
trigger: 'manual'
|
|
||||||
).tooltip('show').one('blur', -> $this.tooltip('hide'))
|
|
45
app/assets/javascripts/behaviors/requires_input.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
/*= require extensions/jquery */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
$.fn.requiresInput = function() {
|
||||||
|
var $button, $form, fieldSelector, requireInput, required;
|
||||||
|
$form = $(this);
|
||||||
|
$button = $('button[type=submit], input[type=submit]', $form);
|
||||||
|
required = '[required=required]';
|
||||||
|
fieldSelector = "input" + required + ", select" + required + ", textarea" + required;
|
||||||
|
requireInput = function() {
|
||||||
|
var values;
|
||||||
|
values = _.map($(fieldSelector, $form), function(field) {
|
||||||
|
return field.value;
|
||||||
|
});
|
||||||
|
if (values.length && _.any(values, _.isEmpty)) {
|
||||||
|
return $button.disable();
|
||||||
|
} else {
|
||||||
|
return $button.enable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requireInput();
|
||||||
|
return $form.on('change input', fieldSelector, requireInput);
|
||||||
|
};
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
var $form, hideOrShowHelpBlock;
|
||||||
|
$form = $('form.js-requires-input');
|
||||||
|
$form.requiresInput();
|
||||||
|
hideOrShowHelpBlock = function(form) {
|
||||||
|
var selected;
|
||||||
|
selected = $('.js-select-namespace option:selected');
|
||||||
|
if (selected.length && selected.data('options-parent') === 'groups') {
|
||||||
|
return form.find('.help-block').hide();
|
||||||
|
} else if (selected.length) {
|
||||||
|
return form.find('.help-block').show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
hideOrShowHelpBlock($form);
|
||||||
|
return $('.select2.js-select-namespace').change(function() {
|
||||||
|
return hideOrShowHelpBlock($form);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,52 +0,0 @@
|
||||||
# Requires Input behavior
|
|
||||||
#
|
|
||||||
# When called on a form with input fields with the `required` attribute, the
|
|
||||||
# form's submit button will be disabled until all required fields have values.
|
|
||||||
#
|
|
||||||
#= require extensions/jquery
|
|
||||||
#
|
|
||||||
# ### Example Markup
|
|
||||||
#
|
|
||||||
# <form class="js-requires-input">
|
|
||||||
# <input type="text" required="required">
|
|
||||||
# <input type="submit" value="Submit">
|
|
||||||
# </form>
|
|
||||||
#
|
|
||||||
$.fn.requiresInput = ->
|
|
||||||
$form = $(this)
|
|
||||||
$button = $('button[type=submit], input[type=submit]', $form)
|
|
||||||
|
|
||||||
required = '[required=required]'
|
|
||||||
fieldSelector = "input#{required}, select#{required}, textarea#{required}"
|
|
||||||
|
|
||||||
requireInput = ->
|
|
||||||
# Collect the input values of *all* required fields
|
|
||||||
values = _.map $(fieldSelector, $form), (field) -> field.value
|
|
||||||
|
|
||||||
# Disable the button if any required fields are empty
|
|
||||||
if values.length && _.any(values, _.isEmpty)
|
|
||||||
$button.disable()
|
|
||||||
else
|
|
||||||
$button.enable()
|
|
||||||
|
|
||||||
# Set initial button state
|
|
||||||
requireInput()
|
|
||||||
|
|
||||||
$form.on 'change input', fieldSelector, requireInput
|
|
||||||
|
|
||||||
$ ->
|
|
||||||
$form = $('form.js-requires-input')
|
|
||||||
$form.requiresInput()
|
|
||||||
|
|
||||||
# Hide or Show the help block when creating a new project
|
|
||||||
# based on the option selected
|
|
||||||
hideOrShowHelpBlock = (form) ->
|
|
||||||
selected = $('.js-select-namespace option:selected')
|
|
||||||
if selected.length and selected.data('options-parent') is 'groups'
|
|
||||||
return form.find('.help-block').hide()
|
|
||||||
else if selected.length
|
|
||||||
form.find('.help-block').show()
|
|
||||||
|
|
||||||
hideOrShowHelpBlock($form)
|
|
||||||
|
|
||||||
$('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form)
|
|
|
@ -1,14 +0,0 @@
|
||||||
$ ->
|
|
||||||
# Toggle button. Show/hide content inside parent container.
|
|
||||||
# Button does not change visibility. If button has icon - it changes chevron style.
|
|
||||||
#
|
|
||||||
# %div.js-toggle-container
|
|
||||||
# %a.js-toggle-button
|
|
||||||
# %div.js-toggle-content
|
|
||||||
#
|
|
||||||
$("body").on "click", ".js-toggle-button", (e) ->
|
|
||||||
$(@).find('i').
|
|
||||||
toggleClass('fa fa-chevron-down').
|
|
||||||
toggleClass('fa fa-chevron-up')
|
|
||||||
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
|
|
||||||
e.preventDefault()
|
|
26
app/assets/javascripts/behaviors/toggler_behavior.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
(function(w) {
|
||||||
|
$(function() {
|
||||||
|
$('.js-toggle-button').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this)
|
||||||
|
.find('.fa')
|
||||||
|
.toggleClass('fa-chevron-down fa-chevron-up')
|
||||||
|
.end()
|
||||||
|
.closest('.js-toggle-container')
|
||||||
|
.find('.js-toggle-content')
|
||||||
|
.toggle()
|
||||||
|
;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we're accessing a permalink, ensure it is not inside a
|
||||||
|
// closed js-toggle-container!
|
||||||
|
var hash = w.gl.utils.getLocationHash();
|
||||||
|
var anchor = hash && document.getElementById(hash);
|
||||||
|
var container = anchor && $(anchor).closest('.js-toggle-container');
|
||||||
|
|
||||||
|
if (container && container.find('.js-toggle-content').is(':hidden')) {
|
||||||
|
container.find('.js-toggle-button').trigger('click');
|
||||||
|
anchor.scrollIntoView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})(window);
|
46
app/assets/javascripts/blob/blob_ci_yaml.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
/*= 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);
|
|
@ -1,23 +0,0 @@
|
||||||
#= require blob/template_selector
|
|
||||||
|
|
||||||
class @BlobCiYamlSelector extends TemplateSelector
|
|
||||||
requestFile: (query) ->
|
|
||||||
Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
|
|
||||||
|
|
||||||
class @BlobCiYamlSelectors
|
|
||||||
constructor: (opts) ->
|
|
||||||
{
|
|
||||||
@$dropdowns = $('.js-gitlab-ci-yml-selector')
|
|
||||||
@editor
|
|
||||||
} = opts
|
|
||||||
|
|
||||||
@$dropdowns.each (i, dropdown) =>
|
|
||||||
$dropdown = $(dropdown)
|
|
||||||
|
|
||||||
new BlobCiYamlSelector(
|
|
||||||
pattern: /(.gitlab-ci.yml)/,
|
|
||||||
data: $dropdown.data('data'),
|
|
||||||
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
|
|
||||||
dropdown: $dropdown,
|
|
||||||
editor: @editor
|
|
||||||
)
|
|
62
app/assets/javascripts/blob/blob_file_dropzone.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
(function() {
|
||||||
|
this.BlobFileDropzone = (function() {
|
||||||
|
function BlobFileDropzone(form, method) {
|
||||||
|
var dropzone, form_dropzone, submitButton;
|
||||||
|
form_dropzone = form.find('.dropzone');
|
||||||
|
Dropzone.autoDiscover = false;
|
||||||
|
dropzone = form_dropzone.dropzone({
|
||||||
|
autoDiscover: false,
|
||||||
|
autoProcessQueue: false,
|
||||||
|
url: form.attr('action'),
|
||||||
|
method: method,
|
||||||
|
clickable: true,
|
||||||
|
uploadMultiple: false,
|
||||||
|
paramName: "file",
|
||||||
|
maxFilesize: gon.max_file_size || 10,
|
||||||
|
parallelUploads: 1,
|
||||||
|
maxFiles: 1,
|
||||||
|
addRemoveLinks: true,
|
||||||
|
previewsContainer: '.dropzone-previews',
|
||||||
|
headers: {
|
||||||
|
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
|
||||||
|
},
|
||||||
|
init: function() {
|
||||||
|
this.on('addedfile', function(file) {
|
||||||
|
$('.dropzone-alerts').html('').hide();
|
||||||
|
});
|
||||||
|
this.on('success', function(header, response) {
|
||||||
|
window.location.href = response.filePath;
|
||||||
|
});
|
||||||
|
this.on('maxfilesexceeded', function(file) {
|
||||||
|
this.removeFile(file);
|
||||||
|
});
|
||||||
|
return this.on('sending', function(file, xhr, formData) {
|
||||||
|
formData.append('target_branch', form.find('.js-target-branch').val());
|
||||||
|
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
|
||||||
|
formData.append('commit_message', form.find('.js-commit-message').val());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(file, errorMessage) {
|
||||||
|
var stripped;
|
||||||
|
stripped = $("<div/>").html(errorMessage).text();
|
||||||
|
$('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show();
|
||||||
|
this.removeFile(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
submitButton = form.find('#submit-all')[0];
|
||||||
|
submitButton.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
|
||||||
|
alert("Please select a file");
|
||||||
|
}
|
||||||
|
dropzone[0].dropzone.processQueue();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlobFileDropzone;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,57 +0,0 @@
|
||||||
class @BlobFileDropzone
|
|
||||||
constructor: (form, method) ->
|
|
||||||
form_dropzone = form.find('.dropzone')
|
|
||||||
Dropzone.autoDiscover = false
|
|
||||||
dropzone = form_dropzone.dropzone(
|
|
||||||
autoDiscover: false
|
|
||||||
autoProcessQueue: false
|
|
||||||
url: form.attr('action')
|
|
||||||
# Rails uses a hidden input field for PUT
|
|
||||||
# http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
|
|
||||||
method: method
|
|
||||||
clickable: true
|
|
||||||
uploadMultiple: false
|
|
||||||
paramName: "file"
|
|
||||||
maxFilesize: gon.max_file_size or 10
|
|
||||||
parallelUploads: 1
|
|
||||||
maxFiles: 1
|
|
||||||
addRemoveLinks: true
|
|
||||||
previewsContainer: '.dropzone-previews'
|
|
||||||
headers:
|
|
||||||
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
|
|
||||||
|
|
||||||
init: ->
|
|
||||||
this.on 'addedfile', (file) ->
|
|
||||||
$('.dropzone-alerts').html('').hide()
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
this.on 'success', (header, response) ->
|
|
||||||
window.location.href = response.filePath
|
|
||||||
return
|
|
||||||
|
|
||||||
this.on 'maxfilesexceeded', (file) ->
|
|
||||||
@removeFile file
|
|
||||||
return
|
|
||||||
|
|
||||||
this.on 'sending', (file, xhr, formData) ->
|
|
||||||
formData.append('target_branch', form.find('.js-target-branch').val())
|
|
||||||
formData.append('create_merge_request', form.find('.js-create-merge-request').val())
|
|
||||||
formData.append('commit_message', form.find('.js-commit-message').val())
|
|
||||||
return
|
|
||||||
|
|
||||||
# Override behavior of adding error underneath preview
|
|
||||||
error: (file, errorMessage) ->
|
|
||||||
stripped = $("<div/>").html(errorMessage).text();
|
|
||||||
$('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show()
|
|
||||||
@removeFile file
|
|
||||||
return
|
|
||||||
)
|
|
||||||
|
|
||||||
submitButton = form.find('#submit-all')[0]
|
|
||||||
submitButton.addEventListener 'click', (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
alert "Please select a file" if dropzone[0].dropzone.getQueuedFiles().length == 0
|
|
||||||
dropzone[0].dropzone.processQueue()
|
|
||||||
return false
|
|
23
app/assets/javascripts/blob/blob_gitignore_selector.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
/*= 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.BlobGitignoreSelector = (function(superClass) {
|
||||||
|
extend(BlobGitignoreSelector, superClass);
|
||||||
|
|
||||||
|
function BlobGitignoreSelector() {
|
||||||
|
return BlobGitignoreSelector.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlobGitignoreSelector.prototype.requestFile = function(query) {
|
||||||
|
return Api.gitignoreText(query.name, this.requestFileSuccess.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
return BlobGitignoreSelector;
|
||||||
|
|
||||||
|
})(TemplateSelector);
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,5 +0,0 @@
|
||||||
#= require blob/template_selector
|
|
||||||
|
|
||||||
class @BlobGitignoreSelector extends TemplateSelector
|
|
||||||
requestFile: (query) ->
|
|
||||||
Api.gitignoreText query.name, @requestFileSuccess.bind(@)
|
|
25
app/assets/javascripts/blob/blob_gitignore_selectors.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
(function() {
|
||||||
|
this.BlobGitignoreSelectors = (function() {
|
||||||
|
function BlobGitignoreSelectors(opts) {
|
||||||
|
var ref;
|
||||||
|
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitignore-selector'), this.editor = opts.editor;
|
||||||
|
this.$dropdowns.each((function(_this) {
|
||||||
|
return function(i, dropdown) {
|
||||||
|
var $dropdown;
|
||||||
|
$dropdown = $(dropdown);
|
||||||
|
return new BlobGitignoreSelector({
|
||||||
|
pattern: /(.gitignore)/,
|
||||||
|
data: $dropdown.data('data'),
|
||||||
|
wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
|
||||||
|
dropdown: $dropdown,
|
||||||
|
editor: _this.editor
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlobGitignoreSelectors;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,17 +0,0 @@
|
||||||
class @BlobGitignoreSelectors
|
|
||||||
constructor: (opts) ->
|
|
||||||
{
|
|
||||||
@$dropdowns = $('.js-gitignore-selector')
|
|
||||||
@editor
|
|
||||||
} = opts
|
|
||||||
|
|
||||||
@$dropdowns.each (i, dropdown) =>
|
|
||||||
$dropdown = $(dropdown)
|
|
||||||
|
|
||||||
new BlobGitignoreSelector(
|
|
||||||
pattern: /(.gitignore)/,
|
|
||||||
data: $dropdown.data('data'),
|
|
||||||
wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
|
|
||||||
dropdown: $dropdown,
|
|
||||||
editor: @editor
|
|
||||||
)
|
|
28
app/assets/javascripts/blob/blob_license_selector.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
/*= 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.BlobLicenseSelector = (function(superClass) {
|
||||||
|
extend(BlobLicenseSelector, superClass);
|
||||||
|
|
||||||
|
function BlobLicenseSelector() {
|
||||||
|
return BlobLicenseSelector.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlobLicenseSelector.prototype.requestFile = function(query) {
|
||||||
|
var data;
|
||||||
|
data = {
|
||||||
|
project: this.dropdown.data('project'),
|
||||||
|
fullname: this.dropdown.data('fullname')
|
||||||
|
};
|
||||||
|
return Api.licenseText(query.id, data, this.requestFileSuccess.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
return BlobLicenseSelector;
|
||||||
|
|
||||||
|
})(TemplateSelector);
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,9 +0,0 @@
|
||||||
#= require blob/template_selector
|
|
||||||
|
|
||||||
class @BlobLicenseSelector extends TemplateSelector
|
|
||||||
requestFile: (query) ->
|
|
||||||
data =
|
|
||||||
project: @dropdown.data('project')
|
|
||||||
fullname: @dropdown.data('fullname')
|
|
||||||
|
|
||||||
Api.licenseText query.id, data, @requestFileSuccess.bind(@)
|
|
25
app/assets/javascripts/blob/blob_license_selectors.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
(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);
|
|
@ -1,17 +0,0 @@
|
||||||
class @BlobLicenseSelectors
|
|
||||||
constructor: (opts) ->
|
|
||||||
{
|
|
||||||
@$dropdowns = $('.js-license-selector')
|
|
||||||
@editor
|
|
||||||
} = opts
|
|
||||||
|
|
||||||
@$dropdowns.each (i, dropdown) =>
|
|
||||||
$dropdown = $(dropdown)
|
|
||||||
|
|
||||||
new BlobLicenseSelector(
|
|
||||||
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
|
|
||||||
data: $dropdown.data('data'),
|
|
||||||
wrapper: $dropdown.closest('.js-license-selector-wrap'),
|
|
||||||
dropdown: $dropdown,
|
|
||||||
editor: @editor
|
|
||||||
)
|
|
|
@ -1,42 +0,0 @@
|
||||||
class @EditBlob
|
|
||||||
constructor: (assets_path, ace_mode = null) ->
|
|
||||||
ace.config.set "modePath", "#{assets_path}/ace"
|
|
||||||
ace.config.loadModule "ace/ext/searchbox"
|
|
||||||
@editor = ace.edit("editor")
|
|
||||||
@editor.focus()
|
|
||||||
@editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
|
|
||||||
|
|
||||||
# Before a form submission, move the content from the Ace editor into the
|
|
||||||
# submitted textarea
|
|
||||||
$('form').submit =>
|
|
||||||
$("#file-content").val(@editor.getValue())
|
|
||||||
|
|
||||||
@initModePanesAndLinks()
|
|
||||||
|
|
||||||
new BlobLicenseSelectors { @editor }
|
|
||||||
new BlobGitignoreSelectors { @editor }
|
|
||||||
new BlobCiYamlSelectors { @editor }
|
|
||||||
|
|
||||||
initModePanesAndLinks: ->
|
|
||||||
@$editModePanes = $(".js-edit-mode-pane")
|
|
||||||
@$editModeLinks = $(".js-edit-mode a")
|
|
||||||
@$editModeLinks.click @editModeLinkClickHandler
|
|
||||||
|
|
||||||
editModeLinkClickHandler: (event) =>
|
|
||||||
event.preventDefault()
|
|
||||||
currentLink = $(event.target)
|
|
||||||
paneId = currentLink.attr("href")
|
|
||||||
currentPane = @$editModePanes.filter(paneId)
|
|
||||||
@$editModeLinks.parent().removeClass "active hover"
|
|
||||||
currentLink.parent().addClass "active hover"
|
|
||||||
@$editModePanes.hide()
|
|
||||||
currentPane.fadeIn 200
|
|
||||||
if paneId is "#preview"
|
|
||||||
$.post currentLink.data("preview-url"),
|
|
||||||
content: @editor.getValue()
|
|
||||||
, (response) ->
|
|
||||||
currentPane.empty().append response
|
|
||||||
currentPane.syntaxHighlight()
|
|
||||||
|
|
||||||
else
|
|
||||||
@editor.focus()
|
|
90
app/assets/javascripts/blob/template_selector.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
};
|
||||||
|
|
||||||
|
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
|
||||||
|
this.editor.setValue(file.content, 1);
|
||||||
|
if (!skipFocus) this.editor.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
|
@ -1,60 +0,0 @@
|
||||||
class @TemplateSelector
|
|
||||||
constructor: (opts = {}) ->
|
|
||||||
{
|
|
||||||
@dropdown,
|
|
||||||
@data,
|
|
||||||
@pattern,
|
|
||||||
@wrapper,
|
|
||||||
@editor,
|
|
||||||
@fileEndpoint,
|
|
||||||
@$input = $('#file_name')
|
|
||||||
} = opts
|
|
||||||
|
|
||||||
@buildDropdown()
|
|
||||||
@bindEvents()
|
|
||||||
@onFilenameUpdate()
|
|
||||||
|
|
||||||
buildDropdown: ->
|
|
||||||
@dropdown.glDropdown(
|
|
||||||
data: @data,
|
|
||||||
filterable: true,
|
|
||||||
selectable: true,
|
|
||||||
toggleLabel: @toggleLabel,
|
|
||||||
search:
|
|
||||||
fields: ['name']
|
|
||||||
clicked: @onClick
|
|
||||||
text: (item) ->
|
|
||||||
item.name
|
|
||||||
)
|
|
||||||
|
|
||||||
bindEvents: ->
|
|
||||||
@$input.on('keyup blur', (e) =>
|
|
||||||
@onFilenameUpdate()
|
|
||||||
)
|
|
||||||
|
|
||||||
toggleLabel: (item) ->
|
|
||||||
item.name
|
|
||||||
|
|
||||||
onFilenameUpdate: ->
|
|
||||||
return unless @$input.length
|
|
||||||
|
|
||||||
filenameMatches = @pattern.test(@$input.val().trim())
|
|
||||||
|
|
||||||
if not filenameMatches
|
|
||||||
@wrapper.addClass('hidden')
|
|
||||||
return
|
|
||||||
|
|
||||||
@wrapper.removeClass('hidden')
|
|
||||||
|
|
||||||
onClick: (item, el, e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
@requestFile(item)
|
|
||||||
|
|
||||||
requestFile: (item) ->
|
|
||||||
# To be implemented on the extending class
|
|
||||||
# e.g.
|
|
||||||
# Api.gitignoreText item.name, @requestFileSuccess.bind(@)
|
|
||||||
|
|
||||||
requestFileSuccess: (file) ->
|
|
||||||
@editor.setValue(file.content, 1)
|
|
||||||
@editor.focus()
|
|
12
app/assets/javascripts/blob_edit/blob_edit_bundle.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/*= require_tree . */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
$(function() {
|
||||||
|
var url = $(".js-edit-blob-form").data("relative-url-root");
|
||||||
|
url += $(".js-edit-blob-form").data("assets-prefix");
|
||||||
|
|
||||||
|
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
|
||||||
|
new NewCommitForm($('.js-edit-blob-form'));
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
66
app/assets/javascripts/blob_edit/edit_blob.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
(function() {
|
||||||
|
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||||
|
|
||||||
|
this.EditBlob = (function() {
|
||||||
|
function EditBlob(assets_path, ace_mode) {
|
||||||
|
if (ace_mode == null) {
|
||||||
|
ace_mode = null;
|
||||||
|
}
|
||||||
|
this.editModeLinkClickHandler = bind(this.editModeLinkClickHandler, this);
|
||||||
|
ace.config.set("modePath", assets_path + "/ace");
|
||||||
|
ace.config.loadModule("ace/ext/searchbox");
|
||||||
|
this.editor = ace.edit("editor");
|
||||||
|
this.editor.focus();
|
||||||
|
if (ace_mode) {
|
||||||
|
this.editor.getSession().setMode("ace/mode/" + ace_mode);
|
||||||
|
}
|
||||||
|
$('form').submit((function(_this) {
|
||||||
|
return function() {
|
||||||
|
return $("#file-content").val(_this.editor.getValue());
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
this.initModePanesAndLinks();
|
||||||
|
new BlobLicenseSelectors({
|
||||||
|
editor: this.editor
|
||||||
|
});
|
||||||
|
new BlobGitignoreSelectors({
|
||||||
|
editor: this.editor
|
||||||
|
});
|
||||||
|
new BlobCiYamlSelectors({
|
||||||
|
editor: this.editor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
EditBlob.prototype.initModePanesAndLinks = function() {
|
||||||
|
this.$editModePanes = $(".js-edit-mode-pane");
|
||||||
|
this.$editModeLinks = $(".js-edit-mode a");
|
||||||
|
return this.$editModeLinks.click(this.editModeLinkClickHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
EditBlob.prototype.editModeLinkClickHandler = function(event) {
|
||||||
|
var currentLink, currentPane, paneId;
|
||||||
|
event.preventDefault();
|
||||||
|
currentLink = $(event.target);
|
||||||
|
paneId = currentLink.attr("href");
|
||||||
|
currentPane = this.$editModePanes.filter(paneId);
|
||||||
|
this.$editModeLinks.parent().removeClass("active hover");
|
||||||
|
currentLink.parent().addClass("active hover");
|
||||||
|
this.$editModePanes.hide();
|
||||||
|
currentPane.fadeIn(200);
|
||||||
|
if (paneId === "#preview") {
|
||||||
|
return $.post(currentLink.data("preview-url"), {
|
||||||
|
content: this.editor.getValue()
|
||||||
|
}, function(response) {
|
||||||
|
currentPane.empty().append(response);
|
||||||
|
return currentPane.syntaxHighlight();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.editor.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditBlob;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
57
app/assets/javascripts/boards/boards_bundle.js.es6
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
//= require vue
|
||||||
|
//= require vue-resource
|
||||||
|
//= require Sortable
|
||||||
|
//= require_tree ./models
|
||||||
|
//= require_tree ./stores
|
||||||
|
//= require_tree ./services
|
||||||
|
//= require_tree ./mixins
|
||||||
|
//= require ./components/board
|
||||||
|
//= require ./components/new_list_dropdown
|
||||||
|
//= require ./vue_resource_interceptor
|
||||||
|
|
||||||
|
$(() => {
|
||||||
|
const $boardApp = document.getElementById('board-app'),
|
||||||
|
Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
|
||||||
|
if (gl.IssueBoardsApp) {
|
||||||
|
gl.IssueBoardsApp.$destroy(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.IssueBoardsApp = new Vue({
|
||||||
|
el: $boardApp,
|
||||||
|
components: {
|
||||||
|
'board': gl.issueBoards.Board
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
state: Store.state,
|
||||||
|
loading: true,
|
||||||
|
endpoint: $boardApp.dataset.endpoint,
|
||||||
|
disabled: $boardApp.dataset.disabled === 'true',
|
||||||
|
issueLinkBase: $boardApp.dataset.issueLinkBase
|
||||||
|
},
|
||||||
|
init: Store.create.bind(Store),
|
||||||
|
created () {
|
||||||
|
gl.boardService = new BoardService(this.endpoint);
|
||||||
|
},
|
||||||
|
ready () {
|
||||||
|
Store.disabled = this.disabled;
|
||||||
|
gl.boardService.all()
|
||||||
|
.then((resp) => {
|
||||||
|
resp.json().forEach((board) => {
|
||||||
|
const list = Store.addList(board);
|
||||||
|
|
||||||
|
if (list.type === 'done') {
|
||||||
|
list.position = Infinity;
|
||||||
|
} else if (list.type === 'backlog') {
|
||||||
|
list.position = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Store.addBlankState();
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
81
app/assets/javascripts/boards/components/board.js.es6
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
//= require ./board_blank_state
|
||||||
|
//= require ./board_delete
|
||||||
|
//= require ./board_list
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.Board = Vue.extend({
|
||||||
|
components: {
|
||||||
|
'board-list': gl.issueBoards.BoardList,
|
||||||
|
'board-delete': gl.issueBoards.BoardDelete,
|
||||||
|
'board-blank-state': gl.issueBoards.BoardBlankState
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
list: Object,
|
||||||
|
disabled: Boolean,
|
||||||
|
issueLinkBase: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
query: '',
|
||||||
|
filters: Store.state.filters
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
query () {
|
||||||
|
this.list.filters = this.getFilterData();
|
||||||
|
this.list.getIssues(true);
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
handler () {
|
||||||
|
this.list.page = 1;
|
||||||
|
this.list.getIssues(true);
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFilterData () {
|
||||||
|
const filters = this.filters;
|
||||||
|
let queryData = { search: this.query };
|
||||||
|
|
||||||
|
Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });
|
||||||
|
|
||||||
|
return queryData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ready () {
|
||||||
|
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||||
|
disabled: this.disabled,
|
||||||
|
group: 'boards',
|
||||||
|
draggable: '.is-draggable',
|
||||||
|
handle: '.js-board-handle',
|
||||||
|
onEnd: (e) => {
|
||||||
|
gl.issueBoards.onEnd();
|
||||||
|
|
||||||
|
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
|
||||||
|
const order = this.sortable.toArray(),
|
||||||
|
$board = this.$parent.$refs.board[e.oldIndex + 1],
|
||||||
|
list = $board.list;
|
||||||
|
|
||||||
|
$board.$destroy(true);
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
Store.state.lists.splice(e.newIndex, 0, list);
|
||||||
|
Store.moveList(list, order);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sortable = Sortable.create(this.$el.parentNode, options);
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
Store.state.lists.$remove(this.list);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
|
@ -0,0 +1,49 @@
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.BoardBlankState = Vue.extend({
|
||||||
|
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' })
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addDefaultLists () {
|
||||||
|
this.clearBlankState();
|
||||||
|
|
||||||
|
this.predefinedLabels.forEach((label, i) => {
|
||||||
|
Store.addList({
|
||||||
|
title: label.title,
|
||||||
|
position: i,
|
||||||
|
list_type: 'label',
|
||||||
|
label: {
|
||||||
|
title: label.title,
|
||||||
|
color: label.color
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the labels
|
||||||
|
gl.boardService.generateDefaultLists()
|
||||||
|
.then((resp) => {
|
||||||
|
resp.json().forEach((listObj) => {
|
||||||
|
const list = Store.findList('title', listObj.title);
|
||||||
|
|
||||||
|
list.id = listObj.id;
|
||||||
|
list.label.id = listObj.label.id;
|
||||||
|
list.getIssues();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearBlankState: Store.removeBlankState.bind(Store)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
43
app/assets/javascripts/boards/components/board_card.js.es6
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.BoardCard = Vue.extend({
|
||||||
|
props: {
|
||||||
|
list: Object,
|
||||||
|
issue: Object,
|
||||||
|
issueLinkBase: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
index: Number
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
filterByLabel (label, e) {
|
||||||
|
let labelToggleText = label.title;
|
||||||
|
const labelIndex = Store.state.filters['label_name'].indexOf(label.title);
|
||||||
|
$(e.target).tooltip('hide');
|
||||||
|
|
||||||
|
if (labelIndex === -1) {
|
||||||
|
Store.state.filters['label_name'].push(label.title);
|
||||||
|
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
|
||||||
|
} else {
|
||||||
|
Store.state.filters['label_name'].splice(labelIndex, 1);
|
||||||
|
labelToggleText = Store.state.filters['label_name'][0];
|
||||||
|
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedLabels = Store.state.filters['label_name'];
|
||||||
|
if (selectedLabels.length === 0) {
|
||||||
|
labelToggleText = 'Label';
|
||||||
|
} else if (selectedLabels.length > 1) {
|
||||||
|
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
|
||||||
|
|
||||||
|
Store.updateFiltersUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
19
app/assets/javascripts/boards/components/board_delete.js.es6
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
(() => {
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.BoardDelete = Vue.extend({
|
||||||
|
props: {
|
||||||
|
list: Object
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteBoard () {
|
||||||
|
$(this.$el).tooltip('hide');
|
||||||
|
|
||||||
|
if (confirm('Are you sure you want to delete this list?')) {
|
||||||
|
this.list.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
87
app/assets/javascripts/boards/components/board_list.js.es6
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
//= require ./board_card
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.BoardList = Vue.extend({
|
||||||
|
components: {
|
||||||
|
'board-card': gl.issueBoards.BoardCard
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: Boolean,
|
||||||
|
list: Object,
|
||||||
|
issues: Array,
|
||||||
|
loading: Boolean,
|
||||||
|
issueLinkBase: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
scrollOffset: 250,
|
||||||
|
filters: Store.state.filters
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
filters: {
|
||||||
|
handler () {
|
||||||
|
this.list.loadingMore = false;
|
||||||
|
this.$els.list.scrollTop = 0;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
listHeight () {
|
||||||
|
return this.$els.list.getBoundingClientRect().height;
|
||||||
|
},
|
||||||
|
scrollHeight () {
|
||||||
|
return this.$els.list.scrollHeight;
|
||||||
|
},
|
||||||
|
scrollTop () {
|
||||||
|
return this.$els.list.scrollTop + this.listHeight();
|
||||||
|
},
|
||||||
|
loadNextPage () {
|
||||||
|
const getIssues = this.list.nextPage();
|
||||||
|
|
||||||
|
if (getIssues) {
|
||||||
|
this.list.loadingMore = true;
|
||||||
|
getIssues.then(() => {
|
||||||
|
this.list.loadingMore = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ready () {
|
||||||
|
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||||
|
group: 'issues',
|
||||||
|
sort: false,
|
||||||
|
disabled: this.disabled,
|
||||||
|
onStart: (e) => {
|
||||||
|
const card = this.$refs.issue[e.oldIndex];
|
||||||
|
|
||||||
|
Store.moving.issue = card.issue;
|
||||||
|
Store.moving.list = card.list;
|
||||||
|
|
||||||
|
gl.issueBoards.onStart();
|
||||||
|
},
|
||||||
|
onAdd: (e) => {
|
||||||
|
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
|
||||||
|
},
|
||||||
|
onRemove: (e) => {
|
||||||
|
this.$refs.issue[e.oldIndex].$destroy(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sortable = Sortable.create(this.$els.list, options);
|
||||||
|
|
||||||
|
// Scroll event on list to load more
|
||||||
|
this.$els.list.onscroll = () => {
|
||||||
|
if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) {
|
||||||
|
this.loadNextPage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
|
@ -0,0 +1,54 @@
|
||||||
|
$(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
$('.js-new-board-list').each(function () {
|
||||||
|
const $this = $(this);
|
||||||
|
|
||||||
|
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('project-id'));
|
||||||
|
|
||||||
|
$this.glDropdown({
|
||||||
|
data(term, callback) {
|
||||||
|
$.get($this.attr('data-labels'))
|
||||||
|
.then((resp) => {
|
||||||
|
callback(resp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
renderRow (label) {
|
||||||
|
const active = Store.findList('title', label.title),
|
||||||
|
$li = $('<li />'),
|
||||||
|
$a = $('<a />', {
|
||||||
|
class: (active ? `is-active js-board-list-${active.id}` : ''),
|
||||||
|
text: label.title,
|
||||||
|
href: '#'
|
||||||
|
}),
|
||||||
|
$labelColor = $('<span />', {
|
||||||
|
class: 'dropdown-label-box',
|
||||||
|
style: `background-color: ${label.color}`
|
||||||
|
});
|
||||||
|
|
||||||
|
return $li.append($a.prepend($labelColor));
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
fields: ['title']
|
||||||
|
},
|
||||||
|
filterable: true,
|
||||||
|
selectable: true,
|
||||||
|
clicked (label, $el, e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!Store.findList('title', label.title)) {
|
||||||
|
Store.new({
|
||||||
|
title: label.title,
|
||||||
|
position: Store.state.lists.length - 2,
|
||||||
|
list_type: 'label',
|
||||||
|
label: {
|
||||||
|
id: label.id,
|
||||||
|
title: label.title,
|
||||||
|
color: label.color
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
((w) => {
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.onStart = () => {
|
||||||
|
$('.has-tooltip').tooltip('hide')
|
||||||
|
.tooltip('disable');
|
||||||
|
document.body.classList.add('is-dragging');
|
||||||
|
};
|
||||||
|
|
||||||
|
gl.issueBoards.onEnd = () => {
|
||||||
|
$('.has-tooltip').tooltip('enable');
|
||||||
|
document.body.classList.remove('is-dragging');
|
||||||
|
};
|
||||||
|
|
||||||
|
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
|
||||||
|
|
||||||
|
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
|
||||||
|
let defaultSortOptions = {
|
||||||
|
forceFallback: true,
|
||||||
|
fallbackClass: 'is-dragging',
|
||||||
|
fallbackOnBody: true,
|
||||||
|
ghostClass: 'is-ghost',
|
||||||
|
filter: '.has-tooltip',
|
||||||
|
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||||
|
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||||
|
scrollSpeed: 20,
|
||||||
|
onStart: gl.issueBoards.onStart,
|
||||||
|
onEnd: gl.issueBoards.onEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
|
||||||
|
return defaultSortOptions;
|
||||||
|
};
|
||||||
|
})(window);
|
44
app/assets/javascripts/boards/models/issue.js.es6
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class ListIssue {
|
||||||
|
constructor (obj) {
|
||||||
|
this.id = obj.iid;
|
||||||
|
this.title = obj.title;
|
||||||
|
this.confidential = obj.confidential;
|
||||||
|
this.labels = [];
|
||||||
|
|
||||||
|
if (obj.assignee) {
|
||||||
|
this.assignee = new ListUser(obj.assignee);
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.labels.forEach((label) => {
|
||||||
|
this.labels.push(new ListLabel(label));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.priority = this.labels.reduce((max, label) => {
|
||||||
|
return (label.priority < max) ? label.priority : max;
|
||||||
|
}, Infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
addLabel (label) {
|
||||||
|
if (!this.findLabel(label)) {
|
||||||
|
this.labels.push(new ListLabel(label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findLabel (findLabel) {
|
||||||
|
return this.labels.filter( label => label.title === findLabel.title )[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
removeLabel (removeLabel) {
|
||||||
|
if (removeLabel) {
|
||||||
|
this.labels = this.labels.filter( label => removeLabel.title !== label.title );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeLabels (labels) {
|
||||||
|
labels.forEach(this.removeLabel.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
getLists () {
|
||||||
|
return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) );
|
||||||
|
}
|
||||||
|
}
|
10
app/assets/javascripts/boards/models/label.js.es6
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
class ListLabel {
|
||||||
|
constructor (obj) {
|
||||||
|
this.id = obj.id;
|
||||||
|
this.title = obj.title;
|
||||||
|
this.color = obj.color;
|
||||||
|
this.textColor = obj.text_color;
|
||||||
|
this.description = obj.description;
|
||||||
|
this.priority = (obj.priority !== null) ? obj.priority : Infinity;
|
||||||
|
}
|
||||||
|
}
|
125
app/assets/javascripts/boards/models/list.js.es6
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
class List {
|
||||||
|
constructor (obj) {
|
||||||
|
this.id = obj.id;
|
||||||
|
this._uid = this.guid();
|
||||||
|
this.position = obj.position;
|
||||||
|
this.title = obj.title;
|
||||||
|
this.type = obj.list_type;
|
||||||
|
this.preset = ['backlog', 'done', 'blank'].indexOf(this.type) > -1;
|
||||||
|
this.filters = gl.issueBoards.BoardsStore.state.filters;
|
||||||
|
this.page = 1;
|
||||||
|
this.loading = true;
|
||||||
|
this.loadingMore = false;
|
||||||
|
this.issues = [];
|
||||||
|
|
||||||
|
if (obj.label) {
|
||||||
|
this.label = new ListLabel(obj.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.type !== 'blank' && this.id) {
|
||||||
|
this.getIssues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guid() {
|
||||||
|
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
||||||
|
return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
save () {
|
||||||
|
return gl.boardService.createList(this.label.id)
|
||||||
|
.then((resp) => {
|
||||||
|
const data = resp.json();
|
||||||
|
|
||||||
|
this.id = data.id;
|
||||||
|
this.type = data.list_type;
|
||||||
|
this.position = data.position;
|
||||||
|
|
||||||
|
return this.getIssues();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
gl.issueBoards.BoardsStore.state.lists.$remove(this);
|
||||||
|
gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
|
||||||
|
|
||||||
|
gl.boardService.destroyList(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
update () {
|
||||||
|
gl.boardService.updateList(this.id, this.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage () {
|
||||||
|
if (Math.floor(this.issues.length / 20) === this.page) {
|
||||||
|
this.page++;
|
||||||
|
|
||||||
|
return this.getIssues(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canSearch () {
|
||||||
|
return this.type === 'backlog';
|
||||||
|
}
|
||||||
|
|
||||||
|
getIssues (emptyIssues = true) {
|
||||||
|
const filters = this.filters;
|
||||||
|
let data = { page: this.page };
|
||||||
|
|
||||||
|
Object.keys(filters).forEach((key) => { data[key] = filters[key]; });
|
||||||
|
|
||||||
|
if (this.label) {
|
||||||
|
data.label_name = data.label_name.filter( label => label !== this.label.title );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emptyIssues) {
|
||||||
|
this.loading = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gl.boardService.getIssuesForList(this.id, data)
|
||||||
|
.then((resp) => {
|
||||||
|
const data = resp.json();
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
if (emptyIssues) {
|
||||||
|
this.issues = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createIssues(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createIssues (data) {
|
||||||
|
data.forEach((issueObj) => {
|
||||||
|
this.addIssue(new ListIssue(issueObj));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addIssue (issue, listFrom) {
|
||||||
|
this.issues.push(issue);
|
||||||
|
|
||||||
|
if (this.label) {
|
||||||
|
issue.addLabel(this.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listFrom) {
|
||||||
|
gl.boardService.moveIssue(issue.id, listFrom.id, this.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findIssue (id) {
|
||||||
|
return this.issues.filter( issue => issue.id === id )[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
removeIssue (removeIssue) {
|
||||||
|
this.issues = this.issues.filter((issue) => {
|
||||||
|
const matchesRemove = removeIssue.id === issue.id;
|
||||||
|
|
||||||
|
if (matchesRemove) {
|
||||||
|
issue.removeLabel(this.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !matchesRemove;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
8
app/assets/javascripts/boards/models/user.js.es6
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class ListUser {
|
||||||
|
constructor (user) {
|
||||||
|
this.id = user.id;
|
||||||
|
this.name = user.name;
|
||||||
|
this.username = user.username;
|
||||||
|
this.avatar = user.avatar_url;
|
||||||
|
}
|
||||||
|
}
|
61
app/assets/javascripts/boards/services/board_service.js.es6
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
class BoardService {
|
||||||
|
constructor (root) {
|
||||||
|
Vue.http.options.root = root;
|
||||||
|
|
||||||
|
this.lists = Vue.resource(`${root}/lists{/id}`, {}, {
|
||||||
|
generate: {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${root}/lists/generate.json`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.issue = Vue.resource(`${root}/issues{/id}`, {});
|
||||||
|
this.issues = Vue.resource(`${root}/lists{/id}/issues`, {});
|
||||||
|
|
||||||
|
Vue.http.interceptors.push((request, next) => {
|
||||||
|
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
all () {
|
||||||
|
return this.lists.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateDefaultLists () {
|
||||||
|
return this.lists.generate({});
|
||||||
|
}
|
||||||
|
|
||||||
|
createList (label_id) {
|
||||||
|
return this.lists.save({}, {
|
||||||
|
list: {
|
||||||
|
label_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateList (id, position) {
|
||||||
|
return this.lists.update({ id }, {
|
||||||
|
list: {
|
||||||
|
position
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyList (id) {
|
||||||
|
return this.lists.delete({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
getIssuesForList (id, filter = {}) {
|
||||||
|
let data = { id };
|
||||||
|
Object.keys(filter).forEach((key) => { data[key] = filter[key]; });
|
||||||
|
|
||||||
|
return this.issues.get(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveIssue (id, from_list_id, to_list_id) {
|
||||||
|
return this.issue.update({ id }, {
|
||||||
|
from_list_id,
|
||||||
|
to_list_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
112
app/assets/javascripts/boards/stores/boards_store.js.es6
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
(() => {
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.BoardsStore = {
|
||||||
|
disabled: false,
|
||||||
|
state: {},
|
||||||
|
moving: {
|
||||||
|
issue: {},
|
||||||
|
list: {}
|
||||||
|
},
|
||||||
|
create () {
|
||||||
|
this.state.lists = [];
|
||||||
|
this.state.filters = {
|
||||||
|
author_id: gl.utils.getParameterValues('author_id')[0],
|
||||||
|
assignee_id: gl.utils.getParameterValues('assignee_id')[0],
|
||||||
|
milestone_title: gl.utils.getParameterValues('milestone_title')[0],
|
||||||
|
label_name: gl.utils.getParameterValues('label_name[]')
|
||||||
|
};
|
||||||
|
},
|
||||||
|
addList (listObj) {
|
||||||
|
const list = new List(listObj);
|
||||||
|
this.state.lists.push(list);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
new (listObj) {
|
||||||
|
const list = this.addList(listObj),
|
||||||
|
backlogList = this.findList('type', 'backlog', 'backlog');
|
||||||
|
|
||||||
|
list
|
||||||
|
.save()
|
||||||
|
.then(() => {
|
||||||
|
// Remove any new issues from the backlog
|
||||||
|
// as they will be visible in the new list
|
||||||
|
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
|
||||||
|
});
|
||||||
|
this.removeBlankState();
|
||||||
|
},
|
||||||
|
updateNewListDropdown (listId) {
|
||||||
|
$(`.js-board-list-${listId}`).removeClass('is-active');
|
||||||
|
},
|
||||||
|
shouldAddBlankState () {
|
||||||
|
// Decide whether to add the blank state
|
||||||
|
return !(this.state.lists.filter( list => list.type !== 'backlog' && list.type !== 'done' )[0]);
|
||||||
|
},
|
||||||
|
addBlankState () {
|
||||||
|
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
|
||||||
|
|
||||||
|
this.addList({
|
||||||
|
id: 'blank',
|
||||||
|
list_type: 'blank',
|
||||||
|
title: 'Welcome to your Issue Board!',
|
||||||
|
position: 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeBlankState () {
|
||||||
|
this.removeList('blank');
|
||||||
|
|
||||||
|
$.cookie('issue_board_welcome_hidden', 'true', {
|
||||||
|
expires: 365 * 10
|
||||||
|
});
|
||||||
|
},
|
||||||
|
welcomeIsHidden () {
|
||||||
|
return $.cookie('issue_board_welcome_hidden') === 'true';
|
||||||
|
},
|
||||||
|
removeList (id, type = 'blank') {
|
||||||
|
const list = this.findList('id', id, type);
|
||||||
|
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
this.state.lists = this.state.lists.filter( list => list.id !== id );
|
||||||
|
},
|
||||||
|
moveList (listFrom, orderLists) {
|
||||||
|
orderLists.forEach((id, i) => {
|
||||||
|
const list = this.findList('id', parseInt(id));
|
||||||
|
|
||||||
|
list.position = i;
|
||||||
|
});
|
||||||
|
listFrom.update();
|
||||||
|
},
|
||||||
|
moveIssueToList (listFrom, listTo, issue) {
|
||||||
|
const issueTo = listTo.findIssue(issue.id),
|
||||||
|
issueLists = issue.getLists(),
|
||||||
|
listLabels = issueLists.map( listIssue => listIssue.label );
|
||||||
|
|
||||||
|
// Add to new lists issues if it doesn't already exist
|
||||||
|
if (!issueTo) {
|
||||||
|
listTo.addIssue(issue, listFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listTo.type === 'done' && listFrom.type !== 'backlog') {
|
||||||
|
issueLists.forEach((list) => {
|
||||||
|
list.removeIssue(issue);
|
||||||
|
})
|
||||||
|
issue.removeLabels(listLabels);
|
||||||
|
} else {
|
||||||
|
listFrom.removeIssue(issue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findList (key, val, type = 'label') {
|
||||||
|
return this.state.lists.filter((list) => {
|
||||||
|
const byType = type ? list['type'] === type : true;
|
||||||
|
|
||||||
|
return list[key] === val && byType;
|
||||||
|
})[0];
|
||||||
|
},
|
||||||
|
updateFiltersUrl () {
|
||||||
|
history.pushState(null, null, `?${$.param(this.state.filters)}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
119
app/assets/javascripts/boards/test_utils/simulate_drag.js
Executable file
|
@ -0,0 +1,119 @@
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function simulateEvent(el, type, options) {
|
||||||
|
var event;
|
||||||
|
if (!el) return;
|
||||||
|
var ownerDocument = el.ownerDocument;
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (/^mouse/.test(type)) {
|
||||||
|
event = ownerDocument.createEvent('MouseEvents');
|
||||||
|
event.initMouseEvent(type, true, true, ownerDocument.defaultView,
|
||||||
|
options.button, options.screenX, options.screenY, options.clientX, options.clientY,
|
||||||
|
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el);
|
||||||
|
} else {
|
||||||
|
event = ownerDocument.createEvent('CustomEvent');
|
||||||
|
|
||||||
|
event.initCustomEvent(type, true, true, ownerDocument.defaultView,
|
||||||
|
options.button, options.screenX, options.screenY, options.clientX, options.clientY,
|
||||||
|
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el);
|
||||||
|
|
||||||
|
event.dataTransfer = {
|
||||||
|
data: {},
|
||||||
|
|
||||||
|
setData: function (type, val) {
|
||||||
|
this.data[type] = val;
|
||||||
|
},
|
||||||
|
|
||||||
|
getData: function (type) {
|
||||||
|
return this.data[type];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.dispatchEvent) {
|
||||||
|
el.dispatchEvent(event);
|
||||||
|
} else if (el.fireEvent) {
|
||||||
|
el.fireEvent('on' + type, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTraget(target) {
|
||||||
|
var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
|
||||||
|
var children = el.children;
|
||||||
|
|
||||||
|
return (
|
||||||
|
children[target.index] ||
|
||||||
|
children[target.index === 'first' ? 0 : -1] ||
|
||||||
|
children[target.index === 'last' ? children.length - 1 : -1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRect(el) {
|
||||||
|
var rect = el.getBoundingClientRect();
|
||||||
|
var width = rect.right - rect.left;
|
||||||
|
var height = rect.bottom - rect.top;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: rect.left,
|
||||||
|
y: rect.top,
|
||||||
|
cx: rect.left + width / 2,
|
||||||
|
cy: rect.top + height / 2,
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
hw: width / 2,
|
||||||
|
wh: height / 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateDrag(options, callback) {
|
||||||
|
options.to.el = options.to.el || options.from.el;
|
||||||
|
|
||||||
|
var fromEl = getTraget(options.from);
|
||||||
|
var toEl = getTraget(options.to);
|
||||||
|
var scrollable = options.scrollable;
|
||||||
|
|
||||||
|
var fromRect = getRect(fromEl);
|
||||||
|
var toRect = getRect(toEl);
|
||||||
|
|
||||||
|
var startTime = new Date().getTime();
|
||||||
|
var duration = options.duration || 1000;
|
||||||
|
simulateEvent(fromEl, 'mousedown', {button: 0});
|
||||||
|
options.ontap && options.ontap();
|
||||||
|
window.SIMULATE_DRAG_ACTIVE = 1;
|
||||||
|
|
||||||
|
var dragInterval = setInterval(function loop() {
|
||||||
|
var progress = (new Date().getTime() - startTime) / duration;
|
||||||
|
var x = (fromRect.cx + (toRect.cx - fromRect.cx) * progress) - scrollable.scrollLeft;
|
||||||
|
var y = (fromRect.cy + (toRect.cy - fromRect.cy) * progress) - scrollable.scrollTop;
|
||||||
|
var overEl = fromEl.ownerDocument.elementFromPoint(x, y);
|
||||||
|
|
||||||
|
simulateEvent(overEl, 'mousemove', {
|
||||||
|
clientX: x,
|
||||||
|
clientY: y
|
||||||
|
});
|
||||||
|
|
||||||
|
if (progress >= 1) {
|
||||||
|
options.ondragend && options.ondragend();
|
||||||
|
simulateEvent(toEl, 'mouseup');
|
||||||
|
clearInterval(dragInterval);
|
||||||
|
window.SIMULATE_DRAG_ACTIVE = 0;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return {
|
||||||
|
target: fromEl,
|
||||||
|
fromList: fromEl.parentNode,
|
||||||
|
toList: toEl.parentNode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Export
|
||||||
|
window.simulateEvent = simulateEvent;
|
||||||
|
window.simulateDrag = simulateDrag;
|
||||||
|
})();
|
|
@ -0,0 +1,10 @@
|
||||||
|
Vue.http.interceptors.push((request, next) => {
|
||||||
|
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
|
||||||
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
Vue.activeResources--;
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
});
|
|
@ -1,37 +0,0 @@
|
||||||
class @Breakpoints
|
|
||||||
instance = null;
|
|
||||||
|
|
||||||
class BreakpointInstance
|
|
||||||
BREAKPOINTS = ["xs", "sm", "md", "lg"]
|
|
||||||
|
|
||||||
constructor: ->
|
|
||||||
@setup()
|
|
||||||
|
|
||||||
setup: ->
|
|
||||||
allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
|
|
||||||
".device-#{breakpoint}"
|
|
||||||
return if $(allDeviceSelector.join(",")).length
|
|
||||||
|
|
||||||
# Create all the elements
|
|
||||||
els = $.map BREAKPOINTS, (breakpoint) ->
|
|
||||||
"<div class='device-#{breakpoint} visible-#{breakpoint}'></div>"
|
|
||||||
$("body").append els.join('')
|
|
||||||
|
|
||||||
visibleDevice: ->
|
|
||||||
allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
|
|
||||||
".device-#{breakpoint}"
|
|
||||||
$(allDeviceSelector.join(",")).filter(":visible")
|
|
||||||
|
|
||||||
getBreakpointSize: ->
|
|
||||||
$visibleDevice = @visibleDevice
|
|
||||||
# the page refreshed via turbolinks
|
|
||||||
if not $visibleDevice().length
|
|
||||||
@setup()
|
|
||||||
$visibleDevice = @visibleDevice()
|
|
||||||
return $visibleDevice.attr("class").split("visible-")[1]
|
|
||||||
|
|
||||||
@get: ->
|
|
||||||
return instance ?= new BreakpointInstance
|
|
||||||
|
|
||||||
$ =>
|
|
||||||
@bp = Breakpoints.get()
|
|
68
app/assets/javascripts/breakpoints.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
(function() {
|
||||||
|
this.Breakpoints = (function() {
|
||||||
|
var BreakpointInstance, instance;
|
||||||
|
|
||||||
|
function Breakpoints() {}
|
||||||
|
|
||||||
|
instance = null;
|
||||||
|
|
||||||
|
BreakpointInstance = (function() {
|
||||||
|
var BREAKPOINTS;
|
||||||
|
|
||||||
|
BREAKPOINTS = ["xs", "sm", "md", "lg"];
|
||||||
|
|
||||||
|
function BreakpointInstance() {
|
||||||
|
this.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
BreakpointInstance.prototype.setup = function() {
|
||||||
|
var allDeviceSelector, els;
|
||||||
|
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
|
||||||
|
return ".device-" + breakpoint;
|
||||||
|
});
|
||||||
|
if ($(allDeviceSelector.join(",")).length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
els = $.map(BREAKPOINTS, function(breakpoint) {
|
||||||
|
return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
|
||||||
|
});
|
||||||
|
return $("body").append(els.join(''));
|
||||||
|
};
|
||||||
|
|
||||||
|
BreakpointInstance.prototype.visibleDevice = function() {
|
||||||
|
var allDeviceSelector;
|
||||||
|
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
|
||||||
|
return ".device-" + breakpoint;
|
||||||
|
});
|
||||||
|
return $(allDeviceSelector.join(",")).filter(":visible");
|
||||||
|
};
|
||||||
|
|
||||||
|
BreakpointInstance.prototype.getBreakpointSize = function() {
|
||||||
|
var $visibleDevice;
|
||||||
|
$visibleDevice = this.visibleDevice;
|
||||||
|
if (!$visibleDevice().length) {
|
||||||
|
this.setup();
|
||||||
|
}
|
||||||
|
$visibleDevice = this.visibleDevice();
|
||||||
|
return $visibleDevice.attr("class").split("visible-")[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
return BreakpointInstance;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
Breakpoints.get = function() {
|
||||||
|
return instance != null ? instance : instance = new BreakpointInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Breakpoints;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
$((function(_this) {
|
||||||
|
return function() {
|
||||||
|
return _this.bp = Breakpoints.get();
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
|
||||||
|
}).call(this);
|
34
app/assets/javascripts/broadcast_message.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
(function() {
|
||||||
|
$(function() {
|
||||||
|
var previewPath;
|
||||||
|
$('input#broadcast_message_color').on('input', function() {
|
||||||
|
var previewColor;
|
||||||
|
previewColor = $(this).val();
|
||||||
|
return $('div.broadcast-message-preview').css('background-color', previewColor);
|
||||||
|
});
|
||||||
|
$('input#broadcast_message_font').on('input', function() {
|
||||||
|
var previewColor;
|
||||||
|
previewColor = $(this).val();
|
||||||
|
return $('div.broadcast-message-preview').css('color', previewColor);
|
||||||
|
});
|
||||||
|
previewPath = $('textarea#broadcast_message_message').data('preview-path');
|
||||||
|
return $('textarea#broadcast_message_message').on('input', function() {
|
||||||
|
var message;
|
||||||
|
message = $(this).val();
|
||||||
|
if (message === '') {
|
||||||
|
return $('.js-broadcast-message-preview').text("Your message here");
|
||||||
|
} else {
|
||||||
|
return $.ajax({
|
||||||
|
url: previewPath,
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
broadcast_message: {
|
||||||
|
message: message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,22 +0,0 @@
|
||||||
$ ->
|
|
||||||
$('input#broadcast_message_color').on 'input', ->
|
|
||||||
previewColor = $(@).val()
|
|
||||||
$('div.broadcast-message-preview').css('background-color', previewColor)
|
|
||||||
|
|
||||||
$('input#broadcast_message_font').on 'input', ->
|
|
||||||
previewColor = $(@).val()
|
|
||||||
$('div.broadcast-message-preview').css('color', previewColor)
|
|
||||||
|
|
||||||
previewPath = $('textarea#broadcast_message_message').data('preview-path')
|
|
||||||
|
|
||||||
$('textarea#broadcast_message_message').on 'input', ->
|
|
||||||
message = $(@).val()
|
|
||||||
|
|
||||||
if message == ''
|
|
||||||
$('.js-broadcast-message-preview').text("Your message here")
|
|
||||||
else
|
|
||||||
$.ajax(
|
|
||||||
url: previewPath
|
|
||||||
type: "POST"
|
|
||||||
data: { broadcast_message: { message: message } }
|
|
||||||
)
|
|
|
@ -1,114 +0,0 @@
|
||||||
class @Build
|
|
||||||
@interval: null
|
|
||||||
@state: null
|
|
||||||
|
|
||||||
constructor: (@page_url, @build_url, @build_status, @state) ->
|
|
||||||
clearInterval(Build.interval)
|
|
||||||
|
|
||||||
# Init breakpoint checker
|
|
||||||
@bp = Breakpoints.get()
|
|
||||||
@hideSidebar()
|
|
||||||
$('.js-build-sidebar').niceScroll()
|
|
||||||
$(document)
|
|
||||||
.off 'click', '.js-sidebar-build-toggle'
|
|
||||||
.on 'click', '.js-sidebar-build-toggle', @toggleSidebar
|
|
||||||
|
|
||||||
$(window)
|
|
||||||
.off 'resize.build'
|
|
||||||
.on 'resize.build', @hideSidebar
|
|
||||||
|
|
||||||
@updateArtifactRemoveDate()
|
|
||||||
|
|
||||||
if $('#build-trace').length
|
|
||||||
@getInitialBuildTrace()
|
|
||||||
@initScrollButtonAffix()
|
|
||||||
|
|
||||||
if @build_status is "running" or @build_status is "pending"
|
|
||||||
#
|
|
||||||
# Bind autoscroll button to follow build output
|
|
||||||
#
|
|
||||||
$('#autoscroll-button').on 'click', ->
|
|
||||||
state = $(this).data("state")
|
|
||||||
if "enabled" is state
|
|
||||||
$(this).data "state", "disabled"
|
|
||||||
$(this).text "enable autoscroll"
|
|
||||||
else
|
|
||||||
$(this).data "state", "enabled"
|
|
||||||
$(this).text "disable autoscroll"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Check for new build output if user still watching build page
|
|
||||||
# Only valid for runnig build when output changes during time
|
|
||||||
#
|
|
||||||
Build.interval = setInterval =>
|
|
||||||
if window.location.href.split("#").first() is @page_url
|
|
||||||
@getBuildTrace()
|
|
||||||
, 4000
|
|
||||||
|
|
||||||
getInitialBuildTrace: ->
|
|
||||||
$.ajax
|
|
||||||
url: @build_url
|
|
||||||
dataType: 'json'
|
|
||||||
success: (build_data) ->
|
|
||||||
$('.js-build-output').html build_data.trace_html
|
|
||||||
|
|
||||||
if build_data.status is 'success' or build_data.status is 'failed'
|
|
||||||
$('.js-build-refresh').remove()
|
|
||||||
|
|
||||||
getBuildTrace: ->
|
|
||||||
$.ajax
|
|
||||||
url: "#{@page_url}/trace.json?state=#{encodeURIComponent(@state)}"
|
|
||||||
dataType: "json"
|
|
||||||
success: (log) =>
|
|
||||||
if log.state
|
|
||||||
@state = log.state
|
|
||||||
|
|
||||||
if log.status is "running"
|
|
||||||
if log.append
|
|
||||||
$('.js-build-output').append log.html
|
|
||||||
else
|
|
||||||
$('.js-build-output').html log.html
|
|
||||||
@checkAutoscroll()
|
|
||||||
else if log.status isnt @build_status
|
|
||||||
Turbolinks.visit @page_url
|
|
||||||
|
|
||||||
checkAutoscroll: ->
|
|
||||||
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
|
|
||||||
|
|
||||||
initScrollButtonAffix: ->
|
|
||||||
$buildScroll = $('#js-build-scroll')
|
|
||||||
$body = $('body')
|
|
||||||
$buildTrace = $('#build-trace')
|
|
||||||
|
|
||||||
$buildScroll.affix(
|
|
||||||
offset:
|
|
||||||
bottom: ->
|
|
||||||
$body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
|
|
||||||
)
|
|
||||||
|
|
||||||
shouldHideSidebar: ->
|
|
||||||
bootstrapBreakpoint = @bp.getBreakpointSize()
|
|
||||||
|
|
||||||
bootstrapBreakpoint is 'xs' or bootstrapBreakpoint is 'sm'
|
|
||||||
|
|
||||||
toggleSidebar: =>
|
|
||||||
if @shouldHideSidebar()
|
|
||||||
$('.js-build-sidebar')
|
|
||||||
.toggleClass 'right-sidebar-expanded right-sidebar-collapsed'
|
|
||||||
|
|
||||||
hideSidebar: =>
|
|
||||||
if @shouldHideSidebar()
|
|
||||||
$('.js-build-sidebar')
|
|
||||||
.removeClass 'right-sidebar-expanded'
|
|
||||||
.addClass 'right-sidebar-collapsed'
|
|
||||||
else
|
|
||||||
$('.js-build-sidebar')
|
|
||||||
.removeClass 'right-sidebar-collapsed'
|
|
||||||
.addClass 'right-sidebar-expanded'
|
|
||||||
|
|
||||||
updateArtifactRemoveDate: ->
|
|
||||||
$date = $('.js-artifacts-remove')
|
|
||||||
|
|
||||||
if $date.length
|
|
||||||
date = $date.text()
|
|
||||||
$date.text $.timefor(new Date(date), ' ')
|
|
162
app/assets/javascripts/build.js
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
(function() {
|
||||||
|
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||||
|
|
||||||
|
this.Build = (function() {
|
||||||
|
Build.interval = null;
|
||||||
|
|
||||||
|
Build.state = null;
|
||||||
|
|
||||||
|
function Build(options) {
|
||||||
|
this.page_url = options.page_url;
|
||||||
|
this.build_url = options.build_url;
|
||||||
|
this.build_status = options.build_status;
|
||||||
|
this.state = options.state1;
|
||||||
|
this.build_stage = options.build_stage;
|
||||||
|
this.hideSidebar = bind(this.hideSidebar, this);
|
||||||
|
this.toggleSidebar = bind(this.toggleSidebar, this);
|
||||||
|
this.updateDropdown = bind(this.updateDropdown, this);
|
||||||
|
clearInterval(Build.interval);
|
||||||
|
this.bp = Breakpoints.get();
|
||||||
|
$('.js-build-sidebar').niceScroll();
|
||||||
|
|
||||||
|
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.updateArtifactRemoveDate();
|
||||||
|
if ($('#build-trace').length) {
|
||||||
|
this.getInitialBuildTrace();
|
||||||
|
this.initScrollButtonAffix();
|
||||||
|
}
|
||||||
|
if (this.build_status === "running" || this.build_status === "pending") {
|
||||||
|
$('#autoscroll-button').on('click', function() {
|
||||||
|
var state;
|
||||||
|
state = $(this).data("state");
|
||||||
|
if ("enabled" === state) {
|
||||||
|
$(this).data("state", "disabled");
|
||||||
|
return $(this).text("enable autoscroll");
|
||||||
|
} else {
|
||||||
|
$(this).data("state", "enabled");
|
||||||
|
return $(this).text("disable autoscroll");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Build.interval = setInterval((function(_this) {
|
||||||
|
return function() {
|
||||||
|
if (window.location.href.split("#").first() === _this.page_url) {
|
||||||
|
return _this.getBuildTrace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(this), 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Build.prototype.getInitialBuildTrace = function() {
|
||||||
|
return $.ajax({
|
||||||
|
url: this.build_url,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(build_data) {
|
||||||
|
$('.js-build-output').html(build_data.trace_html);
|
||||||
|
if (build_data.status === 'success' || build_data.status === 'failed') {
|
||||||
|
return $('.js-build-refresh').remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.getBuildTrace = function() {
|
||||||
|
return $.ajax({
|
||||||
|
url: this.page_url + "/trace.json?state=" + (encodeURIComponent(this.state)),
|
||||||
|
dataType: "json",
|
||||||
|
success: (function(_this) {
|
||||||
|
return function(log) {
|
||||||
|
if (log.state) {
|
||||||
|
_this.state = log.state;
|
||||||
|
}
|
||||||
|
if (log.status === "running") {
|
||||||
|
if (log.append) {
|
||||||
|
$('.js-build-output').append(log.html);
|
||||||
|
} else {
|
||||||
|
$('.js-build-output').html(log.html);
|
||||||
|
}
|
||||||
|
return _this.checkAutoscroll();
|
||||||
|
} else if (log.status !== _this.build_status) {
|
||||||
|
return Turbolinks.visit(_this.page_url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(this)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.checkAutoscroll = function() {
|
||||||
|
if ("enabled" === $("#autoscroll-button").data("state")) {
|
||||||
|
return $("html,body").scrollTop($("#build-trace").height());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.initScrollButtonAffix = function() {
|
||||||
|
var $body, $buildScroll, $buildTrace;
|
||||||
|
$buildScroll = $('#js-build-scroll');
|
||||||
|
$body = $('body');
|
||||||
|
$buildTrace = $('#build-trace');
|
||||||
|
return $buildScroll.affix({
|
||||||
|
offset: {
|
||||||
|
bottom: function() {
|
||||||
|
return $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.shouldHideSidebar = function() {
|
||||||
|
var bootstrapBreakpoint;
|
||||||
|
bootstrapBreakpoint = this.bp.getBreakpointSize();
|
||||||
|
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.toggleSidebar = function() {
|
||||||
|
if (this.shouldHideSidebar()) {
|
||||||
|
return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.hideSidebar = function() {
|
||||||
|
if (this.shouldHideSidebar()) {
|
||||||
|
return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
|
||||||
|
} else {
|
||||||
|
return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.updateArtifactRemoveDate = function() {
|
||||||
|
var $date, date;
|
||||||
|
$date = $('.js-artifacts-remove');
|
||||||
|
if ($date.length) {
|
||||||
|
date = $date.text();
|
||||||
|
return $date.text($.timefor(new Date(date.replace(/-/g, '/')), ' '));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.populateJobs = function(stage) {
|
||||||
|
$('.build-job').hide();
|
||||||
|
$('.build-job[data-stage="' + stage + '"]').show();
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.updateStageDropdownText = function(stage) {
|
||||||
|
$('.stage-selection').text(stage);
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.updateDropdown = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var stage = e.currentTarget.text;
|
||||||
|
this.updateStageDropdownText(stage);
|
||||||
|
this.populateJobs(stage);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Build;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
27
app/assets/javascripts/build_artifacts.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
(function() {
|
||||||
|
this.BuildArtifacts = (function() {
|
||||||
|
function BuildArtifacts() {
|
||||||
|
this.disablePropagation();
|
||||||
|
this.setupEntryClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildArtifacts.prototype.disablePropagation = function() {
|
||||||
|
$('.top-block').on('click', '.download', function(e) {
|
||||||
|
return e.stopPropagation();
|
||||||
|
});
|
||||||
|
return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
|
||||||
|
return e.stopImmediatePropagation();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BuildArtifacts.prototype.setupEntryClick = function() {
|
||||||
|
return $('.tree-holder').on('click', 'tr[data-link]', function(e) {
|
||||||
|
return window.location = this.dataset.link;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return BuildArtifacts;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,14 +0,0 @@
|
||||||
class @BuildArtifacts
|
|
||||||
constructor: () ->
|
|
||||||
@disablePropagation()
|
|
||||||
@setupEntryClick()
|
|
||||||
|
|
||||||
disablePropagation: ->
|
|
||||||
$('.top-block').on 'click', '.download', (e) ->
|
|
||||||
e.stopPropagation()
|
|
||||||
$('.tree-holder').on 'click', 'tr[data-link] a', (e) ->
|
|
||||||
e.stopImmediatePropagation()
|
|
||||||
|
|
||||||
setupEntryClick: ->
|
|
||||||
$('.tree-holder').on 'click', 'tr[data-link]', (e) ->
|
|
||||||
window.location = @dataset.link
|
|
13
app/assets/javascripts/commit.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
(function() {
|
||||||
|
this.Commit = (function() {
|
||||||
|
function Commit() {
|
||||||
|
$('.files .diff-file').each(function() {
|
||||||
|
return new CommitFile(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Commit;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,4 +0,0 @@
|
||||||
class @Commit
|
|
||||||
constructor: ->
|
|
||||||
$('.files .diff-file').each ->
|
|
||||||
new CommitFile(this)
|
|
13
app/assets/javascripts/commit/file.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
(function() {
|
||||||
|
this.CommitFile = (function() {
|
||||||
|
function CommitFile(file) {
|
||||||
|
if ($('.image', file).length) {
|
||||||
|
new ImageFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommitFile;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -1,5 +0,0 @@
|
||||||
class @CommitFile
|
|
||||||
|
|
||||||
constructor: (file) ->
|
|
||||||
if $('.image', file).length
|
|
||||||
new ImageFile(file)
|
|
175
app/assets/javascripts/commit/image-file.js
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
(function() {
|
||||||
|
this.ImageFile = (function() {
|
||||||
|
var prepareFrames;
|
||||||
|
|
||||||
|
ImageFile.availWidth = 900;
|
||||||
|
|
||||||
|
ImageFile.viewModes = ['two-up', 'swipe'];
|
||||||
|
|
||||||
|
function ImageFile(file) {
|
||||||
|
this.file = file;
|
||||||
|
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
|
||||||
|
return function(deletedWidth, deletedHeight) {
|
||||||
|
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
|
||||||
|
if (width === deletedWidth && height === deletedHeight) {
|
||||||
|
return _this.initViewModes();
|
||||||
|
} else {
|
||||||
|
return _this.initView('two-up');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageFile.prototype.initViewModes = function() {
|
||||||
|
var viewMode;
|
||||||
|
viewMode = ImageFile.viewModes[0];
|
||||||
|
$('.view-modes', this.file).removeClass('hide');
|
||||||
|
$('.view-modes-menu', this.file).on('click', 'li', (function(_this) {
|
||||||
|
return function(event) {
|
||||||
|
if (!$(event.currentTarget).hasClass('active')) {
|
||||||
|
return _this.activateViewMode(event.currentTarget.className);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
return this.activateViewMode(viewMode);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageFile.prototype.activateViewMode = function(viewMode) {
|
||||||
|
$('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active');
|
||||||
|
return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) {
|
||||||
|
return function() {
|
||||||
|
$(".view." + viewMode, _this.file).fadeIn(200);
|
||||||
|
return _this.initView(viewMode);
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageFile.prototype.initView = function(viewMode) {
|
||||||
|
return this.views[viewMode].call(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
prepareFrames = function(view) {
|
||||||
|
var maxHeight, maxWidth;
|
||||||
|
maxWidth = 0;
|
||||||
|
maxHeight = 0;
|
||||||
|
$('.frame', view).each((function(_this) {
|
||||||
|
return function(index, frame) {
|
||||||
|
var height, width;
|
||||||
|
width = $(frame).width();
|
||||||
|
height = $(frame).height();
|
||||||
|
maxWidth = width > maxWidth ? width : maxWidth;
|
||||||
|
return maxHeight = height > maxHeight ? height : maxHeight;
|
||||||
|
};
|
||||||
|
})(this)).css({
|
||||||
|
width: maxWidth,
|
||||||
|
height: maxHeight
|
||||||
|
});
|
||||||
|
return [maxWidth, maxHeight];
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageFile.prototype.views = {
|
||||||
|
'two-up': function() {
|
||||||
|
return $('.two-up.view .wrap', this.file).each((function(_this) {
|
||||||
|
return function(index, wrap) {
|
||||||
|
$('img', wrap).each(function() {
|
||||||
|
var currentWidth;
|
||||||
|
currentWidth = $(this).width();
|
||||||
|
if (currentWidth > ImageFile.availWidth / 2) {
|
||||||
|
return $(this).width(ImageFile.availWidth / 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return _this.requestImageInfo($('img', wrap), function(width, height) {
|
||||||
|
$('.image-info .meta-width', wrap).text(width + "px");
|
||||||
|
$('.image-info .meta-height', wrap).text(height + "px");
|
||||||
|
return $('.image-info', wrap).removeClass('hide');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
},
|
||||||
|
'swipe': function() {
|
||||||
|
var maxHeight, maxWidth;
|
||||||
|
maxWidth = 0;
|
||||||
|
maxHeight = 0;
|
||||||
|
return $('.swipe.view', this.file).each((function(_this) {
|
||||||
|
return function(index, view) {
|
||||||
|
var ref;
|
||||||
|
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
|
||||||
|
$('.swipe-frame', view).css({
|
||||||
|
width: maxWidth + 16,
|
||||||
|
height: maxHeight + 28
|
||||||
|
});
|
||||||
|
$('.swipe-wrap', view).css({
|
||||||
|
width: maxWidth + 1,
|
||||||
|
height: maxHeight + 2
|
||||||
|
});
|
||||||
|
return $('.swipe-bar', view).css({
|
||||||
|
left: 0
|
||||||
|
}).draggable({
|
||||||
|
axis: 'x',
|
||||||
|
containment: 'parent',
|
||||||
|
drag: function(event) {
|
||||||
|
return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
|
||||||
|
},
|
||||||
|
stop: function(event) {
|
||||||
|
return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
},
|
||||||
|
'onion-skin': function() {
|
||||||
|
var dragTrackWidth, maxHeight, maxWidth;
|
||||||
|
maxWidth = 0;
|
||||||
|
maxHeight = 0;
|
||||||
|
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
|
||||||
|
return $('.onion-skin.view', this.file).each((function(_this) {
|
||||||
|
return function(index, view) {
|
||||||
|
var ref;
|
||||||
|
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
|
||||||
|
$('.onion-skin-frame', view).css({
|
||||||
|
width: maxWidth + 16,
|
||||||
|
height: maxHeight + 28
|
||||||
|
});
|
||||||
|
$('.swipe-wrap', view).css({
|
||||||
|
width: maxWidth + 1,
|
||||||
|
height: maxHeight + 2
|
||||||
|
});
|
||||||
|
return $('.dragger', view).css({
|
||||||
|
left: dragTrackWidth
|
||||||
|
}).draggable({
|
||||||
|
axis: 'x',
|
||||||
|
containment: 'parent',
|
||||||
|
drag: function(event) {
|
||||||
|
return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
|
||||||
|
},
|
||||||
|
stop: function(event) {
|
||||||
|
return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageFile.prototype.requestImageInfo = function(img, callback) {
|
||||||
|
var domImg;
|
||||||
|
domImg = img.get(0);
|
||||||
|
if (domImg) {
|
||||||
|
if (domImg.complete) {
|
||||||
|
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
|
||||||
|
} else {
|
||||||
|
return img.on('load', (function(_this) {
|
||||||
|
return function() {
|
||||||
|
return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return ImageFile;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
}).call(this);
|