New upstream version 8.11.3+dfsg
1
.gitignore
vendored
|
@ -30,6 +30,7 @@
|
|||
/config/secrets.yml
|
||||
/config/sidekiq.yml
|
||||
/coverage/*
|
||||
/coverage-javascript/
|
||||
/db/*.sqlite3
|
||||
/db/*.sqlite3-journal
|
||||
/db/data.yml
|
||||
|
|
144
.gitlab-ci.yml
|
@ -1,7 +1,7 @@
|
|||
image: "ruby:2.1"
|
||||
image: "ruby:2.3.1"
|
||||
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby-231"
|
||||
paths:
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
@ -15,6 +15,7 @@ variables:
|
|||
USE_DB: "true"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
GIT_DEPTH: "20"
|
||||
PHANTOMJS_VERSION: "2.1.1"
|
||||
|
||||
before_script:
|
||||
- source ./scripts/prepare_build.sh
|
||||
|
@ -28,6 +29,7 @@ stages:
|
|||
- prepare
|
||||
- test
|
||||
- post-test
|
||||
- pages
|
||||
|
||||
# Prepare and merge knapsack tests
|
||||
.knapsack-state: &knapsack-state
|
||||
|
@ -40,6 +42,7 @@ stages:
|
|||
paths:
|
||||
- knapsack/
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
paths:
|
||||
- knapsack/
|
||||
|
||||
|
@ -81,8 +84,10 @@ update-knapsack:
|
|||
- cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
|
||||
- knapsack rspec
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
paths:
|
||||
- knapsack/
|
||||
- coverage/
|
||||
|
||||
.spinach-knapsack: &spinach-knapsack
|
||||
stage: test
|
||||
|
@ -97,8 +102,10 @@ update-knapsack:
|
|||
- 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)'
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
paths:
|
||||
- knapsack/
|
||||
- coverage/
|
||||
|
||||
rspec 0 20: *rspec-knapsack
|
||||
rspec 1 20: *rspec-knapsack
|
||||
|
@ -132,68 +139,68 @@ spinach 7 10: *spinach-knapsack
|
|||
spinach 8 10: *spinach-knapsack
|
||||
spinach 9 10: *spinach-knapsack
|
||||
|
||||
# Execute all testing suites against Ruby 2.3
|
||||
.ruby-23: &ruby-23
|
||||
image: "ruby:2.3"
|
||||
# Execute all testing suites against Ruby 2.1
|
||||
.ruby-21: &ruby-21
|
||||
image: "ruby:2.1"
|
||||
<<: *use-db
|
||||
only:
|
||||
- master
|
||||
cache:
|
||||
key: "ruby-23"
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
||||
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
|
||||
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
|
||||
<<: *rspec-knapsack
|
||||
<<: *ruby-23
|
||||
<<: *ruby-21
|
||||
|
||||
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23
|
||||
.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
|
||||
<<: *spinach-knapsack
|
||||
<<: *ruby-23
|
||||
<<: *ruby-21
|
||||
|
||||
rspec 0 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 1 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 2 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 3 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 4 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 5 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 6 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 7 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 8 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 9 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 10 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 11 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 12 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 13 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 14 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 15 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 16 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 17 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 18 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 19 20 ruby23: *rspec-knapsack-ruby23
|
||||
rspec 0 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 1 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 2 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 3 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 4 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 5 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 6 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 7 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 8 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 9 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 10 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 11 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 12 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 13 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 14 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 15 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 16 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 17 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 18 20 ruby21: *rspec-knapsack-ruby21
|
||||
rspec 19 20 ruby21: *rspec-knapsack-ruby21
|
||||
|
||||
spinach 0 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 1 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 2 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 3 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 4 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 5 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 6 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 7 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 8 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 9 10 ruby23: *spinach-knapsack-ruby23
|
||||
spinach 0 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 1 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 2 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 3 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 4 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 5 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 6 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 7 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 8 10 ruby21: *spinach-knapsack-ruby21
|
||||
spinach 9 10 ruby21: *spinach-knapsack-ruby21
|
||||
|
||||
# Other generic tests
|
||||
|
||||
.static-analyses-variables: &static-analyses-variables
|
||||
.ruby-static-analysis: &ruby-static-analysis
|
||||
variables:
|
||||
SIMPLECOV: "false"
|
||||
USE_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
|
||||
.exec: &exec
|
||||
<<: *static-analyses-variables
|
||||
<<: *ruby-static-analysis
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec $CI_BUILD_NAME
|
||||
|
@ -216,20 +223,54 @@ teaspoon:
|
|||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
|
||||
- apt-get install --assume-yes nodejs
|
||||
- npm install --global istanbul
|
||||
- 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:
|
||||
stage: test
|
||||
<<: *static-analyses-variables
|
||||
<<: *ruby-static-analysis
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- "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:
|
||||
stage: post-test
|
||||
variables:
|
||||
USE_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
script:
|
||||
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
|
||||
when: on_failure
|
||||
|
@ -238,3 +279,20 @@ notify:slack:
|
|||
- tags@gitlab-org/gitlab-ce
|
||||
- master@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.
|
||||
Style/EmptyLinesAroundBlockBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Keeps track of empty lines around class bodies.
|
||||
Style/EmptyLinesAroundClassBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Keeps track of empty lines around module bodies.
|
||||
Style/EmptyLinesAroundModuleBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Keeps track of empty lines around method bodies.
|
||||
Style/EmptyLinesAroundMethodBody:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Avoid the use of END blocks.
|
||||
Style/EndBlock:
|
||||
|
@ -291,6 +291,10 @@ Style/MultilineMethodDefinitionBraceLayout:
|
|||
Style/MultilineOperationIndentation:
|
||||
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).
|
||||
Style/NegatedIf:
|
||||
Enabled: true
|
||||
|
@ -369,6 +373,10 @@ Style/SpaceAfterNot:
|
|||
Style/SpaceAfterSemicolon:
|
||||
Enabled: true
|
||||
|
||||
# Use space around equals in parameter default
|
||||
Style/SpaceAroundEqualsInParameterDefault:
|
||||
Enabled: true
|
||||
|
||||
# Use a space around keywords if appropriate.
|
||||
Style/SpaceAroundKeyword:
|
||||
Enabled: true
|
||||
|
@ -506,6 +514,15 @@ Metrics/PerceivedComplexity:
|
|||
|
||||
#################### 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
|
||||
# without parentheses.
|
||||
Lint/AmbiguousOperator:
|
||||
|
|
|
@ -19,10 +19,6 @@ Lint/AssignmentInCondition:
|
|||
Lint/HandleExceptions:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 21
|
||||
Lint/IneffectiveAccessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
Lint/Loop:
|
||||
Enabled: false
|
||||
|
@ -48,10 +44,6 @@ Lint/UnusedBlockArgument:
|
|||
Lint/UnusedMethodArgument:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 11
|
||||
Lint/UselessAccessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 12
|
||||
# Cop supports --auto-correct.
|
||||
Performance/PushSplat:
|
||||
|
@ -226,10 +218,6 @@ Style/LineEndConcatenation:
|
|||
Style/MethodCallParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3
|
||||
Style/MultilineTernaryOperator:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 62
|
||||
# Cop supports --auto-correct.
|
||||
Style/MutableConstant:
|
||||
|
@ -351,13 +339,6 @@ Style/SingleLineBlockParams:
|
|||
Style/SingleLineMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 14
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: space, no_space
|
||||
Style/SpaceAroundEqualsInParameterDefault:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 119
|
||||
# Cop supports --auto-correct.
|
||||
# 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.
|
||||
|
||||
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
|
||||
- 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
|
||||
|
@ -20,6 +198,7 @@ v 8.10.3
|
|||
- 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
|
||||
- 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
|
||||
- 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 GFM autocomplete not working on wiki pages
|
||||
- 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
|
||||
- Support U2F devices in Firefox. !5177
|
||||
- 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
|
||||
- Updated project header design
|
||||
- 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
|
||||
- Updated layout for Projects, Groups, Users on Admin area. !4424
|
||||
- Fix changing issue state columns in milestone view
|
||||
|
@ -159,6 +342,7 @@ v 8.10.0
|
|||
- Fix new snippet style bug (elliotec)
|
||||
- Instrument Rinku usage
|
||||
- Be explicit to define merge request discussion variables
|
||||
- Use cache for todos counter calling TodoService
|
||||
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
|
||||
- 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)
|
||||
|
|
|
@ -41,6 +41,8 @@ abbreviation.
|
|||
If you have read this guide and want to know how the GitLab [core team]
|
||||
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
|
||||
|
||||
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
|
||||
[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. 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 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
|
||||
- string literal quoting style **Option A**: single quoted by default
|
||||
1. [Rails](https://github.com/bbatsov/rails-style-guide)
|
||||
1. [Newlines styleguide][newlines-styleguide]
|
||||
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. [Shell commands](doc/development/shell_commands.md) created by GitLab
|
||||
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
|
||||
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation 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
|
||||
[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/
|
||||
|
|
|
@ -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'
|
||||
|
||||
gem 'rails', '4.2.7'
|
||||
gem 'rails', '4.2.7.1'
|
||||
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
||||
|
||||
# Responders respond_to and respond_with
|
||||
|
@ -9,6 +9,7 @@ gem 'responders', '~> 2.0'
|
|||
# Specify a sprockets version due to increased performance
|
||||
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
|
||||
gem 'sprockets', '~> 3.6.0'
|
||||
gem 'sprockets-es6'
|
||||
|
||||
# Default values for AR models
|
||||
gem 'default_value_for', '~> 3.0.0'
|
||||
|
@ -19,7 +20,7 @@ gem 'pg', '~> 0.18.2', group: :postgres
|
|||
|
||||
# Authentication libraries
|
||||
gem 'devise', '~> 4.0'
|
||||
gem 'doorkeeper', '~> 4.0'
|
||||
gem 'doorkeeper', '~> 4.2.0'
|
||||
gem 'omniauth', '~> 1.3.1'
|
||||
gem 'omniauth-auth0', '~> 1.4.1'
|
||||
gem 'omniauth-azure-oauth2', '~> 0.0.6'
|
||||
|
@ -52,7 +53,7 @@ gem 'browser', '~> 2.2'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem 'gitlab_git', '~> 10.3.2'
|
||||
gem 'gitlab_git', '~> 10.4.7'
|
||||
|
||||
# LDAP Auth
|
||||
# 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'
|
||||
|
||||
# API
|
||||
gem 'grape', '~> 0.13.0'
|
||||
gem 'grape', '~> 0.15.0'
|
||||
gem 'grape-entity', '~> 0.4.2'
|
||||
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'
|
||||
|
||||
# HAML
|
||||
gem 'hamlit', '~> 2.5'
|
||||
gem 'hamlit', '~> 2.6.1'
|
||||
|
||||
# Files attachments
|
||||
gem 'carrierwave', '~> 0.10.0'
|
||||
|
@ -153,7 +154,7 @@ gem 'settingslogic', '~> 2.0.9'
|
|||
|
||||
# Misc
|
||||
|
||||
gem 'version_sorter', '~> 2.0.0'
|
||||
gem 'version_sorter', '~> 2.1.0'
|
||||
|
||||
# Cache
|
||||
gem 'redis-rails', '~> 4.0.0'
|
||||
|
@ -162,9 +163,6 @@ gem 'redis-rails', '~> 4.0.0'
|
|||
gem 'redis', '~> 3.2'
|
||||
gem 'connection_pool', '~> 2.0'
|
||||
|
||||
# Campfire integration
|
||||
gem 'tinder', '~> 1.10.0'
|
||||
|
||||
# HipChat integration
|
||||
gem 'hipchat', '~> 1.5.0'
|
||||
|
||||
|
@ -203,7 +201,7 @@ gem 'licensee', '~> 8.0.0'
|
|||
gem 'rack-attack', '~> 4.3.1'
|
||||
|
||||
# Ace editor
|
||||
gem 'ace-rails-ap', '~> 4.0.2'
|
||||
gem 'ace-rails-ap', '~> 4.1.0'
|
||||
|
||||
# Keyboard shortcuts
|
||||
gem 'mousetrap-rails', '~> 1.4.6'
|
||||
|
@ -211,7 +209,8 @@ gem 'mousetrap-rails', '~> 1.4.6'
|
|||
# Detect and convert string character encoding
|
||||
gem 'charlock_holmes', '~> 0.7.3'
|
||||
|
||||
# Parse duration
|
||||
# Parse time & duration
|
||||
gem 'chronic', '~> 0.10.2'
|
||||
gem 'chronic_duration', '~> 0.10.6'
|
||||
|
||||
gem 'sass-rails', '~> 5.0.0'
|
||||
|
@ -224,7 +223,7 @@ gem 'addressable', '~> 2.3.8'
|
|||
gem 'bootstrap-sass', '~> 3.3.0'
|
||||
gem 'font-awesome-rails', '~> 4.6.1'
|
||||
gem 'gemojione', '~> 3.0'
|
||||
gem 'gon', '~> 6.0.1'
|
||||
gem 'gon', '~> 6.1.0'
|
||||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||
gem 'jquery-rails', '~> 4.1.0'
|
||||
gem 'jquery-ui-rails', '~> 5.0.0'
|
||||
|
@ -252,7 +251,7 @@ group :development do
|
|||
|
||||
gem 'letter_opener_web', '~> 1.3.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 'web-console', '~> 2.0'
|
||||
|
||||
|
@ -274,7 +273,7 @@ group :development, :test do
|
|||
gem 'awesome_print', '~> 1.2.0', require: false
|
||||
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 'rspec-rails', '~> 3.5.0'
|
||||
gem 'rspec-retry', '~> 0.4.5'
|
||||
|
@ -302,7 +301,7 @@ group :development, :test do
|
|||
gem 'rubocop', '~> 0.41.2', require: false
|
||||
gem 'rubocop-rspec', '~> 1.5.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 'flay', '~> 2.6.1', require: false
|
||||
gem 'bundler-audit', '~> 0.5.0', require: false
|
||||
|
@ -316,6 +315,7 @@ end
|
|||
group :test do
|
||||
gem 'shoulda-matchers', '~> 2.8.0', require: false
|
||||
gem 'email_spec', '~> 1.6.0'
|
||||
gem 'json-schema', '~> 2.6.2'
|
||||
gem 'webmock', '~> 1.21.0'
|
||||
gem 'test_after_commit', '~> 0.4.2'
|
||||
gem 'sham_rack', '~> 1.3.6'
|
||||
|
@ -325,7 +325,7 @@ group :production do
|
|||
gem 'gitlab_meta', '7.0'
|
||||
end
|
||||
|
||||
gem 'newrelic_rpm', '~> 3.14'
|
||||
gem 'newrelic_rpm', '~> 3.16'
|
||||
|
||||
gem 'octokit', '~> 4.3.0'
|
||||
|
||||
|
@ -333,6 +333,8 @@ gem 'mail_room', '~> 0.8'
|
|||
|
||||
gem 'email_reply_parser', '~> 0.5.8'
|
||||
|
||||
gem 'ruby-prof', '~> 0.15.9'
|
||||
|
||||
## CI
|
||||
gem 'activerecord-session_store', '~> 1.0.0'
|
||||
gem 'nested_form', '~> 0.3.2'
|
||||
|
@ -347,5 +349,5 @@ gem 'paranoia', '~> 2.0'
|
|||
gem 'health_check', '~> 2.1.0'
|
||||
|
||||
# System information
|
||||
gem 'vmstat', '~> 2.1.1'
|
||||
gem 'vmstat', '~> 2.2'
|
||||
gem 'sys-filesystem', '~> 1.1.6'
|
||||
|
|
160
Gemfile.lock
|
@ -2,35 +2,35 @@ GEM
|
|||
remote: https://rubygems.org/
|
||||
specs:
|
||||
RedCloth (4.3.2)
|
||||
ace-rails-ap (4.0.2)
|
||||
actionmailer (4.2.7)
|
||||
actionpack (= 4.2.7)
|
||||
actionview (= 4.2.7)
|
||||
activejob (= 4.2.7)
|
||||
ace-rails-ap (4.1.0)
|
||||
actionmailer (4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activejob (= 4.2.7.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.7)
|
||||
actionview (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
actionpack (4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
actionview (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
activejob (4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
activejob (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
activemodel (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.7)
|
||||
activemodel (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
activerecord (4.2.7.1)
|
||||
activemodel (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
arel (~> 6.0)
|
||||
activerecord-session_store (1.0.0)
|
||||
actionpack (>= 4.0, < 5.1)
|
||||
|
@ -38,7 +38,7 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 1.5.2, < 3)
|
||||
railties (>= 4.0, < 5.1)
|
||||
activesupport (4.2.7)
|
||||
activesupport (4.2.7.1)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
|
@ -59,7 +59,7 @@ GEM
|
|||
oauth2 (~> 1.0)
|
||||
asciidoctor (1.5.3)
|
||||
ast (2.3.0)
|
||||
attr_encrypted (3.0.1)
|
||||
attr_encrypted (3.0.3)
|
||||
encryptor (~> 3.0.0)
|
||||
attr_required (1.0.0)
|
||||
autoprefixer-rails (6.2.3)
|
||||
|
@ -85,6 +85,10 @@ GEM
|
|||
faraday (~> 0.9)
|
||||
faraday_middleware (~> 0.10)
|
||||
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)
|
||||
base32 (0.3.2)
|
||||
bcrypt (3.1.11)
|
||||
|
@ -100,9 +104,9 @@ GEM
|
|||
brakeman (3.3.2)
|
||||
browser (2.2.0)
|
||||
builder (3.2.2)
|
||||
bullet (5.0.0)
|
||||
bullet (5.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.9.0)
|
||||
uniform_notifier (~> 1.10.0)
|
||||
bundler-audit (0.5.0)
|
||||
bundler (~> 1.2)
|
||||
thor (~> 0.18)
|
||||
|
@ -124,6 +128,7 @@ GEM
|
|||
mime-types (>= 1.16)
|
||||
cause (0.1)
|
||||
charlock_holmes (0.7.3)
|
||||
chronic (0.10.2)
|
||||
chronic_duration (0.10.6)
|
||||
numerizer (~> 0.1.1)
|
||||
chunky_png (1.3.5)
|
||||
|
@ -149,11 +154,11 @@ GEM
|
|||
d3_rails (3.5.11)
|
||||
railties (>= 3.1.0)
|
||||
daemons (1.2.3)
|
||||
database_cleaner (1.4.1)
|
||||
database_cleaner (1.5.3)
|
||||
debug_inspector (0.0.2)
|
||||
debugger-ruby_core_source (1.3.8)
|
||||
default_value_for (3.0.1)
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
default_value_for (3.0.2)
|
||||
activerecord (>= 3.2.0, < 5.1)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (4.1.1)
|
||||
|
@ -171,7 +176,7 @@ GEM
|
|||
diff-lcs (1.2.5)
|
||||
diffy (3.0.7)
|
||||
docile (1.1.5)
|
||||
doorkeeper (4.0.0)
|
||||
doorkeeper (4.2.0)
|
||||
railties (>= 4.2)
|
||||
dropzonejs-rails (0.7.2)
|
||||
rails (> 3.1)
|
||||
|
@ -274,7 +279,7 @@ GEM
|
|||
diff-lcs (~> 1.1)
|
||||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_git (10.3.2)
|
||||
gitlab_git (10.4.7)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
|
@ -285,7 +290,7 @@ GEM
|
|||
omniauth (~> 1.0)
|
||||
pyu-ruby-sasl (~> 0.0.3.1)
|
||||
rubyntlm (~> 0.3)
|
||||
globalid (0.3.6)
|
||||
globalid (0.3.7)
|
||||
activesupport (>= 4.1.0)
|
||||
gollum-grit_adapter (1.0.1)
|
||||
gitlab-grit (~> 2.7, >= 2.7.1)
|
||||
|
@ -299,12 +304,12 @@ GEM
|
|||
gollum-rugged_adapter (0.4.2)
|
||||
mime-types (>= 1.15)
|
||||
rugged (~> 0.24.0, >= 0.21.3)
|
||||
gon (6.0.1)
|
||||
gon (6.1.0)
|
||||
actionpack (>= 3.0)
|
||||
json
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
grape (0.13.0)
|
||||
grape (0.15.0)
|
||||
activesupport
|
||||
builder
|
||||
hashie (>= 2.1.0)
|
||||
|
@ -317,7 +322,7 @@ GEM
|
|||
grape-entity (0.4.8)
|
||||
activesupport
|
||||
multi_json (>= 1.3.2)
|
||||
hamlit (2.5.0)
|
||||
hamlit (2.6.1)
|
||||
temple (~> 0.7.6)
|
||||
thor
|
||||
tilt
|
||||
|
@ -331,11 +336,10 @@ GEM
|
|||
activesupport (>= 2)
|
||||
nokogiri (~> 1.4)
|
||||
htmlentities (4.3.4)
|
||||
http_parser.rb (0.5.3)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.7.0.1)
|
||||
httpclient (2.8.2)
|
||||
i18n (0.7.0)
|
||||
ice_nine (0.11.1)
|
||||
influxdb (0.2.3)
|
||||
|
@ -353,6 +357,8 @@ GEM
|
|||
jquery-ui-rails (5.0.5)
|
||||
railties (>= 3.2.16)
|
||||
json (1.8.3)
|
||||
json-schema (2.6.2)
|
||||
addressable (~> 2.3.8)
|
||||
jwt (1.5.4)
|
||||
kaminari (0.17.0)
|
||||
actionpack (>= 3.0.0)
|
||||
|
@ -400,7 +406,7 @@ GEM
|
|||
nested_form (0.3.2)
|
||||
net-ldap (0.12.1)
|
||||
net-ssh (3.0.1)
|
||||
newrelic_rpm (3.14.1.311)
|
||||
newrelic_rpm (3.16.0.318)
|
||||
nokogiri (1.6.8)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
|
@ -505,7 +511,7 @@ GEM
|
|||
rack-cors (0.4.0)
|
||||
rack-mount (0.8.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-oauth2 (1.2.1)
|
||||
rack-oauth2 (1.2.3)
|
||||
activesupport (>= 2.3)
|
||||
attr_required (>= 0.0.5)
|
||||
httpclient (>= 2.4)
|
||||
|
@ -515,16 +521,16 @@ GEM
|
|||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.7)
|
||||
actionmailer (= 4.2.7)
|
||||
actionpack (= 4.2.7)
|
||||
actionview (= 4.2.7)
|
||||
activejob (= 4.2.7)
|
||||
activemodel (= 4.2.7)
|
||||
activerecord (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
rails (4.2.7.1)
|
||||
actionmailer (= 4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activejob (= 4.2.7.1)
|
||||
activemodel (= 4.2.7.1)
|
||||
activerecord (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.7)
|
||||
railties (= 4.2.7.1)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
|
@ -534,9 +540,9 @@ GEM
|
|||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
railties (4.2.7)
|
||||
actionpack (= 4.2.7)
|
||||
activesupport (= 4.2.7)
|
||||
railties (4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.1.0)
|
||||
|
@ -571,7 +577,7 @@ GEM
|
|||
redis-store (~> 1.1.0)
|
||||
redis-store (1.1.7)
|
||||
redis (>= 2.2)
|
||||
request_store (1.3.0)
|
||||
request_store (1.3.1)
|
||||
rerun (0.11.0)
|
||||
listen (~> 3.0)
|
||||
responders (2.1.1)
|
||||
|
@ -616,6 +622,7 @@ GEM
|
|||
rubocop (>= 0.40.0)
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-prof (0.15.9)
|
||||
ruby-progressbar (1.8.1)
|
||||
ruby-saml (1.3.0)
|
||||
nokogiri (>= 1.5.10)
|
||||
|
@ -667,10 +674,9 @@ GEM
|
|||
redis-namespace (>= 1.5.2)
|
||||
rufus-scheduler (>= 2.0.24)
|
||||
sidekiq (>= 4.0.0)
|
||||
simple_oauth (0.1.9)
|
||||
simplecov (0.11.2)
|
||||
simplecov (0.12.0)
|
||||
docile (~> 1.1.0)
|
||||
json (~> 1.8)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
sinatra (1.4.7)
|
||||
|
@ -700,6 +706,10 @@ GEM
|
|||
sprockets (3.6.3)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-es6 (0.9.0)
|
||||
babel-source (>= 5.8.11)
|
||||
babel-transpiler
|
||||
sprockets (>= 3.0.0)
|
||||
sprockets-rails (3.1.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
|
@ -733,21 +743,8 @@ GEM
|
|||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
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)
|
||||
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)
|
||||
thread_safe (~> 0.1)
|
||||
u2f (0.2.1)
|
||||
|
@ -766,16 +763,16 @@ GEM
|
|||
unicorn-worker-killer (0.4.4)
|
||||
get_process_mem (~> 0)
|
||||
unicorn (>= 4, < 6)
|
||||
uniform_notifier (1.9.0)
|
||||
uniform_notifier (1.10.0)
|
||||
uuid (2.3.8)
|
||||
macaddr (~> 1.0)
|
||||
version_sorter (2.0.0)
|
||||
version_sorter (2.1.0)
|
||||
virtus (1.0.5)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
vmstat (2.1.1)
|
||||
vmstat (2.2.0)
|
||||
warden (1.2.6)
|
||||
rack (>= 1.0)
|
||||
web-console (2.3.0)
|
||||
|
@ -802,7 +799,7 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
RedCloth (~> 4.3.2)
|
||||
ace-rails-ap (~> 4.0.2)
|
||||
ace-rails-ap (~> 4.1.0)
|
||||
activerecord-session_store (~> 1.0.0)
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
addressable (~> 2.3.8)
|
||||
|
@ -821,24 +818,25 @@ DEPENDENCIES
|
|||
bootstrap-sass (~> 3.3.0)
|
||||
brakeman (~> 3.3.0)
|
||||
browser (~> 2.2)
|
||||
bullet (~> 5.0.0)
|
||||
bullet (~> 5.2.0)
|
||||
bundler-audit (~> 0.5.0)
|
||||
byebug (~> 8.2.1)
|
||||
capybara (~> 2.6.2)
|
||||
capybara-screenshot (~> 1.0.0)
|
||||
carrierwave (~> 0.10.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
chronic (~> 0.10.2)
|
||||
chronic_duration (~> 0.10.6)
|
||||
coffee-rails (~> 4.1.0)
|
||||
connection_pool (~> 2.0)
|
||||
creole (~> 0.5.0)
|
||||
d3_rails (~> 3.5.0)
|
||||
database_cleaner (~> 1.4.0)
|
||||
database_cleaner (~> 1.5.0)
|
||||
default_value_for (~> 3.0.0)
|
||||
devise (~> 4.0)
|
||||
devise-two-factor (~> 3.0.0)
|
||||
diffy (~> 3.0.3)
|
||||
doorkeeper (~> 4.0)
|
||||
doorkeeper (~> 4.2.0)
|
||||
dropzonejs-rails (~> 0.7.1)
|
||||
email_reply_parser (~> 0.5.8)
|
||||
email_spec (~> 1.6.0)
|
||||
|
@ -861,15 +859,15 @@ DEPENDENCIES
|
|||
github-linguist (~> 4.7.0)
|
||||
github-markup (~> 1.4)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_git (~> 10.3.2)
|
||||
gitlab_git (~> 10.4.7)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.2)
|
||||
gon (~> 6.0.1)
|
||||
grape (~> 0.13.0)
|
||||
gon (~> 6.1.0)
|
||||
grape (~> 0.15.0)
|
||||
grape-entity (~> 0.4.2)
|
||||
hamlit (~> 2.5)
|
||||
hamlit (~> 2.6.1)
|
||||
health_check (~> 2.1.0)
|
||||
hipchat (~> 1.5.0)
|
||||
html-pipeline (~> 1.11.0)
|
||||
|
@ -879,6 +877,7 @@ DEPENDENCIES
|
|||
jquery-rails (~> 4.1.0)
|
||||
jquery-turbolinks (~> 2.1.0)
|
||||
jquery-ui-rails (~> 5.0.0)
|
||||
json-schema (~> 2.6.2)
|
||||
jwt
|
||||
kaminari (~> 0.17.0)
|
||||
knapsack (~> 1.11.0)
|
||||
|
@ -893,7 +892,7 @@ DEPENDENCIES
|
|||
mysql2 (~> 0.3.16)
|
||||
nested_form (~> 0.3.2)
|
||||
net-ssh (~> 3.0.1)
|
||||
newrelic_rpm (~> 3.14)
|
||||
newrelic_rpm (~> 3.16)
|
||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||
oauth2 (~> 1.2.0)
|
||||
octokit (~> 4.3.0)
|
||||
|
@ -920,7 +919,7 @@ DEPENDENCIES
|
|||
rack-attack (~> 4.3.1)
|
||||
rack-cors (~> 0.4.0)
|
||||
rack-oauth2 (~> 1.2.1)
|
||||
rails (= 4.2.7)
|
||||
rails (= 4.2.7.1)
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rainbow (~> 2.1.0)
|
||||
rblineprof (~> 0.3.6)
|
||||
|
@ -940,6 +939,7 @@ DEPENDENCIES
|
|||
rubocop (~> 0.41.2)
|
||||
rubocop-rspec (~> 1.5.0)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
ruby-prof (~> 0.15.9)
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 5.0.0)
|
||||
scss_lint (~> 0.47.0)
|
||||
|
@ -952,7 +952,7 @@ DEPENDENCIES
|
|||
shoulda-matchers (~> 2.8.0)
|
||||
sidekiq (~> 4.0)
|
||||
sidekiq-cron (~> 0.4.0)
|
||||
simplecov (~> 0.11.0)
|
||||
simplecov (= 0.12.0)
|
||||
sinatra (~> 1.4.4)
|
||||
six (~> 0.2.0)
|
||||
slack-notifier (~> 1.2.0)
|
||||
|
@ -963,6 +963,7 @@ DEPENDENCIES
|
|||
spring-commands-spinach (~> 1.1.0)
|
||||
spring-commands-teaspoon (~> 0.0.2)
|
||||
sprockets (~> 3.6.0)
|
||||
sprockets-es6
|
||||
state_machines-activerecord (~> 0.4.0)
|
||||
sys-filesystem (~> 1.1.6)
|
||||
task_list (~> 1.0.2)
|
||||
|
@ -970,7 +971,6 @@ DEPENDENCIES
|
|||
teaspoon-jasmine (~> 2.2.0)
|
||||
test_after_commit (~> 0.4.2)
|
||||
thin (~> 1.7.0)
|
||||
tinder (~> 1.10.0)
|
||||
turbolinks (~> 2.5.0)
|
||||
u2f (~> 0.2.1)
|
||||
uglifier (~> 2.7.2)
|
||||
|
@ -978,9 +978,9 @@ DEPENDENCIES
|
|||
unf (~> 0.1.4)
|
||||
unicorn (~> 4.9.0)
|
||||
unicorn-worker-killer (~> 0.4.2)
|
||||
version_sorter (~> 2.0.0)
|
||||
version_sorter (~> 2.1.0)
|
||||
virtus (~> 1.0.1)
|
||||
vmstat (~> 2.1.1)
|
||||
vmstat (~> 2.2)
|
||||
web-console (~> 2.0)
|
||||
webmock (~> 1.21.0)
|
||||
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,
|
||||
etc.).
|
||||
|
||||
- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
|
||||
|
||||
## Common actions
|
||||
|
||||
### 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);
|