summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Lopez <james@jameslopez.es>2016-01-29 14:27:10 +0100
committerJames Lopez <james@jameslopez.es>2016-01-29 14:27:10 +0100
commit7ca6779654ed2da5ba31ab9256406feb1b7fb8ee (patch)
tree8c3d1e2eb642b667c9e46babdc786a4d4dc3c789
parent4d2da5fd2585fead823c4450e54613dadf882c0d (diff)
parentf5860ce6466bf8934bc01254351bffd005dfeafe (diff)
downloadgitlab-ce-7ca6779654ed2da5ba31ab9256406feb1b7fb8ee.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/atom-url-issue
-rw-r--r--.gitlab-ci.yml36
-rw-r--r--.ruby-version2
-rw-r--r--CHANGELOG33
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile13
-rw-r--r--Gemfile.lock74
-rw-r--r--README.md2
-rw-r--r--app/assets/javascripts/application.js.coffee24
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee1
-rw-r--r--app/assets/javascripts/issue.js.coffee1
-rw-r--r--app/assets/javascripts/notes.js.coffee90
-rw-r--r--app/assets/javascripts/projects_list.js.coffee4
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee4
-rw-r--r--app/assets/stylesheets/framework/blocks.scss6
-rw-r--r--app/assets/stylesheets/framework/common.scss8
-rw-r--r--app/assets/stylesheets/framework/files.scss1
-rw-r--r--app/assets/stylesheets/framework/forms.scss38
-rw-r--r--app/assets/stylesheets/framework/header.scss6
-rw-r--r--app/assets/stylesheets/framework/highlight.scss18
-rw-r--r--app/assets/stylesheets/framework/panels.scss12
-rw-r--r--app/assets/stylesheets/framework/tables.scss6
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss13
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss6
-rw-r--r--app/assets/stylesheets/framework/typography.scss20
-rw-r--r--app/assets/stylesheets/highlight/dark.scss8
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss8
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss8
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss8
-rw-r--r--app/assets/stylesheets/highlight/white.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss5
-rw-r--r--app/assets/stylesheets/pages/groups.scss10
-rw-r--r--app/assets/stylesheets/pages/issues.scss5
-rw-r--r--app/assets/stylesheets/pages/notes.scss1
-rw-r--r--app/assets/stylesheets/pages/projects.scss50
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/controllers/application_controller.rb23
-rw-r--r--app/controllers/dashboard/projects_controller.rb2
-rw-r--r--app/controllers/dashboard_controller.rb6
-rw-r--r--app/controllers/groups_controller.rb16
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb15
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb4
-rw-r--r--app/controllers/projects/blame_controller.rb24
-rw-r--r--app/controllers/projects/compare_controller.rb2
-rw-r--r--app/controllers/projects/forks_controller.rb11
-rw-r--r--app/controllers/projects/imports_controller.rb20
-rw-r--r--app/controllers/projects/notes_controller.rb22
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb6
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/labels_helper.rb6
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/helpers/snippets_helper.rb75
-rw-r--r--app/models/event.rb12
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/issue.rb1
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/merge_request_diff.rb28
-rw-r--r--app/models/project.rb9
-rw-r--r--app/models/project_wiki.rb1
-rw-r--r--app/models/repository.rb6
-rw-r--r--app/models/tree.rb16
-rw-r--r--app/services/projects/import_service.rb67
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/admin/applications/_form.html.haml2
-rw-r--r--app/views/admin/deploy_keys/index.html.haml2
-rw-r--r--app/views/admin/groups/_form.html.haml3
-rw-r--r--app/views/admin/groups/index.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml3
-rw-r--r--app/views/admin/projects/index.html.haml6
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/dashboard/projects/index.atom.builder2
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/_dropdown.html.haml3
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--app/views/groups/group_members/index.html.haml3
-rw-r--r--app/views/groups/projects.html.haml4
-rw-r--r--app/views/groups/show.atom.builder2
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/projects/blame/show.html.haml10
-rw-r--r--app/views/projects/blob/_text.html.haml8
-rw-r--r--app/views/projects/blob/preview.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml2
-rw-r--r--app/views/projects/commit_statuses/_commit_status.html.haml2
-rw-r--r--app/views/projects/forks/index.html.haml58
-rw-r--r--app/views/projects/forks/new.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml15
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml2
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml5
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml3
-rw-r--r--app/views/projects/notes/discussions/_diff.html.haml2
-rw-r--r--app/views/projects/project_members/_group_members.html.haml2
-rw-r--r--app/views/projects/project_members/_team.html.haml2
-rw-r--r--app/views/projects/project_members/index.html.haml3
-rw-r--r--app/views/projects/show.atom.builder2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml6
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/shared/_promo.html.haml6
-rw-r--r--app/views/shared/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_search_form.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml6
-rw-r--r--app/views/shared/projects/_project.html.haml21
-rw-r--r--app/views/shared/snippets/_blob.html.haml3
-rw-r--r--app/views/users/show.atom.builder2
-rw-r--r--app/workers/repository_import_worker.rb46
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/haml.rb6
-rw-r--r--config/initializers/monkey_patch.rb48
-rw-r--r--config/routes.rb2
-rw-r--r--doc/administration/environment_variables.md1
-rw-r--r--doc/administration/housekeeping.md4
-rw-r--r--doc/api/README.md255
-rw-r--r--doc/api/branches.md108
-rw-r--r--doc/api/deploy_keys.md74
-rw-r--r--doc/api/issues.md404
-rw-r--r--doc/api/labels.md152
-rw-r--r--doc/api/merge_requests.md74
-rw-r--r--doc/api/namespaces.md40
-rw-r--r--doc/api/settings.md98
-rw-r--r--doc/api/system_hooks.md118
-rw-r--r--doc/ci/build_artifacts/README.md7
-rw-r--r--doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md6
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/development/ci_setup.md2
-rw-r--r--doc/install/installation.md10
-rw-r--r--doc/install/requirements.md3
-rw-r--r--doc/integration/README.md4
-rw-r--r--doc/integration/external-issue-tracker.md2
-rw-r--r--doc/integration/facebook.md6
-rw-r--r--doc/integration/github.md2
-rw-r--r--doc/integration/gitlab.md2
-rw-r--r--doc/integration/gmail_action_buttons_for_gitlab.md2
-rw-r--r--doc/integration/google.md2
-rw-r--r--doc/integration/img/facebook_api_keys.png (renamed from doc/integration/facebook_api_keys.png)bin125921 -> 125921 bytes
-rw-r--r--doc/integration/img/facebook_app_settings.png (renamed from doc/integration/facebook_app_settings.png)bin134387 -> 134387 bytes
-rw-r--r--doc/integration/img/facebook_website_url.png (renamed from doc/integration/facebook_website_url.png)bin42292 -> 42292 bytes
-rw-r--r--doc/integration/img/github_app.png (renamed from doc/integration/github_app.png)bin75297 -> 75297 bytes
-rw-r--r--doc/integration/img/gitlab_app.png (renamed from doc/integration/gitlab_app.png)bin55325 -> 55325 bytes
-rw-r--r--doc/integration/img/gmail_action_buttons_for_gitlab.png (renamed from doc/integration/gmail_actions_button.png)bin17321 -> 17321 bytes
-rw-r--r--doc/integration/img/google_app.png (renamed from doc/integration/google_app.png)bin52669 -> 52669 bytes
-rw-r--r--doc/integration/img/oauth_provider_admin_application.png (renamed from doc/integration/oauth_provider/admin_application.png)bin55533 -> 55533 bytes
-rw-r--r--doc/integration/img/oauth_provider_application_form.png (renamed from doc/integration/oauth_provider/application_form.png)bin25075 -> 25075 bytes
-rw-r--r--doc/integration/img/oauth_provider_authorized_application.png (renamed from doc/integration/oauth_provider/authorized_application.png)bin17260 -> 17260 bytes
-rw-r--r--doc/integration/img/oauth_provider_user_wide_applications.png (renamed from doc/integration/oauth_provider/user_wide_applications.png)bin46238 -> 46238 bytes
-rw-r--r--doc/integration/img/twitter_app_api_keys.png (renamed from doc/integration/twitter_app_api_keys.png)bin72200 -> 72200 bytes
-rw-r--r--doc/integration/img/twitter_app_details.png (renamed from doc/integration/twitter_app_details.png)bin121621 -> 121621 bytes
-rw-r--r--doc/integration/jira-integration-points.pngbin67854 -> 0 bytes
-rw-r--r--doc/integration/jira.md150
-rw-r--r--doc/integration/oauth_provider.md8
-rw-r--r--doc/integration/omniauth.md30
-rw-r--r--doc/integration/slack.md12
-rw-r--r--doc/integration/twitter.md6
-rw-r--r--doc/markdown/markdown.md5
-rw-r--r--doc/profile/preferences.md5
-rw-r--r--doc/project_services/img/jira_add_gitlab_commit_message.pngbin0 -> 57136 bytes
-rw-r--r--doc/project_services/img/jira_add_user_to_group.pngbin0 -> 59251 bytes
-rw-r--r--doc/project_services/img/jira_create_new_group.pngbin0 -> 41294 bytes
-rw-r--r--doc/project_services/img/jira_create_new_group_name.pngbin0 -> 12535 bytes
-rw-r--r--doc/project_services/img/jira_create_new_user.pngbin0 -> 26532 bytes
-rw-r--r--doc/project_services/img/jira_group_access.pngbin0 -> 46028 bytes
-rw-r--r--doc/project_services/img/jira_issue_closed.pngbin0 -> 92601 bytes
-rw-r--r--doc/project_services/img/jira_issue_reference.png (renamed from doc/integration/img/jira_issue_reference.png)bin39942 -> 39942 bytes
-rw-r--r--doc/project_services/img/jira_issues_workflow.pngbin0 -> 105237 bytes
-rw-r--r--doc/project_services/img/jira_merge_request_close.png (renamed from doc/integration/img/jira_merge_request_close.png)bin111150 -> 111150 bytes
-rw-r--r--doc/project_services/img/jira_project_name.png (renamed from doc/integration/img/jira_project_name.png)bin60598 -> 60598 bytes
-rw-r--r--doc/project_services/img/jira_reference_commit_message_in_jira_issue.pngbin0 -> 42452 bytes
-rw-r--r--doc/project_services/img/jira_service.png (renamed from doc/integration/img/jira_service.png)bin59082 -> 59082 bytes
-rw-r--r--doc/project_services/img/jira_service_close_issue.png (renamed from doc/integration/img/jira_service_close_issue.png)bin88433 -> 88433 bytes
-rw-r--r--doc/project_services/img/jira_service_page.png (renamed from doc/integration/img/jira_service_page.png)bin35496 -> 35496 bytes
-rw-r--r--doc/project_services/img/jira_submit_gitlab_merge_request.pngbin0 -> 63063 bytes
-rw-r--r--doc/project_services/img/jira_user_management_link.pngbin0 -> 58211 bytes
-rw-r--r--doc/project_services/img/jira_workflow_screenshot.png (renamed from doc/integration/img/jira_workflow_screenshot.png)bin121534 -> 121534 bytes
-rw-r--r--doc/project_services/jira.md212
-rw-r--r--doc/project_services/project_services.md2
-rw-r--r--doc/update/8.3-to-8.4.md2
-rw-r--r--doc/workflow/forking/fork_button.pngbin68271 -> 0 bytes
-rw-r--r--doc/workflow/forking/groups.pngbin98109 -> 0 bytes
-rw-r--r--doc/workflow/forking_workflow.md59
-rw-r--r--doc/workflow/img/forking_workflow_choose_namespace.pngbin0 -> 70405 bytes
-rw-r--r--doc/workflow/img/forking_workflow_fork_button.pngbin0 -> 26438 bytes
-rw-r--r--doc/workflow/img/forking_workflow_path_taken_error.pngbin0 -> 22380 bytes
-rw-r--r--features/dashboard/dashboard.feature30
-rw-r--r--features/groups.feature4
-rw-r--r--features/project/builds/artifacts.feature9
-rw-r--r--features/project/fork.feature15
-rw-r--r--features/project/issues/issues.feature22
-rw-r--r--features/project/merge_requests.feature33
-rw-r--r--features/steps/dashboard/dashboard.rb1
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/project/builds/artifacts.rb10
-rw-r--r--features/steps/project/fork.rb25
-rw-r--r--features/steps/project/forked_merge_requests.rb4
-rw-r--r--features/steps/project/merge_requests.rb31
-rw-r--r--features/steps/project/wiki.rb2
-rw-r--r--features/steps/shared/diff_note.rb12
-rw-r--r--features/steps/shared/issuable.rb13
-rw-r--r--features/support/capybara.rb8
-rw-r--r--lib/api/helpers.rb5
-rw-r--r--lib/api/merge_requests.rb351
-rw-r--r--lib/banzai/filter/sanitization_filter.rb4
-rw-r--r--lib/ci/api/api.rb2
-rw-r--r--lib/ci/api/builds.rb2
-rw-r--r--lib/ci/api/helpers.rb10
-rw-r--r--lib/ci/api/runners.rb1
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb49
-rw-r--r--lib/gitlab/blame.rb54
-rw-r--r--lib/gitlab/current_settings.rb31
-rw-r--r--lib/gitlab/github_import/importer.rb17
-rw-r--r--lib/gitlab/metrics/instrumentation.rb22
-rw-r--r--lib/gitlab/snippet_search_results.rb82
-rwxr-xr-xscripts/ci/prepare_build.sh22
-rwxr-xr-xscripts/prepare_build.sh16
-rw-r--r--spec/controllers/blame_controller_spec.rb14
-rw-r--r--spec/controllers/groups_controller_spec.rb23
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb109
-rw-r--r--spec/factories/commit_statuses.rb8
-rw-r--r--spec/features/commits_spec.rb127
-rw-r--r--spec/features/login_spec.rb10
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb2
-rw-r--r--spec/helpers/labels_helper_spec.rb5
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb5
-rw-r--r--spec/lib/gitlab/blame_spec.rb24
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb11
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb10
-rw-r--r--spec/models/build_spec.rb2
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb12
-rw-r--r--spec/models/event_spec.rb21
-rw-r--r--spec/models/external_issue_spec.rb15
-rw-r--r--spec/models/repository_spec.rb20
-rw-r--r--spec/models/tree_spec.rb64
-rw-r--r--spec/requests/api/merge_requests_spec.rb62
-rw-r--r--spec/requests/ci/api/builds_spec.rb15
-rw-r--r--spec/requests/ci/api/runners_spec.rb14
-rw-r--r--spec/routing/project_routing_spec.rb4
-rw-r--r--spec/services/projects/import_service_spec.rb106
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/capybara.rb6
-rw-r--r--spec/support/test_env.rb17
-rwxr-xr-xvendor/assets/javascripts/Chart.js3477
-rw-r--r--vendor/assets/javascripts/chart-lib.min.js11
-rw-r--r--vendor/assets/javascripts/fuzzaldrin-plus.js1161
-rw-r--r--vendor/assets/javascripts/fuzzaldrin-plus.min.js1
-rw-r--r--vendor/assets/javascripts/g.bar-min.js8
-rw-r--r--vendor/assets/javascripts/g.bar.js674
-rw-r--r--vendor/assets/javascripts/g.raphael-min.js7
-rw-r--r--vendor/assets/javascripts/g.raphael.js861
-rw-r--r--vendor/assets/javascripts/jquery.nicescroll.js3634
-rw-r--r--vendor/assets/javascripts/jquery.nicescroll.min.js118
258 files changed, 12958 insertions, 1732 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ac8390074f4..dbdbae9d787 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,15 @@
-# This file is generated by GitLab CI
+image: "ruby:2.2"
+
+services:
+ - mysql:latest
+ - postgres:latest
+ - redis:latest
+
+variables:
+ MYSQL_ALLOW_EMPTY_PASSWORD: "1"
+
before_script:
- - ./scripts/prepare_build.sh
+ - source ./scripts/prepare_build.sh
- ruby -v
- which ruby
- gem install bundler --no-ri --no-rdoc
@@ -125,3 +134,26 @@ bundler:audit:
- ruby
- mysql
allow_failure: true
+
+# Ruby 2.1 jobs
+
+spec:ruby21:
+ image: ruby:2.1
+ script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec
+ tags:
+ - ruby
+ - mysql
+ only:
+ - master
+
+spinach:ruby21:
+ image: ruby:2.1
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach
+ tags:
+ - ruby
+ - mysql
+ only:
+ - master
diff --git a/.ruby-version b/.ruby-version
index 04b10b4f150..530cdd91a20 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.1.7
+2.2.4
diff --git a/CHANGELOG b/CHANGELOG
index 7c14a922c88..fe0504ec996 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,42 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased)
+ - Ensure rake tasks that don't need a DB connection can be run without one
- Add "visibility" flag to GET /projects api endpoint
+ - Ignore binary files in code search to prevent Error 500 (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination
+ - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
+ set it up
+ - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
+ - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
+ - Don't vendor minified JS
+ - Display 404 error on group not found
+ - Track project import failure
+ - Fix visibility level text in admin area (Zeger-Jan van de Weg)
+ - Update the ExternalIssue regex pattern (Blake Hitchcock)
+ - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
+ - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
+
+v 8.4.2
+ - Bump required gitlab-workhorse version to bring in a fix for missing
+ artifacts in the build artifacts browser
+ - Get rid of those ugly borders on the file tree view
+ - Fix updating the runner information when asking for builds
+ - Bump gitlab_git version to 7.2.24 in order to bring in a performance
+ improvement when checking if a repository was empty
+ - Add instrumentation for Gitlab::Git::Repository instance methods so we can
+ track them in Performance Monitoring.
+ - Correctly highlight MR diff when MR has merge conflicts
+ - Increase contrast between highlighted code comments and inline diff marker
+ - Fix method undefined when using external commit status in builds
+ - Fix highlighting in blame view.
+
+v 8.4.1
+ - Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3),
+ and Nokogiri (1.6.7.2)
+ - Fix redirect loop during import
+ - Fix diff highlighting for all syntax themes
v 8.4.0
- Allow LDAP users to change their email if it was not set by the LDAP server
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ee6cdce3c29..b6160487433 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.6.1
+0.6.2
diff --git a/Gemfile b/Gemfile
index 746d8e5d6de..a09d44f8bfd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source "https://rubygems.org"
-gem 'rails', '4.2.4'
+gem 'rails', '4.2.5.1'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -103,7 +103,8 @@ gem 'asciidoctor', '~> 1.5.2'
gem 'rouge', '~> 1.10.1'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
-gem 'nokogiri', '1.6.7.1'
+# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
+gem 'nokogiri', '1.6.7.2'
# Diffs
gem 'diffy', '~> 3.0.3'
@@ -212,6 +213,9 @@ gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
+# Sentry integration
+gem 'sentry-raven'
+
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
@@ -293,15 +297,12 @@ end
group :production do
gem "gitlab_meta", '7.0'
-
- # Sentry integration
- gem 'sentry-raven'
end
gem "newrelic_rpm", '~> 3.9.4.245'
gem 'newrelic-grape'
-gem 'octokit', '~> 3.7.0'
+gem 'octokit', '~> 3.8.0'
gem "mail_room", "~> 0.6.1"
diff --git a/Gemfile.lock b/Gemfile.lock
index 5e0718af70f..ec92964df25 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,41 +4,41 @@ GEM
CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
- actionmailer (4.2.4)
- actionpack (= 4.2.4)
- actionview (= 4.2.4)
- activejob (= 4.2.4)
+ actionmailer (4.2.5.1)
+ actionpack (= 4.2.5.1)
+ actionview (= 4.2.5.1)
+ activejob (= 4.2.5.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.4)
- actionview (= 4.2.4)
- activesupport (= 4.2.4)
+ actionpack (4.2.5.1)
+ actionview (= 4.2.5.1)
+ activesupport (= 4.2.5.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.4)
- activesupport (= 4.2.4)
+ actionview (4.2.5.1)
+ activesupport (= 4.2.5.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.4)
- activesupport (= 4.2.4)
+ activejob (4.2.5.1)
+ activesupport (= 4.2.5.1)
globalid (>= 0.3.0)
- activemodel (4.2.4)
- activesupport (= 4.2.4)
+ activemodel (4.2.5.1)
+ activesupport (= 4.2.5.1)
builder (~> 3.1)
- activerecord (4.2.4)
- activemodel (= 4.2.4)
- activesupport (= 4.2.4)
+ activerecord (4.2.5.1)
+ activemodel (= 4.2.5.1)
+ activesupport (= 4.2.5.1)
arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5)
- activesupport (4.2.4)
+ activesupport (4.2.5.1)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -356,7 +356,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.2.0)
gemojione (~> 2.1)
- gitlab_git (7.2.23)
+ gitlab_git (7.2.24)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -482,7 +482,7 @@ GEM
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
- nokogiri (1.6.7.1)
+ nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
oauth (0.4.7)
@@ -492,7 +492,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
- octokit (3.7.1)
+ octokit (3.8.0)
sawyer (~> 0.6.0, >= 0.5.3)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
@@ -588,16 +588,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.4)
- actionmailer (= 4.2.4)
- actionpack (= 4.2.4)
- actionview (= 4.2.4)
- activejob (= 4.2.4)
- activemodel (= 4.2.4)
- activerecord (= 4.2.4)
- activesupport (= 4.2.4)
+ rails (4.2.5.1)
+ actionmailer (= 4.2.5.1)
+ actionpack (= 4.2.5.1)
+ actionview (= 4.2.5.1)
+ activejob (= 4.2.5.1)
+ activemodel (= 4.2.5.1)
+ activerecord (= 4.2.5.1)
+ activesupport (= 4.2.5.1)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.4)
+ railties (= 4.2.5.1)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -605,11 +605,11 @@ GEM
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
- rails-html-sanitizer (1.0.2)
+ rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.4)
- actionpack (= 4.2.4)
- activesupport (= 4.2.4)
+ railties (4.2.5.1)
+ actionpack (= 4.2.5.1)
+ activesupport (= 4.2.5.1)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
@@ -725,7 +725,7 @@ GEM
activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3)
thor (~> 0.14)
- sentry-raven (0.15.3)
+ sentry-raven (0.15.4)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.6.0)
@@ -962,10 +962,10 @@ DEPENDENCIES
net-ssh (~> 3.0.1)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
- nokogiri (= 1.6.7.1)
+ nokogiri (= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
- octokit (~> 3.7.0)
+ octokit (~> 3.8.0)
omniauth (~> 1.2.2)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2)
@@ -988,7 +988,7 @@ DEPENDENCIES
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
- rails (= 4.2.4)
+ rails (= 4.2.5.1)
rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2)
rblineprof
diff --git a/README.md b/README.md
index 3ec1d4a776c..22dbf841bdc 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
-- Ruby (MRI) 2.1
+- Ruby (MRI) 2.1 or 2.2
- Git 1.7.10+
- Redis 2.8+
- MySQL or PostgreSQL
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index c095e5ae2b1..d5e6ff0717a 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -5,7 +5,10 @@
# the compiled file.
#
#= require jquery
-#= require jquery-ui
+#= require jquery-ui/autocomplete
+#= require jquery-ui/datepicker
+#= require jquery-ui/effect-highlight
+#= require jquery-ui/sortable
#= require jquery_ujs
#= require jquery.cookie
#= require jquery.endless-scroll
@@ -21,9 +24,9 @@
#= require bootstrap
#= require select2
#= require raphael
-#= require g.raphael-min
-#= require g.bar-min
-#= require chart-lib.min
+#= require g.raphael
+#= require g.bar
+#= require Chart
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
@@ -38,9 +41,9 @@
#= require shortcuts_dashboard_navigation
#= require shortcuts_issuable
#= require shortcuts_network
-#= require jquery.nicescroll.min
+#= require jquery.nicescroll
#= require_tree .
-#= require fuzzaldrin-plus.min
+#= require fuzzaldrin-plus
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -203,4 +206,13 @@ $ ->
form = btn.closest("form")
new ConfirmDangerModal(form, text)
+ $('input[type="search"]').each ->
+ $this = $(this)
+ $this.attr 'value', $this.val()
+ return
+
+ $(document).on 'keyup', 'input[type="search"]' , (e) ->
+ $this = $(this)
+ $this.attr 'value', $this.val()
+
new Aside()
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index f6bf836f19f..390e41ed8d4 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -32,6 +32,7 @@ class @EditBlob
content: editor.getValue()
, (response) ->
currentPane.empty().append response
+ currentPane.syntaxHighlight()
return
else
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index cbc70cd846c..d663e34871c 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -50,6 +50,7 @@ class @Issue
new Flash(issueFailMessage, 'alert')
success: (data, textStatus, jqXHR) ->
if data.saved
+ $(document).trigger('issuable:change');
if isClose
$('a.btn-close').addClass('hidden')
$('a.btn-reopen').removeClass('hidden')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 2bfc5cb2d9c..3347ab65c90 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -15,6 +15,8 @@ class @Notes
@last_fetched_at = last_fetched_at
@view = view
@noteable_url = document.URL
+ @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
+
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding()
@@ -62,6 +64,9 @@ class @Notes
# fetch notes when tab becomes visible
$(document).on "visibilitychange", @visibilityChange
+ # when issue status changes, we need to refresh data
+ $(document).on "issuable:change", @refresh
+
cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form"
@@ -89,7 +94,7 @@ class @Notes
, 15000
refresh: ->
- unless document.hidden or (@noteable_url != document.URL)
+ if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent()
getContent: ->
@@ -101,7 +106,10 @@ class @Notes
notes = data.notes
@last_fetched_at = data.last_fetched_at
$.each notes, (i, note) =>
- @renderNote(note)
+ if note.discussion_with_diff_html?
+ @renderDiscussionNote(note)
+ else
+ @renderNote(note)
###
@@ -116,18 +124,21 @@ class @Notes
flash.pinTo('.header-content')
return
+ if note.award
+ awards_handler.addAwardToEmojiBar(note.note)
+ awards_handler.scrollToAwards()
+
# render note if it not present in loaded list
# or skip if rendered
- if @isNewNote(note) && !note.award
+ else if @isNewNote(note)
@note_ids.push(note.id)
- $('ul.main-notes-list').
- append(note.html).
- syntaxHighlight()
+
+ $('ul.main-notes-list')
+ .append(note.html)
+ .syntaxHighlight()
@initTaskList()
+ @updateNotesCount(1)
- if note.award
- awards_handler.addAwardToEmojiBar(note.note)
- awards_handler.scrollToAwards()
###
Check if note does not exists on page
@@ -144,34 +155,39 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote
###
renderDiscussionNote: (note) ->
+ return unless @isNewNote(note)
+
@note_ids.push(note.id)
- form = $("form[rel='" + note.discussion_id + "']")
+ form = $("#new-discussion-note-form-#{note.discussion_id}")
row = form.closest("tr")
note_html = $(note.html)
note_html.syntaxHighlight()
# is this the first note of discussion?
- if row.is(".js-temp-notes-holder")
+ discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
+ if discussionContainer.length is 0
# insert the note and the reply button after the temp row
row.after note.discussion_html
# remove the note (will be added again below)
row.next().find(".note").remove()
+ # Before that, the container didn't exist
+ discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
+
# Add note to 'Changes' page discussions
- $(".notes[rel='" + note.discussion_id + "']").append note_html
+ discussionContainer.append note_html
# Init discussion on 'Discussion' page if it is merge request page
- if $('body').attr('data-page').indexOf('projects:merge_request') == 0
- discussion_html = $(note.discussion_with_diff_html)
- discussion_html.syntaxHighlight()
- $('ul.main-notes-list').append(discussion_html)
+ if $('body').attr('data-page').indexOf('projects:merge_request') is 0
+ $('ul.main-notes-list')
+ .append(note.discussion_with_diff_html)
+ .syntaxHighlight()
else
# append new note to all matching discussions
- $(".notes[rel='" + note.discussion_id + "']").append note_html
+ discussionContainer.append note_html
- # cleanup after successfully creating a diff/discussion note
- @removeDiscussionNoteForm(form)
+ @updateNotesCount(1)
###
Called in response the main target form has been successfully submitted.
@@ -278,6 +294,9 @@ class @Notes
addDiscussionNote: (xhr, note, status) =>
@renderDiscussionNote(note)
+ # cleanup after successfully creating a diff/discussion note
+ @removeDiscussionNoteForm($("#new-discussion-note-form-#{note.discussion_id}"))
+
###
Called in response to the edit note form being submitted
@@ -349,30 +368,32 @@ class @Notes
Removes the actual note from view.
Removes the whole discussion if the last note is being removed.
###
- removeNote: ->
- note = $(this).closest(".note")
- note_id = note.attr('id')
+ removeNote: (e) =>
+ noteId = $(e.currentTarget)
+ .closest(".note")
+ .attr("id")
- $('.note[id="' + note_id + '"]').each ->
- note = $(this)
+ # A same note appears in the "Discussion" and in the "Changes" tab, we have
+ # to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
+ # where $("#noteId") would return only one.
+ $(".note[id='#{noteId}']").each (i, el) =>
+ note = $(el)
notes = note.closest(".notes")
- count = notes.closest(".issuable-details").find(".notes-tab .badge")
# check if this is the last note for this line
if notes.find(".note").length is 1
- # for discussions
- notes.closest(".discussion").remove()
+ # "Discussions" tab
+ notes.closest(".timeline-entry").remove()
- # for diff lines
+ # "Changes" tab / commit view
notes.closest("tr").remove()
- # update notes count
- oldNum = parseInt(count.text())
- count.text(oldNum - 1)
-
note.remove()
+ # Decrement the "Discussions" counter only once
+ @updateNotesCount(-1)
+
###
Called in response to clicking the delete attachment link
@@ -412,7 +433,7 @@ class @Notes
###
setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target
- form.attr "rel", dataHolder.data("discussionId")
+ form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode")
@@ -542,3 +563,6 @@ class @Notes
updateTaskList: ->
$('form', this).submit()
+
+ updateNotesCount: (updateCount) ->
+ @notesCountBadge.text(parseInt(@notesCountBadge.text()) + updateCount)
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index f2887af190b..b71509dbc5a 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -9,11 +9,13 @@ class @ProjectsList
$(".projects-list-filter").keyup ->
terms = $(this).val()
uiBox = $('div.projects-list-holder')
+ filterSelector = $(this).data('filter-selector') || 'span.filter-title'
+
if terms == "" || terms == undefined
uiBox.find("ul.projects-list li").show()
else
uiBox.find("ul.projects-list li").each (index) ->
- name = $(this).find("span.filter-title").text()
+ name = $(this).find(filterSelector).text()
if name.toLowerCase().search(terms.toLowerCase()) == -1
$(this).hide()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index bb532194682..f717a753cf8 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -5,11 +5,11 @@ class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
Mousetrap.bind('a', ->
- $('.js-assignee').select2('open')
+ $('.block.assignee .edit-link').trigger('click')
return false
)
Mousetrap.bind('m', ->
- $('.js-milestone').select2('open')
+ $('.block.milestone .edit-link').trigger('click')
return false
)
Mousetrap.bind('r', =>
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d0f5d33bf4d..bd89cc7dc1d 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -146,6 +146,10 @@
border-bottom: 1px solid $border-color;
&.oneline-block {
- line-height: 42px;
+ line-height: 36px;
+ }
+
+ > .controls {
+ float: right;
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 9bc814cfd2d..6ea2219073c 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -311,14 +311,6 @@ table {
}
}
-.wiki .highlight, .note-body .highlight {
- margin: 12px 0 12px 0;
-}
-
-.wiki .code {
- overflow-x: auto;
-}
-
.footer-links {
margin-bottom: 20px;
a {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index a4791cf6b34..00cb756b376 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -7,6 +7,7 @@
border: 1px solid $border-color;
&.readme-holder {
+ margin-top: 10px;
border-bottom: 0;
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 4dab806d50e..d097e4d32f7 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -2,11 +2,42 @@ textarea {
resize: vertical;
}
-input[type='search'].search-text-input {
- background-image: image-url("icon-search.png");
+input {
+ border-radius: $border-radius-base;
+}
+
+input[type='search'] {
+ background-color: white;
+ padding-left: 10px;
+}
+
+input[type='search'].search-input {
background-repeat: no-repeat;
background-position: 10px;
- padding-left: 25px;
+ background-size: 16px;
+ background-position-x: 30%;
+ padding-left: 10px;
+ background-color: $gray-light;
+
+ &.search-input[value=""] {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
+ }
+
+ &.search-input::-webkit-input-placeholder {
+ text-align: center;
+ }
+
+ &.search-input:-moz-placeholder { /* Firefox 18- */
+ text-align: center;
+ }
+
+ &.search-input::-moz-placeholder { /* Firefox 19+ */
+ text-align: center;
+ }
+
+ &.search-input:-ms-input-placeholder {
+ text-align: center;
+ }
}
input[type='text'].danger {
@@ -74,6 +105,7 @@ label {
.form-control {
@include box-shadow(none);
+ border-radius: 3px;
}
.form-control-inline {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index ba5e72c8c5a..f875b1460e7 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -108,16 +108,10 @@ header {
.search-input {
width: 220px;
- background-image: image-url("icon-search.png");
- background-repeat: no-repeat;
- background-position: 195px;
- @include input-big;
&:focus {
@include box-shadow(none);
outline: none;
- border-color: #DDD;
- background-color: #FFF;
}
}
}
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 2e13ee842e0..9854df4c45c 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -17,6 +17,7 @@
overflow-y: hidden;
white-space: pre;
word-wrap: normal;
+ border-left: 1px solid;
code {
font-family: $monospace_font;
@@ -25,7 +26,7 @@
padding: 0;
.line {
- display: inline;
+ display: inline-block;
}
}
}
@@ -53,18 +54,3 @@
}
}
}
-
-.note-text .code {
- border: none;
- box-shadow: none;
- background: $background-color;
- padding: 1em;
- overflow-x: auto;
-
- code {
- font-family: $monospace_font;
- white-space: pre;
- word-wrap: normal;
- padding: 0;
- }
-}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 57b9451b264..ae7bdf14c40 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -2,7 +2,13 @@
margin-bottom: $gl-padding;
.panel-heading {
- padding: 7px $gl-padding;
+ padding: $gl-vert-padding $gl-padding;
+ line-height: 36px;
+
+ .controls {
+ margin-top: -2px;
+ float: right;
+ }
}
.panel-body {
@@ -14,7 +20,3 @@
}
}
}
-
-.container-blank .panel .panel-heading {
- line-height: 42px !important;
-}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index c4e9f467ce4..75b770ae5a2 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -33,12 +33,12 @@ table {
background-color: $background-color;
font-weight: normal;
font-size: 15px;
- border-bottom: 1px solid $border-color !important;
+ border-bottom: 1px solid $border-color;
}
td {
- border-color: $table-border-color !important;
- border-bottom: 1px solid;
+ border-color: $table-border-color;
+ border-bottom: 1px solid $border-color;
}
}
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 88072606bf5..3e709244879 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -114,22 +114,9 @@
*
*/
-.container-blank .panel .panel-heading {
- font-size: 17px;
- line-height: 38px;
-}
-
.panel {
box-shadow: none;
- .panel-heading {
- .panel-head-actions {
- position: relative;
- top: -5px;
- float: right;
- }
- }
-
.panel-body {
form, pre {
margin: 0;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 798cd224ad0..33270388e64 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -22,9 +22,9 @@ $brand-info: $gl-info;
$brand-warning: $gl-warning;
$brand-danger: $gl-danger;
-$border-radius-base: 2px !default;
-$border-radius-large: 2px !default;
-$border-radius-small: 2px !default;
+$border-radius-base: 3px !default;
+$border-radius-large: 3px !default;
+$border-radius-small: 3px !default;
//== Scaffolding
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index ab4f71af039..8d8f41287da 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -87,8 +87,8 @@
}
p {
- color:#5c5d5e;
- margin:6px 0 0 0;
+ color: #5c5d5e;
+ margin: 6px 0 0 0;
}
table {
@@ -102,11 +102,10 @@
}
pre {
- margin: 12px 0 12px 0 !important;
- background-color: #f8fafc;
- font-size: 13px !important;
- color: #5b6169;
- line-height: 1.6em !important;
+ margin: 12px 0 12px 0;
+ font-size: 13px;
+ line-height: 1.6em;
+ overflow-x: auto;
@include border-radius(2px);
}
@@ -116,7 +115,7 @@
ul, ol {
padding: 0;
- margin: 6px 0 6px 18px !important;
+ margin: 6px 0 6px 28px !important;
}
li {
@@ -204,11 +203,6 @@ h1, h2, h3, h4, h5, h6 {
pre {
font-family: $monospace_font;
- &.dark {
- background: #333;
- color: $background-color;
- }
-
&.plain-readme {
background: none;
border: none;
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index c7f7a5aaf07..b794da2ce98 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -10,8 +10,8 @@
}
// Code itself
- pre.code {
- border-left: 1px solid #666;
+ pre.code, .diff-line-num {
+ border-color: #666;
}
&, pre.code, .line_holder .line_content {
@@ -22,11 +22,11 @@
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
- @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.3), #808080);
+ @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
}
.diff-line-num.old, .line_content.old {
- @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.3), #808080);
+ @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080);
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 516d8aef960..9098e07adcd 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -10,8 +10,8 @@
}
// Code itself
- pre.code {
- border-left: 1px solid #555;
+ pre.code, .diff-line-num {
+ border-color: #555;
}
&, pre.code, .line_holder .line_content {
@@ -22,11 +22,11 @@
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
- @include diff_background(rgba(166, 226, 46, 0.2), rgba(166, 226, 46, 0.3), #808080);
+ @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
}
.diff-line-num.old, .line_content.old {
- @include diff_background(rgba(254, 147, 140, 0.2), rgba(254, 147, 140, 0.3), #808080);
+ @include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080);
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index ae7ecc65c39..8b1a2824f76 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -10,8 +10,8 @@
}
// Code itself
- pre.code {
- border-left: 1px solid #113b46;
+ pre.code, .diff-line-num {
+ border-color: #113b46;
}
&, pre.code, .line_holder .line_content {
@@ -22,11 +22,11 @@
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
- @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.3), #808080);
+ @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
}
.diff-line-num.old, .line_content.old {
- @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.3), #808080);
+ @include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46);
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 1c138572145..7ad89dd2c7c 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -10,8 +10,8 @@
}
// Code itself
- pre.code {
- border-left: 1px solid #c5d0d4;
+ pre.code, .diff-line-num {
+ border-color: #c5d0d4;
}
&, pre.code, .line_holder .line_content {
@@ -22,11 +22,11 @@
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
- @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.3), #FAF3DD);
+ @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
}
.diff-line-num.old, .line_content.old {
- @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.3), #FAF3DD);
+ @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4);
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 8a932e6540e..8a091028a6c 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -10,8 +10,8 @@
}
// Code itself
- pre.code {
- border-left: 1px solid $border-color;
+ pre.code, .diff-line-num {
+ border-color: $border-color;
}
&, pre.code, .line_holder .line_content {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 5215df04a6e..a7925e79549 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -79,10 +79,8 @@
margin: 0px;
padding: 0px;
border: none;
- background: $background-color;
- color: rgba(0, 0, 0, 0.3);
padding: 0px 5px;
- border-right: 1px solid $border-color;
+ border-right: 1px solid;
text-align: right;
min-width: 35px;
max-width: 50px;
@@ -92,7 +90,6 @@
float: left;
width: 35px;
font-weight: normal;
- color: rgba(0, 0, 0, 0.3);
&:hover {
text-decoration: underline;
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 263993f59a5..fdd86979a36 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,5 +1,15 @@
.member-search-form {
float: left;
+
+ input[type='search'] {
+ width: 225px;
+ vertical-align: bottom;
+
+ @media (max-width: $screen-xs-max) {
+ width: 100px;
+ vertical-align: bottom;
+ }
+ }
}
.milestone-row {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index ad92cc22815..dd6a251f811 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -49,11 +49,6 @@
.issue-search-form {
margin: 0;
height: 24px;
-
- .issue_search {
- border: 1px solid #DDD !important;
- background-color: #f4f4f4;
- }
}
form.edit-issue {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index f6343e6bb1e..19ead07c06a 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -154,6 +154,7 @@ ul.notes {
text-align: center;
padding: 10px 0;
background: #FFF;
+ color: $text-color;
}
&.notes_line2 {
text-align: center;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 003a4c22f20..e4ea47cc4a2 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -564,3 +564,53 @@ pre.light-well {
color: #E62958;
margin-top: 2px;
}
+
+/*
+ * Forks list rendered on Project's forks page
+ */
+
+.forks-top-block {
+ padding: 16px 0;
+}
+
+.projects-search-form {
+ .dropdown-toggle.btn {
+ margin-top: -3px;
+ }
+
+ &.fork-search-form {
+ margin: 0;
+ margin-top: -$gl-padding;
+ padding-bottom: 0;
+
+ input {
+ /* Small devices (tablets, 768px and up) */
+ @media (min-width: $screen-sm-min) { width: 180px; }
+
+ /* Medium devices (desktops, 992px and up) */
+ @media (min-width: $screen-md-min) { width: 350px; }
+
+ /* Large devices (large desktops, 1200px and up) */
+ @media (min-width: $screen-lg-min) { width: 400px; }
+ }
+
+ .sort-forks {
+ width: 160px;
+ }
+
+ .fork-link {
+ float: right;
+ margin-left: $gl-padding;
+ }
+ }
+}
+
+.private-forks-notice .private-fork-icon {
+ i:nth-child(1) {
+ color: #2AA056;
+ }
+
+ i:nth-child(2) {
+ color: #FFFFFF;
+ }
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 6a6dd7dfc85..c7411617cb3 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -22,8 +22,6 @@
&:hover {
td {
background: $hover;
- border-top: 1px solid #ADF;
- border-bottom: 1px solid #ADF;
}
cursor: pointer;
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 2d735b90597..824175c8a6c 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -298,7 +298,8 @@ class ApplicationController < ActionController::Base
end
def set_filters_params
- params[:sort] ||= 'id_desc'
+ set_default_sort
+
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@@ -405,4 +406,24 @@ class ApplicationController < ActionController::Base
current_user.nil? && root_path == request.path
end
+
+ private
+
+ def set_default_sort
+ key = if is_a_listing_page_for?('issues') || is_a_listing_page_for?('merge_requests')
+ 'issuable_sort'
+ end
+
+ cookies[key] = params[:sort] if key && params[:sort].present?
+ params[:sort] = cookies[key] if key
+ params[:sort] ||= 'id_desc'
+ end
+
+ def is_a_listing_page_for?(page_type)
+ controller_name, action_name = params.values_at(:controller, :action)
+
+ (controller_name == "projects/#{page_type}" && action_name == 'index') ||
+ (controller_name == 'groups' && action_name == page_type) ||
+ (controller_name == 'dashboard' && action_name == page_type)
+ end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 58e9049f158..0b7fcdf5e9e 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -36,7 +36,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private
def load_events
- @events = Event.in_projects(@projects.pluck(:id))
+ @events = Event.in_projects(@projects)
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 087da935087..139e40db180 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -23,14 +23,14 @@ class DashboardController < Dashboard::ApplicationController
protected
def load_events
- project_ids =
+ projects =
if params[:filter] == "starred"
current_user.starred_projects
else
current_user.authorized_projects
- end.pluck(:id)
+ end
- @events = Event.in_projects(project_ids)
+ @events = Event.in_projects(projects)
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index fb26a4e6fc3..ad6b3eae932 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -2,17 +2,18 @@ class GroupsController < Groups::ApplicationController
include IssuesAction
include MergeRequestsAction
- skip_before_action :authenticate_user!, only: [:show, :issues, :merge_requests]
respond_to :html
- before_action :group, except: [:new, :create]
+
+ skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests]
+ before_action :group, except: [:index, :new, :create]
# Authorize
- before_action :authorize_read_group!, except: [:show, :new, :create, :autocomplete]
+ before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete]
before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
before_action :authorize_create_group!, only: [:new, :create]
# Load group projects
- before_action :load_projects, except: [:new, :create, :projects, :edit, :update, :autocomplete]
+ before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
before_action :event_filter, only: :show
layout :determine_layout
@@ -81,16 +82,13 @@ class GroupsController < Groups::ApplicationController
def group
@group ||= Group.find_by(path: params[:id])
+ @group || render_404
end
def load_projects
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
end
- def project_ids
- @projects.pluck(:id)
- end
-
# Dont allow unauthorized access to group
def authorize_read_group!
unless @group and (@projects.present? or can?(current_user, :read_group, @group))
@@ -123,7 +121,7 @@ class GroupsController < Groups::ApplicationController
end
def load_events
- @events = Event.in_projects(project_ids)
+ @events = Event.in_projects(@projects)
@events = event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 4cad98b8e98..4c13228fce9 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -21,15 +21,16 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# We only find ourselves here
# if the authentication to LDAP was successful.
def ldap
- @user = Gitlab::LDAP::User.new(oauth)
- @user.save if @user.changed? # will also save new users
- gl_user = @user.gl_user
- gl_user.remember_me = params[:remember_me] if @user.persisted?
+ ldap_user = Gitlab::LDAP::User.new(oauth)
+ ldap_user.save if ldap_user.changed? # will also save new users
+
+ @user = ldap_user.gl_user
+ @user.remember_me = params[:remember_me] if ldap_user.persisted?
# Do additional LDAP checks for the user filter and EE features
- if @user.allowed?
- log_audit_event(gl_user, with: :ldap)
- sign_in_and_redirect(gl_user)
+ if ldap_user.allowed?
+ log_audit_event(@user, with: :ldap)
+ sign_in_and_redirect(@user)
else
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 6e91d9b4ad9..f3bfede4354 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -13,10 +13,10 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.save! if current_user.changed?
if two_factor_grace_period_expired?
- flash.now[:alert] = 'You must configure Two-Factor Authentication in your account.'
+ flash.now[:alert] = 'You must enable Two-factor Authentication for your account.'
else
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = "You must configure Two-Factor Authentication in your account until #{l(grace_period_deadline)}."
+ flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
end
@qr_code = build_qr_code
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index 9ea518e6c85..f576d0be1fc 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -8,28 +8,6 @@ class Projects::BlameController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
- @blame = group_blame_lines
- end
-
- def group_blame_lines
- blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path)
-
- prev_sha = nil
- groups = []
- current_group = nil
-
- blame.each do |commit, line|
- if prev_sha && prev_sha == commit.sha
- current_group[:lines] << line
- else
- groups << current_group if current_group.present?
- current_group = { commit: commit, lines: [line] }
- end
-
- prev_sha = commit.sha
- end
-
- groups << current_group if current_group.present?
- groups
+ @blame_groups = Gitlab::Blame.new(@blob, @commit).groups
end
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index f8ec76cd4e5..7bbe75b3974 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController
@commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
@commit = @project.commit(head_ref)
- @base_commit = @project.commit(base_ref)
+ @base_commit = @project.merge_base_commit(base_ref, head_ref)
@diff_refs = [@base_commit, @commit]
@line_notes = []
end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 750181f0c19..e61e01c4a59 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -3,6 +3,15 @@ class Projects::ForksController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :authorize_download_code!
+ def index
+ @sort = params[:sort] || 'id_desc'
+ @all_forks = project.forks.includes(:creator).order_by(@sort)
+
+ @public_forks, @protected_forks = @all_forks.partition do |project|
+ can?(current_user, :read_project, project)
+ end
+ end
+
def new
@namespaces = current_user.manageable_namespaces
@namespaces.delete(@project.namespace)
@@ -10,7 +19,7 @@ class Projects::ForksController < Projects::ApplicationController
def create
namespace = Namespace.find(params[:namespace_key])
-
+
@forked_project = namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 8d8035ef5ff..07f355c35b1 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -1,8 +1,8 @@
class Projects::ImportsController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!
- before_action :require_no_repo, except: :show
- before_action :redirect_if_progress, except: :show
+ before_action :require_no_repo, only: [:new, :create]
+ before_action :redirect_if_progress, only: [:new, :create]
def new
end
@@ -24,11 +24,11 @@ class Projects::ImportsController < Projects::ApplicationController
end
def show
- if @project.repository_exists? || @project.import_finished?
+ if @project.import_finished?
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
- redirect_to project_path(@project), notice: "The project was successfully forked."
+ redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice
end
elsif @project.import_failed?
redirect_to new_namespace_project_import_path(@project.namespace, @project)
@@ -36,6 +36,7 @@ class Projects::ImportsController < Projects::ApplicationController
if continue_params && continue_params[:notice_now]
flash.now[:notice] = continue_params[:notice_now]
end
+
# Render
end
end
@@ -44,6 +45,7 @@ class Projects::ImportsController < Projects::ApplicationController
def continue_params
continue_params = params[:continue]
+
if continue_params
continue_params.permit(:to, :notice, :notice_now)
else
@@ -51,8 +53,16 @@ class Projects::ImportsController < Projects::ApplicationController
end
end
+ def finished_notice
+ if @project.forked?
+ 'The project was successfully forked.'
+ else
+ 'The project was successfully imported.'
+ end
+ end
+
def require_no_repo
- if @project.repository_exists? && !@project.import_in_progress?
+ if @project.repository_exists?
redirect_to(namespace_project_path(@project.namespace, @project))
end
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 6f1e186d408..4a2599dda37 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -11,11 +11,9 @@ class Projects::NotesController < Projects::ApplicationController
notes_json = { notes: [], last_fetched_at: current_fetched_at }
@notes.each do |note|
- notes_json[:notes] << {
- id: note.id,
- html: note_to_html(note),
- valid: note.valid?
- }
+ next if note.cross_reference_not_visible_for?(current_user)
+
+ notes_json[:notes] << note_json(note)
end
render json: notes_json
@@ -25,7 +23,7 @@ class Projects::NotesController < Projects::ApplicationController
@note = Notes::CreateService.new(project, current_user, note_params).execute
respond_to do |format|
- format.json { render_note_json(@note) }
+ format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
end
end
@@ -34,7 +32,7 @@ class Projects::NotesController < Projects::ApplicationController
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
respond_to do |format|
- format.json { render_note_json(@note) }
+ format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
end
end
@@ -99,6 +97,8 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_to_discussion_html(note)
+ return unless note.for_diff_line?
+
if params[:view] == 'parallel'
template = "projects/notes/_diff_notes_with_reply_parallel"
locals =
@@ -131,9 +131,9 @@ class Projects::NotesController < Projects::ApplicationController
)
end
- def render_note_json(note)
+ def note_json(note)
if note.valid?
- render json: {
+ {
valid: true,
id: note.id,
discussion_id: note.discussion_id,
@@ -144,7 +144,7 @@ class Projects::NotesController < Projects::ApplicationController
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
else
- render json: {
+ {
valid: false,
award: note.is_award,
errors: note.errors
@@ -163,8 +163,6 @@ class Projects::NotesController < Projects::ApplicationController
)
end
- private
-
def find_current_user_notes
@notes = NotesFinder.new.execute(project, current_user, params)
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 825f85199be..44eb58e418b 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -2,6 +2,8 @@ class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
include Recaptcha::ClientHelper
+ skip_before_action :check_2fa_requirement, only: [:destroy]
+
prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :store_redirect_path, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new]
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f3a2723ee0d..a2458ad3be0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -171,7 +171,7 @@ module ApplicationHelper
def search_placeholder
if @project && @project.persisted?
- 'Search in this project'
+ 'Search'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 8b689b29a41..694c03206bd 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -36,8 +36,7 @@ module BlobHelper
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
- continue: continue_params)
+ fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to "Edit", fork_path, class: 'btn', method: :post
end
@@ -62,8 +61,7 @@ module BlobHelper
notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
- continue: continue_params)
+ fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index d26f007c8e6..1d14ee52cfc 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -152,7 +152,7 @@ module CommitsHelper
options = {
class: "commit-#{options[:source]}-link has_tooltip",
- data: { :'original-title' => sanitize(source_email) }
+ data: { 'original-title'.to_sym => sanitize(source_email) }
}
if user.nil?
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 5724d3aabec..84c6d0883b0 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -7,7 +7,7 @@ module IconsHelper
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
- fa_icon(names, options)
+ options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
end
def spinner(text = nil, visible = false)
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index a2c3d4d2f32..92eac0560bd 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -83,7 +83,11 @@ module LabelsHelper
end
def text_color_for_bg(bg_color)
- r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
+ if bg_color.length == 4
+ r, g, b = bg_color[1, 4].scan(/./).map { |v| (v * 2).hex }
+ else
+ r, g, b = bg_color[1, 7].scan(/.{2}/).map(&:hex)
+ end
if (r + g + b) > 500
'#333333'
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 77ba612548a..8c8b355028c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -40,7 +40,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
- link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe
+ link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
end
end
@@ -116,7 +116,7 @@ module ProjectsHelper
private
def get_project_nav_tabs(project, current_user)
- nav_tabs = [:home]
+ nav_tabs = [:home, :forks]
if !project.empty_repo? && can?(current_user, :download_code, project)
nav_tabs << [:files, :commits, :network, :graphs]
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 906cb12cd48..bc36434f549 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -17,4 +17,79 @@ module SnippetsHelper
snippet_path(snippet)
end
end
+
+ # Get an array of line numbers surrounding a matching
+ # line, bounded by min/max.
+ #
+ # @returns Array of line numbers
+ def bounded_line_numbers(line, min, max, surrounding_lines)
+ lower = line - surrounding_lines > min ? line - surrounding_lines : min
+ upper = line + surrounding_lines < max ? line + surrounding_lines : max
+ (lower..upper).to_a
+ end
+
+ # Returns a sorted set of lines to be included in a snippet preview.
+ # This ensures matching adjacent lines do not display duplicated
+ # surrounding code.
+ #
+ # @returns Array, unique and sorted.
+ def matching_lines(lined_content, surrounding_lines)
+ used_lines = []
+ lined_content.each_with_index do |line, line_number|
+ used_lines.concat bounded_line_numbers(
+ line_number,
+ 0,
+ lined_content.size,
+ surrounding_lines
+ ) if line.include?(query)
+ end
+
+ used_lines.uniq.sort
+ end
+
+ # 'Chunkify' entire snippet. Splits the snippet data into matching lines +
+ # surrounding_lines() worth of unmatching lines.
+ #
+ # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
+ def chunk_snippet(snippet, surrounding_lines = 3)
+ lined_content = snippet.content.split("\n")
+ used_lines = matching_lines(lined_content, surrounding_lines)
+
+ snippet_chunk = []
+ snippet_chunks = []
+ snippet_start_line = 0
+ last_line = -1
+
+ # Go through each used line, and add consecutive lines as a single chunk
+ # to the snippet chunk array.
+ used_lines.each do |line_number|
+ if last_line < 0
+ # Start a new chunk.
+ snippet_start_line = line_number
+ snippet_chunk << lined_content[line_number]
+ elsif last_line == line_number - 1
+ # Consecutive line, continue chunk.
+ snippet_chunk << lined_content[line_number]
+ else
+ # Non-consecutive line, add chunk to chunk array.
+ snippet_chunks << {
+ data: snippet_chunk.join("\n"),
+ start_line: snippet_start_line + 1
+ }
+
+ # Start a new chunk.
+ snippet_chunk = [lined_content[line_number]]
+ snippet_start_line = line_number
+ end
+ last_line = line_number
+ end
+ # Add final chunk to chunk array
+ snippet_chunks << {
+ data: snippet_chunk.join("\n"),
+ start_line: snippet_start_line + 1
+ }
+
+ # Return snippet with chunk array
+ { snippet_object: snippet, snippet_chunks: snippet_chunks }
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 01d008035a5..4be23a1cf72 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -47,7 +47,11 @@ class Event < ActiveRecord::Base
# Scopes
scope :recent, -> { reorder(id: :desc) }
scope :code_push, -> { where(action: PUSHED) }
- scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
+
+ scope :in_projects, ->(projects) do
+ where(project_id: projects.select(:id).reorder(nil)).recent
+ end
+
scope :with_associations, -> { includes(project: :namespace) }
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
@@ -64,12 +68,6 @@ class Event < ActiveRecord::Base
[Event::CREATED, Event::CLOSED, Event::MERGED])
end
- def latest_update_time
- row = select(:updated_at, :project_id).reorder(id: :desc).take
-
- row ? row.updated_at : nil
- end
-
def limit_recent(limit = 20, offset = nil)
recent.limit(limit).offset(offset)
end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 49f6c95e045..2ca79df0a29 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -31,7 +31,7 @@ class ExternalIssue
# Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern
- %r{(?<issue>([A-Z\-]+-)\d+)}
+ %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
def to_reference(_from_project = nil)
diff --git a/app/models/group.rb b/app/models/group.rb
index 5a31b46920c..76042b3e3fd 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -19,7 +19,7 @@ require 'file_size_validator'
class Group < Namespace
include Gitlab::ConfigHelper
include Referable
-
+
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
alias_method :members, :group_members
has_many :users, through: :group_members
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 7beba984608..5f58c0508fd 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -38,6 +38,7 @@ class Issue < ActiveRecord::Base
scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) }
+ scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
state_machine :state, initial: :opened do
event :close do
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 41dd248d80a..0af60645545 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -183,8 +183,8 @@ class MergeRequest < ActiveRecord::Base
def diff_base_commit
if merge_request_diff
merge_request_diff.base_commit
- else
- self.target_project.commit(self.target_branch)
+ elsif source_sha
+ self.target_project.merge_base_commit(self.source_sha, self.target_branch)
end
end
@@ -489,7 +489,7 @@ class MergeRequest < ActiveRecord::Base
end
def source_sha
- commits.first.sha
+ last_commit.try(:sha)
end
def fetch_ref
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index ba0194cd0a6..c95179d6046 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -48,14 +48,11 @@ class MergeRequestDiff < ActiveRecord::Base
end
def diffs_no_whitespace
- # Get latest sha of branch from source project
- source_sha = merge_request.source_project.commit(source_branch).sha
-
compare_result = Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
- merge_request.target_project.repository.raw_repository,
- merge_request.target_branch,
- source_sha,
+ self.repository.raw_repository,
+ self.target_branch,
+ self.source_sha,
), { ignore_whitespace_change: true }
)
@diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs))
@@ -83,8 +80,6 @@ class MergeRequestDiff < ActiveRecord::Base
@last_commit_short_sha ||= last_commit.short_id
end
- private
-
def dump_commits(commits)
commits.map(&:to_hash)
end
@@ -163,7 +158,7 @@ class MergeRequestDiff < ActiveRecord::Base
self.st_diffs = new_diffs
- self.base_commit_sha = merge_request.target_project.commit(target_branch).try(:sha)
+ self.base_commit_sha = self.repository.merge_base(self.source_sha, self.target_branch)
self.save
end
@@ -181,7 +176,10 @@ class MergeRequestDiff < ActiveRecord::Base
merge_request.target_project.repository
end
- private
+ def source_sha
+ source_commit = merge_request.source_project.commit(source_branch)
+ source_commit.try(:sha)
+ end
def compare_result
@compare_result ||=
@@ -189,15 +187,11 @@ class MergeRequestDiff < ActiveRecord::Base
# Update ref for merge request
merge_request.fetch_ref
- # Get latest sha of branch from source project
- source_commit = merge_request.source_project.commit(source_branch)
- source_sha = source_commit.try(:sha)
-
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
- merge_request.target_project.repository.raw_repository,
- merge_request.target_branch,
- source_sha,
+ self.repository.raw_repository,
+ self.target_branch,
+ self.source_sha
)
)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 5579710a476..238932f59a7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -348,6 +348,11 @@ class Project < ActiveRecord::Base
repository.commit(id)
end
+ def merge_base_commit(first_commit_id, second_commit_id)
+ sha = repository.merge_base(first_commit_id, second_commit_id)
+ repository.commit(sha) if sha
+ end
+
def saved?
id && persisted?
end
@@ -904,4 +909,8 @@ class Project < ActiveRecord::Base
def runners_token
ensure_runners_token!
end
+
+ def wiki
+ @wiki ||= ProjectWiki.new(self, self.owner)
+ end
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 8ce47495971..c847eba8d1c 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -12,6 +12,7 @@ class ProjectWiki
# Returns a string describing what went wrong after
# an operation fails.
attr_reader :error_message
+ attr_reader :project
def initialize(project, user = nil)
@project = project
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d9ff71c01ed..130daddd9d1 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -57,7 +57,7 @@ class Repository
# This method return true if repository contains some content visible in project page.
#
def has_visible_content?
- !raw_repository.branches.empty?
+ raw_repository.branch_count > 0
end
def commit(id = 'HEAD')
@@ -589,6 +589,8 @@ class Repository
def merge_base(first_commit_id, second_commit_id)
rugged.merge_base(first_commit_id, second_commit_id)
+ rescue Rugged::ReferenceError
+ nil
end
def is_ancestor?(ancestor_id, descendant_id)
@@ -598,7 +600,7 @@ class Repository
def search_files(query, ref)
offset = 2
- args = %W(#{Gitlab.config.git.bin_path} grep -i -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
+ args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
diff --git a/app/models/tree.rb b/app/models/tree.rb
index e0e04d8859f..b28f31cdd6e 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -17,12 +17,20 @@ class Tree
def readme
return @readme if defined?(@readme)
- # Take the first previewable readme, or return nil if none is available or
- # we can't preview any of them
- readme_tree = blobs.find do |blob|
- blob.readme? && (previewable?(blob.name) || plain?(blob.name))
+ available_readmes = blobs.select(&:readme?)
+
+ previewable_readmes = available_readmes.select do |blob|
+ previewable?(blob.name)
+ end
+
+ plain_readmes = available_readmes.select do |blob|
+ plain?(blob.name)
end
+ # Prioritize previewable over plain readmes
+ readme_tree = previewable_readmes.first || plain_readmes.first
+
+ # Return if we can't preview any of them
if readme_tree.nil?
return @readme = nil
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
new file mode 100644
index 00000000000..2015897dd19
--- /dev/null
+++ b/app/services/projects/import_service.rb
@@ -0,0 +1,67 @@
+module Projects
+ class ImportService < BaseService
+ include Gitlab::ShellAdapter
+
+ class Error < StandardError; end
+
+ ALLOWED_TYPES = [
+ 'bitbucket',
+ 'fogbugz',
+ 'gitlab',
+ 'github',
+ 'google_code'
+ ]
+
+ def execute
+ if unknown_url?
+ # In this case, we only want to import issues, not a repository.
+ create_repository
+ else
+ import_repository
+ end
+
+ import_data
+
+ success
+ rescue Error => e
+ error(e.message)
+ end
+
+ private
+
+ def create_repository
+ unless project.create_repository
+ raise Error, 'The repository could not be created.'
+ end
+ end
+
+ def import_repository
+ begin
+ gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
+ rescue Gitlab::Shell::Error => e
+ raise Error, e.message
+ end
+ end
+
+ def import_data
+ return unless has_importer?
+
+ unless importer.execute
+ raise Error, 'The remote data could not be imported.'
+ end
+ end
+
+ def has_importer?
+ ALLOWED_TYPES.include?(project.import_type)
+ end
+
+ def importer
+ class_name = "Gitlab::#{project.import_type.camelize}Import::Importer"
+ class_name.constantize.new(project)
+ end
+
+ def unknown_url?
+ project.import_url == Project::UNKNOWN_IMPORT_URL
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index c4020c8273b..baadca09518 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -14,11 +14,11 @@
.form-group.project-visibility-level-holder
= f.label :default_project_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project)
+ = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
.form-group.project-visibility-level-holder
= f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: PersonalSnippet)
+ = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
@@ -268,4 +268,4 @@
= f.text_field :sentry_dsn, class: 'form-control'
.form-actions
- = f.submit 'Save', class: 'btn btn-primary'
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index fa4e6335c73..e18f7b499dd 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -22,5 +22,5 @@
%code= Doorkeeper.configuration.native_redirect_uri
for local tests
.form-actions
- = f.submit 'Submit', class: "btn btn-primary wide"
+ = f.submit 'Submit', class: "btn btn-save wide"
= link_to "Cancel", admin_applications_path, class: "btn btn-default"
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 841e6971fb2..41c43899978 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -2,7 +2,7 @@
.panel.panel-default
.panel-heading
Public deploy keys (#{@deploy_keys.count})
- .panel-head-actions
+ .controls
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- if @deploy_keys.any?
.table-holder
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 8de2ba74a79..198026a1f75 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -21,6 +21,5 @@
- else
.form-actions
- = f.submit 'Save changes', class: "btn btn-primary"
+ = f.submit 'Save changes', class: "btn btn-save"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
-
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 3940210e19b..118d3cfea07 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -17,7 +17,7 @@
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index b120f4dea67..53b3cd04c68 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -37,8 +37,7 @@
- @hooks.each do |hook|
%li
.list-item-name
- = link_to admin_hook_path(hook) do
- %strong= hook.url
+ %strong= hook.url
%p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
.pull-right
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index d9b481404f7..d39c0f44031 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Projects"
= render 'shared/show_aside'
-.row
+.row.prepend-top-default
%aside.col-md-3
.admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do
@@ -47,10 +47,10 @@
.panel.panel-default
.panel-heading
Projects (#{@projects.total_count})
- .panel-head-actions
+ .controls
.dropdown.inline
%button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index b050a4d01c3..b6b1168bd37 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -32,7 +32,7 @@
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder
index 2e2712c5146..d4daf07c6c0 100644
--- a/app/views/dashboard/projects/index.atom.builder
+++ b/app/views/dashboard/projects/index.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html"
xml.id dashboard_projects_url
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index fcb07b04083..8ffca96bb4e 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -18,7 +18,7 @@
.pull-right
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml
index b23a3c1e5c1..a988d4c8154 100644
--- a/app/views/explore/projects/_dropdown.html.haml
+++ b/app/views/explore/projects/_dropdown.html.haml
@@ -1,6 +1,6 @@
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path)
@@ -24,4 +24,3 @@
= sort_title_recently_updated
= link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
-
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index e2f97fd9337..3430f56a9c9 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,5 +1,4 @@
- header_title group_title(@group, "Settings", edit_group_path(@group))
-- @blank_container = true
.panel.panel-default.prepend-top-default
.panel-heading
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 6a8acc42af9..6b7fd5746d6 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,6 +1,5 @@
- page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group))
-- @blank_container = true
.group-members-page.prepend-top-default
- if current_user && current_user.can?(:admin_group_member, @group)
@@ -20,7 +19,7 @@
group members
%small
(#{@members.total_count})
- .pull-right
+ .controls
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 9ca11ed1177..dd75766121e 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -6,9 +6,9 @@
%strong= @group.name
projects:
- if can? current_user, :admin_group, @group
- .panel-head-actions
+ .controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
- %i.fa.fa-plus
+ = icon('plus')
New Project
%ul.well-list
- @projects.each do |project|
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index 5cc0f5e1d2e..c66b82bb484 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html"
xml.id group_url(@group)
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 270ccfd387f..319974e12c5 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -98,6 +98,13 @@
%span
Wiki
+ - if project_nav_tab? :forks
+ = nav_link(controller: :forks, action: :index) do
+ = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks' do
+ = icon('code-fork fw')
+ %span
+ Forks
+
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a42fd38de3a..52bfc595fda 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,6 +1,5 @@
- page_title "Account"
- header_title page_title, profile_account_path
-- @blank_container = true
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 53dcac78a9f..eb6fbfaffa0 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -15,12 +15,11 @@
.file-content.blame.code.js-syntax-highlight
%table
- current_line = 1
- - blame_highlighter = highlighter(@blob.name, @blob.data, nowrap: true)
- - @blame.each do |blame_group|
+ - @blame_groups.each do |blame_group|
%tr
%td.blame-commit
.commit
- - commit = Commit.new(blame_group[:commit], @project)
+ - commit = blame_group[:commit]
.commit-row-title
%strong
= link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
@@ -38,8 +37,7 @@
\
- current_line += line_count
%td.lines
- %pre{class: 'code highlight'}
+ %pre.code.highlight
%code
- blame_group[:lines].each do |line|
- :preserve
- #{blame_highlighter.highlight(line)}
+ #{line}
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 4429c395aee..906e5ccb360 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -2,8 +2,8 @@
.file-content.wiki
= render_markup(blob.name, blob.data)
- else
- .file-content.code
- - unless blob.empty?
- = render 'shared/file_highlight', blob: blob
- - else
+ - unless blob.empty?
+ = render 'shared/file_highlight', blob: blob
+ - else
+ .file-content.code
.nothing-here-block Empty file
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index c5a269f334c..541dc96c45f 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -8,7 +8,7 @@
.file-content.wiki
= raw render_markup(@blob.name, @content)
- else
- .file-content.code
+ .file-content.code.js-syntax-highlight
- unless @diff_lines.empty?
%table.text-file
- @diff_lines.each do |line|
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 204def60794..7afea5a5049 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -10,7 +10,7 @@
&nbsp;
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= @sort.humanize
- else
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 511863d774e..e7c85edff96 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -46,7 +46,7 @@
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
index 1736dccaf3c..2e3c956ddc4 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -66,7 +66,7 @@
%td
.pull-right
- - if current_user && can?(current_user, :read_build_artifacts, commit_status.project) && commit_status.artifacts?
+ - if current_user && can?(current_user, :read_build_artifacts, commit_status.project) && commit_status.artifacts_download_url
= link_to commit_status.artifacts_download_url, title: 'Download artifacts' do
%i.fa.fa-download
- if current_user && can?(current_user, :manage_builds, commit_status.project)
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
new file mode 100644
index 00000000000..a362185210a
--- /dev/null
+++ b/app/views/projects/forks/index.html.haml
@@ -0,0 +1,58 @@
+.gray-content-block.top-block.clearfix.white.forks-top-block
+ .pull-left
+ - public_count = @public_forks.size
+ - protected_count = @protected_forks.size
+ - full_count_title = "#{public_count} public and #{protected_count} private"
+ == #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
+
+ .pull-right
+ .projects-search-form.fork-search-form
+ = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control',
+ spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
+
+ .dropdown.inline.prepend-left-10
+ %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light sort:
+ - if @sort.present?
+ = sort_options_hash[@sort]
+ - else
+ = sort_title_recently_created
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li
+ - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
+ = link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do
+ = sort_title_recently_created
+ = link_to page_filter_path(sort: sort_value_oldest_created, without: excluded_filters) do
+ = sort_title_oldest_created
+ = link_to page_filter_path(sort: sort_value_recently_updated, without: excluded_filters) do
+ = sort_title_recently_updated
+ = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
+ = sort_title_oldest_updated
+
+ .fork-link.inline
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'pull-right btn btn-new' do
+ = icon('code-fork fw')
+ Fork
+ - else
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'pull-right btn btn-new' do
+ = icon('code-fork fw')
+ Fork
+
+
+.projects-list-holder
+ - if @public_forks.blank?
+ %ul.content-list
+ %li
+ .nothing-here-block No forks to show
+ - else
+ = render 'shared/projects/list', projects: @public_forks, use_creator_avatar: true,
+ forks: true, show_last_commit_as_description: true
+
+ - if protected_count > 0
+ %ul.projects-list.private-forks-notice
+ %li.project-row
+ = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
+ %strong= pluralize(protected_count, 'private fork')
+ %span you have no access to.
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 8a2c027a455..edabc2d3b44 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -22,7 +22,7 @@
- else
.fork-thumbnail
- = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 7ed898ce72f..51dcca7a1ab 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -35,7 +35,7 @@
Edit
.issue-details.issuable-details
- .detail-page-description.gray-content-block.second-block
+ .detail-page-description.content-block
%h2.title
= markdown escape_once(@issue.title), pipeline: :single_line
%div
@@ -50,7 +50,7 @@
.merge-requests
= render 'merge_requests'
- .gray-content-block.second-block.oneline-block
+ .content-block
= render 'votes/votes_block', votable: @issue
.row
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 200bfa5ac4f..8641c3d8b4b 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -66,7 +66,7 @@
.tab-content
#notes.notes.tab-pane.voting_notes
- .gray-content-block.second-block.oneline-block
+ .content-block.oneline-block
= render 'votes/votes_block', votable: @merge_request
.row
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 7f904ec42a0..a8f09f855d4 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.middle-block.oneline-block
+.content-block.oneline-block
= icon("sort-amount-desc")
Most recent commits displayed first
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 877cc3d744b..0dbd159298e 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -45,6 +45,10 @@
- unless @merge_request.can_be_merged_by?(current_user)
%p
Note that pushing to GitLab requires write access to this repository.
+ %p
+ %strong Tip:
+ You can also checkout merge requests locally by
+ %a{href: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/workflow/merge_requests.md#checkout-merge-requests-locally', target: '_blank'} following these guidelines
:javascript
$(function(){
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 0f81e5e8914..905823f79d9 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,4 +1,4 @@
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.content-block
%h2.title
= markdown escape_once(@merge_request.title), pipeline: :single_line
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 1142c584592..528a4f9552f 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -32,7 +32,7 @@
= icon('pencil-square-o')
Edit
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.content-block
%h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line
%div
@@ -73,8 +73,8 @@
.tab-content
.tab-pane.active#tab-issues
- .gray-content-block.middle-block
- .pull-right
+ .content-block.oneline-block
+ .controls
- if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
@@ -94,8 +94,8 @@
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
.tab-pane#tab-merge-requests
- .gray-content-block.middle-block
- .pull-right
+ .content-block.oneline-block
+ .controls
- if can?(current_user, :read_merge_request, @project)
= link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
@@ -117,9 +117,8 @@
= render 'merge_request', merge_request: merge_request
.tab-pane#tab-participants
- .gray-content-block.middle-block
- .oneline
- All participants to this milestone
+ .content-block.oneline-block
+ All participants to this milestone
%ul.bordered-list
- @users.each do |user|
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index c731baf0a65..11f9859a90f 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -7,7 +7,7 @@
%i.fa.fa-comment
= notes.count
%td.notes_content
- %ul.notes{ rel: note.discussion_id }
+ %ul.notes{ data: { discussion_id: note.discussion_id } }
= render notes
.discussion-reply-holder
= link_to_reply_diff(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index c6726cbafa3..bb761ed2f94 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -8,7 +8,7 @@
%i.fa.fa-comment
= notes_left.count
%td.notes_content.parallel.old
- %ul.notes{ rel: note1.discussion_id }
+ %ul.notes{ data: { discussion_id: note1.discussion_id } }
= render notes_left
.discussion-reply-holder
@@ -23,7 +23,7 @@
%i.fa.fa-comment
= notes_right.count
%td.notes_content.parallel.new
- %ul.notes{ rel: note2.discussion_id }
+ %ul.notes{ data: { discussion_id: note2.discussion_id } }
= render notes_right
.discussion-reply-holder
@@ -31,4 +31,3 @@
- else
%td.notes_line.new= ""
%td.notes_content.parallel.new= ""
-
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 922535e5c4a..e858c412836 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,4 +1,4 @@
-%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
.timeline-entry-inner
.timeline-icon
%a{href: user_path(note.author)}
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 6903fad4a0a..3da2f2060b8 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -20,8 +20,7 @@
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else
.panel.panel-default
- .notes{ rel: discussion_notes.first.discussion_id }
+ .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
= render discussion_notes
.discussion-reply-holder
= link_to_reply_diff(discussion_notes.first)
-
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
index 92bbaf0961a..820e31ccd61 100644
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ b/app/views/projects/notes/discussions/_diff.html.haml
@@ -9,7 +9,7 @@
= diff.new_path
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- .diff-content
+ .diff-content.code.js-syntax-highlight
%table
- note.truncated_diff_lines.each do |line|
- type = line.type
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 1c2458fa144..c53033e367c 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -5,7 +5,7 @@
%small
(#{members.count})
- if can?(current_user, :admin_group_member, @group)
- .pull-right
+ .controls
= link_to group_group_members_path(@group), class: 'btn' do
= icon('pencil-square-o')
Manage group members
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index ccddab13aaf..e8dce30425f 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -4,7 +4,7 @@
project members
%small
(#{members.count})
- .pull-right
+ .controls
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 6239a148905..0f8848a5cbe 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,13 +1,12 @@
- page_title "Members"
= render "header_title"
-- @blank_container = true
.project-members-page.prepend-top-default
- if can?(current_user, :admin_project_member, @project)
.panel.panel-default
.panel-heading
Add new user to project
- .pull-right
+ .controls
= link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
Import members
.panel-body
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index d6762219108..2468509242a 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project)
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 3343288ad2b..3eb626e6dca 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -40,7 +40,7 @@
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('pencil fw')
@@ -49,7 +49,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to upload a file again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
@@ -58,7 +58,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to create a new directory again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('folder fw')
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 2efa616d664..faeb2b55c6f 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -6,7 +6,7 @@
- if merge_request.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(merge_request.description))
+ = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project }))
%span.light
#{merge_request.project.name_with_namespace}
.pull-right
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index 3596aabe309..09edf4000d5 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,5 +1,5 @@
.gitlab-promo
= link_to 'Homepage', promo_url
- = link_to "Blog", promo_url + '/blog/'
- = link_to "@gitlab", "https://twitter.com/gitlab"
- = link_to "Requests", "http://feedback.gitlab.com/"
+ = link_to 'Blog', promo_url + '/blog/'
+ = link_to '@gitlab', 'https://twitter.com/gitlab'
+ = link_to 'Requests', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#feature-proposals'
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index af3d35de325..f09ab25276d 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,6 +1,6 @@
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/shared/issuable/_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index 3a5ad00aa91..6672ea79629 100644
--- a/app/views/shared/issuable/_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
@@ -1,6 +1,6 @@
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
.append-right-10.hidden-xs.hidden-sm
- = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input', spellcheck: false }
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input', spellcheck: false }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index e5ffe1e29ae..b3f45373f6b 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -1,14 +1,18 @@
- projects_limit = 20 unless local_assigns[:projects_limit]
- avatar = true unless local_assigns[:avatar] == false
+- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
- stars = true unless local_assigns[:stars] == false
+- forks = false unless local_assigns[:forks] == true
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
+- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
%ul.projects-list
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
- avatar: avatar, stars: stars, css_class: css_class, ci: ci
+ avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
+ forks: forks, show_last_commit_as_description: show_last_commit_as_description
- if projects.size > projects_limit
%li.bottom.center
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 5db8056b77c..2aeeed63c95 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -1,9 +1,11 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
+- forks = false unless local_assigns[:forks] == true
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- css_class = '' unless local_assigns[:css_class]
-- css_class += " no-description" unless project.description.present?
+- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
+- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2']
- cache_key.push(ci_commit.status) if ci_commit
@@ -13,7 +15,10 @@
= link_to project_path(project), class: dom_class(project) do
- if avatar
.dash-project-avatar
- = project_icon(project, alt: '', class: 'avatar project-avatar s46')
+ - if use_creator_avatar
+ = image_tag avatar_icon(project.creator.email, 46), class: "avatar s46", alt:''
+ - else
+ = project_icon(project, alt: '', class: 'avatar project-avatar s46')
%span.project-full-name
%span.namespace-name
- if project.namespace && !skip_namespace
@@ -26,10 +31,18 @@
- if ci_commit
= render_ci_status(ci_commit)
&nbsp;
+ - if forks
+ %span
+ = icon('code-fork')
+ = project.forks_count
- if stars
%span
- %i.fa.fa-star
+ = icon('star')
= project.star_count
- - if project.description.present?
+ - if show_last_commit_as_description
+ .project-description
+ = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
+ class: "commit-row-message"
+ - elsif project.description.present?
.project-description
= markdown(project.description, pipeline: :description)
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index d26a99bb14c..e0e41fc4bea 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -3,8 +3,7 @@
.file-content.wiki
= render_markup(@snippet.file_name, @snippet.data)
- else
- .file-content.code
- = render 'shared/file_highlight', blob: @snippet
+ = render 'shared/file_highlight', blob: @snippet
- else
.file-content.code
.nothing-here-block Empty file
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
index 114d1e7a379..e9e466c6350 100644
--- a/app/views/users/show.atom.builder
+++ b/app/views/users/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"
xml.id user_url(@user)
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index d18c0706b30..e295a9ddd14 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -4,52 +4,20 @@ class RepositoryImportWorker
sidekiq_options queue: :gitlab_shell
- def perform(project_id)
- project = Project.find(project_id)
+ attr_accessor :project, :current_user
- if project.import_url == Project::UNKNOWN_IMPORT_URL
- # In this case, we only want to import issues, not a repository.
- unless project.create_repository
- project.update(import_error: "The repository could not be created.")
- project.import_fail
- return
- end
- else
- begin
- gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
- rescue Gitlab::Shell::Error => e
- project.update(import_error: e.message)
- project.import_fail
- return
- end
- end
+ def perform(project_id)
+ @project = Project.find(project_id)
+ @current_user = @project.creator
- data_import_result =
- case project.import_type
- when 'github'
- Gitlab::GithubImport::Importer.new(project).execute
- when 'gitlab'
- Gitlab::GitlabImport::Importer.new(project).execute
- when 'bitbucket'
- Gitlab::BitbucketImport::Importer.new(project).execute
- when 'google_code'
- Gitlab::GoogleCodeImport::Importer.new(project).execute
- when 'fogbugz'
- Gitlab::FogbugzImport::Importer.new(project).execute
- else
- true
- end
+ result = Projects::ImportService.new(project, current_user).execute
- unless data_import_result
- project.update(import_error: "The remote issue data could not be imported.")
+ if result[:status] == :error
+ project.update(import_error: result[:message])
project.import_fail
return
end
- if project.import_type == 'bitbucket'
- Gitlab::BitbucketImport::KeyDeleter.new(project).execute
- end
-
project.import_finish
end
end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 04a7c16ebde..d8170557f7e 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -176,7 +176,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
diff --git a/config/initializers/haml.rb b/config/initializers/haml.rb
index 7e8ddb3716b..1516476815a 100644
--- a/config/initializers/haml.rb
+++ b/config/initializers/haml.rb
@@ -1 +1,7 @@
Haml::Template.options[:ugly] = true
+
+# Remove the `:coffee` and `:coffeescript` filters
+#
+# See https://git.io/vztMu and http://stackoverflow.com/a/17571242/223897
+Haml::Filters.remove_filter('coffee')
+Haml::Filters.remove_filter('coffeescript')
diff --git a/config/initializers/monkey_patch.rb b/config/initializers/monkey_patch.rb
new file mode 100644
index 00000000000..62b05a55285
--- /dev/null
+++ b/config/initializers/monkey_patch.rb
@@ -0,0 +1,48 @@
+## This patch is from rails 4.2-stable. Remove it when 4.2.6 is released
+## https://github.com/rails/rails/issues/21108
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AbstractMysqlAdapter < AbstractAdapter
+ # SHOW VARIABLES LIKE 'name'
+ def show_variable(name)
+ variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
+ variables.first['Value'] unless variables.empty?
+ rescue ActiveRecord::StatementInvalid
+ nil
+ end
+
+
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
+ # to give it some prompting in the form of a subsubquery. Ugh!
+ def subquery_for(key, select)
+ subsubselect = select.clone
+ subsubselect.projections = [key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(key.name)
+ # Materialized subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subselect.from subsubselect.distinct.as('__active_record_temp')
+ end
+ end
+ end
+end
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlAdapter < AbstractMysqlAdapter
+ ADAPTER_NAME = 'MySQL'.freeze
+
+ # Get the client encoding for this database
+ def client_encoding
+ return @client_encoding if @client_encoding
+
+ result = exec_query(
+ "select @@character_set_client",
+ 'SCHEMA')
+ @client_encoding = ENCODINGS[result.rows.last.last]
+ end
+ end
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index 75418db8d25..fdfdb449085 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -554,7 +554,7 @@ Rails.application.routes.draw do
end
end
- resource :fork, only: [:new, :create]
+ resources :forks, only: [:index, :new, :create]
resource :import, only: [:new, :create, :show]
resources :refs, only: [] do
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 42a27dcf6d6..0faef526d43 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -47,6 +47,7 @@ GITLAB_DATABASE_PORT | 5432
## Adding more variables
We welcome merge requests to make more settings configurable via variables.
+Please make changes in the file config/initializers/1_settings.rb
Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
## Omnibus configuration
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index c27cf1812dc..a5fa7d358a2 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -4,8 +4,8 @@ _**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_
---
-The housekeeping function runs [`git gc`][man] on the current project Git
-repository.
+The housekeeping function runs `git gc` ([man page][man]) on the current
+project Git repository.
`git gc` runs a number of housekeeping tasks, such as compressing file
revisions (to reduce disk space and increase performance) and removing
diff --git a/doc/api/README.md b/doc/api/README.md
index 4d2fb582833..9f3ad126320 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,7 +1,13 @@
# GitLab API
+Automate GitLab via a simple and powerful API. All definitions can be found
+under [`/lib/api`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api).
+
## Resources
+Documentation for various API resources can be found separately in the
+following locations:
+
- [Users](users.md)
- [Session](session.md)
- [Projects](projects.md) including setting Webhooks
@@ -27,16 +33,15 @@
- [Build triggers](build_triggers.md)
- [Build Variables](build_variables.md)
-## Clients
-
-Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
-You can use [GitLab as an OAuth2 client](oauth2.md) to make API calls.
+## Authentication
-## Introduction
+All API requests require authentication. You need to pass a `private_token`
+parameter via query string or header. If passed as a header, the header name
+must be `PRIVATE-TOKEN` (uppercase and with a dash instead of an underscore).
+You can find or reset your private token in your account page (`/profile/account`).
-All API requests require authentication. You need to pass a `private_token` parameter by URL or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
-
-If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
+If `private_token` is invalid or omitted, then an error message will be
+returned with status code `401`:
```json
{
@@ -44,71 +49,83 @@ If no, or an invalid, `private_token` is provided then an error message will be
}
```
-API requests should be prefixed with `api` and the API version. The API version is defined in `lib/api.rb`.
+API requests should be prefixed with `api` and the API version. The API version
+is defined in [`lib/api.rb`][lib-api-url].
Example of a valid API request:
-```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U
+```shell
+GET https://gitlab.example.com/api/v3/projects?private_token=9koXpg98eAheJpvBs5tK
```
-Example for a valid API request using curl and authentication via header:
+Example of a valid API request using cURL and authentication via header:
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/projects"
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects"
```
-The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
+The API uses JSON to serialize data. You don't need to specify `.json` at the
+end of an API URL.
## Authentication with OAuth2 token
-Instead of the private_token you can transmit the OAuth2 access token as a header or as a parameter.
+Instead of the `private_token` you can transmit the OAuth2 access token as a
+header or as a parameter.
-### OAuth2 token (as a parameter)
+Example of OAuth2 token as a parameter:
-```
-curl https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+```shell
+curl https://gitlab.example.com/api/v3/user?access_token=OAUTH-TOKEN
```
-### OAuth2 token (as a header)
+Example of OAuth2 token as a header:
-```
-curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+```shell
+curl -H "Authorization: Bearer OAUTH-TOKEN" https://example.com/api/v3/user
```
Read more about [GitLab as an OAuth2 client](oauth2.md).
## Status codes
-The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
-
-API request types:
-
-- `GET` requests access one or more resources and return the result as JSON
-- `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
-- `GET`, `PUT` and `DELETE` return `200 OK` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
-- `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
-
-The following list shows the possible return codes for API requests.
-
-Return values:
-
-- `200 OK` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
-- `201 Created` - The `POST` request was successful and the resource is returned as JSON
-- `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
-- `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
-- `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
-- `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
-- `405 Method Not Allowed` - The request is not supported
-- `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
-- `422 Unprocessable` - The entity could not be processed
-- `500 Server Error` - While handling the request something went wrong on the server side
+The API is designed to return different status codes according to context and
+action. This way, if a request results in an error, the caller is able to get
+insight into what went wrong.
+
+The following table gives an overview of how the API functions generally behave.
+
+| Request type | Description |
+| ------------ | ----------- |
+| `GET` | Access one or more resources and return the result as JSON. |
+| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. |
+| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. |
+| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. |
+
+The following table shows the possible return codes for API requests.
+
+| Return values | Description |
+| ------------- | ----------- |
+| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. |
+| `201 Created` | The `POST` request was successful and the resource is returned as JSON. |
+| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. |
+| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. |
+| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. |
+| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. |
+| `405 Method Not Allowed` | The request is not supported. |
+| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. |
+| `422 Unprocessable` | The entity could not be processed. |
+| `500 Server Error` | While handling the request something went wrong server-side. |
## Sudo
-All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by URL or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
+All API requests support performing an API call as if you were another user,
+provided your private token is from an administrator account. You need to pass
+the `sudo` parameter either via query string or a header with an ID/username of
+the user you want to perform the operation as. If passed as a header, the
+header name must be `SUDO` (uppercase).
-If a non administrative `private_token` is provided then an error message will be returned with status code 403:
+If a non administrative `private_token` is provided, then an error message will
+be returned with status code `403`:
```json
{
@@ -116,7 +133,8 @@ If a non administrative `private_token` is provided then an error message will b
}
```
-If the sudo user id or username cannot be found then an error message will be returned with status code 404:
+If the sudo user ID or username cannot be found, an error message will be
+returned with status code `404`:
```json
{
@@ -124,32 +142,45 @@ If the sudo user id or username cannot be found then an error message will be re
}
```
-Example of a valid API with sudo request:
+---
-```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=username
-```
+Example of a valid API call and a request using cURL with sudo request,
+providing a username:
+```shell
+GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username
```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=23
+
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "https://gitlab.example.com/api/v3/projects"
```
-Example for a valid API request with sudo using curl and authentication via header:
+Example of a valid API call and a request using cURL with sudo request,
+providing an ID:
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "http://example.com/api/v3/projects"
+```shell
+GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
```
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects"
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v3/projects"
```
## Pagination
-When listing resources you can pass the following parameters:
+Sometimes the returned result will span across many pages. When listing
+resources you can pass the following parameters:
+
+| Parameter | Description |
+| --------- | ----------- |
+| `page` | Page number (default: `1`) |
+| `per_page`| Number of items to list per page (default: `20`, max: `100`) |
-- `page` (default: `1`) - page number
-- `per_page` (default: `20`, max: `100`) - number of items to list per page
+In the example below, we list 50 [namespaces](namespaces.md) per page.
+
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/namespaces?per_page=50
+```
### Pagination Link header
@@ -199,65 +230,93 @@ Additional pagination headers are also sent back.
| `X-Next-Page` | The index of the next page |
| `X-Prev-Page` | The index of the previous page |
-## id vs iid
+## `id` vs `iid`
-When you work with API you may notice two similar fields in api entities: id and iid. The main difference between them is scope. Example:
+When you work with the API, you may notice two similar fields in API entities:
+`id` and `iid`. The main difference between them is scope.
-Issue:
+For example, an issue might have `id: 46` and `iid: 5`.
- id: 46
- iid: 5
+| Parameter | Description |
+| --------- | ----------- |
+| `id` | Is unique across all issues and is used for any API call |
+| `iid` | Is unique only in scope of a single project. When you browse issues or merge requests with the Web UI, you see the `iid` |
+
+That means that if you want to get an issue via the API you should use the `id`:
+
+```bash
+GET /projects/42/issues/:id
+```
-- id - is unique across all issues. It's used for any api call.
-- iid - is unique only in scope of a single project. When you browse issues or merge requests with Web UI, you see iid.
+On the other hand, if you want to create a link to a web page you should use
+the `iid`:
-So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`. But when you want to create a link to web page - use `http:://host/project/issues/:iid.json`
+```bash
+GET /projects/42/issues/:iid
+```
## Data validation and error reporting
-When working with the API you may encounter validation errors. In such case, the API will answer with an HTTP `400` status.
+When working with the API you may encounter validation errors, in which case
+the API will answer with an HTTP `400` status.
+
Such errors appear in two cases:
-* A required attribute of the API request is missing, e.g. the title of an issue is not given
-* An attribute did not pass the validation, e.g. user bio is too long
+- A required attribute of the API request is missing, e.g., the title of an
+ issue is not given
+- An attribute did not pass the validation, e.g., user bio is too long
When an attribute is missing, you will get something like:
- HTTP/1.1 400 Bad Request
- Content-Type: application/json
-
- {
- "message":"400 (Bad request) \"title\" not given"
- }
-
-When a validation error occurs, error messages will be different. They will hold all details of validation errors:
+```
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+{
+ "message":"400 (Bad request) \"title\" not given"
+}
+```
- HTTP/1.1 400 Bad Request
- Content-Type: application/json
+When a validation error occurs, error messages will be different. They will
+hold all details of validation errors:
- {
- "message": {
- "bio": [
- "is too long (maximum is 255 characters)"
- ]
- }
+```
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+{
+ "message": {
+ "bio": [
+ "is too long (maximum is 255 characters)"
+ ]
}
+}
+```
-This makes error messages more machine-readable. The format can be described as follow:
+This makes error messages more machine-readable. The format can be described as
+follows:
- {
- "message": {
+```json
+{
+ "message": {
+ "<property-name>": [
+ "<error-message>",
+ "<error-message>",
+ ...
+ ],
+ "<embed-entity>": {
"<property-name>": [
"<error-message>",
"<error-message>",
...
],
- "<embed-entity>": {
- "<property-name>": [
- "<error-message>",
- "<error-message>",
- ...
- ],
- }
}
}
+}
+```
+
+## Clients
+
+There are many unofficial GitLab API Clients for most of the popular
+programming languages. Visit the [GitLab website] for a complete list.
+
+[GitLab website]: https://about.gitlab.com/applications/#api-clients "Clients using the GitLab API"
+[lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 6a9c10c8520..abc4732c395 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -8,13 +8,21 @@ Get a list of repository branches from a project, sorted by name alphabetically.
GET /projects/:id/repository/branches
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
-- `id` (required) - The ID of a project
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches
+```
+
+Example response:
```json
[
{
+ "name": "master",
+ "protected": true,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
@@ -27,10 +35,9 @@ Parameters:
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
]
- },
- "name": "master",
- "protected": true
- }
+ }
+ },
+ ...
]
```
@@ -42,13 +49,21 @@ Get a single project repository branch.
GET /projects/:id/repository/branches/:branch
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master
+```
+
+Example response:
```json
{
+ "name": "master",
+ "protected": true,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
@@ -61,25 +76,30 @@ Parameters:
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
]
- },
- "name": "master",
- "protected": true
+ }
}
```
## Protect repository branch
-Protects a single project repository branch. This is an idempotent function, protecting an already
-protected repository branch still returns a `200 OK` status code.
+Protects a single project repository branch. This is an idempotent function,
+protecting an already protected repository branch still returns a `200 OK`
+status code.
```
PUT /projects/:id/repository/branches/:branch/protect
```
-Parameters:
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect
+```
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
+
+Example response:
```json
{
@@ -103,17 +123,24 @@ Parameters:
## Unprotect repository branch
-Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
-unprotected repository branch still returns a `200 OK` status code.
+Unprotects a single project repository branch. This is an idempotent function,
+unprotecting an already unprotected repository branch still returns a `200 OK`
+status code.
```
PUT /projects/:id/repository/branches/:branch/unprotect
```
-Parameters:
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/unprotect
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+Example response:
```json
{
@@ -141,11 +168,17 @@ Parameters:
POST /projects/:id/repository/branches
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch_name` | string | yes | The name of the branch |
+| `ref` | string | yes | The branch name or commit SHA to create branch from |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch_name=newbranch&ref=master"
+```
-- `id` (required) - The ID of a project
-- `branch_name` (required) - The name of the branch
-- `ref` (required) - Create branch from commit SHA or existing branch
+Example response:
```json
{
@@ -162,12 +195,13 @@ Parameters:
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
]
},
- "name": "master",
+ "name": "newbranch",
"protected": false
}
```
-It return 200 if succeed or 400 if failed with error message explaining reason.
+It returns `200` if it succeeds or `400` if failed with an error message
+explaining the reason.
## Delete repository branch
@@ -175,18 +209,22 @@ It return 200 if succeed or 400 if failed with error message explaining reason.
DELETE /projects/:id/repository/branches/:branch
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+It returns `200` if it succeeds, `404` if the branch to be deleted does not exist
+or `400` for other reasons. In case of an error, an explaining message is provided.
-It return 200 if succeed, 404 if the branch to be deleted does not exist
-or 400 for other reasons. In case of an error, an explaining message is provided.
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch"
+```
-Success response:
+Example response:
```json
{
- "branch_name": "my-removed-branch"
+ "branch_name": "newbranch"
}
```
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index e4492fc609c..9da1fe22e61 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -8,9 +8,15 @@ Get a list of a project's deploy keys.
GET /projects/:id/keys
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys"
+```
-- `id` (required) - The ID of the project
+Example response:
```json
[
@@ -39,8 +45,16 @@ GET /projects/:id/keys/:key_id
Parameters:
-- `id` (required) - The ID of the project
-- `key_id` (required) - The ID of the deploy key
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `key_id` | integer | yes | The ID of the deploy key |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/11"
+```
+
+Example response:
```json
{
@@ -54,17 +68,34 @@ Parameters:
## Add deploy key
Creates a new deploy key for a project.
-If deploy key already exists in another project - it will be joined to project but only if original one was is accessible by same user
+
+If the deploy key already exists in another project, it will be joined to current
+project only if original one was is accessible by the same user.
```
POST /projects/:id/keys
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `title` | string | yes | New deploy key's title |
+| `key` | string | yes | New deploy key |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/keys/"
+```
-- `id` (required) - The ID of the project
-- `title` (required) - New deploy key's title
-- `key` (required) - New deploy key
+Example response:
+
+```json
+{
+ "key" : "ssh-rsa AAAA...",
+ "id" : 12,
+ "title" : "My deploy key",
+ "created_at" : "2015-08-29T12:44:31.550Z"
+}
+```
## Delete deploy key
@@ -74,7 +105,26 @@ Delete a deploy key from a project
DELETE /projects/:id/keys/:key_id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `key_id` | integer | yes | The ID of the deploy key |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/13"
+```
-- `id` (required) - The ID of the project
-- `key_id` (required) - The ID of the deploy key
+Example response:
+
+```json
+{
+ "updated_at" : "2015-08-29T12:50:57.259Z",
+ "key" : "ssh-rsa AAAA...",
+ "public" : false,
+ "title" : "My deploy key",
+ "user_id" : null,
+ "created_at" : "2015-08-29T12:50:57.259Z",
+ "fingerprint" : "6a:33:1f:74:51:c0:39:81:79:ec:7a:31:f8:40:20:43",
+ "id" : 13
+}
+```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index d407bc35d79..9e704648b25 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -1,9 +1,20 @@
# Issues
+Every API call to issues must be authenticated.
+
+If a user is not a member of a project and the project is private, a `GET`
+request on that project will result to a `404` status code.
+
+## Issues pagination
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
+
## List issues
-Get all issues created by authenticated user. This function takes pagination parameters
-`page` and `per_page` to restrict the list of issues.
+Get all issues created by the authenticated user.
```
GET /issues
@@ -14,81 +25,65 @@ GET /issues?labels=foo,bar
GET /issues?labels=foo,bar&state=opened
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
+| `labels` | string | no | Comma-separated list of label names |
+| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/issues
+```
-- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
-- `labels` (optional) - Comma-separated list of label names
-- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+Example response:
```json
[
- {
- "id": 43,
- "iid": 3,
- "project_id": 8,
- "title": "4xx/5xx pages",
- "description": "",
- "labels": [],
- "milestone": null,
- "assignee": null,
- "author": {
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
- "name": "John Smith",
- "state": "active",
- "created_at": "2012-05-23T08:00:58Z"
- },
- "state": "closed",
- "updated_at": "2012-07-02T17:53:12Z",
- "created_at": "2012-07-02T17:53:12Z"
- },
- {
- "id": 42,
- "iid": 4,
- "project_id": 8,
- "title": "Add user settings",
- "description": "",
- "labels": [
- "feature"
- ],
- "milestone": {
- "id": 1,
- "title": "v1.0",
- "description": "",
- "due_date": "2012-07-20",
- "state": "reopened",
- "updated_at": "2012-07-04T13:42:48Z",
- "created_at": "2012-07-04T13:42:48Z"
- },
- "assignee": {
- "id": 2,
- "username": "jack_smith",
- "email": "jack@example.com",
- "name": "Jack Smith",
- "state": "active",
- "created_at": "2012-05-23T08:01:01Z"
- },
- "author": {
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
- "name": "John Smith",
- "state": "active",
- "created_at": "2012-05-23T08:00:58Z"
- },
- "state": "opened",
- "updated_at": "2012-07-12T13:43:19Z",
- "created_at": "2012-06-28T12:58:06Z"
- }
+ {
+ "state" : "opened",
+ "description" : "Ratione dolores corrupti mollitia soluta quia.",
+ "author" : {
+ "state" : "active",
+ "id" : 18,
+ "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "username" : "eileen.lowe"
+ },
+ "milestone" : {
+ "project_id" : 1,
+ "description" : "Ducimus nam enim ex consequatur cumque ratione.",
+ "state" : "closed",
+ "due_date" : null,
+ "iid" : 2,
+ "created_at" : "2016-01-04T15:31:39.996Z",
+ "title" : "v4.0",
+ "id" : 17,
+ "updated_at" : "2016-01-04T15:31:39.996Z"
+ },
+ "project_id" : 1,
+ "assignee" : {
+ "state" : "active",
+ "id" : 1,
+ "name" : "Administrator",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root"
+ },
+ "updated_at" : "2016-01-04T15:31:51.081Z",
+ "id" : 76,
+ "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
+ "created_at" : "2016-01-04T15:31:51.081Z",
+ "iid" : 6,
+ "labels" : []
+ },
]
```
## List project issues
-Get a list of project issues. This function accepts pagination parameters `page` and `per_page`
-to return the list of project issues.
+Get a list of a project's issues.
```
GET /projects/:id/issues
@@ -102,67 +97,123 @@ GET /projects/:id/issues?milestone=1.0.0&state=opened
GET /projects/:id/issues?iid=42
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `iid` | integer | no | Return the issue having the given `iid` |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
+| `labels` | string | no | Comma-separated list of label names |
+| `milestone` | string| no | The milestone title |
+| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
+
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues
+```
+
+Example response:
-- `id` (required) - The ID of a project
-- `iid` (optional) - Return the issue having the given `iid`
-- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
-- `labels` (optional) - Comma-separated list of label names
-- `milestone` (optional) - Milestone title
-- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+```json
+[
+ {
+ "project_id" : 4,
+ "milestone" : {
+ "due_date" : null,
+ "project_id" : 4,
+ "state" : "closed",
+ "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+ "iid" : 3,
+ "id" : 11,
+ "title" : "v3.0",
+ "created_at" : "2016-01-04T15:31:39.788Z",
+ "updated_at" : "2016-01-04T15:31:39.788Z"
+ },
+ "author" : {
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root",
+ "id" : 1,
+ "name" : "Administrator"
+ },
+ "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+ "state" : "closed",
+ "iid" : 1,
+ "assignee" : {
+ "avatar_url" : null,
+ "web_url" : "https://gitlab.example.com/u/lennie",
+ "state" : "active",
+ "username" : "lennie",
+ "id" : 9,
+ "name" : "Dr. Luella Kovacek"
+ },
+ "labels" : [],
+ "id" : 41,
+ "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+ "updated_at" : "2016-01-04T15:31:46.176Z",
+ "created_at" : "2016-01-04T15:31:46.176Z"
+ }
+]
+```
## Single issue
-Gets a single project issue.
+Get a single project issue.
```
GET /projects/:id/issues/:issue_id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id`| integer | yes | The ID of a project's issue |
-- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project issue
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/41
+```
+
+Example response:
```json
{
- "id": 42,
- "iid": 3,
- "project_id": 8,
- "title": "Add user settings",
- "description": "",
- "labels": [
- "feature"
- ],
- "milestone": {
- "id": 1,
- "title": "v1.0",
- "description": "",
- "due_date": "2012-07-20",
- "state": "closed",
- "updated_at": "2012-07-04T13:42:48Z",
- "created_at": "2012-07-04T13:42:48Z"
- },
- "assignee": {
- "id": 2,
- "username": "jack_smith",
- "email": "jack@example.com",
- "name": "Jack Smith",
- "state": "active",
- "created_at": "2012-05-23T08:01:01Z"
- },
- "author": {
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
- "name": "John Smith",
- "state": "active",
- "created_at": "2012-05-23T08:00:58Z"
- },
- "state": "opened",
- "updated_at": "2012-07-12T13:43:19Z",
- "created_at": "2012-06-28T12:58:06Z"
+ "project_id" : 4,
+ "milestone" : {
+ "due_date" : null,
+ "project_id" : 4,
+ "state" : "closed",
+ "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+ "iid" : 3,
+ "id" : 11,
+ "title" : "v3.0",
+ "created_at" : "2016-01-04T15:31:39.788Z",
+ "updated_at" : "2016-01-04T15:31:39.788Z"
+ },
+ "author" : {
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root",
+ "id" : 1,
+ "name" : "Administrator"
+ },
+ "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+ "state" : "closed",
+ "iid" : 1,
+ "assignee" : {
+ "avatar_url" : null,
+ "web_url" : "https://gitlab.example.com/u/lennie",
+ "state" : "active",
+ "username" : "lennie",
+ "id" : 9,
+ "name" : "Dr. Luella Kovacek"
+ },
+ "labels" : [],
+ "id" : 41,
+ "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+ "updated_at" : "2016-01-04T15:31:46.176Z",
+ "created_at" : "2016-01-04T15:31:46.176Z"
}
```
@@ -170,57 +221,122 @@ Parameters:
Creates a new project issue.
+If the operation is successful, a status code of `200` and the newly-created
+issue is returned. If an error occurs, an error number and a message explaining
+the reason is returned.
+
```
POST /projects/:id/issues
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `title` | string | yes | The title of an issue |
+| `description` | string | no | The description of an issue |
+| `assignee_id` | integer | no | The ID of a user to assign issue |
+| `milestone_id` | integer | no | The ID of a milestone to assign issue |
+| `labels` | string | no | Comma-separated label names for an issue |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
+```
-- `id` (required) - The ID of a project
-- `title` (required) - The title of an issue
-- `description` (optional) - The description of an issue
-- `assignee_id` (optional) - The ID of a user to assign issue
-- `milestone_id` (optional) - The ID of a milestone to assign issue
-- `labels` (optional) - Comma-separated label names for an issue
+Example response:
-If the operation is successful, 200 and the newly created issue is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
+```json
+{
+ "project_id" : 4,
+ "id" : 84,
+ "created_at" : "2016-01-07T12:44:33.959Z",
+ "iid" : 14,
+ "title" : "Issues with auth",
+ "state" : "opened",
+ "assignee" : null,
+ "labels" : [
+ "bug"
+ ],
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "description" : null,
+ "updated_at" : "2016-01-07T12:44:33.959Z",
+ "milestone" : null
+}
+```
## Edit issue
-Updates an existing project issue. This function is also used to mark an issue as closed.
+Updates an existing project issue. This call is also used to mark an issue as
+closed.
+
+If the operation is successful, a code of `200` and the updated issue is
+returned. If an error occurs, an error number and a message explaining the
+reason is returned.
```
PUT /projects/:id/issues/:issue_id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+| `title` | string | no | The title of an issue |
+| `description` | string | no | The description of an issue |
+| `assignee_id` | integer | no | The ID of a user to assign the issue to |
+| `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
+| `labels` | string | no | Comma-separated label names for an issue |
+| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
+
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
+```
-- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project's issue
-- `title` (optional) - The title of an issue
-- `description` (optional) - The description of an issue
-- `assignee_id` (optional) - The ID of a user to assign issue
-- `milestone_id` (optional) - The ID of a milestone to assign issue
-- `labels` (optional) - Comma-separated label names for an issue
-- `state_event` (optional) - The state event of an issue ('close' to close issue and 'reopen' to reopen it)
+Example response:
-If the operation is successful, 200 and the updated issue is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
+```json
+{
+ "created_at" : "2016-01-07T12:46:01.410Z",
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "username" : "eileen.lowe",
+ "id" : 18,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/eileen.lowe"
+ },
+ "state" : "closed",
+ "title" : "Issues with auth",
+ "project_id" : 4,
+ "description" : null,
+ "updated_at" : "2016-01-07T12:55:16.213Z",
+ "iid" : 15,
+ "labels" : [
+ "bug"
+ ],
+ "id" : 85,
+ "assignee" : null,
+ "milestone" : null
+}
+```
## Delete existing issue (**Deprecated**)
-The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`.
+This call is deprecated and returns a `405 Method Not Allowed` error if called.
+An issue gets now closed and is done by calling
+`PUT /projects/:id/issues/:issue_id` with the parameter `state_event` set to
+`close`. See [edit issue](#edit-issue) for more details.
```
DELETE /projects/:id/issues/:issue_id
```
-Parameters:
-
-- `id` (required) - The project ID
-- `issue_id` (required) - The ID of the issue
-
## Comments on issues
-Comments are done via the notes resource.
+Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/labels.md b/doc/api/labels.md
index de41f35d284..6496ffe9fd1 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -2,83 +2,153 @@
## List labels
-Get all labels for given project.
+Get all labels for a given project.
```
GET /projects/:id/labels
```
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels
+```
+
+Example response:
+
```json
[
- {
- "name": "Awesome",
- "color": "#DD10AA"
- },
- {
- "name": "Documentation",
- "color": "#1E80DD"
- },
- {
- "name": "Feature",
- "color": "#11FF22"
- },
- {
- "name": "Bug",
- "color": "#EE1122"
- }
+ {
+ "name" : "bug",
+ "color" : "#d9534f"
+ },
+ {
+ "color" : "#d9534f",
+ "name" : "confirmed"
+ },
+ {
+ "name" : "critical",
+ "color" : "#d9534f"
+ },
+ {
+ "color" : "#428bca",
+ "name" : "discussion"
+ },
+ {
+ "name" : "documentation",
+ "color" : "#f0ad4e"
+ },
+ {
+ "color" : "#5cb85c",
+ "name" : "enhancement"
+ },
+ {
+ "color" : "#428bca",
+ "name" : "suggestion"
+ },
+ {
+ "color" : "#f0ad4e",
+ "name" : "support"
+ }
]
```
## Create a new label
-Creates a new label for given repository with given name and color.
+Creates a new label for the given repository with the given name and color.
+
+It returns 200 if the label was successfully created, 400 for wrong parameters
+and 409 if the label already exists.
```
POST /projects/:id/labels
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the label |
+| `color` | string | yes | The color of the label in 6-digit hex notation with leading `#` sign |
+
+```bash
+curl --data "name=feature&color=#5843AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+```
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the label
-- `color` (required) - Color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)
+Example response:
-It returns 200 and the newly created label, if the operation succeeds.
-If the label already exists, 409 and an error message is returned.
-If label parameters are invalid, 400 and an explaining error message is returned.
+```json
+{
+ "name" : "feature",
+ "color" : "#5843AD"
+}
+```
## Delete a label
-Deletes a label given by its name.
+Deletes a label with a given name.
+
+It returns 200 if the label was successfully deleted, 400 for wrong parameters
+and 404 if the label does not exist.
+In case of an error, an additional error message is returned.
```
DELETE /projects/:id/labels
```
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the label to be deleted
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the label |
-It returns 200 if the label successfully was deleted, 400 for wrong parameters
-and 404 if the label does not exist.
-In case of an error, additionally an error message is returned.
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug"
+```
+
+Example response:
+
+```json
+{
+ "title" : "feature",
+ "color" : "#5843AD",
+ "updated_at" : "2015-11-03T21:22:30.737Z",
+ "template" : false,
+ "project_id" : 1,
+ "created_at" : "2015-11-03T21:22:30.737Z",
+ "id" : 9
+}
+```
## Edit an existing label
-Updates an existing label with new name or now color. At least one parameter
+Updates an existing label with new name or new color. At least one parameter
is required, to update the label.
+It returns 200 if the label was successfully deleted, 400 for wrong parameters
+and 404 if the label does not exist.
+In case of an error, an additional error message is returned.
+
```
PUT /projects/:id/labels
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the existing label |
+| `new_name` | string | yes if `color` if not provided | The new name of the label |
+| `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
+
+```bash
+curl -X PUT --data "name=documentation&new_name=docs&color=#8E44AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+```
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the existing label
-- `new_name` (optional) - The new name of the label
-- `color` (optional) - New color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)
+Example response:
-On success, this method returns 200 with the updated label.
-If required parameters are missing or parameters are invalid, 400 is returned.
-If the label to be updated is missing, 404 is returned.
-In case of an error, additionally an error message is returned.
+```json
+{
+ "color" : "#8E44AD",
+ "name" : "docs"
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 8bc0a67067a..85ed31320b9 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -60,7 +60,7 @@ Parameters:
Shows information about a single merge request.
```
-GET /projects/:id/merge_request/:merge_request_id
+GET /projects/:id/merge_requests/:merge_request_id
```
Parameters:
@@ -105,7 +105,7 @@ Parameters:
Get a list of merge request commits.
```
-GET /projects/:id/merge_request/:merge_request_id/commits
+GET /projects/:id/merge_requests/:merge_request_id/commits
```
Parameters:
@@ -142,7 +142,7 @@ Parameters:
Shows information about the merge request including its files and changes.
```
-GET /projects/:id/merge_request/:merge_request_id/changes
+GET /projects/:id/merge_requests/:merge_request_id/changes
```
Parameters:
@@ -264,7 +264,7 @@ If an error occurs, an error number and a message explaining the reason is retur
Updates an existing merge request. You can change the target branch, title, or even close the MR.
```
-PUT /projects/:id/merge_request/:merge_request_id
+PUT /projects/:id/merge_requests/:merge_request_id
```
Parameters:
@@ -323,7 +323,7 @@ If merge request is already merged or closed - you get 405 and error message 'Me
If you don't have permissions to accept this merge request - you'll get a 401
```
-PUT /projects/:id/merge_request/:merge_request_id/merge
+PUT /projects/:id/merge_requests/:merge_request_id/merge
```
Parameters:
@@ -373,7 +373,7 @@ If the merge request is already merged or closed - you get 405 and error message
In case the merge request is not set to be merged when the build succeeds, you'll also get a 406 error.
```
-PUT /projects/:id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds
+PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds
```
Parameters:
@@ -409,66 +409,6 @@ Parameters:
}
```
-## Post comment to MR
-
-Adds a comment to a merge request.
-
-```
-POST /projects/:id/merge_request/:merge_request_id/comments
-```
-
-Parameters:
-
-- `id` (required) - The ID of a project
-- `merge_request_id` (required) - ID of merge request
-- `note` (required) - Text of comment
-
-```json
-{
- "note": "text1"
-}
-```
-
-## Get the comments on a MR
-
-Gets all the comments associated with a merge request.
-
-```
-GET /projects/:id/merge_request/:merge_request_id/comments
-```
-
-Parameters:
-
-- `id` (required) - The ID of a project
-- `merge_request_id` (required) - ID of merge request
-
-```json
-[
- {
- "note": "this is the 1st comment on the 2merge merge request",
- "author": {
- "id": 11,
- "username": "admin",
- "email": "admin@example.com",
- "name": "Administrator",
- "state": "active",
- "created_at": "2014-03-06T08:17:35.000Z"
- }
- },
- {
- "note": "Status changed to closed",
- "author": {
- "id": 11,
- "username": "admin",
- "email": "admin@example.com",
- "name": "Administrator",
- "state": "active",
- "created_at": "2014-03-06T08:17:35.000Z"
- }
- }
-]
-```
-
## Comments on merge requets
-Comments are done via the notes resource.
+Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 7b3238441f6..42d9ce3d391 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -1,13 +1,29 @@
# Namespaces
+Usernames and groupnames fall under a special category called namespaces.
+
+For users and groups supported API calls see the [users](users.md) and
+[groups](groups.md) documentation respectively.
+
+[Pagination](README.md#pagination) is used.
+
## List namespaces
-Get a list of namespaces. (As user: my namespaces, as admin: all namespaces)
+Get a list of the namespaces of the authenticated user. If the user is an
+administrator, a list of all namespaces in the GitLab instance is shown.
```
GET /namespaces
```
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces
+```
+
+Example response:
+
```json
[
{
@@ -23,22 +39,32 @@ GET /namespaces
]
```
-You can search for namespaces by name or path, see below.
-
## Search for namespace
-Get all namespaces that match your string in their name or path.
+Get all namespaces that match a string in their name or path.
```
GET /namespaces?search=foobar
```
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `search` | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
+
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces?search=twitter
+```
+
+Example response:
+
```json
[
{
- "id": 1,
- "path": "user1",
- "kind": "user"
+ "id": 4,
+ "path": "twitter",
+ "kind": "group"
}
]
```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 96867c67915..001de76c7af 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -1,67 +1,77 @@
# Application settings
-This API allows you to read and modify GitLab instance application settings.
+These API calls allow you to read and modify GitLab instance application
+settings as appear in `/admin/application_settings`. You have to be an
+administrator in order to perform this action.
+## Get current application settings
-## Get current application settings:
+List the current application settings of the GitLab instance.
```
GET /application/settings
```
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings
+```
+
+Example response:
+
```json
{
- "id": 1,
- "default_projects_limit": 10,
- "signup_enabled": true,
- "signin_enabled": true,
- "gravatar_enabled": true,
- "sign_in_text": "",
- "created_at": "2015-06-12T15:51:55.432Z",
- "updated_at": "2015-06-30T13:22:42.210Z",
- "home_page_url": "",
- "default_branch_protection": 2,
- "twitter_sharing_enabled": true,
- "restricted_visibility_levels": [],
- "max_attachment_size": 10,
- "session_expire_delay": 10080,
- "default_project_visibility": 0,
- "default_snippet_visibility": 0,
- "restricted_signup_domains": [],
- "user_oauth_applications": true,
- "after_sign_out_path": ""
+ "default_projects_limit" : 10,
+ "signup_enabled" : true,
+ "id" : 1,
+ "default_branch_protection" : 2,
+ "restricted_visibility_levels" : [],
+ "signin_enabled" : true,
+ "twitter_sharing_enabled" : true,
+ "after_sign_out_path" : null,
+ "max_attachment_size" : 10,
+ "user_oauth_applications" : true,
+ "updated_at" : "2016-01-04T15:44:55.176Z",
+ "session_expire_delay" : 10080,
+ "home_page_url" : null,
+ "default_snippet_visibility" : 0,
+ "restricted_signup_domains" : [],
+ "created_at" : "2016-01-04T15:44:55.176Z",
+ "default_project_visibility" : 0,
+ "gravatar_enabled" : true,
+ "sign_in_text" : null
}
```
-## Change application settings:
-
-
+## Change application settings
```
PUT /application/settings
```
-Parameters:
-
-- `default_projects_limit` - project limit per user
-- `signup_enabled` - enable registration
-- `signin_enabled` - enable login via GitLab account
-- `gravatar_enabled` - enable gravatar
-- `sign_in_text` - text on login page
-- `home_page_url` - redirect to this URL when not logged in
-- `default_branch_protection` - determine if developers can push to master
-- `twitter_sharing_enabled` - allow users to share project creation in twitter
-- `restricted_visibility_levels` - restrict certain visibility levels
-- `max_attachment_size` - limit attachment size
-- `session_expire_delay` - session lifetime
-- `default_project_visibility` - what visibility level new project receives
-- `default_snippet_visibility` - what visibility level new snippet receives
-- `restricted_signup_domains` - force people to use only corporate emails for signup
-- `user_oauth_applications` - allow users to create oauth applications
-- `after_sign_out_path` - where redirect user after logout
+| Attribute | Type | Required | Description |
+| --------- | ---- | :------: | ----------- |
+| `default_projects_limit` | integer | no | Project limit per user. Default is `10` |
+| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
+| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. |
+| `gravatar_enabled` | boolean | no | Enable Gravatar |
+| `sign_in_text` | string | no | Text on login page |
+| `home_page_url` | string | no | Redirect to this URL when not logged in |
+| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. |
+| `twitter_sharing_enabled` | boolean | no | Allow users to share project creation on Twitter |
+| `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. |
+| `max_attachment_size` | integer | no | Limit attachment size in MB |
+| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
+| `default_project_visibility` | integer | no | What visibility level new projects receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
+| `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
+| `restricted_signup_domains` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
+| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
+| `after_sign_out_path` | string | no | Where to redirect users after logout |
-All parameters are optional. You can send only one that you want to change.
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
+```
+Example response:
```json
{
@@ -79,7 +89,7 @@ All parameters are optional. You can send only one that you want to change.
"restricted_visibility_levels": [],
"max_attachment_size": 10,
"session_expire_delay": 10080,
- "default_project_visibility": 0,
+ "default_project_visibility": 1,
"default_snippet_visibility": 0,
"restricted_signup_domains": [],
"user_oauth_applications": true,
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index f9637d8a6c4..dc036d7e27f 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -1,40 +1,71 @@
# System hooks
-All methods require admin authorization.
+All methods require administrator authorization.
-The URL endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks).
+The URL endpoint of the system hooks can also be configured using the UI in
+the admin area under **Hooks** (`/admin/hooks`).
+
+Read more about [system hooks](../system_hooks/system_hooks.md).
## List system hooks
-Get list of system hooks
+Get a list of all system hooks.
+
+---
```
GET /hooks
```
-Parameters:
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks
+```
-- **none**
+Example response:
```json
[
- {
- "id": 3,
- "url": "http://example.com/hook",
- "created_at": "2013-10-02T10:15:31Z"
- }
+ {
+ "id" : 1,
+ "url" : "https://gitlab.example.com/hook",
+ "created_at" : "2015-11-04T20:07:35.874Z"
+ }
]
```
-## Add new system hook hook
+## Add new system hook
+
+Add a new system hook.
+
+---
```
POST /hooks
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `url` | string | yes | The hook URL |
+
+Example request:
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/hooks?url=https://gitlab.example.com/hook"
+```
+
+Example response:
-- `url` (required) - The hook URL
+```json
+[
+ {
+ "id" : 2,
+ "url" : "https://gitlab.example.com/hook",
+ "created_at" : "2015-11-04T20:07:35.874Z"
+ }
+]
+```
## Test system hook
@@ -42,29 +73,68 @@ Parameters:
GET /hooks/:id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the hook |
-- `id` (required) - The ID of hook
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+```
+
+Example response:
```json
{
- "event_name": "project_create",
- "name": "Ruby",
- "path": "ruby",
- "project_id": 1,
- "owner_name": "Someone",
- "owner_email": "example@gitlabhq.com"
+ "project_id" : 1,
+ "owner_email" : "example@gitlabhq.com",
+ "owner_name" : "Someone",
+ "name" : "Ruby",
+ "path" : "ruby",
+ "event_name" : "project_create"
}
```
## Delete system hook
-Deletes a system hook. This is an idempotent API function and returns `200 OK` even if the hook is not available. If the hook is deleted it is also returned as JSON.
+Deletes a system hook. This is an idempotent API function and returns `200 OK`
+even if the hook is not available.
+
+If the hook is deleted, a JSON object is returned. An error is raised if the
+hook is not found.
+
+---
```
DELETE /hooks/:id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the hook |
+
+Example request:
-- `id` (required) - The ID of hook
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+```
+
+Example response:
+
+```json
+{
+ "note_events" : false,
+ "project_id" : null,
+ "enable_ssl_verification" : true,
+ "url" : "https://gitlab.example.com/hook",
+ "updated_at" : "2015-11-04T20:12:15.931Z",
+ "issues_events" : false,
+ "merge_requests_events" : false,
+ "created_at" : "2015-11-04T20:12:15.931Z",
+ "service_id" : null,
+ "id" : 2,
+ "push_events" : true,
+ "tag_push_events" : false
+}
+```
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
index 61418cef2c6..71db5aa5dc8 100644
--- a/doc/ci/build_artifacts/README.md
+++ b/doc/ci/build_artifacts/README.md
@@ -11,6 +11,11 @@ Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
changed to `ZIP`, and it is now possible to browse its contents, with the added
ability of downloading the files separately.
+**Note:**
+The artifacts browser will be available only for new artifacts that are sent
+to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
+browse old artifacts already uploaded to GitLab.
+
## Enabling build artifacts
_If you are searching for ways to use artifacts, jump to
@@ -152,7 +157,7 @@ inside GitLab that make that possible.
1. While inside a specific build, you are presented with a download button
along with the one that browses the archive
-1. And finally, when browsing and archive you can see the download button at
+1. And finally, when browsing an archive you can see the download button at
the top right corner
---
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index e52e1547461..c1bb47e4291 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -56,12 +56,12 @@ gitlab-ci-multi-runner register \
--non-interactive \
--url "https://gitlab.com/ci/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
- --description "ruby-2.1" \
+ --description "ruby-2.2" \
--executor "docker" \
- --docker-image ruby:2.1 \
+ --docker-image ruby:2.2 \
--docker-postgres latest
```
-With the command above, you create a runner that uses [ruby:2.1](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
+With the command above, you create a runner that uses [ruby:2.2](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index c8d5ecb8eef..4d280297dbb 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -33,7 +33,7 @@ The YAML syntax allows for using more complex job specifications than in the
above example:
```yaml
-image: ruby:2.1
+image: ruby:2.2
services:
- postgres
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
index f9b48868182..05db30b4a7e 100644
--- a/doc/development/ci_setup.md
+++ b/doc/development/ci_setup.md
@@ -26,7 +26,7 @@ We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master
# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
- Language: Ruby
-- Ruby version: 2.1.2
+- Ruby version: 2.2.4
- database.yml: pg
Build commands
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 4772ed3c566..2cc2dbef41b 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -107,7 +107,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-_**Note:** The current supported Ruby versions are 2.1.x. Ruby 2.2 and 2.3 are
+_**Note:** The current supported Ruby versions are 2.1.x and 2.2.x. Ruby 2.3 is
currently not supported._
The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
@@ -123,9 +123,9 @@ Remove the old Ruby 1.8 if present:
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.7.tar.gz
- echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.1.7.tar.gz' | shasum -c - && tar xzf ruby-2.1.7.tar.gz
- cd ruby-2.1.7
+ curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz
+ echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.2.4.tar.gz' | shasum -c - && tar xzf ruby-2.2.4.tar.gz
+ cd ruby-2.2.4
./configure --disable-install-rdoc
make
sudo make install
@@ -355,7 +355,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout 0.5.4
+ sudo -u git -H git checkout 0.6.2
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index c0425f27ab1..006dae8ca9a 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -32,8 +32,7 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions
-GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2
-and 2.3.
+GitLab requires Ruby (MRI) 2.1.x or 2.2.x and currently does not work with version 2.3.
You will have to use the standard MRI implementation of Ruby.
We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 5edac746c7b..83116bc8370 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -5,10 +5,10 @@ trackers and external authentication.
See the documentation below for details on how to configure these services.
-- [Jira](jira.md) Integrate with the JIRA issue tracker
+- [Jira](../project_services/jira.md) Integrate with the JIRA issue tracker
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd and Azure
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [CAS](cas.md) Configure GitLab to sign in using CAS
- [Slack](slack.md) Integrate with the Slack chat service
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 3543a67dd49..a2d7e922aad 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -19,7 +19,7 @@ To enable an external issue tracker you must configure the appropriate **Service
Visit the links below for details:
- [Redmine](../project_services/redmine.md)
-- [Jira](jira.md)
+- [Jira](../project_services/jira.md)
### Service Template
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index bc1f1673086..77bb75cbfca 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -19,7 +19,7 @@ something else descriptive.
1. Enter the address of your GitLab installation at the bottom of the package
- ![Facebook Website URL](facebook_website_url.png)
+ ![Facebook Website URL](img/facebook_website_url.png)
1. Choose "Next"
@@ -29,7 +29,7 @@ something else descriptive.
1. Fill in a contact email for your app
- ![Facebook App Settings](facebook_app_settings.png)
+ ![Facebook App Settings](img/facebook_app_settings.png)
1. Choose "Save Changes"
@@ -45,7 +45,7 @@ something else descriptive.
1. You should now see an app key and app secret (see screenshot). Keep this page open as you continue configuration.
- ![Facebook API Keys](facebook_api_keys.png)
+ ![Facebook API Keys](img/facebook_api_keys.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/github.md b/doc/integration/github.md
index a789d2c814f..886784a27c9 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -22,7 +22,7 @@ GitHub will generate an application ID and secret key for you to use.
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration.
- ![GitHub app](github_app.png)
+ ![GitHub app](img/github_app.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 80e3c0142a0..b215cc7c609 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -28,7 +28,7 @@ GitLab.com will generate an application ID and secret key for you to use.
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration.
- ![GitLab app](gitlab_app.png)
+ ![GitLab app](img/gitlab_app.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/gmail_action_buttons_for_gitlab.md b/doc/integration/gmail_action_buttons_for_gitlab.md
index de45f25ad62..05a91d9bef9 100644
--- a/doc/integration/gmail_action_buttons_for_gitlab.md
+++ b/doc/integration/gmail_action_buttons_for_gitlab.md
@@ -4,7 +4,7 @@ GitLab supports [Google actions in email](https://developers.google.com/gmail/ma
If correctly setup, emails that require an action will be marked in Gmail.
-![gmail_actions_button.png](gmail_actions_button.png)
+![gmail_actions_button.png](img/gmail_action_buttons_for_gitlab.png)
To get this functioning, you need to be registered with Google.
[See how to register with Google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 91e9b2495cc..f9a20dd840d 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -25,7 +25,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
- Application type: "Web Application"
- Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here.
- Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
-1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](google_app.png)
+1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](img/google_app.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/facebook_api_keys.png b/doc/integration/img/facebook_api_keys.png
index d6c44ac0f11..d6c44ac0f11 100644
--- a/doc/integration/facebook_api_keys.png
+++ b/doc/integration/img/facebook_api_keys.png
Binary files differ
diff --git a/doc/integration/facebook_app_settings.png b/doc/integration/img/facebook_app_settings.png
index 30dd21e198a..30dd21e198a 100644
--- a/doc/integration/facebook_app_settings.png
+++ b/doc/integration/img/facebook_app_settings.png
Binary files differ
diff --git a/doc/integration/facebook_website_url.png b/doc/integration/img/facebook_website_url.png
index dc3088bb2fa..dc3088bb2fa 100644
--- a/doc/integration/facebook_website_url.png
+++ b/doc/integration/img/facebook_website_url.png
Binary files differ
diff --git a/doc/integration/github_app.png b/doc/integration/img/github_app.png
index d890345ced9..d890345ced9 100644
--- a/doc/integration/github_app.png
+++ b/doc/integration/img/github_app.png
Binary files differ
diff --git a/doc/integration/gitlab_app.png b/doc/integration/img/gitlab_app.png
index 3f9391a821b..3f9391a821b 100644
--- a/doc/integration/gitlab_app.png
+++ b/doc/integration/img/gitlab_app.png
Binary files differ
diff --git a/doc/integration/gmail_actions_button.png b/doc/integration/img/gmail_action_buttons_for_gitlab.png
index b08f54d137b..b08f54d137b 100644
--- a/doc/integration/gmail_actions_button.png
+++ b/doc/integration/img/gmail_action_buttons_for_gitlab.png
Binary files differ
diff --git a/doc/integration/google_app.png b/doc/integration/img/google_app.png
index 5a62ad35009..5a62ad35009 100644
--- a/doc/integration/google_app.png
+++ b/doc/integration/img/google_app.png
Binary files differ
diff --git a/doc/integration/oauth_provider/admin_application.png b/doc/integration/img/oauth_provider_admin_application.png
index a5f34512aa8..a5f34512aa8 100644
--- a/doc/integration/oauth_provider/admin_application.png
+++ b/doc/integration/img/oauth_provider_admin_application.png
Binary files differ
diff --git a/doc/integration/oauth_provider/application_form.png b/doc/integration/img/oauth_provider_application_form.png
index ae135db2627..ae135db2627 100644
--- a/doc/integration/oauth_provider/application_form.png
+++ b/doc/integration/img/oauth_provider_application_form.png
Binary files differ
diff --git a/doc/integration/oauth_provider/authorized_application.png b/doc/integration/img/oauth_provider_authorized_application.png
index d3ce05be9cc..d3ce05be9cc 100644
--- a/doc/integration/oauth_provider/authorized_application.png
+++ b/doc/integration/img/oauth_provider_authorized_application.png
Binary files differ
diff --git a/doc/integration/oauth_provider/user_wide_applications.png b/doc/integration/img/oauth_provider_user_wide_applications.png
index 719e1974068..719e1974068 100644
--- a/doc/integration/oauth_provider/user_wide_applications.png
+++ b/doc/integration/img/oauth_provider_user_wide_applications.png
Binary files differ
diff --git a/doc/integration/twitter_app_api_keys.png b/doc/integration/img/twitter_app_api_keys.png
index 1076337172a..1076337172a 100644
--- a/doc/integration/twitter_app_api_keys.png
+++ b/doc/integration/img/twitter_app_api_keys.png
Binary files differ
diff --git a/doc/integration/twitter_app_details.png b/doc/integration/img/twitter_app_details.png
index b95e8af8a74..b95e8af8a74 100644
--- a/doc/integration/twitter_app_details.png
+++ b/doc/integration/img/twitter_app_details.png
Binary files differ
diff --git a/doc/integration/jira-integration-points.png b/doc/integration/jira-integration-points.png
deleted file mode 100644
index 0692a7b458a..00000000000
--- a/doc/integration/jira-integration-points.png
+++ /dev/null
Binary files differ
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index de574d53410..78aa6634116 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -1,149 +1,3 @@
-# GitLab Jira integration
+# GitLab JIRA integration
-GitLab can be configured to interact with Jira. Configuration happens via
-username and password. Connecting to a Jira server via CAS is not possible.
-
-Each project can be configured to connect to a different Jira instance, see the
-[configuration](#configuration) section. If you have one Jira instance you can
-pre-fill the settings page with a default template. To configure the template
-see the [Services Templates][services-templates] document.
-
-Once the project is connected to Jira, you can reference and close the issues
-in Jira directly from GitLab.
-
-## Table of Contents
-
-* [Referencing Jira Issues from GitLab](#referencing-jira-issues)
-* [Closing Jira Issues from GitLab](#closing-jira-issues)
-* [Configuration](#configuration)
-
-### Referencing Jira Issues
-
-When GitLab project has Jira issue tracker configured and enabled, mentioning
-Jira issue in GitLab will automatically add a comment in Jira issue with the
-link back to GitLab. This means that in comments in merge requests and commits
-referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the
-format:
-
-```
- USER mentioned this issue in LINK_TO_THE_MENTION
-```
-
-* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
-* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
-Can be commit or merge request.
-
-![example of mentioning or closing the Jira issue](img/jira_issue_reference.png)
-
----
-
-### Closing Jira Issues
-
-Jira issues can be closed directly from GitLab by using trigger words, eg.
-`Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and
-merge requests. When a commit which contains the trigger word in the commit
-message is pushed, GitLab will add a comment in the mentioned Jira issue.
-
-For example, for project named `PROJECT` in Jira, we implemented a new feature
-and created a merge request in GitLab.
-
-This feature was requested in Jira issue `PROJECT-7`. Merge request in GitLab
-contains the improvement and in merge request description we say that this
-merge request `Closes PROJECT-7` issue.
-
-Once this merge request is merged, the Jira issue will be automatically closed
-with a link to the commit that resolved the issue.
-
-![A Git commit that causes the Jira issue to be closed](img/jira_merge_request_close.png)
-
----
-
-![The GitLab integration user leaves a comment on Jira](img/jira_service_close_issue.png)
-
----
-
-## Configuration
-
-### Configuring JIRA
-
-We need to create a user in JIRA which will have access to all projects that
-need to integrate with GitLab. Login to your JIRA instance as admin and under
-Administration go to User Management and create a new user.
-
-As an example, we'll create a user named `gitlab` and add it to `jira-developers`
-group.
-
-**It is important that the user `gitlab` has write-access to projects in JIRA**
-
-### Configuring GitLab
-
-JIRA configuration in GitLab is done via a project's **Services**.
-
-#### GitLab 7.8 and up with JIRA v6.x
-
-See next section.
-
-#### GitLab 7.8 and up
-
-_The currently supported JIRA versions are v6.x and v7.x._
-
-To enable JIRA integration in a project, navigate to the project's
-**Settings > Services > JIRA**.
-
-Fill in the required details on the page as described in the table below.
-
-| Field | Description |
-| ----- | ----------- |
-| `description` | A name for the issue tracker (to differentiate between instances, for instance). |
-| `project url` | The URL to the JIRA project which is being linked to this GitLab project. |
-| `issues url` | The URL to the JIRA project issues overview for the project that is linked to this GitLab project. |
-| `new issue url` | This is the URL to create a new issue in JIRA for the project linked to this GitLab project. |
-| `api url` | The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`. |
-| `username` | The username of the user created in [configuring JIRA step](#configuring-jira). |
-| `password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
-| `Jira issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). By default, this ID is `2` (in the example image, this is `2` as well) |
-
-After saving the configuration, your GitLab project will be able to interact
-with the linked JIRA project.
-
-![Jira service page](img/jira_service_page.png)
-
----
-
-#### GitLab 6.x-7.7 with JIRA v6.x
-
-_**Note:** GitLab versions 7.8 and up contain various integration improvements.
-We strongly recommend upgrading._
-
-In `gitlab.yml` enable the JIRA issue tracker section by
-[uncommenting these lines][jira-gitlab-yml]. This will make sure that all
-issues within GitLab are pointing to the JIRA issue tracker.
-
-After you set this, you will be able to close issues in JIRA by a commit in
-GitLab.
-
-Go to your project's **Settings** page and fill in the project name for the
-JIRA project:
-
-![Set the JIRA project name in GitLab to 'NEW'](img/jira_project_name.png)
-
----
-
-You can also enable the JIRA service that will allow you to interact with JIRA
-issues. Go to the **Settings > Services > JIRA** and:
-
-1. Tick the active check box to enable the service
-1. Supply the URL to JIRA server, for example http://jira.example.com
-1. Supply the username of a user we created under `Configuring JIRA` section,
- for example `gitlab`
-1. Supply the password of the user
-1. Optional: supply the JIRA API version, default is version `2`
-1. Optional: supply the JIRA issue transition ID (issue transition to closed).
- This is dependent on JIRA settings, default is `2`
-1. Hit save
-
-
-![Jira services page](img/jira_service.png)
-
-[services-templates]: ../project_services/services_templates.md
-[jira-gitlab-yml]: https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115
+This document was moved under [project_services/jira](../project_services/jira.md).
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index 192c321f712..dbe5a175c82 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -15,16 +15,16 @@ GitLab has two ways to add new OAuth2 application to an instance, you can add ap
### Adding application through profile
Go to your profile section 'Application' and press button 'New Application'
-![applications](oauth_provider/user_wide_applications.png)
+![applications](img/oauth_provider_user_wide_applications.png)
After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com.
-![application_form](oauth_provider/application_form.png)
+![application_form](img/oauth_provider_application_form.png)
### Authorized application
Every application you authorized will be shown in your "Authorized application" sections.
-![authorized_application](oauth_provider/authorized_application.png)
+![authorized_application](img/oauth_provider_authorized_application.png)
At any time you can revoke access just clicking button "Revoke"
@@ -32,4 +32,4 @@ At any time you can revoke access just clicking button "Revoke"
If you want to create application that does not belong to certain user you can create it from admin area
-![admin_application](oauth_provider/admin_application.png) \ No newline at end of file
+![admin_application](img/oauth_provider_admin_application.png)
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index e9e17eb4165..8e6627b2be5 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -9,6 +9,23 @@ Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
+## Supported Providers
+
+This is a list of the current supported OmniAuth providers. Before proceeding
+on each provider's documentation, make sure to first read this document as it
+contains some settings that are common for all providers.
+
+- [GitHub](github.md)
+- [Bitbucket](bitbucket.md)
+- [GitLab.com](gitlab.md)
+- [Google](google.md)
+- [Facebook](facebook.md)
+- [Twitter](twitter.md)
+- [Shibboleth](shibboleth.md)
+- [SAML](saml.md)
+- [Crowd](crowd.md)
+- [Azure](azure.md)
+
## Initial OmniAuth Configuration
Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider.
@@ -67,19 +84,6 @@ If you want to change these settings:
Now we can choose one or more of the Supported Providers below to continue configuration.
-## Supported Providers
-
-- [GitHub](github.md)
-- [Bitbucket](bitbucket.md)
-- [GitLab.com](gitlab.md)
-- [Google](google.md)
-- [Facebook](facebook.md)
-- [Twitter](twitter.md)
-- [Shibboleth](shibboleth.md)
-- [SAML](saml.md)
-- [Crowd](crowd.md)
-- [Azure](azure.md)
-
## Enable OmniAuth for an Existing User
Existing users can enable OmniAuth for specific providers after the account is created. For example, if the user originally signed in with LDAP an OmniAuth provider such as Twitter can be enabled. Follow the steps below to enable an OmniAuth provider for an existing user.
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 84f1d74c058..ecbe0d3e887 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -6,15 +6,17 @@ To enable Slack integration you must create an Incoming WebHooks integration on
1. [Sign in to Slack](https://slack.com/signin)
-1. Select **Configure Integrations** from the dropdown next to your team name.
+1. Select **Apps & Custom Integrations** from the dropdown next to your team name.
-1. Select the **All Services** tab
+1. Click the **Configure** link (right-upper corner).
-1. Click **Add** next to Incoming Webhooks
+1. Select the **Custom integrations** tab.
-1. Pick Incoming WebHooks
+1. Click the **Incoming WebHooks** row.
-1. Choose the channel name you want to send notifications to
+1. Click the **Add configuration** button.
+
+1. Choose the channel name you want to send notifications to.
1. Click **Add Incoming WebHooks Integration**
- Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index 52ed4a22339..4769f26b259 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -14,7 +14,7 @@ To enable the Twitter OmniAuth provider you must register your application with
- Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Developer Agreement".
- ![Twitter App Details](twitter_app_details.png)
+ ![Twitter App Details](img/twitter_app_details.png)
1. Select "Create your Twitter application."
1. Select the "Settings" tab.
@@ -27,7 +27,7 @@ To enable the Twitter OmniAuth provider you must register your application with
1. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
- ![Twitter app](twitter_app_api_keys.png)
+ ![Twitter app](img/twitter_app_api_keys.png)
1. On your GitLab server, open the configuration file.
@@ -76,4 +76,4 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Restart GitLab for the changes to take effect.
-On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. \ No newline at end of file
+On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index bc8e7d155e7..83c77742b19 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -88,6 +88,9 @@ GFM will autolink almost any URL you copy and paste into your text.
## Code and Syntax Highlighting
+_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the rouge website._
+
Blocks of code are either fenced by lines with three back-ticks <code>```</code>, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
```no-highlight
@@ -585,3 +588,5 @@ By including colons in the header row, you can align the text within that column
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
+
+[rouge]: http://rouge.jneen.net/ "Rouge website"
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index f17bbe8f2aa..073b8797508 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -12,6 +12,9 @@ The default is **Charcoal**.
## Syntax highlighting theme
+_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the rouge website._
+
Changing this setting allows the user to customize the theme used when viewing
syntax highlighted code on the site.
@@ -36,3 +39,5 @@ The default is **Your Projects**.
It allows user to choose what content he or she want to see on project page.
The default is **Readme**.
+
+[rouge]: http://rouge.jneen.net/ "Rouge website"
diff --git a/doc/project_services/img/jira_add_gitlab_commit_message.png b/doc/project_services/img/jira_add_gitlab_commit_message.png
new file mode 100644
index 00000000000..85e54861b3e
--- /dev/null
+++ b/doc/project_services/img/jira_add_gitlab_commit_message.png
Binary files differ
diff --git a/doc/project_services/img/jira_add_user_to_group.png b/doc/project_services/img/jira_add_user_to_group.png
new file mode 100644
index 00000000000..e4576433889
--- /dev/null
+++ b/doc/project_services/img/jira_add_user_to_group.png
Binary files differ
diff --git a/doc/project_services/img/jira_create_new_group.png b/doc/project_services/img/jira_create_new_group.png
new file mode 100644
index 00000000000..edaa1326058
--- /dev/null
+++ b/doc/project_services/img/jira_create_new_group.png
Binary files differ
diff --git a/doc/project_services/img/jira_create_new_group_name.png b/doc/project_services/img/jira_create_new_group_name.png
new file mode 100644
index 00000000000..9e518ad7843
--- /dev/null
+++ b/doc/project_services/img/jira_create_new_group_name.png
Binary files differ
diff --git a/doc/project_services/img/jira_create_new_user.png b/doc/project_services/img/jira_create_new_user.png
new file mode 100644
index 00000000000..57e433dd818
--- /dev/null
+++ b/doc/project_services/img/jira_create_new_user.png
Binary files differ
diff --git a/doc/project_services/img/jira_group_access.png b/doc/project_services/img/jira_group_access.png
new file mode 100644
index 00000000000..47716ca6d0e
--- /dev/null
+++ b/doc/project_services/img/jira_group_access.png
Binary files differ
diff --git a/doc/project_services/img/jira_issue_closed.png b/doc/project_services/img/jira_issue_closed.png
new file mode 100644
index 00000000000..cabec1ae137
--- /dev/null
+++ b/doc/project_services/img/jira_issue_closed.png
Binary files differ
diff --git a/doc/integration/img/jira_issue_reference.png b/doc/project_services/img/jira_issue_reference.png
index 15739a22dc7..15739a22dc7 100644
--- a/doc/integration/img/jira_issue_reference.png
+++ b/doc/project_services/img/jira_issue_reference.png
Binary files differ
diff --git a/doc/project_services/img/jira_issues_workflow.png b/doc/project_services/img/jira_issues_workflow.png
new file mode 100644
index 00000000000..28e17be3a84
--- /dev/null
+++ b/doc/project_services/img/jira_issues_workflow.png
Binary files differ
diff --git a/doc/integration/img/jira_merge_request_close.png b/doc/project_services/img/jira_merge_request_close.png
index 1e78daf105f..1e78daf105f 100644
--- a/doc/integration/img/jira_merge_request_close.png
+++ b/doc/project_services/img/jira_merge_request_close.png
Binary files differ
diff --git a/doc/integration/img/jira_project_name.png b/doc/project_services/img/jira_project_name.png
index 5986fdb63fb..5986fdb63fb 100644
--- a/doc/integration/img/jira_project_name.png
+++ b/doc/project_services/img/jira_project_name.png
Binary files differ
diff --git a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png
new file mode 100644
index 00000000000..0149181dc86
--- /dev/null
+++ b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png
Binary files differ
diff --git a/doc/integration/img/jira_service.png b/doc/project_services/img/jira_service.png
index 1f6628c4371..1f6628c4371 100644
--- a/doc/integration/img/jira_service.png
+++ b/doc/project_services/img/jira_service.png
Binary files differ
diff --git a/doc/integration/img/jira_service_close_issue.png b/doc/project_services/img/jira_service_close_issue.png
index 67dfc6144c4..67dfc6144c4 100644
--- a/doc/integration/img/jira_service_close_issue.png
+++ b/doc/project_services/img/jira_service_close_issue.png
Binary files differ
diff --git a/doc/integration/img/jira_service_page.png b/doc/project_services/img/jira_service_page.png
index 2b37eda3520..2b37eda3520 100644
--- a/doc/integration/img/jira_service_page.png
+++ b/doc/project_services/img/jira_service_page.png
Binary files differ
diff --git a/doc/project_services/img/jira_submit_gitlab_merge_request.png b/doc/project_services/img/jira_submit_gitlab_merge_request.png
new file mode 100644
index 00000000000..e935d9362aa
--- /dev/null
+++ b/doc/project_services/img/jira_submit_gitlab_merge_request.png
Binary files differ
diff --git a/doc/project_services/img/jira_user_management_link.png b/doc/project_services/img/jira_user_management_link.png
new file mode 100644
index 00000000000..2745916972c
--- /dev/null
+++ b/doc/project_services/img/jira_user_management_link.png
Binary files differ
diff --git a/doc/integration/img/jira_workflow_screenshot.png b/doc/project_services/img/jira_workflow_screenshot.png
index 8635a32eb68..8635a32eb68 100644
--- a/doc/integration/img/jira_workflow_screenshot.png
+++ b/doc/project_services/img/jira_workflow_screenshot.png
Binary files differ
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
new file mode 100644
index 00000000000..d6b2e7f521b
--- /dev/null
+++ b/doc/project_services/jira.md
@@ -0,0 +1,212 @@
+# GitLab JIRA integration
+
+GitLab can be configured to interact with [JIRA Core] either using an
+on-premises instance or the SaaS solution that Atlassian offers. Configuration
+happens via username and password on a per-project basis. Connecting to a JIRA
+server via CAS is not possible.
+
+Each project can be configured to connect to a different JIRA instance or, in
+case you have a single JIRA instance, you can pre-fill the JIRA service
+settings page in GitLab with a default template. To configure the JIRA template,
+see the [Services Templates documentation][services-templates].
+
+Once the GitLab project is connected to JIRA, you can reference and close the
+issues in JIRA directly from GitLab's merge requests.
+
+## Configuration
+
+The configuration consists of two parts:
+
+- [JIRA configuration](#configuring-jira)
+- [GitLab configuration](#configuring-gitlab)
+
+### Configuring JIRA
+
+First things first, we need to create a user in JIRA which will have access to
+all projects that need to integrate with GitLab.
+
+We have split this stage in steps so it is easier to follow.
+
+---
+
+1. Login to your JIRA instance as an administrator and under **Administration**
+ go to **User Management** to create a new user.
+
+ ![JIRA user management link](img/jira_user_management_link.png)
+
+ ---
+
+1. The next step is to create a new user (e.g., `gitlab`) who has write access
+ to projects in JIRA. Enter the user's name and a _valid_ e-mail address
+ since JIRA sends a verification e-mail to set-up the password.
+ _**Note:** JIRA creates the username automatically by using the e-mail
+ prefix. You can change it later if you want._
+
+ ![JIRA create new user](img/jira_create_new_user.png)
+
+ ---
+
+1. Now, let's create a `gitlab-developers` group which will have write access
+ to projects in JIRA. Go to the **Groups** tab and select **Create group**.
+
+ ![JIRA create new user](img/jira_create_new_group.png)
+
+ ---
+
+ Give it an optional description and hit **Create group**.
+
+ ![JIRA create new group](img/jira_create_new_group_name.png)
+
+ ---
+
+1. Give the newly-created group write access by going to
+ **Application access > View configuration** and adding the `gitlab-developers`
+ group to JIRA Core.
+
+ ![JIRA group access](img/jira_group_access.png)
+
+ ---
+
+1. Add the `gitlab` user to the `gitlab-developers` group by going to
+ **Users > GitLab user > Add group** and selecting the `gitlab-developers`
+ group from the dropdown menu. Notice that the group says _Access_ which is
+ what we aim for.
+
+ ![JIRA add user to group](img/jira_add_user_to_group.png)
+
+---
+
+The JIRA configuration is over. Write down the new JIRA username and its
+password as they will be needed when configuring GitLab in the next section.
+
+### Configuring GitLab
+
+_**Note:** The currently supported JIRA versions are v6.x and v7.x. and GitLab
+7.8 or higher is required._
+
+---
+
+Assuming you [have already configured JIRA](#configuring-jira), now it's time
+to configure GitLab.
+
+JIRA configuration in GitLab is done via a project's
+[**Services**](../project_services/project_services.md).
+
+To enable JIRA integration in a project, navigate to the project's
+**Settings > Services > JIRA**.
+
+Fill in the required details on the page, as described in the table below.
+
+| Setting | Description |
+| ------- | ----------- |
+| `Description` | A name for the issue tracker (to differentiate between instances, for example). |
+| `Project url` | The URL to the JIRA project which is being linked to this GitLab project. It is of the form: `https://<jira_host_url>/issues/?jql=project=<jira_project>`. |
+| `Issues url` | The URL to the JIRA project issues overview for the project that is linked to this GitLab project. It is of the form: `https://<jira_host_url>/browse/:id`. Leave `:id` as-is, it gets replaced by GitLab at runtime. |
+| `New issue url` | This is the URL to create a new issue in JIRA for the project linked to this GitLab project, and it is of the form: `https://<jira_host_url>/secure/CreateIssue.jspa` |
+| `Api url` | The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`. It is of the form: `https://<jira_host_url>/rest/api/2`. |
+| `Username` | The username of the user created in [configuring JIRA step](#configuring-jira). |
+| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
+| `JIRA issue transition` | This setting is very important to set up correctly. It is the ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot](img/jira_issues_workflow.png)). By default, this ID is set to `2` |
+
+After saving the configuration, your GitLab project will be able to interact
+with the linked JIRA project.
+
+![JIRA service page](img/jira_service_page.png)
+
+---
+
+## JIRA issues
+
+By now you should have [configured JIRA](#configuring-jira) and enabled the
+[JIRA service in GitLab](#configuring-gitlab). If everything is set up correctly
+you should be able to reference and close JIRA issues by just mentioning their
+ID in GitLab commits and merge requests.
+
+### Referencing JIRA Issues
+
+If you reference a JIRA issue, e.g., `GITLAB-1`, in a commit comment, a link
+which points back to JIRA is created.
+
+The same works for comments in merge requests as well.
+
+![JIRA add GitLab commit message](img/jira_add_gitlab_commit_message.png)
+
+---
+
+The mentioning action is two-fold, so a comment with a JIRA issue in GitLab
+will automatically add a comment in that particular JIRA issue with the link
+back to GitLab.
+
+
+![JIRA reference commit message](img/jira_reference_commit_message_in_jira_issue.png)
+
+---
+
+The comment on the JIRA issue is of the form:
+
+> USER mentioned this issue in LINK_TO_THE_MENTION
+
+Where:
+
+| Format | Description |
+| ------ | ----------- |
+| `USER` | A user that mentioned the issue. This is the link to the user profile in GitLab. |
+| `LINK_TO_THE_MENTION` | Link to the origin of mention with a name of the entity where JIRA issue was mentioned. Can be commit or merge request. |
+
+### Closing JIRA issues
+
+JIRA issues can be closed directly from GitLab by using trigger words in
+commits and merge requests. When a commit which contains the trigger word
+followed by the JIRA issue ID in the commit message is pushed, GitLab will
+add a comment in the mentioned JIRA issue and immediately close it (provided
+the transition ID was set up correctly).
+
+There are currently three trigger words, and you can use either one to achieve
+the same goal:
+
+- `Resolves GITLAB-1`
+- `Closes GITLAB-1`
+- `Fixes GITLAB-1`
+
+where `GITLAB-1` the issue ID of the JIRA project.
+
+### JIRA issue closing example
+
+Let's say for example that we submitted a bug fix and created a merge request
+in GitLab. The workflow would be something like this:
+
+1. Create a new branch
+1. Fix the bug
+1. Commit the changes and push branch to GitLab
+1. Open a new merge request and reference the JIRA issue including one of the
+ trigger words, e.g.: `Fixes GITLAB-1`, in the description
+1. Submit the merge request
+1. Ask someone to review
+1. Merge the merge request
+1. The JIRA issue is automatically closed
+
+---
+
+In the following screenshot you can see what the link references to the JIRA
+issue look like.
+
+![JIRA - submit a GitLab merge request](img/jira_submit_gitlab_merge_request.png)
+
+---
+
+Once this merge request is merged, the JIRA issue will be automatically closed
+with a link to the commit that resolved the issue.
+
+![The GitLab integration user leaves a comment on JIRA](img/jira_issue_closed.png)
+
+---
+
+You can see from the above image that there are four references to GitLab:
+
+- The first is from a comment in a specific commit
+- The second is from the JIRA issue reference in the merge request description
+- The third is from the actual commit that solved the issue
+- And the fourth is from the commit that the merge request created
+
+[services-templates]: ../project_services/services_templates.md "Services templates documentation"
+[JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website"
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index e3403127723..55db3e4f2f3 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -22,7 +22,7 @@ further configuration instructions and details. Contributions are welcome.
| Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
| [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
-| JIRA | Jira issue tracker |
+| [JIRA](jira.md) | JIRA issue tracker |
| JetBrains TeamCity CI | A continuous integration and build server |
| PivotalTracker | Project Management Software (Source Commits Endpoint) |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md
index 164cbcb53ce..616b1f58b65 100644
--- a/doc/update/8.3-to-8.4.md
+++ b/doc/update/8.3-to-8.4.md
@@ -48,7 +48,7 @@ which should already be on your system from GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
-sudo -u git -H git checkout 0.6.1
+sudo -u git -H git checkout 0.6.2
sudo -u git -H make
```
diff --git a/doc/workflow/forking/fork_button.png b/doc/workflow/forking/fork_button.png
deleted file mode 100644
index def4266476a..00000000000
--- a/doc/workflow/forking/fork_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/forking/groups.png b/doc/workflow/forking/groups.png
deleted file mode 100644
index 3ac64b3c8e7..00000000000
--- a/doc/workflow/forking/groups.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
index 8edf7c6ab3d..217a4a4012f 100644
--- a/doc/workflow/forking_workflow.md
+++ b/doc/workflow/forking_workflow.md
@@ -1,36 +1,59 @@
# Project forking workflow
-Forking a project to your own namespace is useful if you have no write access to the project you want to contribute
-to. If you do have write access or can request it we recommend working together in the same repository since it is simpler.
-See our **[GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)** article for more information about using
-branches to work together.
+Forking a project to your own namespace is useful if you have no write
+access to the project you want to contribute to. If you do have write
+access or can request it, we recommend working together in the same
+repository since it is simpler. See our [GitLab Flow](gitlab_flow.md)
+document more information about using branches to work together.
## Creating a fork
-In order to create a fork of a project, all you need to do is click on the fork button located on the top right side
-of the screen, close to the project's URL and right next to the stars button.
+Forking a project is in most cases a two-step process.
-![Fork button](forking/fork_button.png)
-Once you do that you'll be presented with a screen where you can choose the namespace to fork to. Only namespaces
-(groups and your own namespace) where you have write access to, will be shown. Click on the namespace to create your
-fork there.
+1. Click on the fork button located in the middle of the page or a project's
+ home page right next to the stars button.
-![Groups view](forking/groups.png)
+ ![Fork button](img/forking_workflow_fork_button.png)
-After the forking is done, you can start working on the newly created repository. There you will have full
-[Owner](../permissions/permissions.md) access, so you can set it up as you please.
+ ---
+
+1. Once you do that, you'll be presented with a screen where you can choose
+ the namespace to fork to. Only namespaces (groups and your own
+ namespace) where you have write access to, will be shown. Click on the
+ namespace to create your fork there.
+
+ ![Choose namespace](img/forking_workflow_choose_namespace.png)
+
+ ---
+
+ **Note:**
+ If the namespace you chose to fork the project to has another project with
+ the same path name, you will be presented with a warning that the forking
+ could not be completed. Try to resolve the error and repeat the forking
+ process.
+
+ ![Path taken error](img/forking_workflow_path_taken_error.png)
+
+ ---
+
+After the forking is done, you can start working on the newly created
+repository. There, you will have full [Owner](../permissions/permissions.md)
+access, so you can set it up as you please.
## Merging upstream
-Once you are ready to send your code back to the main project, you need to create a merge request. Choose your forked
-project's main branch as the source and the original project's main branch as the destination and create the merge request.
+Once you are ready to send your code back to the main project, you need
+to create a merge request. Choose your forked project's main branch as
+the source and the original project's main branch as the destination and
+create the [merge request](merge_requests.md).
![Selecting branches](forking/branch_select.png)
-You can then assign the merge request to someone to have them review your changes. Upon pressing the 'Accept Merge Request'
-button, your changes will be added to the repository and branch you're merging into.
+You can then assign the merge request to someone to have them review
+your changes. Upon pressing the 'Accept Merge Request' button, your
+changes will be added to the repository and branch you're merging into.
![New merge request](forking/merge_request.png)
-
+[gitlab flow]: https://about.gitlab.com/2014/09/29/gitlab-flow/ "GitLab Flow blog post"
diff --git a/doc/workflow/img/forking_workflow_choose_namespace.png b/doc/workflow/img/forking_workflow_choose_namespace.png
new file mode 100644
index 00000000000..eefe5769554
--- /dev/null
+++ b/doc/workflow/img/forking_workflow_choose_namespace.png
Binary files differ
diff --git a/doc/workflow/img/forking_workflow_fork_button.png b/doc/workflow/img/forking_workflow_fork_button.png
new file mode 100644
index 00000000000..49e68d33e89
--- /dev/null
+++ b/doc/workflow/img/forking_workflow_fork_button.png
Binary files differ
diff --git a/doc/workflow/img/forking_workflow_path_taken_error.png b/doc/workflow/img/forking_workflow_path_taken_error.png
new file mode 100644
index 00000000000..7a3139506fe
--- /dev/null
+++ b/doc/workflow/img/forking_workflow_path_taken_error.png
Binary files differ
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index b667b587c5b..c3b3577c449 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -41,3 +41,33 @@ Feature: Dashboard
And user with name "John Doe" left project "Shop"
When I visit dashboard activity page
Then I should see "John Doe left project Shop" event
+
+ @javascript
+ Scenario: Sorting Issues
+ Given I visit dashboard issues page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard activity page
+ And I visit dashboard issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Project's issues after sorting
+ Given I visit dashboard issues page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Sorting Merge Requests
+ Given I visit dashboard merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard activity page
+ And I visit dashboard merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Project's merge requests after sorting
+ Given I visit dashboard merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" merge requests page
+ Then The list should be sorted by "Oldest updated"
diff --git a/features/groups.feature b/features/groups.feature
index c803e952980..55fffb012ae 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -3,6 +3,10 @@ Feature: Groups
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
+ Scenario: I should not see a group if it does not exist
+ When I visit group "NonExistentGroup" page
+ Then page status code should be 404
+
Scenario: I should have back to group button
When I visit group "Owned" page
Then I should see back to dashboard button
diff --git a/features/project/builds/artifacts.feature b/features/project/builds/artifacts.feature
index 1185854453a..52dc15f2eb6 100644
--- a/features/project/builds/artifacts.feature
+++ b/features/project/builds/artifacts.feature
@@ -51,3 +51,12 @@ Feature: Project Builds Artifacts
And I click artifacts browse button
And I click a link to file within build artifacts
Then download of a file extracted from build artifacts should start
+
+ @javascript
+ Scenario: I click on a row in an artifacts table
+ Given recent build has artifacts available
+ And recent build has artifacts metadata available
+ When I visit recent build details page
+ And I click artifacts browse button
+ And I click a first row within build artifacts table
+ Then page with a coresponding path is loading
diff --git a/features/project/fork.feature b/features/project/fork.feature
index 37cd53ee977..12695204e47 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -25,3 +25,18 @@ Feature: Project Fork
Then I should see "New merge request"
And I click link "New merge request"
Then I should see the new merge request page for my namespace
+
+ Scenario: Viewing forks of a Project
+ Given I click link "Fork"
+ When I fork to my namespace
+ And I visit the forks page of the "Shop" project
+ Then I should see my fork on the list
+
+ Scenario: Viewing private forks of a Project
+ Given There is an existent fork of the "Shop" project
+ And I click link "Fork"
+ When I fork to my namespace
+ And I visit the forks page of the "Shop" project
+ Then I should see my fork on the list
+ And I should not see the other fork listed
+ And I should see a private fork notice
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 1502b0952cd..0b3d03aa2a5 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -60,6 +60,28 @@ Feature: Project Issues
Then I should see "Release 0.4" at the top
@javascript
+ Scenario: Visiting Issues after being sorted the list
+ Given I visit project "Shop" issues page
+ And I sort the list by "Oldest updated"
+ And I visit my project's home page
+ And I visit project "Shop" issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Merge Requests after being sorted the list
+ Given I visit project "Shop" issues page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Merge Requests from a differente Project after sorting
+ Given I visit project "Shop" merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
Scenario: I search issue
Given I fill in issue search with "Re"
Then I should see "Release 0.4" in issues
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index f1629a26f10..ca1ee6b3c2b 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -85,6 +85,28 @@ Feature: Project Merge Requests
Then I should see "Bug NS-04" at the top
@javascript
+ Scenario: Visiting Merge Requests after being sorted the list
+ Given I visit project "Shop" merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit my project's home page
+ And I visit project "Shop" merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Issues after being sorted the list
+ Given I visit project "Shop" merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Merge Requests from a differente Project after sorting
+ Given I visit project "Shop" merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
Scenario: Visiting Merge Requests after commenting on diffs
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
@@ -102,6 +124,15 @@ Feature: Project Merge Requests
And I leave a comment like "Line is wrong" on diff
And I switch to the merge request's comments tab
Then I should see a discussion has started on diff
+ And I should see a badge of "1" next to the discussion link
+
+ @javascript
+ Scenario: I see a new comment on merge request diff from another user in the discussion tab
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And user "John Doe" leaves a comment like "Line is wrong" on diff
+ Then I should see a discussion by user "John Doe" has started on diff
+ And I should see a badge of "1" next to the discussion link
@javascript
Scenario: I edit a comment on a merge request diff
@@ -119,9 +150,11 @@ Feature: Project Merge Requests
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
And I leave a comment like "Line is wrong" on diff
+ And I should see a badge of "1" next to the discussion link
And I delete the comment "Line is wrong" on diff
And I click on the Discussion tab
Then I should not see any discussion
+ And I should see a badge of "0" next to the discussion link
@javascript
Scenario: I comment on a line of a commit in merge request
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 63f0ec2b6e8..5062e348844 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -2,6 +2,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
+ include SharedIssuable
step 'I should see "New Project" link' do
expect(page).to have_link "New project"
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 4c5122d1b7d..1e2a78a6029 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -120,6 +120,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
end
+ step 'I visit group "NonExistentGroup" page' do
+ visit group_path(-1)
+ end
+
private
def assigned_to_me(key)
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 25f2f4e837c..1bdb57af9d1 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -73,4 +73,14 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
expect(response_json[:archive]).to end_with('build_artifacts.zip')
expect(response_json[:entry]).to eq Base64.encode64('ci_artifacts.txt')
end
+
+ step 'I click a first row within build artifacts table' do
+ row = first('tr[data-link]')
+ @row_path = row['data-link']
+ row.click
+ end
+
+ step 'page with a coresponding path is loading' do
+ expect(current_path).to eq @row_path
+ end
end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index e98bd51ca89..5810276ced3 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -49,4 +49,29 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see the new merge request page for my namespace' do
current_path.should have_content(/#{current_user.namespace.name}/i)
end
+
+ step 'I visit the forks page of the "Shop" project' do
+ @project = Project.where(name: 'Shop').last
+ visit namespace_project_forks_path(@project.namespace, @project)
+ end
+
+ step 'I should see my fork on the list' do
+ page.within('.projects-list-holder') do
+ project = @user.fork_of(@project)
+ expect(page).to have_content("#{project.namespace.human_name} / #{project.name}")
+ end
+ end
+
+ step 'There is an existent fork of the "Shop" project' do
+ user = create(:user, name: 'Mike')
+ @forked_project = Projects::ForkService.new(@project, user).execute
+ end
+
+ step 'I should not see the other fork listed' do
+ expect(page).not_to have_content("#{@forked_project.namespace.human_name} / #{@forked_project.name}")
+ end
+
+ step 'I should see a private fork notice' do
+ expect(page).to have_content("1 private fork")
+ end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index cbdce78dc0c..7e4425ff662 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -43,7 +43,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
expect(page).to have_css("h3.page-title", text: "New Merge Request")
- fill_in "merge_request_title", with: "Merge Request On Forked Project"
+ page.within 'form#new_merge_request' do
+ fill_in "merge_request_title", with: "Merge Request On Forked Project"
+ end
end
step 'I submit the merge request' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 8af635689e0..337893e6209 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -181,6 +181,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
leave_comment "Line is wrong"
end
+ step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do
+ mr = MergeRequest.find_by(title: "Bug NS-05")
+ create(:note_on_merge_request_diff, project: project,
+ noteable_id: mr.id,
+ author: user_exists("John Doe"),
+ line_code: sample_commit.line_code,
+ note: 'Line is wrong')
+ end
+
step 'I leave a comment like "Line is wrong" on diff in commit' do
click_diff_line(sample_commit.line_code)
leave_comment "Line is wrong"
@@ -238,6 +247,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
+ step 'I should see a discussion by user "John Doe" has started on diff' do
+ page.within(".notes .discussion") do
+ page.should have_content "#{user_exists("John Doe").name} started a discussion"
+ page.should have_content sample_commit.line_code_path
+ page.should have_content "Line is wrong"
+ end
+ end
+
+ step 'I should see a badge of "1" next to the discussion link' do
+ expect_discussion_badge_to_have_counter("1")
+ end
+
+ step 'I should see a badge of "0" next to the discussion link' do
+ expect_discussion_badge_to_have_counter("0")
+ end
+
step 'I should see a discussion has started on commit diff' do
page.within(".notes .discussion") do
page.should have_content "#{current_user.name} started a discussion on commit"
@@ -452,4 +477,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
def have_visible_content (text)
have_css("*", text: text, visible: true)
end
+
+ def expect_discussion_badge_to_have_counter(value)
+ page.within(".notes-tab .badge") do
+ page.should have_content value
+ end
+ end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index d753ae14590..2a735afbe7b 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -163,7 +163,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I search for Wiki content' do
- fill_in "Search in this project", with: "wiki_content"
+ fill_in "Search", with: "wiki_content"
click_button "Search"
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index c6a0ae2ba38..06e69441894 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -23,7 +23,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[rel$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click")
sleep 0.05
@@ -33,7 +33,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
click_parallel_diff_line(sample_commit.line_code, 'old')
- page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Old comment"
find(".js-comment-button").trigger("click")
end
@@ -41,7 +41,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
click_parallel_diff_line(sample_commit.line_code, 'new')
- page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "New comment"
find(".js-comment-button").trigger("click")
end
@@ -51,7 +51,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[rel$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:"
find('.js-md-preview-button').click
end
@@ -62,7 +62,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.del_line_code)
- page.within("form[rel$='#{sample_commit.del_line_code}']") do
+ page.within("form[id$='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up"
find('.js-md-preview-button').click
end
@@ -91,7 +91,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[rel$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}']") do
fill_in 'note[note]', with: ':smile:'
click_button('Add Comment')
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 4c5f7488efb..25c2b476f43 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -106,6 +106,19 @@ module SharedIssuable
edit_issuable
end
+ step 'I sort the list by "Oldest updated"' do
+ find('button.dropdown-toggle.btn').click
+ page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+ click_link "Oldest updated"
+ end
+ end
+
+ step 'The list should be sorted by "Oldest updated"' do
+ page.within('div.dropdown.inline.prepend-left-10') do
+ expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
+ end
+ end
+
def create_issuable_for_project(project_name:, title:, type: :issue)
project = Project.find_by(name: project_name)
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 4156c7ec484..38069ff8835 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -9,10 +9,6 @@ Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout)
end
-Spinach.hooks.on_tag("javascript") do
- Capybara.current_driver = Capybara.javascript_driver
-end
-
Capybara.default_wait_time = timeout
Capybara.ignore_hidden_elements = false
@@ -22,3 +18,7 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
end
+
+Spinach.hooks.before_run do
+ TestEnv.warm_asset_cache
+end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3f528b9f7c0..9dacf7c1e86 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -153,10 +153,11 @@ module API
end
def attributes_for_keys(keys, custom_params = nil)
+ params_hash = custom_params || params
attrs = {}
keys.each do |key|
- if params[key].present? or (params.has_key?(key) and params[key] == false)
- attrs[key] = params[key]
+ if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false)
+ attrs[key] = params_hash[key]
end
end
ActionController::Parameters.new(attrs).permit!
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 5c97fe1c88c..dd7f24f3279 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -59,55 +59,6 @@ module API
present paginate(merge_requests), with: Entities::MergeRequest
end
- # Show MR
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of MR
- #
- # Example:
- # GET /projects/:id/merge_request/:merge_request_id
- #
- get ":id/merge_request/:merge_request_id" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
-
- authorize! :read_merge_request, merge_request
-
- present merge_request, with: Entities::MergeRequest
- end
-
- # Show MR commits
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of MR
- #
- # Example:
- # GET /projects/:id/merge_request/:merge_request_id/commits
- #
- get ':id/merge_request/:merge_request_id/commits' do
- merge_request = user_project.merge_requests.
- find(params[:merge_request_id])
- authorize! :read_merge_request, merge_request
- present merge_request.commits, with: Entities::RepoCommit
- end
-
- # Show MR changes
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of MR
- #
- # Example:
- # GET /projects/:id/merge_request/:merge_request_id/changes
- #
- get ':id/merge_request/:merge_request_id/changes' do
- merge_request = user_project.merge_requests.
- find(params[:merge_request_id])
- authorize! :read_merge_request, merge_request
- present merge_request, with: Entities::MergeRequestChanges
- end
-
# Create MR
#
# Parameters:
@@ -148,146 +99,206 @@ module API
end
end
- # Update MR
+ # Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0
+ # Use "merge_requests/:merge_request_id/..." instead.
#
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # target_branch - The target branch
- # assignee_id - Assignee user ID
- # title - Title of MR
- # state_event - Status of MR. (close|reopen|merge)
- # description - Description of MR
- # labels (optional) - Labels for a MR as a comma-separated list
- # Example:
- # PUT /projects/:id/merge_request/:merge_request_id
- #
- put ":id/merge_request/:merge_request_id" do
- attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
- authorize! :update_merge_request, merge_request
-
- # Ensure source_branch is not specified
- if params[:source_branch].present?
- render_api_error!('Source branch cannot be changed', 400)
- end
-
- # Validate label names in advance
- if (errors = validate_label_params(params)).any?
- render_api_error!({ labels: errors }, 400)
- end
-
- merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
-
- if merge_request.valid?
- # Find or create labels and attach to issue
- unless params[:labels].nil?
- merge_request.remove_labels
- merge_request.add_labels_by_names(params[:labels].split(","))
- end
+ [":id/merge_request/:merge_request_id", ":id/merge_requests/:merge_request_id"].each do |path|
+ # Show MR
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_requests/:merge_request_id
+ #
+ get path do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ authorize! :read_merge_request, merge_request
present merge_request, with: Entities::MergeRequest
- else
- handle_merge_request_errors! merge_request.errors
end
- end
-
- # Merge MR
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # merge_commit_message (optional) - Custom merge commit message
- # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible
- # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds
- # Example:
- # PUT /projects/:id/merge_request/:merge_request_id/merge
- #
- put ":id/merge_request/:merge_request_id/merge" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
-
- # Merge request can not be merged
- # because user dont have permissions to push into target branch
- unauthorized! unless merge_request.can_be_merged_by?(current_user)
- not_allowed! if !merge_request.open? || merge_request.work_in_progress?
- merge_request.check_if_can_be_merged
-
- render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged?
-
- merge_params = {
- commit_message: params[:merge_commit_message],
- should_remove_source_branch: params[:should_remove_source_branch]
- }
-
- if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active?
- ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
- execute(merge_request)
- else
- ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params).
- execute(merge_request)
+ # Show MR commits
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_requests/:merge_request_id/commits
+ #
+ get "#{path}/commits" do
+ merge_request = user_project.merge_requests.
+ find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
+ present merge_request.commits, with: Entities::RepoCommit
end
- present merge_request, with: Entities::MergeRequest
- end
+ # Show MR changes
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_requests/:merge_request_id/changes
+ #
+ get "#{path}/changes" do
+ merge_request = user_project.merge_requests.
+ find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
+ present merge_request, with: Entities::MergeRequestChanges
+ end
- # Cancel Merge if Merge When build succeeds is enabled
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- #
- post ":id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ # Update MR
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # target_branch - The target branch
+ # assignee_id - Assignee user ID
+ # title - Title of MR
+ # state_event - Status of MR. (close|reopen|merge)
+ # description - Description of MR
+ # labels (optional) - Labels for a MR as a comma-separated list
+ # Example:
+ # PUT /projects/:id/merge_requests/:merge_request_id
+ #
+ put path do
+ attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ authorize! :update_merge_request, merge_request
+
+ # Ensure source_branch is not specified
+ if params[:source_branch].present?
+ render_api_error!('Source branch cannot be changed', 400)
+ end
- unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+ # Validate label names in advance
+ if (errors = validate_label_params(params)).any?
+ render_api_error!({ labels: errors }, 400)
+ end
- ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request)
- end
+ merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
- # Get a merge request's comments
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # Examples:
- # GET /projects/:id/merge_request/:merge_request_id/comments
- #
- get ":id/merge_request/:merge_request_id/comments" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ if merge_request.valid?
+ # Find or create labels and attach to issue
+ unless params[:labels].nil?
+ merge_request.remove_labels
+ merge_request.add_labels_by_names(params[:labels].split(","))
+ end
- authorize! :read_merge_request, merge_request
+ present merge_request, with: Entities::MergeRequest
+ else
+ handle_merge_request_errors! merge_request.errors
+ end
+ end
- present paginate(merge_request.notes.fresh), with: Entities::MRNote
- end
+ # Merge MR
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # merge_commit_message (optional) - Custom merge commit message
+ # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible
+ # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds
+ # Example:
+ # PUT /projects/:id/merge_requests/:merge_request_id/merge
+ #
+ put "#{path}/merge" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ # Merge request can not be merged
+ # because user dont have permissions to push into target branch
+ unauthorized! unless merge_request.can_be_merged_by?(current_user)
+ not_allowed! if !merge_request.open? || merge_request.work_in_progress?
+
+ merge_request.check_if_can_be_merged
+
+ render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged?
+
+ merge_params = {
+ commit_message: params[:merge_commit_message],
+ should_remove_source_branch: params[:should_remove_source_branch]
+ }
+
+ if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active?
+ ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
+ execute(merge_request)
+ else
+ ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params).
+ execute(merge_request)
+ end
- # Post comment to merge request
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # note (required) - Text of comment
- # Examples:
- # POST /projects/:id/merge_request/:merge_request_id/comments
- #
- post ":id/merge_request/:merge_request_id/comments" do
- required_attributes! [:note]
+ present merge_request, with: Entities::MergeRequest
+ end
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ # Cancel Merge if Merge When build succeeds is enabled
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ #
+ post "#{path}/cancel_merge_when_build_succeeds" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
- authorize! :create_note, merge_request
+ unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
- opts = {
- note: params[:note],
- noteable_type: 'MergeRequest',
- noteable_id: merge_request.id
- }
+ ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request)
+ end
- note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+ # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0.
+ # Use GET "/projects/:id/merge_requests/:merge_request_id/notes" instead
+ #
+ # Get a merge request's comments
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # Examples:
+ # GET /projects/:id/merge_requests/:merge_request_id/comments
+ #
+ get "#{path}/comments" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ authorize! :read_merge_request, merge_request
+
+ present paginate(merge_request.notes.fresh), with: Entities::MRNote
+ end
- if note.save
- present note, with: Entities::MRNote
- else
- render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0.
+ # Use POST "/projects/:id/merge_requests/:merge_request_id/notes" instead
+ #
+ # Post comment to merge request
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # note (required) - Text of comment
+ # Examples:
+ # POST /projects/:id/merge_requests/:merge_request_id/comments
+ #
+ post "#{path}/comments" do
+ required_attributes! [:note]
+
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ authorize! :create_note, merge_request
+
+ opts = {
+ note: params[:note],
+ noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id
+ }
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+
+ if note.save
+ present note, with: Entities::MRNote
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
end
end
end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 3f49d492f2f..d1e11eedec3 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -43,6 +43,10 @@ module Banzai
# Allow span elements
whitelist[:elements].push('span')
+ # Allow abbr elements with title attribute
+ whitelist[:elements].push('abbr')
+ whitelist[:attributes]['abbr'] = %w(title)
+
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 5c347e432b4..4e85d2c3c74 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -25,7 +25,7 @@ module Ci
format :json
- helpers Helpers
+ helpers ::Ci::API::Helpers
helpers ::API::Helpers
helpers Gitlab::CurrentSettings
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 690bbf97a89..416b0b5f0b4 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -13,13 +13,13 @@ module Ci
post "register" do
authenticate_runner!
update_runner_last_contact
+ update_runner_info
required_attributes! [:token]
not_found! unless current_runner.active?
build = Ci::RegisterBuildService.new.execute(current_runner)
if build
- update_runner_info
present build, with: Entities::BuildDetails
else
not_found!
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 1c91204e98c..199d62d9b8a 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -34,10 +34,14 @@ module Ci
@runner ||= Runner.find_by_token(params[:token].to_s)
end
- def update_runner_info
+ def get_runner_version_from_params
return unless params["info"].present?
- info = attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"])
- current_runner.update(info)
+ attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"])
+ end
+
+ def update_runner_info
+ current_runner.assign_attributes(get_runner_version_from_params)
+ current_runner.save if current_runner.changed?
end
def max_artifacts_size
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index bfc14fe7a6b..192b1d18a51 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -47,6 +47,7 @@ module Ci
return forbidden! unless runner
if runner.id
+ runner.update(get_runner_version_from_params)
present runner, with: Entities::Runner
else
not_found!
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 2355b3c6ddc..3f483847efa 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -13,12 +13,36 @@ module Gitlab
end
def execute
- project_identifier = project.import_source
+ import_issues if has_issues?
- return true unless client.project(project_identifier)["has_issues"]
+ true
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error.new, e.message
+ ensure
+ Gitlab::BitbucketImport::KeyDeleter.new(project).execute
+ end
- #Issues && Comments
- issues = client.issues(project_identifier)
+ private
+
+ def gl_user_id(project, bitbucket_id)
+ if bitbucket_id
+ user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
+ (user && user.id) || project.creator_id
+ else
+ project.creator_id
+ end
+ end
+
+ def identifier
+ project.import_source
+ end
+
+ def has_issues?
+ client.project(identifier)["has_issues"]
+ end
+
+ def import_issues
+ issues = client.issues(identifier)
issues.each do |issue|
body = ''
@@ -33,7 +57,7 @@ module Gitlab
body = @formatter.author_line(author)
body += issue["content"]
- comments = client.issue_comments(project_identifier, issue["local_id"])
+ comments = client.issue_comments(identifier, issue["local_id"])
if comments.any?
body += @formatter.comments_header
@@ -56,20 +80,9 @@ module Gitlab
author_id: gl_user_id(project, reporter)
)
end
-
- true
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
end
-
- private
-
- def gl_user_id(project, bitbucket_id)
- if bitbucket_id
- user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
- (user && user.id) || project.creator_id
- else
- project.creator_id
- end
- end
end
end
end
diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb
new file mode 100644
index 00000000000..313e6b9fc03
--- /dev/null
+++ b/lib/gitlab/blame.rb
@@ -0,0 +1,54 @@
+module Gitlab
+ class Blame
+ attr_accessor :blob, :commit
+
+ def initialize(blob, commit)
+ @blob = blob
+ @commit = commit
+ end
+
+ def groups(highlight: true)
+ prev_sha = nil
+ groups = []
+ current_group = nil
+
+ i = 0
+ blame.each do |commit, line|
+ commit = Commit.new(commit, project)
+
+ sha = commit.sha
+ if prev_sha != sha
+ groups << current_group if current_group
+ current_group = { commit: commit, lines: [] }
+ end
+
+ line = highlighted_lines[i].html_safe if highlight
+ current_group[:lines] << line
+
+ prev_sha = sha
+ i += 1
+ end
+ groups << current_group if current_group
+
+ groups
+ end
+
+ private
+
+ def blame
+ @blame ||= Gitlab::Git::Blame.new(repository, @commit.id, @blob.path)
+ end
+
+ def highlighted_lines
+ @highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines
+ end
+
+ def project
+ commit.project
+ end
+
+ def repository
+ project.repository
+ end
+ end
+end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index ea054255820..a6b2f14521c 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -4,11 +4,14 @@ module Gitlab
key = :current_application_settings
RequestStore.store[key] ||= begin
+ settings = nil
+
if connect_to_db?
- ApplicationSetting.current || ApplicationSetting.create_from_defaults
- else
- fake_application_settings
+ settings = ApplicationSetting.current
+ settings ||= ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
end
+
+ settings || fake_application_settings
end
end
@@ -18,28 +21,32 @@ module Gitlab
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
+ twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
- import_sources: Settings.gitlab['import_sources'],
+ default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
+ import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
+ require_two_factor_authentication: false,
+ two_factor_grace_period: 48
)
end
private
def connect_to_db?
- use_db = if ENV['USE_DB'] == "false"
- false
- else
- true
- end
-
- use_db && ActiveRecord::Base.connection.active? &&
- ActiveRecord::Base.connection.table_exists?('application_settings')
+ # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
+ active_db_connection = ActiveRecord::Base.connection.active? rescue false
+
+ ENV['USE_DB'] != 'false' &&
+ active_db_connection &&
+ ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError
false
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 663402e8197..e2a85f29825 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -35,8 +35,8 @@ module Gitlab
end
true
- rescue ActiveRecord::RecordInvalid
- false
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
end
def import_pull_requests
@@ -53,8 +53,8 @@ module Gitlab
end
true
- rescue ActiveRecord::RecordInvalid
- false
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
end
def import_comments(issue_number, noteable)
@@ -83,10 +83,13 @@ module Gitlab
true
rescue Gitlab::Shell::Error => e
- if e.message =~ /repository not exported/
- true
+ # GitHub error message when the wiki repo has not been created,
+ # this means that repo has wiki enabled, but have no pages. So,
+ # we can skip the import.
+ if e.message !~ /repository not exported/
+ raise Projects::ImportService::Error, e.message
else
- false
+ true
end
end
end
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index d9fce2e6758..face1921d2e 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -106,20 +106,36 @@ module Gitlab
if type == :instance
target = mod
label = "#{mod.name}##{name}"
+ method = mod.instance_method(name)
else
target = mod.singleton_class
label = "#{mod.name}.#{name}"
+ method = mod.method(name)
+ end
+
+ # Some code out there (e.g. the "state_machine" Gem) checks the arity of
+ # a method to make sure it only passes arguments when the method expects
+ # any. If we were to always overwrite a method to take an `*args`
+ # signature this would break things. As a result we'll make sure the
+ # generated method _only_ accepts regular arguments if the underlying
+ # method also accepts them.
+ if method.arity == 0
+ args_signature = '&block'
+ else
+ args_signature = '*args, &block'
end
+ send_signature = "__send__(#{alias_name.inspect}, #{args_signature})"
+
target.class_eval <<-EOF, __FILE__, __LINE__ + 1
alias_method #{alias_name.inspect}, #{name.inspect}
- def #{name}(*args, &block)
+ def #{name}(#{args_signature})
trans = Gitlab::Metrics::Instrumentation.transaction
if trans
start = Time.now
- retval = __send__(#{alias_name.inspect}, *args, &block)
+ retval = #{send_signature}
duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold
@@ -132,7 +148,7 @@ module Gitlab
retval
else
- __send__(#{alias_name.inspect}, *args, &block)
+ #{send_signature}
end
end
EOF
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index 938219efdb2..38364a0b151 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -1,5 +1,7 @@
module Gitlab
class SnippetSearchResults < SearchResults
+ include SnippetsHelper
+
attr_reader :limit_snippet_ids
def initialize(limit_snippet_ids, query)
@@ -47,85 +49,5 @@ module Gitlab
def default_scope
'snippet_blobs'
end
-
- # Get an array of line numbers surrounding a matching
- # line, bounded by min/max.
- #
- # @returns Array of line numbers
- def bounded_line_numbers(line, min, max)
- lower = line - surrounding_lines > min ? line - surrounding_lines : min
- upper = line + surrounding_lines < max ? line + surrounding_lines : max
- (lower..upper).to_a
- end
-
- # Returns a sorted set of lines to be included in a snippet preview.
- # This ensures matching adjacent lines do not display duplicated
- # surrounding code.
- #
- # @returns Array, unique and sorted.
- def matching_lines(lined_content)
- used_lines = []
- lined_content.each_with_index do |line, line_number|
- used_lines.concat bounded_line_numbers(
- line_number,
- 0,
- lined_content.size
- ) if line.include?(query)
- end
-
- used_lines.uniq.sort
- end
-
- # 'Chunkify' entire snippet. Splits the snippet data into matching lines +
- # surrounding_lines() worth of unmatching lines.
- #
- # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
- def chunk_snippet(snippet)
- lined_content = snippet.content.split("\n")
- used_lines = matching_lines(lined_content)
-
- snippet_chunk = []
- snippet_chunks = []
- snippet_start_line = 0
- last_line = -1
-
- # Go through each used line, and add consecutive lines as a single chunk
- # to the snippet chunk array.
- used_lines.each do |line_number|
- if last_line < 0
- # Start a new chunk.
- snippet_start_line = line_number
- snippet_chunk << lined_content[line_number]
- elsif last_line == line_number - 1
- # Consecutive line, continue chunk.
- snippet_chunk << lined_content[line_number]
- else
- # Non-consecutive line, add chunk to chunk array.
- snippet_chunks << {
- data: snippet_chunk.join("\n"),
- start_line: snippet_start_line + 1
- }
-
- # Start a new chunk.
- snippet_chunk = [lined_content[line_number]]
- snippet_start_line = line_number
- end
- last_line = line_number
- end
- # Add final chunk to chunk array
- snippet_chunks << {
- data: snippet_chunk.join("\n"),
- start_line: snippet_start_line + 1
- }
-
- # Return snippet with chunk array
- { snippet_object: snippet, snippet_chunks: snippet_chunks }
- end
-
- # Defines how many unmatching lines should be
- # included around the matching lines in a snippet
- def surrounding_lines
- 3
- end
end
end
diff --git a/scripts/ci/prepare_build.sh b/scripts/ci/prepare_build.sh
deleted file mode 100755
index 864a683a1bd..00000000000
--- a/scripts/ci/prepare_build.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-if [ -f /.dockerinit ]; then
- export FLAGS=(--deployment --path /cache)
-
- apt-get update -qq
- apt-get install -y -qq nodejs
-
- wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb
- dpkg -i phantomjs_1.9.0-1+b1_amd64.deb
-
- cp config/database.yml.mysql config/database.yml
- sed -i "s/username:.*/username: root/g" config/database.yml
- sed -i "s/password:.*/password:/g" config/database.yml
- sed -i "s/# socket:.*/host: mysql/g" config/database.yml
-else
- export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
-
- cp config/database.yml.mysql config/database.yml
- sed -i "s/username\:.*$/username\: runner/" config/database.yml
- sed -i "s/password\:.*$/password\: 'password'/" config/database.yml
- sed -i "s/gitlab_ci_test/gitlab_ci_test_$((RANDOM/5000))/" config/database.yml
-fi
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 119cc90fc1e..5987988dc8e 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,10 +1,16 @@
#!/bin/bash
+
if [ -f /.dockerinit ]; then
- wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
- dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
+ # Docker runners use `/cache` folder which is persisted every build
+ if [ ! -e /cache/phantomjs_1.9.8-0jessie_amd64.deb ]; then
+ wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+ mv phantomjs_1.9.8-0jessie_amd64.deb /cache
+ fi
+ dpkg -i /cache/phantomjs_1.9.8-0jessie_amd64.deb
apt-get update -qq
- apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client
+ apt-get -o dir::cache::archives="/cache/apt" install -y -qq --force-yes \
+ libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip
cp config/database.yml.mysql config/database.yml
sed -i 's/username:.*/username: root/g' config/database.yml
@@ -13,8 +19,8 @@ if [ -f /.dockerinit ]; then
cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml
- FLAGS=(--deployment --path /cache)
- export FLAGS
+
+ export FLAGS=(--path /cache)
else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml
diff --git a/spec/controllers/blame_controller_spec.rb b/spec/controllers/blame_controller_spec.rb
index 3ad4d5fc0a8..25f06299a29 100644
--- a/spec/controllers/blame_controller_spec.rb
+++ b/spec/controllers/blame_controller_spec.rb
@@ -24,20 +24,6 @@ describe Projects::BlameController do
context "valid file" do
let(:id) { 'master/files/ruby/popen.rb' }
it { is_expected.to respond_with(:success) }
-
- it 'groups blames properly' do
- blame = assigns(:blame)
- # Sanity check a few items
- expect(blame.count).to eq(18)
- expect(blame[0][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
- expect(blame[0][:lines]).to eq(["require 'fileutils'", "require 'open3'", ""])
-
- expect(blame[1][:commit].sha).to eq('874797c3a73b60d2187ed6e2fcabd289ff75171e')
- expect(blame[1][:lines]).to eq(["module Popen", " extend self"])
-
- expect(blame[-1][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
- expect(blame[-1][:lines]).to eq([" end", "end"])
- end
end
end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
new file mode 100644
index 00000000000..938e97298b6
--- /dev/null
+++ b/spec/controllers/groups_controller_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+
+describe GroupsController do
+ describe 'GET index' do
+ context 'as a user' do
+ it 'redirects to Groups Dashboard' do
+ sign_in(create(:user))
+
+ get :index
+
+ expect(response).to redirect_to(dashboard_groups_path)
+ end
+ end
+
+ context 'as a guest' do
+ it 'redirects to Explore Groups' do
+ get :index
+
+ expect(response).to redirect_to(explore_groups_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
new file mode 100644
index 00000000000..85d1d1e0524
--- /dev/null
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+describe Projects::ImportsController do
+ let(:user) { create(:user) }
+
+ describe 'GET #show' do
+ context 'when repository does not exists' do
+ let(:project) { create(:empty_project) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
+ it 'renders template' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to render_template :show
+ end
+
+ it 'sets flash.now if params is present' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'Started' }
+
+ expect(flash.now[:notice]).to eq 'Started'
+ end
+ end
+
+ context 'when repository exists' do
+ let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
+ context 'when import is in progress' do
+ before do
+ project.update_attribute(:import_status, :started)
+ end
+
+ it 'renders template' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to render_template :show
+ end
+
+ it 'sets flash.now if params is present' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'In progress' }
+
+ expect(flash.now[:notice]).to eq 'In progress'
+ end
+ end
+
+ context 'when import failed' do
+ before do
+ project.update_attribute(:import_status, :failed)
+ end
+
+ it 'redirects to new_namespace_project_import_path' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
+ end
+ end
+
+ context 'when import finished' do
+ before do
+ project.update_attribute(:import_status, :finished)
+ end
+
+ context 'when project is a fork' do
+ it 'redirects to namespace_project_path' do
+ allow_any_instance_of(Project).to receive(:forked?).and_return(true)
+
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(flash[:notice]).to eq 'The project was successfully forked.'
+ expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ end
+ end
+
+ context 'when project is external' do
+ it 'redirects to namespace_project_path' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(flash[:notice]).to eq 'The project was successfully imported.'
+ expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ end
+ end
+
+ context 'when continue params is present' do
+ let(:params) do
+ {
+ to: namespace_project_path(project.namespace, project),
+ notice: 'Finished'
+ }
+ end
+
+ it 'redirects to params[:to]' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: params
+
+ expect(flash[:notice]).to eq params[:notice]
+ expect(response).to redirect_to params[:to]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 8898b71e2a3..b7c2b32cb13 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -1,11 +1,15 @@
FactoryGirl.define do
factory :commit_status, class: CommitStatus do
- started_at 'Di 29. Okt 09:51:28 CET 2013'
- finished_at 'Di 29. Okt 09:53:28 CET 2013'
name 'default'
status 'success'
description 'commit status'
commit factory: :ci_commit_with_one_job
+ started_at 'Tue, 26 Jan 2016 08:21:42 +0100'
+ finished_at 'Tue, 26 Jan 2016 08:23:42 +0100'
+
+ after(:build) do |build, evaluator|
+ build.project = build.commit.project
+ end
factory :generic_commit_status, class: GenericCommitStatus do
name 'generic'
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index fe7f07f5b75..5a62da10619 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -16,83 +16,104 @@ describe 'Commits' do
FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha
end
- let!(:build) { FactoryGirl.create :ci_build, commit: commit }
+ context 'commit status is Generic Commit Status' do
+ let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit }
- describe 'Project commits' do
- before do
- visit namespace_project_commits_path(project.namespace, project, :master)
- end
+ describe 'Commit builds' do
+ before do
+ visit ci_status_path(commit)
+ end
- it 'should show build status' do
- page.within("//li[@id='commit-#{commit.short_sha}']") do
- expect(page).to have_css(".ci-status-link")
+ it { expect(page).to have_content commit.sha[0..7] }
+
+ it 'contains generic commit status build' do
+ page.within('.table-holder') do
+ expect(page).to have_content "##{status.id}" # build id
+ expect(page).to have_content 'generic' # build name
+ end
end
end
end
- describe 'Commit builds' do
- before do
- visit ci_status_path(commit)
- end
-
- it { expect(page).to have_content commit.sha[0..7] }
- it { expect(page).to have_content commit.git_commit_message }
- it { expect(page).to have_content commit.git_author_name }
- end
+ context 'commit status is Ci Build' do
+ let!(:build) { FactoryGirl.create :ci_build, commit: commit }
- context 'Download artifacts' do
- let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+ describe 'Project commits' do
+ before do
+ visit namespace_project_commits_path(project.namespace, project, :master)
+ end
- before do
- build.update_attributes(artifacts_file: artifacts_file)
+ it 'should show build status' do
+ page.within("//li[@id='commit-#{commit.short_sha}']") do
+ expect(page).to have_css(".ci-status-link")
+ end
+ end
end
- it do
- visit ci_status_path(commit)
- click_on 'Download artifacts'
- expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
- end
- end
+ describe 'Commit builds' do
+ before do
+ visit ci_status_path(commit)
+ end
- describe 'Cancel all builds' do
- it 'cancels commit' do
- visit ci_status_path(commit)
- click_on 'Cancel running'
- expect(page).to have_content 'canceled'
+ it { expect(page).to have_content commit.sha[0..7] }
+ it { expect(page).to have_content commit.git_commit_message }
+ it { expect(page).to have_content commit.git_author_name }
end
- end
- describe 'Cancel build' do
- it 'cancels build' do
- visit ci_status_path(commit)
- click_on 'Cancel'
- expect(page).to have_content 'canceled'
- end
- end
+ context 'Download artifacts' do
+ let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+
+ before do
+ build.update_attributes(artifacts_file: artifacts_file)
+ end
- describe '.gitlab-ci.yml not found warning' do
- context 'ci builds enabled' do
- it "does not show warning" do
+ it do
visit ci_status_path(commit)
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ click_on 'Download artifacts'
+ expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
end
+ end
- it 'shows warning' do
- stub_ci_commit_yaml_file(nil)
+ describe 'Cancel all builds' do
+ it 'cancels commit' do
visit ci_status_path(commit)
- expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+ click_on 'Cancel running'
+ expect(page).to have_content 'canceled'
end
end
- context 'ci builds disabled' do
- before do
- stub_ci_builds_disabled
- stub_ci_commit_yaml_file(nil)
+ describe 'Cancel build' do
+ it 'cancels build' do
visit ci_status_path(commit)
+ click_on 'Cancel'
+ expect(page).to have_content 'canceled'
end
+ end
+
+ describe '.gitlab-ci.yml not found warning' do
+ context 'ci builds enabled' do
+ it "does not show warning" do
+ visit ci_status_path(commit)
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+
+ it 'shows warning' do
+ stub_ci_commit_yaml_file(nil)
+ visit ci_status_path(commit)
+ expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+
+ context 'ci builds disabled' do
+ before do
+ stub_ci_builds_disabled
+ stub_ci_commit_yaml_file(nil)
+ visit ci_status_path(commit)
+ end
- it 'does not show warning' do
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ it 'does not show warning' do
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
end
end
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 2451e56fe7c..dac9205449a 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -112,10 +112,10 @@ feature 'Login', feature: true do
context 'within the grace period' do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account until')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account before')
end
- it 'two-factor configuration is skippable' do
+ it 'disallows skipping two-factor configuration' do
expect(current_path).to eq new_profile_two_factor_auth_path
click_link 'Configure it later'
@@ -128,10 +128,10 @@ feature 'Login', feature: true do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account.')
end
- it 'two-factor configuration is not skippable' do
+ it 'disallows skipping two-factor configuration' do
expect(current_path).to eq new_profile_two_factor_auth_path
expect(page).not_to have_link('Configure it later')
end
@@ -146,7 +146,7 @@ feature 'Login', feature: true do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account.')
end
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index f0fc6916c4d..1a360cd1ebc 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -167,7 +167,7 @@ describe 'Comments', feature: true do
end
it 'should be removed when canceled' do
- page.within(".diff-file form[rel$='#{line_code}']") do
+ page.within(".diff-file form[id$='#{line_code}']") do
find('.js-close-discussion-note-form').trigger('click')
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0c8d06b7059..0b9176357bc 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -66,5 +66,10 @@ describe LabelsHelper do
it 'uses dark text on light backgrounds' do
expect(text_color_for_bg('#EEEEEE')).to eq('#333333')
end
+
+ it 'supports RGB triplets' do
+ expect(text_color_for_bg('#FFF')).to eq '#333333'
+ expect(text_color_for_bg('#000')).to eq '#FFFFFF'
+ end
end
end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 760d60a4190..9c63d227044 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -75,6 +75,11 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ it 'allows `abbr` elements' do
+ exp = act = %q{<abbr title="HyperText Markup Language">HTML</abbr>}
+ expect(filter(act).to_html).to eq exp
+ end
+
it 'removes `rel` attribute from `a` elements' do
act = %q{<a href="#" rel="nofollow">Link</a>}
exp = %q{<a href="#">Link</a>}
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
new file mode 100644
index 00000000000..89245761b6f
--- /dev/null
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::Blame, lib: true do
+ let(:project) { create(:project) }
+ let(:path) { 'files/ruby/popen.rb' }
+ let(:commit) { project.commit('master') }
+ let(:blob) { project.repository.blob_at(commit.id, path) }
+
+ describe "#groups" do
+ let(:subject) { described_class.new(blob, commit).groups(highlight: false) }
+
+ it 'groups lines properly' do
+ expect(subject.count).to eq(18)
+ expect(subject[0][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ expect(subject[0][:lines]).to eq(["require 'fileutils'", "require 'open3'", ""])
+
+ expect(subject[1][:commit].sha).to eq('874797c3a73b60d2187ed6e2fcabd289ff75171e')
+ expect(subject[1][:lines]).to eq(["module Popen", " extend self"])
+
+ expect(subject[-1][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ expect(subject[-1][:lines]).to eq([" end", "end"])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 99288da1e43..04cf11fc6f1 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -135,6 +135,17 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
message = "resolve #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
+
+ context 'with an external issue tracker reference' do
+ it 'extracts the referenced issue' do
+ jira_project = create(:jira_project, name: 'JIRA_EXT1')
+ jira_issue = ExternalIssue.new("#{jira_project.name}-1", project: jira_project)
+ closing_issue_extractor = described_class.new jira_project
+ message = "Resolve #{jira_issue.to_reference}"
+
+ expect(closing_issue_extractor.closed_by_message(message)).to eq([jira_issue])
+ end
+ end
end
context "with a cross-project reference" do
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 2a37cd40dde..ad4290c43bb 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -66,6 +66,16 @@ describe Gitlab::Metrics::Instrumentation do
@dummy.foo
end
+
+ it 'generates a method with the correct arity when using methods without arguments' do
+ dummy = Class.new do
+ def self.test; end
+ end
+
+ described_class.instrument_method(dummy, :test)
+
+ expect(dummy.method(:test).arity).to eq(0)
+ end
end
describe 'with metrics disabled' do
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index d30bc7d0554..606340d87e4 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Ci::Build, models: true do
- let(:project) { FactoryGirl.create :empty_project }
+ let(:project) { FactoryGirl.create :project }
let(:commit) { FactoryGirl.create :ci_commit, project: project }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 25b3f4e50da..92fdc5cd65d 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -37,7 +37,7 @@ describe CaseSensitivity, models: true do
with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
and_return(criteria)
- expect(model.iwhere(:'foo.bar' => 'bar')).to eq(criteria)
+ expect(model.iwhere('foo.bar'.to_sym => 'bar')).to eq(criteria)
end
end
@@ -87,8 +87,8 @@ describe CaseSensitivity, models: true do
with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
and_return(final)
- got = model.iwhere(:'foo.bar' => 'bar',
- :'foo.baz' => 'baz')
+ got = model.iwhere('foo.bar'.to_sym => 'bar',
+ 'foo.baz'.to_sym => 'baz')
expect(got).to eq(final)
end
@@ -127,7 +127,7 @@ describe CaseSensitivity, models: true do
with(%q{`foo`.`bar` = :value}, value: 'bar').
and_return(criteria)
- expect(model.iwhere(:'foo.bar' => 'bar')).
+ expect(model.iwhere('foo.bar'.to_sym => 'bar')).
to eq(criteria)
end
end
@@ -178,8 +178,8 @@ describe CaseSensitivity, models: true do
with(%q{`foo`.`baz` = :value}, value: 'baz').
and_return(final)
- got = model.iwhere(:'foo.bar' => 'bar',
- :'foo.baz' => 'baz')
+ got = model.iwhere('foo.bar'.to_sym => 'bar',
+ 'foo.baz'.to_sym => 'baz')
expect(got).to eq(final)
end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 071582b0282..ec2a923f91b 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -65,27 +65,6 @@ describe Event, models: true do
it { expect(@event.author).to eq(@user) }
end
- describe '.latest_update_time' do
- describe 'when events are present' do
- let(:time) { Time.utc(2015, 1, 1) }
-
- before do
- create(:closed_issue_event, updated_at: time)
- create(:closed_issue_event, updated_at: time + 5)
- end
-
- it 'returns the latest update time' do
- expect(Event.latest_update_time).to eq(time + 5)
- end
- end
-
- describe 'when no events exist' do
- it 'returns nil' do
- expect(Event.latest_update_time).to be_nil
- end
- end
- end
-
describe '.limit_recent' do
let!(:event1) { create(:closed_issue_event) }
let!(:event2) { create(:closed_issue_event) }
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 6ec6b9037a4..9b144dd1ecc 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -10,6 +10,21 @@ describe ExternalIssue, models: true do
it { is_expected.to include_module(Referable) }
end
+ describe '.reference_pattern' do
+ it 'allows underscores in the project name' do
+ expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ end
+
+ it 'allows numbers in the project name' do
+ expect(ExternalIssue.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
+ end
+
+ it 'requires the project name to begin with A-Z' do
+ expect(ExternalIssue.reference_pattern.match('3EXT_EXT-1234')).to eq nil
+ expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(issue.to_reference).to eq issue.id
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index afbf62035ac..c484ae8fc8c 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -219,4 +219,24 @@ describe Repository, models: true do
end
end
end
+
+ describe '#has_visible_content?' do
+ subject { repository.has_visible_content? }
+
+ describe 'when there are no branches' do
+ before do
+ allow(repository.raw_repository).to receive(:branch_count).and_return(0)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ describe 'when there are branches' do
+ before do
+ allow(repository.raw_repository).to receive(:branch_count).and_return(3)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
end
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
new file mode 100644
index 00000000000..0737999e125
--- /dev/null
+++ b/spec/models/tree_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe Tree, models: true do
+ let(:repository) { create(:project).repository }
+ let(:sha) { repository.root_ref }
+
+ subject { described_class.new(repository, '54fcc214') }
+
+ describe '#readme' do
+ class FakeBlob
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def readme?
+ name =~ /^readme/i
+ end
+ end
+
+ it 'returns nil when repository does not contains a README file' do
+ files = [FakeBlob.new('file'), FakeBlob.new('license'), FakeBlob.new('copying')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme).to eq nil
+ end
+
+ it 'returns nil when repository does not contains a previewable README file' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README.pages'), FakeBlob.new('README.png')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme).to eq nil
+ end
+
+ it 'returns README when repository contains a previewable README file' do
+ files = [FakeBlob.new('README.png'), FakeBlob.new('README'), FakeBlob.new('file')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README'
+ end
+
+ it 'returns first previewable README when repository contains more than one' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README.md'), FakeBlob.new('README.asciidoc')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README.md'
+ end
+
+ it 'returns first plain text README when repository contains more than one' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.txt')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README'
+ end
+
+ it 'prioritizes previewable README file over one in plain text' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.md')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README.md'
+ end
+ end
+end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e194eb93cf4..d7bfa17b0b1 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -109,9 +109,9 @@ describe API::API, api: true do
end
end
- describe "GET /projects/:id/merge_request/:merge_request_id" do
+ describe "GET /projects/:id/merge_requests/:merge_request_id" do
it "should return merge_request" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
@@ -126,14 +126,14 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999", user)
+ get api("/projects/#{project.id}/merge_requests/999", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
context 'valid merge request' do
- before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
+ before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) }
let(:commit) { merge_request.commits.first }
it { expect(response.status).to eq 200 }
@@ -143,20 +143,20 @@ describe API::API, api: true do
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/commits", user)
+ get api("/projects/#{project.id}/merge_requests/999/commits", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
it 'should return the change information of the merge_request' do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/changes", user)
+ get api("/projects/#{project.id}/merge_requests/999/changes", user)
expect(response.status).to eq(404)
end
end
@@ -311,19 +311,19 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
expect(response.status).to eq(200)
expect(json_response['state']).to eq('closed')
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
let(:ci_commit) { create(:ci_commit_without_jobs) }
it "should return merge_request in case of success" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(200)
end
@@ -332,7 +332,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).
to receive(:can_be_merged?).and_return(false)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(406)
expect(json_response['message']).to eq('Branch cannot be merged')
@@ -340,14 +340,14 @@ describe API::API, api: true do
it "should return 405 if merge_request is not open" do
merge_request.close
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "should return 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -355,7 +355,7 @@ describe API::API, api: true do
it "should return 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2)
expect(response.status).to eq(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
@@ -364,7 +364,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
allow(ci_commit).to receive(:active?).and_return(true)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
expect(response.status).to eq(200)
expect(json_response['title']).to eq('Test')
@@ -372,33 +372,33 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id" do
it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
expect(response.status).to eq(200)
expect(json_response['title']).to eq('New title')
end
it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
expect(response.status).to eq(200)
expect(json_response['description']).to eq('New description')
end
it "should return 400 when source_branch is specified" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
expect(response.status).to eq(400)
end
it "should return merge_request with renamed target_branch" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
expect(response.status).to eq(200)
expect(json_response['target_branch']).to eq('wiki')
end
it 'should return 400 on invalid label names' do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}",
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}",
user),
title: 'new issue',
labels: 'label, ?'
@@ -407,11 +407,11 @@ describe API::API, api: true do
end
end
- describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
+ describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
it "should return comment" do
original_count = merge_request.notes.size
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment')
expect(json_response['author']['name']).to eq(user.name)
@@ -420,20 +420,20 @@ describe API::API, api: true do
end
it "should return 400 if note is missing" do
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(400)
end
it "should return 404 if note is attached to non existent merge request" do
- post api("/projects/#{project.id}/merge_request/404/comments", user),
+ post api("/projects/#{project.id}/merge_requests/404/comments", user),
note: 'My comment'
expect(response.status).to eq(404)
end
end
- describe "GET :id/merge_request/:merge_request_id/comments" do
+ describe "GET :id/merge_requests/:merge_request_id/comments" do
it "should return merge_request comments ordered by created_at" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -443,7 +443,7 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999/comments", user)
+ get api("/projects/#{project.id}/merge_requests/999/comments", user)
expect(response.status).to eq(404)
end
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index eec927102aa..244947762dd 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -113,6 +113,21 @@ describe Ci::API::API do
expect(json_response["depends_on_builds"].count).to eq(2)
expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
end
+
+ %w(name version revision platform architecture).each do |param|
+ context "updates runner #{param}" do
+ let(:value) { "#{param}_value" }
+
+ subject { runner.read_attribute(param.to_sym) }
+
+ it do
+ post ci_api("/builds/register"), token: runner.token, info: { param => value }
+ expect(response.status).to eq(404)
+ runner.reload
+ is_expected.to eq(value)
+ end
+ end
+ end
end
describe "PUT /builds/:id" do
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 5942aa7a1b5..db8189ffb79 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -51,6 +51,20 @@ describe Ci::API::API do
expect(response.status).to eq(400)
end
+
+ %w(name version revision platform architecture).each do |param|
+ context "creates runner with #{param} saved" do
+ let(:value) { "#{param}_value" }
+
+ subject { Ci::Runner.first.read_attribute(param.to_sym) }
+
+ it do
+ post ci_api("/runners/register"), token: registration_token, info: { param => value }
+ expect(response.status).to eq(201)
+ is_expected.to eq(value)
+ end
+ end
+ end
end
describe "DELETE /runners/delete" do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 22ba25217f0..22937226fce 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -496,11 +496,11 @@ end
describe Projects::ForksController, 'routing' do
it 'to #new' do
- expect(get('/gitlab/gitlabhq/fork/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #create' do
- expect(post('/gitlab/gitlabhq/fork')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
new file mode 100644
index 00000000000..04f474c736c
--- /dev/null
+++ b/spec/services/projects/import_service_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe Projects::ImportService, services: true do
+ let!(:project) { create(:empty_project) }
+ let(:user) { project.creator }
+
+ subject { described_class.new(project, user) }
+
+ describe '#execute' do
+ context 'with unknown url' do
+ before do
+ project.import_url = Project::UNKNOWN_IMPORT_URL
+ end
+
+ it 'succeeds if repository is created successfully' do
+ expect(project).to receive(:create_repository).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository creation fails' do
+ expect(project).to receive(:create_repository).and_return(false)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'The repository could not be created.'
+ end
+ end
+
+ context 'with known url' do
+ before do
+ project.import_url = 'https://github.com/vim/vim.git'
+ end
+
+ it 'succeeds if repository import is successfully' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository import fails' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Failed to import the repository'
+ end
+ end
+
+ context 'with valid importer' do
+ before do
+ stub_github_omniauth_provider
+
+ project.import_url = 'https://github.com/vim/vim.git'
+ project.import_type = 'github'
+
+ allow(project).to receive(:import_data).and_return(double.as_null_object)
+ end
+
+ it 'succeeds if importer succeeds' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if importer fails' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'The remote data could not be imported.'
+ end
+
+ it 'fails if importer raise an error' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Github: failed to connect API'
+ end
+ end
+
+ def stub_github_omniauth_provider
+ provider = OpenStruct.new(
+ name: 'github',
+ app_id: 'asd123',
+ app_secret: 'asd123'
+ )
+
+ Gitlab.config.omniauth.providers << provider
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 0225a0ee53f..8f381f46e57 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -48,4 +48,10 @@ FactoryGirl::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
+# Work around a Rails 4.2.5.1 issue
+# See https://github.com/rspec/rspec-rails/issues/1532
+RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do
+ alias_method :find_all_anywhere, :find_all
+end
+
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index fed1ab6ee33..a698f484df1 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -19,3 +19,9 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
end
+
+RSpec.configure do |config|
+ config.before(:suite) do
+ TestEnv.warm_asset_cache
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 4f4743bff6d..0d1bd030f3c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -146,6 +146,22 @@ module TestEnv
FileUtils.chmod_R 0755, target_repo_path
end
+ # When no cached assets exist, manually hit the root path to create them
+ #
+ # Otherwise they'd be created by the first test, often timing out and
+ # causing a transient test failure
+ def warm_asset_cache
+ return if warm_asset_cache?
+ return unless defined?(Capybara)
+
+ Capybara.current_session.driver.visit '/'
+ end
+
+ def warm_asset_cache?
+ cache = Rails.root.join(*%w(tmp cache assets test))
+ Dir.exist?(cache) && Dir.entries(cache).length > 2
+ end
+
private
def factory_repo_path
@@ -172,7 +188,6 @@ module TestEnv
'gitlab-test-fork'
end
-
# Prevent developer git configurations from being persisted to test
# repositories
def git_env
diff --git a/vendor/assets/javascripts/Chart.js b/vendor/assets/javascripts/Chart.js
new file mode 100755
index 00000000000..c264262ba73
--- /dev/null
+++ b/vendor/assets/javascripts/Chart.js
@@ -0,0 +1,3477 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.2
+ *
+ * Copyright 2015 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+
+
+(function(){
+
+ "use strict";
+
+ //Declare root variable - window in the browser, global on the server
+ var root = this,
+ previous = root.Chart;
+
+ //Occupy the global variable of Chart, and create a simple base class
+ var Chart = function(context){
+ var chart = this;
+ this.canvas = context.canvas;
+
+ this.ctx = context;
+
+ //Variables global to the chart
+ var computeDimension = function(element,dimension)
+ {
+ if (element['offset'+dimension])
+ {
+ return element['offset'+dimension];
+ }
+ else
+ {
+ return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
+ }
+ }
+
+ var width = this.width = computeDimension(context.canvas,'Width');
+ var height = this.height = computeDimension(context.canvas,'Height');
+
+ // Firefox requires this to work correctly
+ context.canvas.width = width;
+ context.canvas.height = height;
+
+ var width = this.width = context.canvas.width;
+ var height = this.height = context.canvas.height;
+ this.aspectRatio = this.width / this.height;
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
+ helpers.retinaScale(this);
+
+ return this;
+ };
+ //Globally expose the defaults to allow for user updating/changing
+ Chart.defaults = {
+ global: {
+ // Boolean - Whether to animate the chart
+ animation: true,
+
+ // Number - Number of animation steps
+ animationSteps: 60,
+
+ // String - Animation easing effect
+ animationEasing: "easeOutQuart",
+
+ // Boolean - If we should show the scale at all
+ showScale: true,
+
+ // Boolean - If we want to override with a hard coded scale
+ scaleOverride: false,
+
+ // ** Required if scaleOverride is true **
+ // Number - The number of steps in a hard coded scale
+ scaleSteps: null,
+ // Number - The value jump in the hard coded scale
+ scaleStepWidth: null,
+ // Number - The scale starting value
+ scaleStartValue: null,
+
+ // String - Colour of the scale line
+ scaleLineColor: "rgba(0,0,0,.1)",
+
+ // Number - Pixel width of the scale line
+ scaleLineWidth: 1,
+
+ // Boolean - Whether to show labels on the scale
+ scaleShowLabels: true,
+
+ // Interpolated JS string - can access value
+ scaleLabel: "<%=value%>",
+
+ // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
+ scaleIntegersOnly: true,
+
+ // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero: false,
+
+ // String - Scale label font declaration for the scale label
+ scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Scale label font size in pixels
+ scaleFontSize: 12,
+
+ // String - Scale label font weight style
+ scaleFontStyle: "normal",
+
+ // String - Scale label font colour
+ scaleFontColor: "#666",
+
+ // Boolean - whether or not the chart should be responsive and resize when the browser does.
+ responsive: false,
+
+ // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
+ maintainAspectRatio: true,
+
+ // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
+ showTooltips: true,
+
+ // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
+ customTooltips: false,
+
+ // Array - Array of string names to attach tooltip events
+ tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
+
+ // String - Tooltip background colour
+ tooltipFillColor: "rgba(0,0,0,0.8)",
+
+ // String - Tooltip label font declaration for the scale label
+ tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip label font size in pixels
+ tooltipFontSize: 14,
+
+ // String - Tooltip font weight style
+ tooltipFontStyle: "normal",
+
+ // String - Tooltip label font colour
+ tooltipFontColor: "#fff",
+
+ // String - Tooltip title font declaration for the scale label
+ tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip title font size in pixels
+ tooltipTitleFontSize: 14,
+
+ // String - Tooltip title font weight style
+ tooltipTitleFontStyle: "bold",
+
+ // String - Tooltip title font colour
+ tooltipTitleFontColor: "#fff",
+
+ // Number - pixel width of padding around tooltip text
+ tooltipYPadding: 6,
+
+ // Number - pixel width of padding around tooltip text
+ tooltipXPadding: 6,
+
+ // Number - Size of the caret on the tooltip
+ tooltipCaretSize: 8,
+
+ // Number - Pixel radius of the tooltip border
+ tooltipCornerRadius: 6,
+
+ // Number - Pixel offset from point x to tooltip edge
+ tooltipXOffset: 10,
+
+ // String - Template string for single tooltips
+ tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
+
+ // String - Template string for single tooltips
+ multiTooltipTemplate: "<%= value %>",
+
+ // String - Colour behind the legend colour block
+ multiTooltipKeyBackground: '#fff',
+
+ // Function - Will fire on animation progression.
+ onAnimationProgress: function(){},
+
+ // Function - Will fire on animation completion.
+ onAnimationComplete: function(){}
+
+ }
+ };
+
+ //Create a dictionary of chart types, to allow for extension of existing types
+ Chart.types = {};
+
+ //Global Chart helpers object for utility methods and classes
+ var helpers = Chart.helpers = {};
+
+ //-- Basic js utility methods
+ var each = helpers.each = function(loopable,callback,self){
+ var additionalArgs = Array.prototype.slice.call(arguments, 3);
+ // Check to see if null or undefined firstly.
+ if (loopable){
+ if (loopable.length === +loopable.length){
+ var i;
+ for (i=0; i<loopable.length; i++){
+ callback.apply(self,[loopable[i], i].concat(additionalArgs));
+ }
+ }
+ else{
+ for (var item in loopable){
+ callback.apply(self,[loopable[item],item].concat(additionalArgs));
+ }
+ }
+ }
+ },
+ clone = helpers.clone = function(obj){
+ var objClone = {};
+ each(obj,function(value,key){
+ if (obj.hasOwnProperty(key)) objClone[key] = value;
+ });
+ return objClone;
+ },
+ extend = helpers.extend = function(base){
+ each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
+ each(extensionObject,function(value,key){
+ if (extensionObject.hasOwnProperty(key)) base[key] = value;
+ });
+ });
+ return base;
+ },
+ merge = helpers.merge = function(base,master){
+ //Merge properties in left object over to a shallow clone of object right.
+ var args = Array.prototype.slice.call(arguments,0);
+ args.unshift({});
+ return extend.apply(null, args);
+ },
+ indexOf = helpers.indexOf = function(arrayToSearch, item){
+ if (Array.prototype.indexOf) {
+ return arrayToSearch.indexOf(item);
+ }
+ else{
+ for (var i = 0; i < arrayToSearch.length; i++) {
+ if (arrayToSearch[i] === item) return i;
+ }
+ return -1;
+ }
+ },
+ where = helpers.where = function(collection, filterCallback){
+ var filtered = [];
+
+ helpers.each(collection, function(item){
+ if (filterCallback(item)){
+ filtered.push(item);
+ }
+ });
+
+ return filtered;
+ },
+ findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
+ // Default to start of the array
+ if (!startIndex){
+ startIndex = -1;
+ }
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)){
+ return currentItem;
+ }
+ }
+ },
+ findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
+ // Default to end of the array
+ if (!startIndex){
+ startIndex = arrayToSearch.length;
+ }
+ for (var i = startIndex - 1; i >= 0; i--) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)){
+ return currentItem;
+ }
+ }
+ },
+ inherits = helpers.inherits = function(extensions){
+ //Basic javascript inheritance based on the model created in Backbone.js
+ var parent = this;
+ var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
+
+ var Surrogate = function(){ this.constructor = ChartElement;};
+ Surrogate.prototype = parent.prototype;
+ ChartElement.prototype = new Surrogate();
+
+ ChartElement.extend = inherits;
+
+ if (extensions) extend(ChartElement.prototype, extensions);
+
+ ChartElement.__super__ = parent.prototype;
+
+ return ChartElement;
+ },
+ noop = helpers.noop = function(){},
+ uid = helpers.uid = (function(){
+ var id=0;
+ return function(){
+ return "chart-" + id++;
+ };
+ })(),
+ warn = helpers.warn = function(str){
+ //Method for warning of errors
+ if (window.console && typeof window.console.warn == "function") console.warn(str);
+ },
+ amd = helpers.amd = (typeof define == 'function' && define.amd),
+ //-- Math methods
+ isNumber = helpers.isNumber = function(n){
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ },
+ max = helpers.max = function(array){
+ return Math.max.apply( Math, array );
+ },
+ min = helpers.min = function(array){
+ return Math.min.apply( Math, array );
+ },
+ cap = helpers.cap = function(valueToCap,maxValue,minValue){
+ if(isNumber(maxValue)) {
+ if( valueToCap > maxValue ) {
+ return maxValue;
+ }
+ }
+ else if(isNumber(minValue)){
+ if ( valueToCap < minValue ){
+ return minValue;
+ }
+ }
+ return valueToCap;
+ },
+ getDecimalPlaces = helpers.getDecimalPlaces = function(num){
+ if (num%1!==0 && isNumber(num)){
+ return num.toString().split(".")[1].length;
+ }
+ else {
+ return 0;
+ }
+ },
+ toRadians = helpers.radians = function(degrees){
+ return degrees * (Math.PI/180);
+ },
+ // Gets the angle from vertical upright to the point about a centre.
+ getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
+ radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+
+ var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+ //If the segment is in the top left quadrant, we need to add another rotation to the angle
+ if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
+ angle += Math.PI*2;
+ }
+
+ return {
+ angle: angle,
+ distance: radialDistanceFromCenter
+ };
+ },
+ aliasPixel = helpers.aliasPixel = function(pixelWidth){
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
+ },
+ splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
+ //Props to Rob Spencer at scaled innovation for his post on splining between points
+ //http://scaledinnovation.com/analytics/splines/aboutSplines.html
+ var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
+ d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
+ fa=t*d01/(d01+d12),// scaling factor for triangle Ta
+ fb=t*d12/(d01+d12);
+ return {
+ inner : {
+ x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
+ },
+ outer : {
+ x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
+ }
+ };
+ },
+ calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
+ return Math.floor(Math.log(val) / Math.LN10);
+ },
+ calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
+
+ //Set a minimum step of two - a point at the top of the graph, and a point at the base
+ var minSteps = 2,
+ maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
+ skipFitting = (minSteps >= maxSteps);
+
+ var maxValue = max(valuesArray),
+ minValue = min(valuesArray);
+
+ // We need some degree of seperation here to calculate the scales if all the values are the same
+ // Adding/minusing 0.5 will give us a range of 1.
+ if (maxValue === minValue){
+ maxValue += 0.5;
+ // So we don't end up with a graph with a negative start value if we've said always start from zero
+ if (minValue >= 0.5 && !startFromZero){
+ minValue -= 0.5;
+ }
+ else{
+ // Make up a whole number above the values
+ maxValue += 0.5;
+ }
+ }
+
+ var valueRange = Math.abs(maxValue - minValue),
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphRange = graphMax - graphMin,
+ stepValue = Math.pow(10, rangeOrderOfMagnitude),
+ numberOfSteps = Math.round(graphRange / stepValue);
+
+ //If we have more space on the graph we'll use it to give more definition to the data
+ while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
+ if(numberOfSteps > maxSteps){
+ stepValue *=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
+ if (numberOfSteps % 1 !== 0){
+ skipFitting = true;
+ }
+ }
+ //We can fit in double the amount of scale points on the scale
+ else{
+ //If user has declared ints only, and the step value isn't a decimal
+ if (integersOnly && rangeOrderOfMagnitude >= 0){
+ //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
+ if(stepValue/2 % 1 === 0){
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+ //If it would make it a float break out of the loop
+ else{
+ break;
+ }
+ }
+ //If the scale doesn't have to be an int, make the scale more granular anyway.
+ else{
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+
+ }
+ }
+
+ if (skipFitting){
+ numberOfSteps = minSteps;
+ stepValue = graphRange / numberOfSteps;
+ }
+
+ return {
+ steps : numberOfSteps,
+ stepValue : stepValue,
+ min : graphMin,
+ max : graphMin + (numberOfSteps * stepValue)
+ };
+
+ },
+ /* jshint ignore:start */
+ // Blows up jshint errors based on the new Function constructor
+ //Templating methods
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
+ template = helpers.template = function(templateString, valuesObject){
+
+ // If templateString is function rather than string-template - call the function for valuesObject
+
+ if(templateString instanceof Function){
+ return templateString(valuesObject);
+ }
+
+ var cache = {};
+ function tmpl(str, data){
+ // Figure out if we're getting a template, or if we need to
+ // load the template - and be sure to cache the result.
+ var fn = !/\W/.test(str) ?
+ cache[str] = cache[str] :
+
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ new Function("obj",
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){p.push('" +
+
+ // Convert the template into pure JavaScript
+ str
+ .replace(/[\r\t\n]/g, " ")
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, "',$1,'")
+ .split("\t").join("');")
+ .split("%>").join("p.push('")
+ .split("\r").join("\\'") +
+ "');}return p.join('');"
+ );
+
+ // Provide some basic currying to the user
+ return data ? fn( data ) : fn;
+ }
+ return tmpl(templateString,valuesObject);
+ },
+ /* jshint ignore:end */
+ generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
+ var labelsArray = new Array(numberOfSteps);
+ if (labelTemplateString){
+ each(labelsArray,function(val,index){
+ labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
+ });
+ }
+ return labelsArray;
+ },
+ //--Animation methods
+ //Easing functions adapted from Robert Penner's easing equations
+ //http://www.robertpenner.com/easing/
+ easingEffects = helpers.easingEffects = {
+ linear: function (t) {
+ return t;
+ },
+ easeInQuad: function (t) {
+ return t * t;
+ },
+ easeOutQuad: function (t) {
+ return -1 * t * (t - 2);
+ },
+ easeInOutQuad: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
+ return -1 / 2 * ((--t) * (t - 2) - 1);
+ },
+ easeInCubic: function (t) {
+ return t * t * t;
+ },
+ easeOutCubic: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
+ },
+ easeInOutCubic: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t + 2);
+ },
+ easeInQuart: function (t) {
+ return t * t * t * t;
+ },
+ easeOutQuart: function (t) {
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
+ },
+ easeInOutQuart: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
+ },
+ easeInQuint: function (t) {
+ return 1 * (t /= 1) * t * t * t * t;
+ },
+ easeOutQuint: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
+ },
+ easeInOutQuint: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
+ },
+ easeInSine: function (t) {
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
+ },
+ easeOutSine: function (t) {
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
+ },
+ easeInOutSine: function (t) {
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
+ },
+ easeInExpo: function (t) {
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
+ },
+ easeOutExpo: function (t) {
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
+ },
+ easeInOutExpo: function (t) {
+ if (t === 0) return 0;
+ if (t === 1) return 1;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
+ },
+ easeInCirc: function (t) {
+ if (t >= 1) return t;
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
+ },
+ easeOutCirc: function (t) {
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
+ },
+ easeInOutCirc: function (t) {
+ if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+ },
+ easeInElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ },
+ easeOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
+ },
+ easeInOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1 / 2) == 2) return 1;
+ if (!p) p = 1 * (0.3 * 1.5);
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
+ },
+ easeInBack: function (t) {
+ var s = 1.70158;
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
+ },
+ easeOutBack: function (t) {
+ var s = 1.70158;
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
+ },
+ easeInOutBack: function (t) {
+ var s = 1.70158;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+ },
+ easeInBounce: function (t) {
+ return 1 - easingEffects.easeOutBounce(1 - t);
+ },
+ easeOutBounce: function (t) {
+ if ((t /= 1) < (1 / 2.75)) {
+ return 1 * (7.5625 * t * t);
+ } else if (t < (2 / 2.75)) {
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
+ } else if (t < (2.5 / 2.75)) {
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
+ } else {
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
+ }
+ },
+ easeInOutBounce: function (t) {
+ if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
+ }
+ },
+ //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestAnimFrame = helpers.requestAnimFrame = (function(){
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })(),
+ cancelAnimFrame = helpers.cancelAnimFrame = (function(){
+ return window.cancelAnimationFrame ||
+ window.webkitCancelAnimationFrame ||
+ window.mozCancelAnimationFrame ||
+ window.oCancelAnimationFrame ||
+ window.msCancelAnimationFrame ||
+ function(callback) {
+ return window.clearTimeout(callback, 1000 / 60);
+ };
+ })(),
+ animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
+
+ var currentStep = 0,
+ easingFunction = easingEffects[easingString] || easingEffects.linear;
+
+ var animationFrame = function(){
+ currentStep++;
+ var stepDecimal = currentStep/totalSteps;
+ var easeDecimal = easingFunction(stepDecimal);
+
+ callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
+ onProgress.call(chartInstance,easeDecimal,stepDecimal);
+ if (currentStep < totalSteps){
+ chartInstance.animationFrame = requestAnimFrame(animationFrame);
+ } else{
+ onComplete.apply(chartInstance);
+ }
+ };
+ requestAnimFrame(animationFrame);
+ },
+ //-- DOM methods
+ getRelativePosition = helpers.getRelativePosition = function(evt){
+ var mouseX, mouseY;
+ var e = evt.originalEvent || evt,
+ canvas = evt.currentTarget || evt.srcElement,
+ boundingRect = canvas.getBoundingClientRect();
+
+ if (e.touches){
+ mouseX = e.touches[0].clientX - boundingRect.left;
+ mouseY = e.touches[0].clientY - boundingRect.top;
+
+ }
+ else{
+ mouseX = e.clientX - boundingRect.left;
+ mouseY = e.clientY - boundingRect.top;
+ }
+
+ return {
+ x : mouseX,
+ y : mouseY
+ };
+
+ },
+ addEvent = helpers.addEvent = function(node,eventType,method){
+ if (node.addEventListener){
+ node.addEventListener(eventType,method);
+ } else if (node.attachEvent){
+ node.attachEvent("on"+eventType, method);
+ } else {
+ node["on"+eventType] = method;
+ }
+ },
+ removeEvent = helpers.removeEvent = function(node, eventType, handler){
+ if (node.removeEventListener){
+ node.removeEventListener(eventType, handler, false);
+ } else if (node.detachEvent){
+ node.detachEvent("on"+eventType,handler);
+ } else{
+ node["on" + eventType] = noop;
+ }
+ },
+ bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
+ // Create the events object if it's not already present
+ if (!chartInstance.events) chartInstance.events = {};
+
+ each(arrayOfEvents,function(eventName){
+ chartInstance.events[eventName] = function(){
+ handler.apply(chartInstance, arguments);
+ };
+ addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
+ });
+ },
+ unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
+ each(arrayOfEvents, function(handler,eventName){
+ removeEvent(chartInstance.chart.canvas, eventName, handler);
+ });
+ },
+ getMaximumWidth = helpers.getMaximumWidth = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientWidth;
+ },
+ getMaximumHeight = helpers.getMaximumHeight = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientHeight;
+ },
+ getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
+ retinaScale = helpers.retinaScale = function(chart){
+ var ctx = chart.ctx,
+ width = chart.canvas.width,
+ height = chart.canvas.height;
+
+ if (window.devicePixelRatio) {
+ ctx.canvas.style.width = width + "px";
+ ctx.canvas.style.height = height + "px";
+ ctx.canvas.height = height * window.devicePixelRatio;
+ ctx.canvas.width = width * window.devicePixelRatio;
+ ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
+ }
+ },
+ //-- Canvas methods
+ clear = helpers.clear = function(chart){
+ chart.ctx.clearRect(0,0,chart.width,chart.height);
+ },
+ fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
+ return fontStyle + " " + pixelSize+"px " + fontFamily;
+ },
+ longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
+ ctx.font = font;
+ var longest = 0;
+ each(arrayOfStrings,function(string){
+ var textWidth = ctx.measureText(string).width;
+ longest = (textWidth > longest) ? textWidth : longest;
+ });
+ return longest;
+ },
+ drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+ };
+
+
+ //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+ //Destroy method on the chart will remove the instance of the chart from this reference.
+ Chart.instances = {};
+
+ Chart.Type = function(data,options,chart){
+ this.options = options;
+ this.chart = chart;
+ this.id = uid();
+ //Add the chart instance to the global namespace
+ Chart.instances[this.id] = this;
+
+ // Initialize is always called when a chart type is created
+ // By default it is a no op, but it should be extended
+ if (options.responsive){
+ this.resize();
+ }
+ this.initialize.call(this,data);
+ };
+
+ //Core methods that'll be a part of every chart type
+ extend(Chart.Type.prototype,{
+ initialize : function(){return this;},
+ clear : function(){
+ clear(this.chart);
+ return this;
+ },
+ stop : function(){
+ // Stops any current animation loop occuring
+ cancelAnimFrame(this.animationFrame);
+ return this;
+ },
+ resize : function(callback){
+ this.stop();
+ var canvas = this.chart.canvas,
+ newWidth = getMaximumWidth(this.chart.canvas),
+ newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
+
+ canvas.width = this.chart.width = newWidth;
+ canvas.height = this.chart.height = newHeight;
+
+ retinaScale(this.chart);
+
+ if (typeof callback === "function"){
+ callback.apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ return this;
+ },
+ reflow : noop,
+ render : function(reflow){
+ if (reflow){
+ this.reflow();
+ }
+ if (this.options.animation && !reflow){
+ helpers.animationLoop(
+ this.draw,
+ this.options.animationSteps,
+ this.options.animationEasing,
+ this.options.onAnimationProgress,
+ this.options.onAnimationComplete,
+ this
+ );
+ }
+ else{
+ this.draw();
+ this.options.onAnimationComplete.call(this);
+ }
+ return this;
+ },
+ generateLegend : function(){
+ return template(this.options.legendTemplate,this);
+ },
+ destroy : function(){
+ this.clear();
+ unbindEvents(this, this.events);
+ var canvas = this.chart.canvas;
+
+ // Reset canvas height/width attributes starts a fresh with the canvas context
+ canvas.width = this.chart.width;
+ canvas.height = this.chart.height;
+
+ // < IE9 doesn't support removeProperty
+ if (canvas.style.removeProperty) {
+ canvas.style.removeProperty('width');
+ canvas.style.removeProperty('height');
+ } else {
+ canvas.style.removeAttribute('width');
+ canvas.style.removeAttribute('height');
+ }
+
+ delete Chart.instances[this.id];
+ },
+ showTooltip : function(ChartElements, forceRedraw){
+ // Only redraw the chart if we've actually changed what we're hovering on.
+ if (typeof this.activeElements === 'undefined') this.activeElements = [];
+
+ var isChanged = (function(Elements){
+ var changed = false;
+
+ if (Elements.length !== this.activeElements.length){
+ changed = true;
+ return changed;
+ }
+
+ each(Elements, function(element, index){
+ if (element !== this.activeElements[index]){
+ changed = true;
+ }
+ }, this);
+ return changed;
+ }).call(this, ChartElements);
+
+ if (!isChanged && !forceRedraw){
+ return;
+ }
+ else{
+ this.activeElements = ChartElements;
+ }
+ this.draw();
+ if(this.options.customTooltips){
+ this.options.customTooltips(false);
+ }
+ if (ChartElements.length > 0){
+ // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
+ if (this.datasets && this.datasets.length > 1) {
+ var dataArray,
+ dataIndex;
+
+ for (var i = this.datasets.length - 1; i >= 0; i--) {
+ dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
+ dataIndex = indexOf(dataArray, ChartElements[0]);
+ if (dataIndex !== -1){
+ break;
+ }
+ }
+ var tooltipLabels = [],
+ tooltipColors = [],
+ medianPosition = (function(index) {
+
+ // Get all the points at that particular index
+ var Elements = [],
+ dataCollection,
+ xPositions = [],
+ yPositions = [],
+ xMax,
+ yMax,
+ xMin,
+ yMin;
+ helpers.each(this.datasets, function(dataset){
+ dataCollection = dataset.points || dataset.bars || dataset.segments;
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
+ Elements.push(dataCollection[dataIndex]);
+ }
+ });
+
+ helpers.each(Elements, function(element) {
+ xPositions.push(element.x);
+ yPositions.push(element.y);
+
+
+ //Include any colour information about the element
+ tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
+ tooltipColors.push({
+ fill: element._saved.fillColor || element.fillColor,
+ stroke: element._saved.strokeColor || element.strokeColor
+ });
+
+ }, this);
+
+ yMin = min(yPositions);
+ yMax = max(yPositions);
+
+ xMin = min(xPositions);
+ xMax = max(xPositions);
+
+ return {
+ x: (xMin > this.chart.width/2) ? xMin : xMax,
+ y: (yMin + yMax)/2
+ };
+ }).call(this, dataIndex);
+
+ new Chart.MultiTooltip({
+ x: medianPosition.x,
+ y: medianPosition.y,
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ xOffset: this.options.tooltipXOffset,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ titleTextColor: this.options.tooltipTitleFontColor,
+ titleFontFamily: this.options.tooltipTitleFontFamily,
+ titleFontStyle: this.options.tooltipTitleFontStyle,
+ titleFontSize: this.options.tooltipTitleFontSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ labels: tooltipLabels,
+ legendColors: tooltipColors,
+ legendColorBackground : this.options.multiTooltipKeyBackground,
+ title: ChartElements[0].label,
+ chart: this.chart,
+ ctx: this.chart.ctx,
+ custom: this.options.customTooltips
+ }).draw();
+
+ } else {
+ each(ChartElements, function(Element) {
+ var tooltipPosition = Element.tooltipPosition();
+ new Chart.Tooltip({
+ x: Math.round(tooltipPosition.x),
+ y: Math.round(tooltipPosition.y),
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ caretHeight: this.options.tooltipCaretSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ text: template(this.options.tooltipTemplate, Element),
+ chart: this.chart,
+ custom: this.options.customTooltips
+ }).draw();
+ }, this);
+ }
+ }
+ return this;
+ },
+ toBase64Image : function(){
+ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
+ }
+ });
+
+ Chart.Type.extend = function(extensions){
+
+ var parent = this;
+
+ var ChartType = function(){
+ return parent.apply(this,arguments);
+ };
+
+ //Copy the prototype object of the this class
+ ChartType.prototype = clone(parent.prototype);
+ //Now overwrite some of the properties in the base class with the new extensions
+ extend(ChartType.prototype, extensions);
+
+ ChartType.extend = Chart.Type.extend;
+
+ if (extensions.name || parent.prototype.name){
+
+ var chartName = extensions.name || parent.prototype.name;
+ //Assign any potential default values of the new chart type
+
+ //If none are defined, we'll use a clone of the chart type this is being extended from.
+ //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
+ //doesn't define some defaults of their own.
+
+ var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
+
+ Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
+
+ Chart.types[chartName] = ChartType;
+
+ //Register this new chart type in the Chart prototype
+ Chart.prototype[chartName] = function(data,options){
+ var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
+ return new ChartType(data,config,this);
+ };
+ } else{
+ warn("Name not provided for this chart, so it hasn't been registered");
+ }
+ return parent;
+ };
+
+ Chart.Element = function(configuration){
+ extend(this,configuration);
+ this.initialize.apply(this,arguments);
+ this.save();
+ };
+ extend(Chart.Element.prototype,{
+ initialize : function(){},
+ restore : function(props){
+ if (!props){
+ extend(this,this._saved);
+ } else {
+ each(props,function(key){
+ this[key] = this._saved[key];
+ },this);
+ }
+ return this;
+ },
+ save : function(){
+ this._saved = clone(this);
+ delete this._saved._saved;
+ return this;
+ },
+ update : function(newProps){
+ each(newProps,function(value,key){
+ this._saved[key] = this[key];
+ this[key] = value;
+ },this);
+ return this;
+ },
+ transition : function(props,ease){
+ each(props,function(value,key){
+ this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
+ },this);
+ return this;
+ },
+ tooltipPosition : function(){
+ return {
+ x : this.x,
+ y : this.y
+ };
+ },
+ hasValue: function(){
+ return isNumber(this.value);
+ }
+ });
+
+ Chart.Element.extend = inherits;
+
+
+ Chart.Point = Chart.Element.extend({
+ display: true,
+ inRange: function(chartX,chartY){
+ var hitDetectionRange = this.hitDetectionRadius + this.radius;
+ return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
+ },
+ draw : function(){
+ if (this.display){
+ var ctx = this.ctx;
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
+ ctx.closePath();
+
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.stroke();
+ }
+
+
+ //Quick debug for bezier curve splining
+ //Highlights control points and the line between them.
+ //Handy for dev - stripped in the min version.
+
+ // ctx.save();
+ // ctx.fillStyle = "black";
+ // ctx.strokeStyle = "black"
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
+ // ctx.lineTo(this.x, this.y);
+ // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
+ // ctx.stroke();
+
+ // ctx.restore();
+
+
+
+ }
+ });
+
+ Chart.Arc = Chart.Element.extend({
+ inRange : function(chartX,chartY){
+
+ var pointRelativePosition = helpers.getAngleFromPoint(this, {
+ x: chartX,
+ y: chartY
+ });
+
+ //Check if within the range of the open/close angle
+ var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
+ withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
+
+ return (betweenAngles && withinRadius);
+ //Ensure within the outside of the arc centre, but inside arc outer
+ },
+ tooltipPosition : function(){
+ var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
+ rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
+ return {
+ x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
+ y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
+ };
+ },
+ draw : function(animationPercent){
+
+ var easingDecimal = animationPercent || 1;
+
+ var ctx = this.ctx;
+
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
+
+ ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
+
+ ctx.closePath();
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.lineJoin = 'bevel';
+
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ }
+ });
+
+ Chart.Rectangle = Chart.Element.extend({
+ draw : function(){
+ var ctx = this.ctx,
+ halfWidth = this.width/2,
+ leftX = this.x - halfWidth,
+ rightX = this.x + halfWidth,
+ top = this.base - (this.base - this.y),
+ halfStroke = this.strokeWidth / 2;
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (this.showStroke){
+ leftX += halfStroke;
+ rightX -= halfStroke;
+ top += halfStroke;
+ }
+
+ ctx.beginPath();
+
+ ctx.fillStyle = this.fillColor;
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ // It'd be nice to keep this class totally generic to any rectangle
+ // and simply specify which border to miss out.
+ ctx.moveTo(leftX, this.base);
+ ctx.lineTo(leftX, top);
+ ctx.lineTo(rightX, top);
+ ctx.lineTo(rightX, this.base);
+ ctx.fill();
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ },
+ height : function(){
+ return this.base - this.y;
+ },
+ inRange : function(chartX,chartY){
+ return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
+ }
+ });
+
+ Chart.Tooltip = Chart.Element.extend({
+ draw : function(){
+
+ var ctx = this.chart.ctx;
+
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.xAlign = "center";
+ this.yAlign = "above";
+
+ //Distance between the actual element.y position and the start of the tooltip caret
+ var caretPadding = this.caretPadding = 2;
+
+ var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
+ tooltipRectHeight = this.fontSize + 2*this.yPadding,
+ tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
+
+ if (this.x + tooltipWidth/2 >this.chart.width){
+ this.xAlign = "left";
+ } else if (this.x - tooltipWidth/2 < 0){
+ this.xAlign = "right";
+ }
+
+ if (this.y - tooltipHeight < 0){
+ this.yAlign = "below";
+ }
+
+
+ var tooltipX = this.x - tooltipWidth/2,
+ tooltipY = this.y - tooltipHeight;
+
+ ctx.fillStyle = this.fillColor;
+
+ // Custom Tooltips
+ if(this.custom){
+ this.custom(this);
+ }
+ else{
+ switch(this.yAlign)
+ {
+ case "above":
+ //Draw a caret above the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x,this.y - caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case "below":
+ tooltipY = this.y + caretPadding + this.caretHeight;
+ //Draw a caret below the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x, this.y + caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ }
+
+ switch(this.xAlign)
+ {
+ case "left":
+ tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
+ break;
+ case "right":
+ tooltipX = this.x - (this.cornerRadius + this.caretHeight);
+ break;
+ }
+
+ drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
+
+ ctx.fill();
+
+ ctx.fillStyle = this.textColor;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
+ }
+ }
+ });
+
+ Chart.MultiTooltip = Chart.Element.extend({
+ initialize : function(){
+ this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
+
+ this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
+
+ this.ctx.font = this.titleFont;
+
+ var titleWidth = this.ctx.measureText(this.title).width,
+ //Label has a legend square as well so account for this.
+ labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
+ longestTextWidth = max([labelWidth,titleWidth]);
+
+ this.width = longestTextWidth + (this.xPadding*2);
+
+
+ var halfHeight = this.height/2;
+
+ //Check to ensure the height will fit on the canvas
+ if (this.y - halfHeight < 0 ){
+ this.y = halfHeight;
+ } else if (this.y + halfHeight > this.chart.height){
+ this.y = this.chart.height - halfHeight;
+ }
+
+ //Decide whether to align left or right based on position on canvas
+ if (this.x > this.chart.width/2){
+ this.x -= this.xOffset + this.width;
+ } else {
+ this.x += this.xOffset;
+ }
+
+
+ },
+ getLineHeight : function(index){
+ var baseLineHeight = this.y - (this.height/2) + this.yPadding,
+ afterTitleIndex = index-1;
+
+ //If the index is zero, we're getting the title
+ if (index === 0){
+ return baseLineHeight + this.titleFontSize/2;
+ } else{
+ return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
+ }
+
+ },
+ draw : function(){
+ // Custom Tooltips
+ if(this.custom){
+ this.custom(this);
+ }
+ else{
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
+ var ctx = this.ctx;
+ ctx.fillStyle = this.fillColor;
+ ctx.fill();
+ ctx.closePath();
+
+ ctx.textAlign = "left";
+ ctx.textBaseline = "middle";
+ ctx.fillStyle = this.titleTextColor;
+ ctx.font = this.titleFont;
+
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
+
+ ctx.font = this.font;
+ helpers.each(this.labels,function(label,index){
+ ctx.fillStyle = this.textColor;
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
+
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+ //Instead we'll make a white filled block to put the legendColour palette over.
+
+ ctx.fillStyle = this.legendColorBackground;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+ ctx.fillStyle = this.legendColors[index].fill;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+
+ },this);
+ }
+ }
+ });
+
+ Chart.Scale = Chart.Element.extend({
+ initialize : function(){
+ this.fit();
+ },
+ buildYLabels : function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
+ },
+ addXLabel : function(label){
+ this.xLabels.push(label);
+ this.valuesCount++;
+ this.fit();
+ },
+ removeXLabel : function(){
+ this.xLabels.shift();
+ this.valuesCount--;
+ this.fit();
+ },
+ // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
+ fit: function(){
+ // First we need the width of the yLabels, assuming the xLabels aren't rotated
+
+ // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
+ this.startPoint = (this.display) ? this.fontSize : 0;
+ this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
+
+ // Apply padding settings to the start and end point.
+ this.startPoint += this.padding;
+ this.endPoint -= this.padding;
+
+ // Cache the starting height, so can determine if we need to recalculate the scale yAxis
+ var cachedHeight = this.endPoint - this.startPoint,
+ cachedYLabelWidth;
+
+ // Build the current yLabels so we have an idea of what size they'll be to start
+ /*
+ * This sets what is returned from calculateScaleRange as static properties of this class:
+ *
+ this.steps;
+ this.stepValue;
+ this.min;
+ this.max;
+ *
+ */
+ this.calculateYRange(cachedHeight);
+
+ // With these properties set we can now build the array of yLabels
+ // and also the width of the largest yLabel
+ this.buildYLabels();
+
+ this.calculateXLabelRotation();
+
+ while((cachedHeight > this.endPoint - this.startPoint)){
+ cachedHeight = this.endPoint - this.startPoint;
+ cachedYLabelWidth = this.yLabelWidth;
+
+ this.calculateYRange(cachedHeight);
+ this.buildYLabels();
+
+ // Only go through the xLabel loop again if the yLabel width has changed
+ if (cachedYLabelWidth < this.yLabelWidth){
+ this.calculateXLabelRotation();
+ }
+ }
+
+ },
+ calculateXLabelRotation : function(){
+ //Get the width of each grid by calculating the difference
+ //between x offsets between 0 and 1.
+
+ this.ctx.font = this.font;
+
+ var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
+ lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
+ firstRotated,
+ lastRotated;
+
+
+ this.xScalePaddingRight = lastWidth/2 + 3;
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
+
+ this.xLabelRotation = 0;
+ if (this.display){
+ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
+ cosRotation,
+ firstRotatedWidth;
+ this.xLabelWidth = originalLabelWidth;
+ //Allow 3 pixels x2 padding either side for label readability
+ var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
+
+ //Max label rotate should be 90 - also act as a loop counter
+ while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
+ cosRotation = Math.cos(toRadians(this.xLabelRotation));
+
+ firstRotated = cosRotation * firstWidth;
+ lastRotated = cosRotation * lastWidth;
+
+ // We're right aligning the text now.
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
+ this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
+ }
+ this.xScalePaddingRight = this.fontSize/2;
+
+
+ this.xLabelRotation++;
+ this.xLabelWidth = cosRotation * originalLabelWidth;
+
+ }
+ if (this.xLabelRotation > 0){
+ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
+ }
+ }
+ else{
+ this.xLabelWidth = 0;
+ this.xScalePaddingRight = this.padding;
+ this.xScalePaddingLeft = this.padding;
+ }
+
+ },
+ // Needs to be overidden in each Chart type
+ // Otherwise we need to pass all the data into the scale class
+ calculateYRange: noop,
+ drawingArea: function(){
+ return this.startPoint - this.endPoint;
+ },
+ calculateY : function(value){
+ var scalingFactor = this.drawingArea() / (this.min - this.max);
+ return this.endPoint - (scalingFactor * (value - this.min));
+ },
+ calculateX : function(index){
+ var isRotated = (this.xLabelRotation > 0),
+ // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
+ valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
+
+ if (this.offsetGridLines){
+ valueOffset += (valueWidth/2);
+ }
+
+ return Math.round(valueOffset);
+ },
+ update : function(newProps){
+ helpers.extend(this, newProps);
+ this.fit();
+ },
+ draw : function(){
+ var ctx = this.ctx,
+ yLabelGap = (this.endPoint - this.startPoint) / this.steps,
+ xStart = Math.round(this.xScalePaddingLeft);
+ if (this.display){
+ ctx.fillStyle = this.textColor;
+ ctx.font = this.font;
+ each(this.yLabels,function(labelString,index){
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
+ linePositionY = Math.round(yLabelCenter),
+ drawHorizontalLine = this.showHorizontalLines;
+
+ ctx.textAlign = "right";
+ ctx.textBaseline = "middle";
+ if (this.showLabels){
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
+ }
+
+ // This is X axis, so draw it
+ if (index === 0 && !drawHorizontalLine){
+ drawHorizontalLine = true;
+ }
+
+ if (drawHorizontalLine){
+ ctx.beginPath();
+ }
+
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
+
+ if(drawHorizontalLine){
+ ctx.moveTo(xStart, linePositionY);
+ ctx.lineTo(this.width, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ ctx.beginPath();
+ ctx.moveTo(xStart - 5, linePositionY);
+ ctx.lineTo(xStart, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ },this);
+
+ each(this.xLabels,function(label,index){
+ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
+ // Check to see if line/bar here and decide where to place the line
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
+ isRotated = (this.xLabelRotation > 0),
+ drawVerticalLine = this.showVerticalLines;
+
+ // This is Y axis, so draw it
+ if (index === 0 && !drawVerticalLine){
+ drawVerticalLine = true;
+ }
+
+ if (drawVerticalLine){
+ ctx.beginPath();
+ }
+
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+
+ if (drawVerticalLine){
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.startPoint - 3);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+
+
+ // Small lines at the bottom of the base grid line
+ ctx.beginPath();
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.endPoint + 5);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.save();
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
+ ctx.rotate(toRadians(this.xLabelRotation)*-1);
+ ctx.font = this.font;
+ ctx.textAlign = (isRotated) ? "right" : "center";
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
+ ctx.fillText(label, 0, 0);
+ ctx.restore();
+ },this);
+
+ }
+ }
+
+ });
+
+ Chart.RadialScale = Chart.Element.extend({
+ initialize: function(){
+ this.size = min([this.height, this.width]);
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ },
+ calculateCenterOffset: function(value){
+ // Take into account half font size + the yPadding of the top value
+ var scalingFactor = this.drawingArea / (this.max - this.min);
+
+ return (value - this.min) * scalingFactor;
+ },
+ update : function(){
+ if (!this.lineArc){
+ this.setScaleSize();
+ } else {
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ }
+ this.buildYLabels();
+ },
+ buildYLabels: function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ },
+ getCircumference : function(){
+ return ((Math.PI*2) / this.valuesCount);
+ },
+ setScaleSize: function(){
+ /*
+ * Right, this is really confusing and there is a lot of maths going on here
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
+ *
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
+ *
+ * Solution:
+ *
+ * We assume the radius of the polygon is half the size of the canvas at first
+ * at each index we check if the text overlaps.
+ *
+ * Where it does, we store that angle and that index.
+ *
+ * After finding the largest index and angle we calculate how much we need to remove
+ * from the shape radius to move the point inwards by that x.
+ *
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
+ * along with labels.
+ *
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
+ *
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
+ * and position it in the most space efficient manner
+ *
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
+ */
+
+
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
+ var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
+ pointPosition,
+ i,
+ textWidth,
+ halfTextWidth,
+ furthestRight = this.width,
+ furthestRightIndex,
+ furthestRightAngle,
+ furthestLeft = 0,
+ furthestLeftIndex,
+ furthestLeftAngle,
+ xProtrusionLeft,
+ xProtrusionRight,
+ radiusReductionRight,
+ radiusReductionLeft,
+ maxWidthRadius;
+ this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ for (i=0;i<this.valuesCount;i++){
+ // 5px to space the text slightly out - similar to what we do in the draw function.
+ pointPosition = this.getPointPosition(i, largestPossibleRadius);
+ textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
+ if (i === 0 || i === this.valuesCount/2){
+ // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
+ // of the radar chart, so text will be aligned centrally, so we'll half it and compare
+ // w/left and right text sizes
+ halfTextWidth = textWidth/2;
+ if (pointPosition.x + halfTextWidth > furthestRight) {
+ furthestRight = pointPosition.x + halfTextWidth;
+ furthestRightIndex = i;
+ }
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - halfTextWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ else if (i < this.valuesCount/2) {
+ // Less than half the values means we'll left align the text
+ if (pointPosition.x + textWidth > furthestRight) {
+ furthestRight = pointPosition.x + textWidth;
+ furthestRightIndex = i;
+ }
+ }
+ else if (i > this.valuesCount/2){
+ // More than half the values means we'll right align the text
+ if (pointPosition.x - textWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - textWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ }
+
+ xProtrusionLeft = furthestLeft;
+
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
+
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
+
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
+
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
+
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
+
+ // Ensure we actually need to reduce the size of the chart
+ radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
+ radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
+
+ this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
+
+ //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
+
+ },
+ setCenterPoint: function(leftMovement, rightMovement){
+
+ var maxRight = this.width - rightMovement - this.drawingArea,
+ maxLeft = leftMovement + this.drawingArea;
+
+ this.xCenter = (maxLeft + maxRight)/2;
+ // Always vertically in the centre as the text height doesn't change
+ this.yCenter = (this.height/2);
+ },
+
+ getIndexAngle : function(index){
+ var angleMultiplier = (Math.PI * 2) / this.valuesCount;
+ // Start from the top instead of right, so remove a quarter of the circle
+
+ return index * angleMultiplier - (Math.PI/2);
+ },
+ getPointPosition : function(index, distanceFromCenter){
+ var thisAngle = this.getIndexAngle(index);
+ return {
+ x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
+ y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
+ };
+ },
+ draw: function(){
+ if (this.display){
+ var ctx = this.ctx;
+ each(this.yLabels, function(label, index){
+ // Don't draw a centre value
+ if (index > 0){
+ var yCenterOffset = index * (this.drawingArea/this.steps),
+ yHeight = this.yCenter - yCenterOffset,
+ pointPosition;
+
+ // Draw circular lines around the scale
+ if (this.lineWidth > 0){
+ ctx.strokeStyle = this.lineColor;
+ ctx.lineWidth = this.lineWidth;
+
+ if(this.lineArc){
+ ctx.beginPath();
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
+ ctx.closePath();
+ ctx.stroke();
+ } else{
+ ctx.beginPath();
+ for (var i=0;i<this.valuesCount;i++)
+ {
+ pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
+ if (i === 0){
+ ctx.moveTo(pointPosition.x, pointPosition.y);
+ } else {
+ ctx.lineTo(pointPosition.x, pointPosition.y);
+ }
+ }
+ ctx.closePath();
+ ctx.stroke();
+ }
+ }
+ if(this.showLabels){
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+ if (this.showLabelBackdrop){
+ var labelWidth = ctx.measureText(label).width;
+ ctx.fillStyle = this.backdropColor;
+ ctx.fillRect(
+ this.xCenter - labelWidth/2 - this.backdropPaddingX,
+ yHeight - this.fontSize/2 - this.backdropPaddingY,
+ labelWidth + this.backdropPaddingX*2,
+ this.fontSize + this.backdropPaddingY*2
+ );
+ }
+ ctx.textAlign = 'center';
+ ctx.textBaseline = "middle";
+ ctx.fillStyle = this.fontColor;
+ ctx.fillText(label, this.xCenter, yHeight);
+ }
+ }
+ }, this);
+
+ if (!this.lineArc){
+ ctx.lineWidth = this.angleLineWidth;
+ ctx.strokeStyle = this.angleLineColor;
+ for (var i = this.valuesCount - 1; i >= 0; i--) {
+ if (this.angleLineWidth > 0){
+ var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
+ ctx.beginPath();
+ ctx.moveTo(this.xCenter, this.yCenter);
+ ctx.lineTo(outerPosition.x, outerPosition.y);
+ ctx.stroke();
+ ctx.closePath();
+ }
+ // Extra 3px out for some label spacing
+ var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
+ ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ ctx.fillStyle = this.pointLabelFontColor;
+
+ var labelsCount = this.labels.length,
+ halfLabelsCount = this.labels.length/2,
+ quarterLabelsCount = halfLabelsCount/2,
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
+ if (i === 0){
+ ctx.textAlign = 'center';
+ } else if(i === halfLabelsCount){
+ ctx.textAlign = 'center';
+ } else if (i < halfLabelsCount){
+ ctx.textAlign = 'left';
+ } else {
+ ctx.textAlign = 'right';
+ }
+
+ // Set the correct text baseline based on outer positioning
+ if (exactQuarter){
+ ctx.textBaseline = 'middle';
+ } else if (upperHalf){
+ ctx.textBaseline = 'bottom';
+ } else {
+ ctx.textBaseline = 'top';
+ }
+
+ ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
+ }
+ }
+ }
+ }
+ });
+
+ // Attach global event to resize each chart instance when the browser resizes
+ helpers.addEvent(window, "resize", (function(){
+ // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
+ var timeout;
+ return function(){
+ clearTimeout(timeout);
+ timeout = setTimeout(function(){
+ each(Chart.instances,function(instance){
+ // If the responsive flag is set in the chart instance config
+ // Cascade the resize event down to the chart.
+ if (instance.options.responsive){
+ instance.resize(instance.render, true);
+ }
+ });
+ }, 50);
+ };
+ })());
+
+
+ if (amd) {
+ define(function(){
+ return Chart;
+ });
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = Chart;
+ }
+
+ root.Chart = Chart;
+
+ Chart.noConflict = function(){
+ root.Chart = previous;
+ return Chart;
+ };
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+
+ var defaultConfig = {
+ //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero : true,
+
+ //Boolean - Whether grid lines are shown across the chart
+ scaleShowGridLines : true,
+
+ //String - Colour of the grid lines
+ scaleGridLineColor : "rgba(0,0,0,.05)",
+
+ //Number - Width of the grid lines
+ scaleGridLineWidth : 1,
+
+ //Boolean - Whether to show horizontal lines (except X axis)
+ scaleShowHorizontalLines: true,
+
+ //Boolean - Whether to show vertical lines (except Y axis)
+ scaleShowVerticalLines: true,
+
+ //Boolean - If there is a stroke on each bar
+ barShowStroke : true,
+
+ //Number - Pixel width of the bar stroke
+ barStrokeWidth : 2,
+
+ //Number - Spacing between each of the X value sets
+ barValueSpacing : 5,
+
+ //Number - Spacing between data sets within X values
+ barDatasetSpacing : 1,
+
+ //String - A legend template
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
+
+ };
+
+
+ Chart.Type.extend({
+ name: "Bar",
+ defaults : defaultConfig,
+ initialize: function(data){
+
+ //Expose options as a scope variable here so we can access it in the ScaleClass
+ var options = this.options;
+
+ this.ScaleClass = Chart.Scale.extend({
+ offsetGridLines : true,
+ calculateBarX : function(datasetCount, datasetIndex, barIndex){
+ //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
+ var xWidth = this.calculateBaseWidth(),
+ xAbsolute = this.calculateX(barIndex) - (xWidth/2),
+ barWidth = this.calculateBarWidth(datasetCount);
+
+ return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
+ },
+ calculateBaseWidth : function(){
+ return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
+ },
+ calculateBarWidth : function(datasetCount){
+ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
+ var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
+
+ return (baseWidth / datasetCount);
+ }
+ });
+
+ this.datasets = [];
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
+
+ this.eachBars(function(bar){
+ bar.restore(['fillColor', 'strokeColor']);
+ });
+ helpers.each(activeBars, function(activeBar){
+ activeBar.fillColor = activeBar.highlightFill;
+ activeBar.strokeColor = activeBar.highlightStroke;
+ });
+ this.showTooltip(activeBars);
+ });
+ }
+
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
+ this.BarClass = Chart.Rectangle.extend({
+ strokeWidth : this.options.barStrokeWidth,
+ showStroke : this.options.barShowStroke,
+ ctx : this.chart.ctx
+ });
+
+ //Iterate through each of the datasets, and build this into a property of the chart
+ helpers.each(data.datasets,function(dataset,datasetIndex){
+
+ var datasetObject = {
+ label : dataset.label || null,
+ fillColor : dataset.fillColor,
+ strokeColor : dataset.strokeColor,
+ bars : []
+ };
+
+ this.datasets.push(datasetObject);
+
+ helpers.each(dataset.data,function(dataPoint,index){
+ //Add a new point for each piece of data, passing any required data to draw.
+ datasetObject.bars.push(new this.BarClass({
+ value : dataPoint,
+ label : data.labels[index],
+ datasetLabel: dataset.label,
+ strokeColor : dataset.strokeColor,
+ fillColor : dataset.fillColor,
+ highlightFill : dataset.highlightFill || dataset.fillColor,
+ highlightStroke : dataset.highlightStroke || dataset.strokeColor
+ }));
+ },this);
+
+ },this);
+
+ this.buildScale(data.labels);
+
+ this.BarClass.prototype.base = this.scale.endPoint;
+
+ this.eachBars(function(bar, index, datasetIndex){
+ helpers.extend(bar, {
+ width : this.scale.calculateBarWidth(this.datasets.length),
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
+ y: this.scale.endPoint
+ });
+ bar.save();
+ }, this);
+
+ this.render();
+ },
+ update : function(){
+ this.scale.update();
+ // Reset any highlight colours before updating.
+ helpers.each(this.activeElements, function(activeElement){
+ activeElement.restore(['fillColor', 'strokeColor']);
+ });
+
+ this.eachBars(function(bar){
+ bar.save();
+ });
+ this.render();
+ },
+ eachBars : function(callback){
+ helpers.each(this.datasets,function(dataset, datasetIndex){
+ helpers.each(dataset.bars, callback, this, datasetIndex);
+ },this);
+ },
+ getBarsAtEvent : function(e){
+ var barsArray = [],
+ eventPosition = helpers.getRelativePosition(e),
+ datasetIterator = function(dataset){
+ barsArray.push(dataset.bars[barIndex]);
+ },
+ barIndex;
+
+ for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
+ for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
+ if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
+ helpers.each(this.datasets, datasetIterator);
+ return barsArray;
+ }
+ }
+ }
+
+ return barsArray;
+ },
+ buildScale : function(labels){
+ var self = this;
+
+ var dataTotal = function(){
+ var values = [];
+ self.eachBars(function(bar){
+ values.push(bar.value);
+ });
+ return values;
+ };
+
+ var scaleOptions = {
+ templateString : this.options.scaleLabel,
+ height : this.chart.height,
+ width : this.chart.width,
+ ctx : this.chart.ctx,
+ textColor : this.options.scaleFontColor,
+ fontSize : this.options.scaleFontSize,
+ fontStyle : this.options.scaleFontStyle,
+ fontFamily : this.options.scaleFontFamily,
+ valuesCount : labels.length,
+ beginAtZero : this.options.scaleBeginAtZero,
+ integersOnly : this.options.scaleIntegersOnly,
+ calculateYRange: function(currentHeight){
+ var updatedRanges = helpers.calculateScaleRange(
+ dataTotal(),
+ currentHeight,
+ this.fontSize,
+ this.beginAtZero,
+ this.integersOnly
+ );
+ helpers.extend(this, updatedRanges);
+ },
+ xLabels : labels,
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+ lineWidth : this.options.scaleLineWidth,
+ lineColor : this.options.scaleLineColor,
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
+ showVerticalLines : this.options.scaleShowVerticalLines,
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+ padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
+ showLabels : this.options.scaleShowLabels,
+ display : this.options.showScale
+ };
+
+ if (this.options.scaleOverride){
+ helpers.extend(scaleOptions, {
+ calculateYRange: helpers.noop,
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ });
+ }
+
+ this.scale = new this.ScaleClass(scaleOptions);
+ },
+ addData : function(valuesArray,label){
+ //Map the values array for each of the datasets
+ helpers.each(valuesArray,function(value,datasetIndex){
+ //Add a new point for each piece of data, passing any required data to draw.
+ this.datasets[datasetIndex].bars.push(new this.BarClass({
+ value : value,
+ label : label,
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
+ y: this.scale.endPoint,
+ width : this.scale.calculateBarWidth(this.datasets.length),
+ base : this.scale.endPoint,
+ strokeColor : this.datasets[datasetIndex].strokeColor,
+ fillColor : this.datasets[datasetIndex].fillColor
+ }));
+ },this);
+
+ this.scale.addXLabel(label);
+ //Then re-render the chart.
+ this.update();
+ },
+ removeData : function(){
+ this.scale.removeXLabel();
+ //Then re-render the chart.
+ helpers.each(this.datasets,function(dataset){
+ dataset.bars.shift();
+ },this);
+ this.update();
+ },
+ reflow : function(){
+ helpers.extend(this.BarClass.prototype,{
+ y: this.scale.endPoint,
+ base : this.scale.endPoint
+ });
+ var newScaleProps = helpers.extend({
+ height : this.chart.height,
+ width : this.chart.width
+ });
+ this.scale.update(newScaleProps);
+ },
+ draw : function(ease){
+ var easingDecimal = ease || 1;
+ this.clear();
+
+ var ctx = this.chart.ctx;
+
+ this.scale.draw(easingDecimal);
+
+ //Draw all the bars for each dataset
+ helpers.each(this.datasets,function(dataset,datasetIndex){
+ helpers.each(dataset.bars,function(bar,index){
+ if (bar.hasValue()){
+ bar.base = this.scale.endPoint;
+ //Transition then draw
+ bar.transition({
+ x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
+ y : this.scale.calculateY(bar.value),
+ width : this.scale.calculateBarWidth(this.datasets.length)
+ }, easingDecimal).draw();
+ }
+ },this);
+
+ },this);
+ }
+ });
+
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ //Cache a local reference to Chart.helpers
+ helpers = Chart.helpers;
+
+ var defaultConfig = {
+ //Boolean - Whether we should show a stroke on each segment
+ segmentShowStroke : true,
+
+ //String - The colour of each segment stroke
+ segmentStrokeColor : "#fff",
+
+ //Number - The width of each segment stroke
+ segmentStrokeWidth : 2,
+
+ //The percentage of the chart that we cut out of the middle.
+ percentageInnerCutout : 50,
+
+ //Number - Amount of animation steps
+ animationSteps : 100,
+
+ //String - Animation easing effect
+ animationEasing : "easeOutBounce",
+
+ //Boolean - Whether we animate the rotation of the Doughnut
+ animateRotate : true,
+
+ //Boolean - Whether we animate scaling the Doughnut from the centre
+ animateScale : false,
+
+ //String - A legend template
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
+
+ };
+
+
+ Chart.Type.extend({
+ //Passing in a name registers this chart in the Chart namespace
+ name: "Doughnut",
+ //Providing a defaults will also register the deafults in the chart namespace
+ defaults : defaultConfig,
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
+ initialize: function(data){
+
+ //Declare segments as a static property to prevent inheriting across the Chart type prototype
+ this.segments = [];
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
+
+ this.SegmentArc = Chart.Arc.extend({
+ ctx : this.chart.ctx,
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
+
+ helpers.each(this.segments,function(segment){
+ segment.restore(["fillColor"]);
+ });
+ helpers.each(activeSegments,function(activeSegment){
+ activeSegment.fillColor = activeSegment.highlightColor;
+ });
+ this.showTooltip(activeSegments);
+ });
+ }
+ this.calculateTotal(data);
+
+ helpers.each(data,function(datapoint, index){
+ this.addData(datapoint, index, true);
+ },this);
+
+ this.render();
+ },
+ getSegmentsAtEvent : function(e){
+ var segmentsArray = [];
+
+ var location = helpers.getRelativePosition(e);
+
+ helpers.each(this.segments,function(segment){
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
+ },this);
+ return segmentsArray;
+ },
+ addData : function(segment, atIndex, silent){
+ var index = atIndex || this.segments.length;
+ this.segments.splice(index, 0, new this.SegmentArc({
+ value : segment.value,
+ outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
+ innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
+ fillColor : segment.color,
+ highlightColor : segment.highlight || segment.color,
+ showStroke : this.options.segmentShowStroke,
+ strokeWidth : this.options.segmentStrokeWidth,
+ strokeColor : this.options.segmentStrokeColor,
+ startAngle : Math.PI * 1.5,
+ circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
+ label : segment.label
+ }));
+ if (!silent){
+ this.reflow();
+ this.update();
+ }
+ },
+ calculateCircumference : function(value){
+ return (Math.PI*2)*(Math.abs(value) / this.total);
+ },
+ calculateTotal : function(data){
+ this.total = 0;
+ helpers.each(data,function(segment){
+ this.total += Math.abs(segment.value);
+ },this);
+ },
+ update : function(){
+ this.calculateTotal(this.segments);
+
+ // Reset any highlight colours before updating.
+ helpers.each(this.activeElements, function(activeElement){
+ activeElement.restore(['fillColor']);
+ });
+
+ helpers.each(this.segments,function(segment){
+ segment.save();
+ });
+ this.render();
+ },
+
+ removeData: function(atIndex){
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
+ this.segments.splice(indexToDelete, 1);
+ this.reflow();
+ this.update();
+ },
+
+ reflow : function(){
+ helpers.extend(this.SegmentArc.prototype,{
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
+ helpers.each(this.segments, function(segment){
+ segment.update({
+ outerRadius : this.outerRadius,
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
+ });
+ }, this);
+ },
+ draw : function(easeDecimal){
+ var animDecimal = (easeDecimal) ? easeDecimal : 1;
+ this.clear();
+ helpers.each(this.segments,function(segment,index){
+ segment.transition({
+ circumference : this.calculateCircumference(segment.value),
+ outerRadius : this.outerRadius,
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
+ },animDecimal);
+
+ segment.endAngle = segment.startAngle + segment.circumference;
+
+ segment.draw();
+ if (index === 0){
+ segment.startAngle = Math.PI * 1.5;
+ }
+ //Check to see if it's the last segment, if not get the next and update the start angle
+ if (index < this.segments.length-1){
+ this.segments[index+1].startAngle = segment.endAngle;
+ }
+ },this);
+
+ }
+ });
+
+ Chart.types.Doughnut.extend({
+ name : "Pie",
+ defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
+ });
+
+}).call(this);
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ var defaultConfig = {
+
+ ///Boolean - Whether grid lines are shown across the chart
+ scaleShowGridLines : true,
+
+ //String - Colour of the grid lines
+ scaleGridLineColor : "rgba(0,0,0,.05)",
+
+ //Number - Width of the grid lines
+ scaleGridLineWidth : 1,
+
+ //Boolean - Whether to show horizontal lines (except X axis)
+ scaleShowHorizontalLines: true,
+
+ //Boolean - Whether to show vertical lines (except Y axis)
+ scaleShowVerticalLines: true,
+
+ //Boolean - Whether the line is curved between points
+ bezierCurve : true,
+
+ //Number - Tension of the bezier curve between points
+ bezierCurveTension : 0.4,
+
+ //Boolean - Whether to show a dot for each point
+ pointDot : true,
+
+ //Number - Radius of each point dot in pixels
+ pointDotRadius : 4,
+
+ //Number - Pixel width of point dot stroke
+ pointDotStrokeWidth : 1,
+
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
+ pointHitDetectionRadius : 20,
+
+ //Boolean - Whether to show a stroke for datasets
+ datasetStroke : true,
+
+ //Number - Pixel width of dataset stroke
+ datasetStrokeWidth : 2,
+
+ //Boolean - Whether to fill the dataset with a colour
+ datasetFill : true,
+
+ //String - A legend template
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
+
+ };
+
+
+ Chart.Type.extend({
+ name: "Line",
+ defaults : defaultConfig,
+ initialize: function(data){
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
+ this.PointClass = Chart.Point.extend({
+ strokeWidth : this.options.pointDotStrokeWidth,
+ radius : this.options.pointDotRadius,
+ display: this.options.pointDot,
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
+ ctx : this.chart.ctx,
+ inRange : function(mouseX){
+ return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
+ }
+ });
+
+ this.datasets = [];
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
+ this.eachPoints(function(point){
+ point.restore(['fillColor', 'strokeColor']);
+ });
+ helpers.each(activePoints, function(activePoint){
+ activePoint.fillColor = activePoint.highlightFill;
+ activePoint.strokeColor = activePoint.highlightStroke;
+ });
+ this.showTooltip(activePoints);
+ });
+ }
+
+ //Iterate through each of the datasets, and build this into a property of the chart
+ helpers.each(data.datasets,function(dataset){
+
+ var datasetObject = {
+ label : dataset.label || null,
+ fillColor : dataset.fillColor,
+ strokeColor : dataset.strokeColor,
+ pointColor : dataset.pointColor,
+ pointStrokeColor : dataset.pointStrokeColor,
+ points : []
+ };
+
+ this.datasets.push(datasetObject);
+
+
+ helpers.each(dataset.data,function(dataPoint,index){
+ //Add a new point for each piece of data, passing any required data to draw.
+ datasetObject.points.push(new this.PointClass({
+ value : dataPoint,
+ label : data.labels[index],
+ datasetLabel: dataset.label,
+ strokeColor : dataset.pointStrokeColor,
+ fillColor : dataset.pointColor,
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
+ }));
+ },this);
+
+ this.buildScale(data.labels);
+
+
+ this.eachPoints(function(point, index){
+ helpers.extend(point, {
+ x: this.scale.calculateX(index),
+ y: this.scale.endPoint
+ });
+ point.save();
+ }, this);
+
+ },this);
+
+
+ this.render();
+ },
+ update : function(){
+ this.scale.update();
+ // Reset any highlight colours before updating.
+ helpers.each(this.activeElements, function(activeElement){
+ activeElement.restore(['fillColor', 'strokeColor']);
+ });
+ this.eachPoints(function(point){
+ point.save();
+ });
+ this.render();
+ },
+ eachPoints : function(callback){
+ helpers.each(this.datasets,function(dataset){
+ helpers.each(dataset.points,callback,this);
+ },this);
+ },
+ getPointsAtEvent : function(e){
+ var pointsArray = [],
+ eventPosition = helpers.getRelativePosition(e);
+ helpers.each(this.datasets,function(dataset){
+ helpers.each(dataset.points,function(point){
+ if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
+ });
+ },this);
+ return pointsArray;
+ },
+ buildScale : function(labels){
+ var self = this;
+
+ var dataTotal = function(){
+ var values = [];
+ self.eachPoints(function(point){
+ values.push(point.value);
+ });
+
+ return values;
+ };
+
+ var scaleOptions = {
+ templateString : this.options.scaleLabel,
+ height : this.chart.height,
+ width : this.chart.width,
+ ctx : this.chart.ctx,
+ textColor : this.options.scaleFontColor,
+ fontSize : this.options.scaleFontSize,
+ fontStyle : this.options.scaleFontStyle,
+ fontFamily : this.options.scaleFontFamily,
+ valuesCount : labels.length,
+ beginAtZero : this.options.scaleBeginAtZero,
+ integersOnly : this.options.scaleIntegersOnly,
+ calculateYRange : function(currentHeight){
+ var updatedRanges = helpers.calculateScaleRange(
+ dataTotal(),
+ currentHeight,
+ this.fontSize,
+ this.beginAtZero,
+ this.integersOnly
+ );
+ helpers.extend(this, updatedRanges);
+ },
+ xLabels : labels,
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+ lineWidth : this.options.scaleLineWidth,
+ lineColor : this.options.scaleLineColor,
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
+ showVerticalLines : this.options.scaleShowVerticalLines,
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+ padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
+ showLabels : this.options.scaleShowLabels,
+ display : this.options.showScale
+ };
+
+ if (this.options.scaleOverride){
+ helpers.extend(scaleOptions, {
+ calculateYRange: helpers.noop,
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ });
+ }
+
+
+ this.scale = new Chart.Scale(scaleOptions);
+ },
+ addData : function(valuesArray,label){
+ //Map the values array for each of the datasets
+
+ helpers.each(valuesArray,function(value,datasetIndex){
+ //Add a new point for each piece of data, passing any required data to draw.
+ this.datasets[datasetIndex].points.push(new this.PointClass({
+ value : value,
+ label : label,
+ x: this.scale.calculateX(this.scale.valuesCount+1),
+ y: this.scale.endPoint,
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
+ fillColor : this.datasets[datasetIndex].pointColor
+ }));
+ },this);
+
+ this.scale.addXLabel(label);
+ //Then re-render the chart.
+ this.update();
+ },
+ removeData : function(){
+ this.scale.removeXLabel();
+ //Then re-render the chart.
+ helpers.each(this.datasets,function(dataset){
+ dataset.points.shift();
+ },this);
+ this.update();
+ },
+ reflow : function(){
+ var newScaleProps = helpers.extend({
+ height : this.chart.height,
+ width : this.chart.width
+ });
+ this.scale.update(newScaleProps);
+ },
+ draw : function(ease){
+ var easingDecimal = ease || 1;
+ this.clear();
+
+ var ctx = this.chart.ctx;
+
+ // Some helper methods for getting the next/prev points
+ var hasValue = function(item){
+ return item.value !== null;
+ },
+ nextPoint = function(point, collection, index){
+ return helpers.findNextWhere(collection, hasValue, index) || point;
+ },
+ previousPoint = function(point, collection, index){
+ return helpers.findPreviousWhere(collection, hasValue, index) || point;
+ };
+
+ this.scale.draw(easingDecimal);
+
+
+ helpers.each(this.datasets,function(dataset){
+ var pointsWithValues = helpers.where(dataset.points, hasValue);
+
+ //Transition each point first so that the line and point drawing isn't out of sync
+ //We can use this extra loop to calculate the control points of this dataset also in this loop
+
+ helpers.each(dataset.points, function(point, index){
+ if (point.hasValue()){
+ point.transition({
+ y : this.scale.calculateY(point.value),
+ x : this.scale.calculateX(index)
+ }, easingDecimal);
+ }
+ },this);
+
+
+ // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
+ // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
+ if (this.options.bezierCurve){
+ helpers.each(pointsWithValues, function(point, index){
+ var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
+ point.controlPoints = helpers.splineCurve(
+ previousPoint(point, pointsWithValues, index),
+ point,
+ nextPoint(point, pointsWithValues, index),
+ tension
+ );
+
+ // Prevent the bezier going outside of the bounds of the graph
+
+ // Cap puter bezier handles to the upper/lower scale bounds
+ if (point.controlPoints.outer.y > this.scale.endPoint){
+ point.controlPoints.outer.y = this.scale.endPoint;
+ }
+ else if (point.controlPoints.outer.y < this.scale.startPoint){
+ point.controlPoints.outer.y = this.scale.startPoint;
+ }
+
+ // Cap inner bezier handles to the upper/lower scale bounds
+ if (point.controlPoints.inner.y > this.scale.endPoint){
+ point.controlPoints.inner.y = this.scale.endPoint;
+ }
+ else if (point.controlPoints.inner.y < this.scale.startPoint){
+ point.controlPoints.inner.y = this.scale.startPoint;
+ }
+ },this);
+ }
+
+
+ //Draw the line between all the points
+ ctx.lineWidth = this.options.datasetStrokeWidth;
+ ctx.strokeStyle = dataset.strokeColor;
+ ctx.beginPath();
+
+ helpers.each(pointsWithValues, function(point, index){
+ if (index === 0){
+ ctx.moveTo(point.x, point.y);
+ }
+ else{
+ if(this.options.bezierCurve){
+ var previous = previousPoint(point, pointsWithValues, index);
+
+ ctx.bezierCurveTo(
+ previous.controlPoints.outer.x,
+ previous.controlPoints.outer.y,
+ point.controlPoints.inner.x,
+ point.controlPoints.inner.y,
+ point.x,
+ point.y
+ );
+ }
+ else{
+ ctx.lineTo(point.x,point.y);
+ }
+ }
+ }, this);
+
+ ctx.stroke();
+
+ if (this.options.datasetFill && pointsWithValues.length > 0){
+ //Round off the line by going to the base of the chart, back to the start, then fill.
+ ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
+ ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
+ ctx.fillStyle = dataset.fillColor;
+ ctx.closePath();
+ ctx.fill();
+ }
+
+ //Now draw the points over the line
+ //A little inefficient double looping, but better than the line
+ //lagging behind the point positions
+ helpers.each(pointsWithValues,function(point){
+ point.draw();
+ });
+ },this);
+ }
+ });
+
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ //Cache a local reference to Chart.helpers
+ helpers = Chart.helpers;
+
+ var defaultConfig = {
+ //Boolean - Show a backdrop to the scale label
+ scaleShowLabelBackdrop : true,
+
+ //String - The colour of the label backdrop
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
+
+ // Boolean - Whether the scale should begin at zero
+ scaleBeginAtZero : true,
+
+ //Number - The backdrop padding above & below the label in pixels
+ scaleBackdropPaddingY : 2,
+
+ //Number - The backdrop padding to the side of the label in pixels
+ scaleBackdropPaddingX : 2,
+
+ //Boolean - Show line for each value in the scale
+ scaleShowLine : true,
+
+ //Boolean - Stroke a line around each segment in the chart
+ segmentShowStroke : true,
+
+ //String - The colour of the stroke on each segement.
+ segmentStrokeColor : "#fff",
+
+ //Number - The width of the stroke value in pixels
+ segmentStrokeWidth : 2,
+
+ //Number - Amount of animation steps
+ animationSteps : 100,
+
+ //String - Animation easing effect.
+ animationEasing : "easeOutBounce",
+
+ //Boolean - Whether to animate the rotation of the chart
+ animateRotate : true,
+
+ //Boolean - Whether to animate scaling the chart from the centre
+ animateScale : false,
+
+ //String - A legend template
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
+ };
+
+
+ Chart.Type.extend({
+ //Passing in a name registers this chart in the Chart namespace
+ name: "PolarArea",
+ //Providing a defaults will also register the deafults in the chart namespace
+ defaults : defaultConfig,
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
+ initialize: function(data){
+ this.segments = [];
+ //Declare segment class as a chart instance specific class, so it can share props for this instance
+ this.SegmentArc = Chart.Arc.extend({
+ showStroke : this.options.segmentShowStroke,
+ strokeWidth : this.options.segmentStrokeWidth,
+ strokeColor : this.options.segmentStrokeColor,
+ ctx : this.chart.ctx,
+ innerRadius : 0,
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+ this.scale = new Chart.RadialScale({
+ display: this.options.showScale,
+ fontStyle: this.options.scaleFontStyle,
+ fontSize: this.options.scaleFontSize,
+ fontFamily: this.options.scaleFontFamily,
+ fontColor: this.options.scaleFontColor,
+ showLabels: this.options.scaleShowLabels,
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
+ backdropColor: this.options.scaleBackdropColor,
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
+ lineColor: this.options.scaleLineColor,
+ lineArc: true,
+ width: this.chart.width,
+ height: this.chart.height,
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2,
+ ctx : this.chart.ctx,
+ templateString: this.options.scaleLabel,
+ valuesCount: data.length
+ });
+
+ this.updateScaleRange(data);
+
+ this.scale.update();
+
+ helpers.each(data,function(segment,index){
+ this.addData(segment,index,true);
+ },this);
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
+ helpers.each(this.segments,function(segment){
+ segment.restore(["fillColor"]);
+ });
+ helpers.each(activeSegments,function(activeSegment){
+ activeSegment.fillColor = activeSegment.highlightColor;
+ });
+ this.showTooltip(activeSegments);
+ });
+ }
+
+ this.render();
+ },
+ getSegmentsAtEvent : function(e){
+ var segmentsArray = [];
+
+ var location = helpers.getRelativePosition(e);
+
+ helpers.each(this.segments,function(segment){
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
+ },this);
+ return segmentsArray;
+ },
+ addData : function(segment, atIndex, silent){
+ var index = atIndex || this.segments.length;
+
+ this.segments.splice(index, 0, new this.SegmentArc({
+ fillColor: segment.color,
+ highlightColor: segment.highlight || segment.color,
+ label: segment.label,
+ value: segment.value,
+ outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
+ circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
+ startAngle: Math.PI * 1.5
+ }));
+ if (!silent){
+ this.reflow();
+ this.update();
+ }
+ },
+ removeData: function(atIndex){
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
+ this.segments.splice(indexToDelete, 1);
+ this.reflow();
+ this.update();
+ },
+ calculateTotal: function(data){
+ this.total = 0;
+ helpers.each(data,function(segment){
+ this.total += segment.value;
+ },this);
+ this.scale.valuesCount = this.segments.length;
+ },
+ updateScaleRange: function(datapoints){
+ var valuesArray = [];
+ helpers.each(datapoints,function(segment){
+ valuesArray.push(segment.value);
+ });
+
+ var scaleSizes = (this.options.scaleOverride) ?
+ {
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ } :
+ helpers.calculateScaleRange(
+ valuesArray,
+ helpers.min([this.chart.width, this.chart.height])/2,
+ this.options.scaleFontSize,
+ this.options.scaleBeginAtZero,
+ this.options.scaleIntegersOnly
+ );
+
+ helpers.extend(
+ this.scale,
+ scaleSizes,
+ {
+ size: helpers.min([this.chart.width, this.chart.height]),
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2
+ }
+ );
+
+ },
+ update : function(){
+ this.calculateTotal(this.segments);
+
+ helpers.each(this.segments,function(segment){
+ segment.save();
+ });
+
+ this.reflow();
+ this.render();
+ },
+ reflow : function(){
+ helpers.extend(this.SegmentArc.prototype,{
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+ this.updateScaleRange(this.segments);
+ this.scale.update();
+
+ helpers.extend(this.scale,{
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2
+ });
+
+ helpers.each(this.segments, function(segment){
+ segment.update({
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
+ });
+ }, this);
+
+ },
+ draw : function(ease){
+ var easingDecimal = ease || 1;
+ //Clear & draw the canvas
+ this.clear();
+ helpers.each(this.segments,function(segment, index){
+ segment.transition({
+ circumference : this.scale.getCircumference(),
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
+ },easingDecimal);
+
+ segment.endAngle = segment.startAngle + segment.circumference;
+
+ // If we've removed the first segment we need to set the first one to
+ // start at the top.
+ if (index === 0){
+ segment.startAngle = Math.PI * 1.5;
+ }
+
+ //Check to see if it's the last segment, if not get the next and update the start angle
+ if (index < this.segments.length - 1){
+ this.segments[index+1].startAngle = segment.endAngle;
+ }
+ segment.draw();
+ }, this);
+ this.scale.draw();
+ }
+ });
+
+}).call(this);
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+
+
+ Chart.Type.extend({
+ name: "Radar",
+ defaults:{
+ //Boolean - Whether to show lines for each scale point
+ scaleShowLine : true,
+
+ //Boolean - Whether we show the angle lines out of the radar
+ angleShowLineOut : true,
+
+ //Boolean - Whether to show labels on the scale
+ scaleShowLabels : false,
+
+ // Boolean - Whether the scale should begin at zero
+ scaleBeginAtZero : true,
+
+ //String - Colour of the angle line
+ angleLineColor : "rgba(0,0,0,.1)",
+
+ //Number - Pixel width of the angle line
+ angleLineWidth : 1,
+
+ //String - Point label font declaration
+ pointLabelFontFamily : "'Arial'",
+
+ //String - Point label font weight
+ pointLabelFontStyle : "normal",
+
+ //Number - Point label font size in pixels
+ pointLabelFontSize : 10,
+
+ //String - Point label font colour
+ pointLabelFontColor : "#666",
+
+ //Boolean - Whether to show a dot for each point
+ pointDot : true,
+
+ //Number - Radius of each point dot in pixels
+ pointDotRadius : 3,
+
+ //Number - Pixel width of point dot stroke
+ pointDotStrokeWidth : 1,
+
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
+ pointHitDetectionRadius : 20,
+
+ //Boolean - Whether to show a stroke for datasets
+ datasetStroke : true,
+
+ //Number - Pixel width of dataset stroke
+ datasetStrokeWidth : 2,
+
+ //Boolean - Whether to fill the dataset with a colour
+ datasetFill : true,
+
+ //String - A legend template
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
+
+ },
+
+ initialize: function(data){
+ this.PointClass = Chart.Point.extend({
+ strokeWidth : this.options.pointDotStrokeWidth,
+ radius : this.options.pointDotRadius,
+ display: this.options.pointDot,
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
+ ctx : this.chart.ctx
+ });
+
+ this.datasets = [];
+
+ this.buildScale(data);
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
+
+ this.eachPoints(function(point){
+ point.restore(['fillColor', 'strokeColor']);
+ });
+ helpers.each(activePointsCollection, function(activePoint){
+ activePoint.fillColor = activePoint.highlightFill;
+ activePoint.strokeColor = activePoint.highlightStroke;
+ });
+
+ this.showTooltip(activePointsCollection);
+ });
+ }
+
+ //Iterate through each of the datasets, and build this into a property of the chart
+ helpers.each(data.datasets,function(dataset){
+
+ var datasetObject = {
+ label: dataset.label || null,
+ fillColor : dataset.fillColor,
+ strokeColor : dataset.strokeColor,
+ pointColor : dataset.pointColor,
+ pointStrokeColor : dataset.pointStrokeColor,
+ points : []
+ };
+
+ this.datasets.push(datasetObject);
+
+ helpers.each(dataset.data,function(dataPoint,index){
+ //Add a new point for each piece of data, passing any required data to draw.
+ var pointPosition;
+ if (!this.scale.animation){
+ pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
+ }
+ datasetObject.points.push(new this.PointClass({
+ value : dataPoint,
+ label : data.labels[index],
+ datasetLabel: dataset.label,
+ x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
+ y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
+ strokeColor : dataset.pointStrokeColor,
+ fillColor : dataset.pointColor,
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
+ }));
+ },this);
+
+ },this);
+
+ this.render();
+ },
+ eachPoints : function(callback){
+ helpers.each(this.datasets,function(dataset){
+ helpers.each(dataset.points,callback,this);
+ },this);
+ },
+
+ getPointsAtEvent : function(evt){
+ var mousePosition = helpers.getRelativePosition(evt),
+ fromCenter = helpers.getAngleFromPoint({
+ x: this.scale.xCenter,
+ y: this.scale.yCenter
+ }, mousePosition);
+
+ var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
+ pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
+ activePointsCollection = [];
+
+ // If we're at the top, make the pointIndex 0 to get the first of the array.
+ if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
+ pointIndex = 0;
+ }
+
+ if (fromCenter.distance <= this.scale.drawingArea){
+ helpers.each(this.datasets, function(dataset){
+ activePointsCollection.push(dataset.points[pointIndex]);
+ });
+ }
+
+ return activePointsCollection;
+ },
+
+ buildScale : function(data){
+ this.scale = new Chart.RadialScale({
+ display: this.options.showScale,
+ fontStyle: this.options.scaleFontStyle,
+ fontSize: this.options.scaleFontSize,
+ fontFamily: this.options.scaleFontFamily,
+ fontColor: this.options.scaleFontColor,
+ showLabels: this.options.scaleShowLabels,
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
+ backdropColor: this.options.scaleBackdropColor,
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
+ lineColor: this.options.scaleLineColor,
+ angleLineColor : this.options.angleLineColor,
+ angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
+ // Point labels at the edge of each line
+ pointLabelFontColor : this.options.pointLabelFontColor,
+ pointLabelFontSize : this.options.pointLabelFontSize,
+ pointLabelFontFamily : this.options.pointLabelFontFamily,
+ pointLabelFontStyle : this.options.pointLabelFontStyle,
+ height : this.chart.height,
+ width: this.chart.width,
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2,
+ ctx : this.chart.ctx,
+ templateString: this.options.scaleLabel,
+ labels: data.labels,
+ valuesCount: data.datasets[0].data.length
+ });
+
+ this.scale.setScaleSize();
+ this.updateScaleRange(data.datasets);
+ this.scale.buildYLabels();
+ },
+ updateScaleRange: function(datasets){
+ var valuesArray = (function(){
+ var totalDataArray = [];
+ helpers.each(datasets,function(dataset){
+ if (dataset.data){
+ totalDataArray = totalDataArray.concat(dataset.data);
+ }
+ else {
+ helpers.each(dataset.points, function(point){
+ totalDataArray.push(point.value);
+ });
+ }
+ });
+ return totalDataArray;
+ })();
+
+
+ var scaleSizes = (this.options.scaleOverride) ?
+ {
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ } :
+ helpers.calculateScaleRange(
+ valuesArray,
+ helpers.min([this.chart.width, this.chart.height])/2,
+ this.options.scaleFontSize,
+ this.options.scaleBeginAtZero,
+ this.options.scaleIntegersOnly
+ );
+
+ helpers.extend(
+ this.scale,
+ scaleSizes
+ );
+
+ },
+ addData : function(valuesArray,label){
+ //Map the values array for each of the datasets
+ this.scale.valuesCount++;
+ helpers.each(valuesArray,function(value,datasetIndex){
+ var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
+ this.datasets[datasetIndex].points.push(new this.PointClass({
+ value : value,
+ label : label,
+ x: pointPosition.x,
+ y: pointPosition.y,
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
+ fillColor : this.datasets[datasetIndex].pointColor
+ }));
+ },this);
+
+ this.scale.labels.push(label);
+
+ this.reflow();
+
+ this.update();
+ },
+ removeData : function(){
+ this.scale.valuesCount--;
+ this.scale.labels.shift();
+ helpers.each(this.datasets,function(dataset){
+ dataset.points.shift();
+ },this);
+ this.reflow();
+ this.update();
+ },
+ update : function(){
+ this.eachPoints(function(point){
+ point.save();
+ });
+ this.reflow();
+ this.render();
+ },
+ reflow: function(){
+ helpers.extend(this.scale, {
+ width : this.chart.width,
+ height: this.chart.height,
+ size : helpers.min([this.chart.width, this.chart.height]),
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2
+ });
+ this.updateScaleRange(this.datasets);
+ this.scale.setScaleSize();
+ this.scale.buildYLabels();
+ },
+ draw : function(ease){
+ var easeDecimal = ease || 1,
+ ctx = this.chart.ctx;
+ this.clear();
+ this.scale.draw();
+
+ helpers.each(this.datasets,function(dataset){
+
+ //Transition each point first so that the line and point drawing isn't out of sync
+ helpers.each(dataset.points,function(point,index){
+ if (point.hasValue()){
+ point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
+ }
+ },this);
+
+
+
+ //Draw the line between all the points
+ ctx.lineWidth = this.options.datasetStrokeWidth;
+ ctx.strokeStyle = dataset.strokeColor;
+ ctx.beginPath();
+ helpers.each(dataset.points,function(point,index){
+ if (index === 0){
+ ctx.moveTo(point.x,point.y);
+ }
+ else{
+ ctx.lineTo(point.x,point.y);
+ }
+ },this);
+ ctx.closePath();
+ ctx.stroke();
+
+ ctx.fillStyle = dataset.fillColor;
+ ctx.fill();
+
+ //Now draw the points over the line
+ //A little inefficient double looping, but better than the line
+ //lagging behind the point positions
+ helpers.each(dataset.points,function(point){
+ if (point.hasValue()){
+ point.draw();
+ }
+ });
+
+ },this);
+
+ }
+
+ });
+
+
+
+
+
+}).call(this); \ No newline at end of file
diff --git a/vendor/assets/javascripts/chart-lib.min.js b/vendor/assets/javascripts/chart-lib.min.js
deleted file mode 100644
index 3a0a2c87345..00000000000
--- a/vendor/assets/javascripts/chart-lib.min.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/*!
- * Chart.js
- * http://chartjs.org/
- * Version: 1.0.2
- *
- * Copyright 2015 Nick Downie
- * Released under the MIT license
- * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
- */
-(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n<t.length;n++)i.apply(e,[t[n],n].concat(s))}else for(var o in t)i.apply(e,[t[o],o].concat(s))},o=s.clone=function(t){var i={};return n(t,function(e,s){t.hasOwnProperty(s)&&(i[s]=e)}),i},a=s.extend=function(t){return n(Array.prototype.slice.call(arguments,1),function(i){n(i,function(e,s){i.hasOwnProperty(s)&&(t[s]=e)})}),t},h=s.merge=function(){var t=Array.prototype.slice.call(arguments,0);return t.unshift({}),a.apply(null,t)},l=s.indexOf=function(t,i){if(Array.prototype.indexOf)return t.indexOf(i);for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1},r=(s.where=function(t,i){var e=[];return s.each(t,function(t){i(t)&&e.push(t)}),e},s.findNextWhere=function(t,i,e){e||(e=-1);for(var s=e+1;s<t.length;s++){var n=t[s];if(i(n))return n}},s.findPreviousWhere=function(t,i,e){e||(e=t.length);for(var s=e-1;s>=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),-(s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)))},easeOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),s*Math.pow(2,-10*t)*Math.sin(2*(1*t-i)*Math.PI/e)+1)},easeInOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:2==(t/=.5)?1:(e||(e=.3*1.5),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),1>t?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)<Math.pow(e,2)},draw:function(){if(this.display){var t=this.ctx;t.beginPath(),t.arc(this.x,this.y,this.radius,0,2*Math.PI),t.closePath(),t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.fillStyle=this.fillColor,t.fill(),t.stroke()}}}),e.Arc=e.Element.extend({inRange:function(t,i){var e=s.getAngleFromPoint(this,{x:t,y:i}),n=e.angle>=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),t<this.yLabelWidth&&this.calculateXLabelRotation()},calculateXLabelRotation:function(){this.ctx.font=this.font;var t,i,e=this.ctx.measureText(this.xLabels[0]).width,s=this.ctx.measureText(this.xLabels[this.xLabels.length-1]).width;if(this.xScalePaddingRight=s/2+3,this.xScalePaddingLeft=e/2>this.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;i<this.valuesCount;i++)t=this.getPointPosition(i,d),e=this.ctx.measureText(C(this.templateString,{value:this.labels[i]})).width+5,0===i||i===this.valuesCount/2?(s=e/2,t.x+s>p&&(p=t.x+s,n=i),t.x-s<g&&(g=t.x-s,a=i)):i<this.valuesCount/2?t.x+e>p&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e<g&&(g=t.x-e,a=i);l=g,r=Math.ceil(p-this.width),o=this.getIndexAngle(n),h=this.getIndexAngle(a),c=r/Math.sin(o+Math.PI/2),u=l/Math.sin(h+Math.PI/2),c=f(c)?c:0,u=f(u)?u:0,this.drawingArea=d-(u+c)/2,this.setCenterPoint(u,c)},setCenterPoint:function(t,i){var e=this.width-i-this.drawingArea,s=t+this.drawingArea;this.xCenter=(s+e)/2,this.yCenter=this.height/2},getIndexAngle:function(t){var i=2*Math.PI/this.valuesCount;return t*i-Math.PI/2},getPointPosition:function(t,i){var e=this.getIndexAngle(t);return{x:Math.cos(e)*i+this.xCenter,y:Math.sin(e)*i+this.yCenter}},draw:function(){if(this.display){var t=this.ctx;if(n(this.yLabels,function(i,e){if(e>0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a<this.valuesCount;a++)s=this.getPointPosition(a,this.calculateCenterOffset(this.min+e*this.stepValue)),0===a?t.moveTo(s.x,s.y):t.lineTo(s.x,s.y);t.closePath(),t.stroke()}if(this.showLabels){if(t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.showLabelBackdrop){var h=t.measureText(i).width;t.fillStyle=this.backdropColor,t.fillRect(this.xCenter-h/2-this.backdropPaddingX,o-this.fontSize/2-this.backdropPaddingY,h+2*this.backdropPaddingX,this.fontSize+2*this.backdropPaddingY)}t.textAlign="center",t.textBaseline="middle",t.fillStyle=this.fontColor,t.fillText(i,this.xCenter,o)}}},this),!this.lineArc){t.lineWidth=this.angleLineWidth,t.strokeStyle=this.angleLineColor;for(var i=this.valuesCount-1;i>=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<this.datasets.length;a++)for(i=0;i<this.datasets[a].bars.length;i++)if(this.datasets[a].bars[i].inRange(n.x,n.y))return e.each(this.datasets,o),s;return s},buildScale:function(t){var i=this,s=function(){var t=[];return i.eachBars(function(i){t.push(i.value)}),t},n={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(s(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.barShowStroke?this.options.barStrokeWidth:0,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(n,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new this.ScaleClass(n)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].bars.push(new this.BarClass({value:t,label:i,x:this.scale.calculateBarX(this.datasets.length,e,this.scale.valuesCount+1),y:this.scale.endPoint,width:this.scale.calculateBarWidth(this.datasets.length),base:this.scale.endPoint,strokeColor:this.datasets[e].strokeColor,fillColor:this.datasets[e].fillColor}))
-},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.bars.shift()},this),this.update()},reflow:function(){e.extend(this.BarClass.prototype,{y:this.scale.endPoint,base:this.scale.endPoint});var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();this.chart.ctx;this.scale.draw(i),e.each(this.datasets,function(t,s){e.each(t.bars,function(t,e){t.hasValue()&&(t.base=this.scale.endPoint,t.transition({x:this.scale.calculateBarX(this.datasets.length,s,e),y:this.scale.calculateY(t.value),width:this.scale.calculateBarWidth(this.datasets.length)},i).draw())},this)},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle)},this)}}),i.types.Doughnut.extend({name:"Pie",defaults:e.merge(s,{percentageInnerCutout:0})})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,bezierCurve:!0,bezierCurveTension:.4,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)<Math.pow(this.radius+this.hitDetectionRadius,2)}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this),this.buildScale(t.labels),this.eachPoints(function(t,i){e.extend(t,{x:this.scale.calculateX(i),y:this.scale.endPoint}),t.save()},this)},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachPoints(function(t){t.save()}),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.datasets,function(t){e.each(t.points,function(t){t.inRange(s.x,s.y)&&i.push(t)})},this),i},buildScale:function(t){var s=this,n=function(){var t=[];return s.eachPoints(function(i){t.push(i.value)}),t},o={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(n(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.pointDotRadius+this.options.pointDotStrokeWidth,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(o,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new i.Scale(o)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:this.scale.calculateX(this.scale.valuesCount+1),y:this.scale.endPoint,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.points.shift()},this),this.update()},reflow:function(){var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();var s=this.chart.ctx,n=function(t){return null!==t.value},o=function(t,i,s){return e.findNextWhere(i,n,s)||t},a=function(t,i,s){return e.findPreviousWhere(i,n,s)||t};this.scale.draw(i),e.each(this.datasets,function(t){var h=e.where(t.points,n);e.each(t.points,function(t,e){t.hasValue()&&t.transition({y:this.scale.calculateY(t.value),x:this.scale.calculateX(e)},i)},this),this.options.bezierCurve&&e.each(h,function(t,i){var s=i>0&&i<h.length-1?this.options.bezierCurveTension:0;t.controlPoints=e.splineCurve(a(t,h,i),t,o(t,h,i),s),t.controlPoints.outer.y>this.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.y<this.scale.startPoint&&(t.controlPoints.outer.y=this.scale.startPoint),t.controlPoints.inner.y>this.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y<this.scale.startPoint&&(t.controlPoints.inner.y=this.scale.startPoint)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(h,function(t,i){if(0===i)s.moveTo(t.x,t.y);else if(this.options.bezierCurve){var e=a(t,h,i);s.bezierCurveTo(e.controlPoints.outer.x,e.controlPoints.outer.y,t.controlPoints.inner.x,t.controlPoints.inner.y,t.x,t.y)}else s.lineTo(t.x,t.y)},this),s.stroke(),this.options.datasetFill&&h.length>0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle),t.draw()},this),this.scale.draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.Type.extend({name:"Radar",defaults:{scaleShowLine:!0,angleShowLineOut:!0,scaleShowLabels:!1,scaleBeginAtZero:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:10,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this); \ No newline at end of file
diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.js b/vendor/assets/javascripts/fuzzaldrin-plus.js
new file mode 100644
index 00000000000..1985e3f8f6c
--- /dev/null
+++ b/vendor/assets/javascripts/fuzzaldrin-plus.js
@@ -0,0 +1,1161 @@
+/*!
+ * fuzzaldrin-plus.js - 0.3.1
+ * https://github.com/jeancroy/fuzzaldrin-plus
+ *
+ * Copyright 2016 - Jean Christophe Roy
+ * Released under the MIT license
+ * https://github.com/jeancroy/fuzzaldrin-plus/raw/master/LICENSE.md
+ */
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+fuzzaldrinPlus = require('fuzzaldrin-plus');
+
+},{"fuzzaldrin-plus":3}],2:[function(require,module,exports){
+(function() {
+ var PathSeparator, legacy_scorer, pluckCandidates, scorer, sortCandidates;
+
+ scorer = require('./scorer');
+
+ legacy_scorer = require('./legacy');
+
+ pluckCandidates = function(a) {
+ return a.candidate;
+ };
+
+ sortCandidates = function(a, b) {
+ return b.score - a.score;
+ };
+
+ PathSeparator = require('path').sep;
+
+ module.exports = function(candidates, query, _arg) {
+ var allowErrors, bAllowErrors, bKey, candidate, coreQuery, key, legacy, maxInners, maxResults, prepQuery, queryHasSlashes, score, scoredCandidates, spotLeft, string, _i, _j, _len, _len1, _ref;
+ _ref = _arg != null ? _arg : {}, key = _ref.key, maxResults = _ref.maxResults, maxInners = _ref.maxInners, allowErrors = _ref.allowErrors, legacy = _ref.legacy;
+ scoredCandidates = [];
+ spotLeft = (maxInners != null) && maxInners > 0 ? maxInners : candidates.length;
+ bAllowErrors = !!allowErrors;
+ bKey = key != null;
+ prepQuery = scorer.prepQuery(query);
+ if (!legacy) {
+ for (_i = 0, _len = candidates.length; _i < _len; _i++) {
+ candidate = candidates[_i];
+ string = bKey ? candidate[key] : candidate;
+ if (!string) {
+ continue;
+ }
+ score = scorer.score(string, query, prepQuery, bAllowErrors);
+ if (score > 0) {
+ scoredCandidates.push({
+ candidate: candidate,
+ score: score
+ });
+ if (!--spotLeft) {
+ break;
+ }
+ }
+ }
+ } else {
+ queryHasSlashes = prepQuery.depth > 0;
+ coreQuery = prepQuery.core;
+ for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) {
+ candidate = candidates[_j];
+ string = key != null ? candidate[key] : candidate;
+ if (!string) {
+ continue;
+ }
+ score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+ if (!queryHasSlashes) {
+ score = legacy_scorer.basenameScore(string, coreQuery, score);
+ }
+ if (score > 0) {
+ scoredCandidates.push({
+ candidate: candidate,
+ score: score
+ });
+ }
+ }
+ }
+ scoredCandidates.sort(sortCandidates);
+ candidates = scoredCandidates.map(pluckCandidates);
+ if (maxResults != null) {
+ candidates = candidates.slice(0, maxResults);
+ }
+ return candidates;
+ };
+
+}).call(this);
+
+},{"./legacy":4,"./scorer":6,"path":7}],3:[function(require,module,exports){
+(function() {
+ var PathSeparator, filter, legacy_scorer, matcher, prepQueryCache, scorer;
+
+ scorer = require('./scorer');
+
+ legacy_scorer = require('./legacy');
+
+ filter = require('./filter');
+
+ matcher = require('./matcher');
+
+ PathSeparator = require('path').sep;
+
+ prepQueryCache = null;
+
+ module.exports = {
+ filter: function(candidates, query, options) {
+ if (!((query != null ? query.length : void 0) && (candidates != null ? candidates.length : void 0))) {
+ return [];
+ }
+ return filter(candidates, query, options);
+ },
+ prepQuery: function(query) {
+ return scorer.prepQuery(query);
+ },
+ score: function(string, query, prepQuery, _arg) {
+ var allowErrors, coreQuery, legacy, queryHasSlashes, score, _ref;
+ _ref = _arg != null ? _arg : {}, allowErrors = _ref.allowErrors, legacy = _ref.legacy;
+ if (!((string != null ? string.length : void 0) && (query != null ? query.length : void 0))) {
+ return 0;
+ }
+ if (prepQuery == null) {
+ prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+ }
+ if (!legacy) {
+ score = scorer.score(string, query, prepQuery, !!allowErrors);
+ } else {
+ queryHasSlashes = prepQuery.depth > 0;
+ coreQuery = prepQuery.core;
+ score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+ if (!queryHasSlashes) {
+ score = legacy_scorer.basenameScore(string, coreQuery, score);
+ }
+ }
+ return score;
+ },
+ match: function(string, query, prepQuery, _arg) {
+ var allowErrors, baseMatches, matches, query_lw, string_lw, _i, _ref, _results;
+ allowErrors = (_arg != null ? _arg : {}).allowErrors;
+ if (!string) {
+ return [];
+ }
+ if (!query) {
+ return [];
+ }
+ if (string === query) {
+ return (function() {
+ _results = [];
+ for (var _i = 0, _ref = string.length; 0 <= _ref ? _i < _ref : _i > _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
+ return _results;
+ }).apply(this);
+ }
+ if (prepQuery == null) {
+ prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+ }
+ if (!(allowErrors || scorer.isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+ return [];
+ }
+ string_lw = string.toLowerCase();
+ query_lw = prepQuery.query_lw;
+ matches = matcher.match(string, string_lw, prepQuery);
+ if (matches.length === 0) {
+ return matches;
+ }
+ if (string.indexOf(PathSeparator) > -1) {
+ baseMatches = matcher.basenameMatch(string, string_lw, prepQuery);
+ matches = matcher.mergeMatches(matches, baseMatches);
+ }
+ return matches;
+ }
+ };
+
+}).call(this);
+
+},{"./filter":2,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],4:[function(require,module,exports){
+(function() {
+ var PathSeparator, queryIsLastPathSegment;
+
+ PathSeparator = require('path').sep;
+
+ exports.basenameScore = function(string, query, score) {
+ var base, depth, index, lastCharacter, segmentCount, slashCount;
+ index = string.length - 1;
+ while (string[index] === PathSeparator) {
+ index--;
+ }
+ slashCount = 0;
+ lastCharacter = index;
+ base = null;
+ while (index >= 0) {
+ if (string[index] === PathSeparator) {
+ slashCount++;
+ if (base == null) {
+ base = string.substring(index + 1, lastCharacter + 1);
+ }
+ } else if (index === 0) {
+ if (lastCharacter < string.length - 1) {
+ if (base == null) {
+ base = string.substring(0, lastCharacter + 1);
+ }
+ } else {
+ if (base == null) {
+ base = string;
+ }
+ }
+ }
+ index--;
+ }
+ if (base === string) {
+ score *= 2;
+ } else if (base) {
+ score += exports.score(base, query);
+ }
+ segmentCount = slashCount + 1;
+ depth = Math.max(1, 10 - segmentCount);
+ score *= depth * 0.01;
+ return score;
+ };
+
+ exports.score = function(string, query) {
+ var character, characterScore, indexInQuery, indexInString, lowerCaseIndex, minIndex, queryLength, queryScore, stringLength, totalCharacterScore, upperCaseIndex, _ref;
+ if (string === query) {
+ return 1;
+ }
+ if (queryIsLastPathSegment(string, query)) {
+ return 1;
+ }
+ totalCharacterScore = 0;
+ queryLength = query.length;
+ stringLength = string.length;
+ indexInQuery = 0;
+ indexInString = 0;
+ while (indexInQuery < queryLength) {
+ character = query[indexInQuery++];
+ lowerCaseIndex = string.indexOf(character.toLowerCase());
+ upperCaseIndex = string.indexOf(character.toUpperCase());
+ minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+ if (minIndex === -1) {
+ minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+ }
+ indexInString = minIndex;
+ if (indexInString === -1) {
+ return 0;
+ }
+ characterScore = 0.1;
+ if (string[indexInString] === character) {
+ characterScore += 0.1;
+ }
+ if (indexInString === 0 || string[indexInString - 1] === PathSeparator) {
+ characterScore += 0.8;
+ } else if ((_ref = string[indexInString - 1]) === '-' || _ref === '_' || _ref === ' ') {
+ characterScore += 0.7;
+ }
+ string = string.substring(indexInString + 1, stringLength);
+ totalCharacterScore += characterScore;
+ }
+ queryScore = totalCharacterScore / queryLength;
+ return ((queryScore * (queryLength / stringLength)) + queryScore) / 2;
+ };
+
+ queryIsLastPathSegment = function(string, query) {
+ if (string[string.length - query.length - 1] === PathSeparator) {
+ return string.lastIndexOf(query) === string.length - query.length;
+ }
+ };
+
+ exports.match = function(string, query, stringOffset) {
+ var character, indexInQuery, indexInString, lowerCaseIndex, matches, minIndex, queryLength, stringLength, upperCaseIndex, _i, _ref, _results;
+ if (stringOffset == null) {
+ stringOffset = 0;
+ }
+ if (string === query) {
+ return (function() {
+ _results = [];
+ for (var _i = stringOffset, _ref = stringOffset + string.length; stringOffset <= _ref ? _i < _ref : _i > _ref; stringOffset <= _ref ? _i++ : _i--){ _results.push(_i); }
+ return _results;
+ }).apply(this);
+ }
+ queryLength = query.length;
+ stringLength = string.length;
+ indexInQuery = 0;
+ indexInString = 0;
+ matches = [];
+ while (indexInQuery < queryLength) {
+ character = query[indexInQuery++];
+ lowerCaseIndex = string.indexOf(character.toLowerCase());
+ upperCaseIndex = string.indexOf(character.toUpperCase());
+ minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+ if (minIndex === -1) {
+ minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+ }
+ indexInString = minIndex;
+ if (indexInString === -1) {
+ return [];
+ }
+ matches.push(stringOffset + indexInString);
+ stringOffset += indexInString + 1;
+ string = string.substring(indexInString + 1, stringLength);
+ }
+ return matches;
+ };
+
+}).call(this);
+
+},{"path":7}],5:[function(require,module,exports){
+(function() {
+ var PathSeparator, scorer;
+
+ PathSeparator = require('path').sep;
+
+ scorer = require('./scorer');
+
+ exports.basenameMatch = function(subject, subject_lw, prepQuery) {
+ var basePos, depth, end;
+ end = subject.length - 1;
+ while (subject[end] === PathSeparator) {
+ end--;
+ }
+ basePos = subject.lastIndexOf(PathSeparator, end);
+ if (basePos === -1) {
+ return [];
+ }
+ depth = prepQuery.depth;
+ while (depth-- > 0) {
+ basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+ if (basePos === -1) {
+ return [];
+ }
+ }
+ basePos++;
+ end++;
+ return exports.match(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery, basePos);
+ };
+
+ exports.mergeMatches = function(a, b) {
+ var ai, bj, i, j, m, n, out;
+ m = a.length;
+ n = b.length;
+ if (n === 0) {
+ return a.slice();
+ }
+ if (m === 0) {
+ return b.slice();
+ }
+ i = -1;
+ j = 0;
+ bj = b[j];
+ out = [];
+ while (++i < m) {
+ ai = a[i];
+ while (bj <= ai && ++j < n) {
+ if (bj < ai) {
+ out.push(bj);
+ }
+ bj = b[j];
+ }
+ out.push(ai);
+ }
+ while (j < n) {
+ out.push(b[j++]);
+ }
+ return out;
+ };
+
+ exports.match = function(subject, subject_lw, prepQuery, offset) {
+ var DIAGONAL, LEFT, STOP, UP, acro_score, align, backtrack, csc_diag, csc_row, csc_score, i, j, m, matches, move, n, pos, query, query_lw, score, score_diag, score_row, score_up, si_lw, start, trace;
+ if (offset == null) {
+ offset = 0;
+ }
+ query = prepQuery.query;
+ query_lw = prepQuery.query_lw;
+ m = subject.length;
+ n = query.length;
+ acro_score = scorer.scoreAcronyms(subject, subject_lw, query, query_lw).score;
+ score_row = new Array(n);
+ csc_row = new Array(n);
+ STOP = 0;
+ UP = 1;
+ LEFT = 2;
+ DIAGONAL = 3;
+ trace = new Array(m * n);
+ pos = -1;
+ j = -1;
+ while (++j < n) {
+ score_row[j] = 0;
+ csc_row[j] = 0;
+ }
+ i = -1;
+ while (++i < m) {
+ score = 0;
+ score_up = 0;
+ csc_diag = 0;
+ si_lw = subject_lw[i];
+ j = -1;
+ while (++j < n) {
+ csc_score = 0;
+ align = 0;
+ score_diag = score_up;
+ if (query_lw[j] === si_lw) {
+ start = scorer.isWordStart(i, subject, subject_lw);
+ csc_score = csc_diag > 0 ? csc_diag : scorer.scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+ align = score_diag + scorer.scoreCharacter(i, j, start, acro_score, csc_score);
+ }
+ score_up = score_row[j];
+ csc_diag = csc_row[j];
+ if (score > score_up) {
+ move = LEFT;
+ } else {
+ score = score_up;
+ move = UP;
+ }
+ if (align > score) {
+ score = align;
+ move = DIAGONAL;
+ } else {
+ csc_score = 0;
+ }
+ score_row[j] = score;
+ csc_row[j] = csc_score;
+ trace[++pos] = score > 0 ? move : STOP;
+ }
+ }
+ i = m - 1;
+ j = n - 1;
+ pos = i * n + j;
+ backtrack = true;
+ matches = [];
+ while (backtrack && i >= 0 && j >= 0) {
+ switch (trace[pos]) {
+ case UP:
+ i--;
+ pos -= n;
+ break;
+ case LEFT:
+ j--;
+ pos--;
+ break;
+ case DIAGONAL:
+ matches.push(i + offset);
+ j--;
+ i--;
+ pos -= n + 1;
+ break;
+ default:
+ backtrack = false;
+ }
+ }
+ matches.reverse();
+ return matches;
+ };
+
+}).call(this);
+
+},{"./scorer":6,"path":7}],6:[function(require,module,exports){
+(function() {
+ var AcronymResult, PathSeparator, Query, basenameScore, coreChars, countDir, doScore, emptyAcronymResult, file_coeff, isMatch, isSeparator, isWordEnd, isWordStart, miss_coeff, opt_char_re, pos_bonus, scoreAcronyms, scoreCharacter, scoreConsecutives, scoreExact, scoreExactMatch, scorePattern, scorePosition, scoreSize, tau_depth, tau_size, truncatedUpperCase, wm;
+
+ PathSeparator = require('path').sep;
+
+ wm = 150;
+
+ pos_bonus = 20;
+
+ tau_depth = 13;
+
+ tau_size = 85;
+
+ file_coeff = 1.2;
+
+ miss_coeff = 0.75;
+
+ opt_char_re = /[ _\-:\/\\]/g;
+
+ exports.coreChars = coreChars = function(query) {
+ return query.replace(opt_char_re, '');
+ };
+
+ exports.score = function(string, query, prepQuery, allowErrors) {
+ var score, string_lw;
+ if (prepQuery == null) {
+ prepQuery = new Query(query);
+ }
+ if (allowErrors == null) {
+ allowErrors = false;
+ }
+ if (!(allowErrors || isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+ return 0;
+ }
+ string_lw = string.toLowerCase();
+ score = doScore(string, string_lw, prepQuery);
+ return Math.ceil(basenameScore(string, string_lw, prepQuery, score));
+ };
+
+ Query = (function() {
+ function Query(query) {
+ if (!(query != null ? query.length : void 0)) {
+ return null;
+ }
+ this.query = query;
+ this.query_lw = query.toLowerCase();
+ this.core = coreChars(query);
+ this.core_lw = this.core.toLowerCase();
+ this.core_up = truncatedUpperCase(this.core);
+ this.depth = countDir(query, query.length);
+ }
+
+ return Query;
+
+ })();
+
+ exports.prepQuery = function(query) {
+ return new Query(query);
+ };
+
+ exports.isMatch = isMatch = function(subject, query_lw, query_up) {
+ var i, j, m, n, qj_lw, qj_up, si;
+ m = subject.length;
+ n = query_lw.length;
+ if (!m || n > m) {
+ return false;
+ }
+ i = -1;
+ j = -1;
+ while (++j < n) {
+ qj_lw = query_lw[j];
+ qj_up = query_up[j];
+ while (++i < m) {
+ si = subject[i];
+ if (si === qj_lw || si === qj_up) {
+ break;
+ }
+ }
+ if (i === m) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ doScore = function(subject, subject_lw, prepQuery) {
+ var acro, acro_score, align, csc_diag, csc_row, csc_score, i, j, m, miss_budget, miss_left, mm, n, pos, query, query_lw, record_miss, score, score_diag, score_row, score_up, si_lw, start, sz;
+ query = prepQuery.query;
+ query_lw = prepQuery.query_lw;
+ m = subject.length;
+ n = query.length;
+ acro = scoreAcronyms(subject, subject_lw, query, query_lw);
+ acro_score = acro.score;
+ if (acro.count === n) {
+ return scoreExact(n, m, acro_score, acro.pos);
+ }
+ pos = subject_lw.indexOf(query_lw);
+ if (pos > -1) {
+ return scoreExactMatch(subject, subject_lw, query, query_lw, pos, n, m);
+ }
+ score_row = new Array(n);
+ csc_row = new Array(n);
+ sz = scoreSize(n, m);
+ miss_budget = Math.ceil(miss_coeff * n) + 5;
+ miss_left = miss_budget;
+ j = -1;
+ while (++j < n) {
+ score_row[j] = 0;
+ csc_row[j] = 0;
+ }
+ i = subject_lw.indexOf(query_lw[0]);
+ if (i > -1) {
+ i--;
+ }
+ mm = subject_lw.lastIndexOf(query_lw[n - 1], m);
+ if (mm > i) {
+ m = mm + 1;
+ }
+ while (++i < m) {
+ score = 0;
+ score_diag = 0;
+ csc_diag = 0;
+ si_lw = subject_lw[i];
+ record_miss = true;
+ j = -1;
+ while (++j < n) {
+ score_up = score_row[j];
+ if (score_up > score) {
+ score = score_up;
+ }
+ csc_score = 0;
+ if (query_lw[j] === si_lw) {
+ start = isWordStart(i, subject, subject_lw);
+ csc_score = csc_diag > 0 ? csc_diag : scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+ align = score_diag + scoreCharacter(i, j, start, acro_score, csc_score);
+ if (align > score) {
+ score = align;
+ miss_left = miss_budget;
+ } else {
+ if (record_miss && --miss_left <= 0) {
+ return score_row[n - 1] * sz;
+ }
+ record_miss = false;
+ }
+ }
+ score_diag = score_up;
+ csc_diag = csc_row[j];
+ csc_row[j] = csc_score;
+ score_row[j] = score;
+ }
+ }
+ return score * sz;
+ };
+
+ exports.isWordStart = isWordStart = function(pos, subject, subject_lw) {
+ var curr_s, prev_s;
+ if (pos === 0) {
+ return true;
+ }
+ curr_s = subject[pos];
+ prev_s = subject[pos - 1];
+ return isSeparator(curr_s) || isSeparator(prev_s) || (curr_s !== subject_lw[pos] && prev_s === subject_lw[pos - 1]);
+ };
+
+ exports.isWordEnd = isWordEnd = function(pos, subject, subject_lw, len) {
+ var curr_s, next_s;
+ if (pos === len - 1) {
+ return true;
+ }
+ curr_s = subject[pos];
+ next_s = subject[pos + 1];
+ return isSeparator(curr_s) || isSeparator(next_s) || (curr_s === subject_lw[pos] && next_s !== subject_lw[pos + 1]);
+ };
+
+ isSeparator = function(c) {
+ return c === ' ' || c === '.' || c === '-' || c === '_' || c === '/' || c === '\\';
+ };
+
+ scorePosition = function(pos) {
+ var sc;
+ if (pos < pos_bonus) {
+ sc = pos_bonus - pos;
+ return 100 + sc * sc;
+ } else {
+ return Math.max(100 + pos_bonus - pos, 0);
+ }
+ };
+
+ scoreSize = function(n, m) {
+ return tau_size / (tau_size + Math.abs(m - n));
+ };
+
+ scoreExact = function(n, m, quality, pos) {
+ return 2 * n * (wm * quality + scorePosition(pos)) * scoreSize(n, m);
+ };
+
+ exports.scorePattern = scorePattern = function(count, len, sameCase, start, end) {
+ var bonus, sz;
+ sz = count;
+ bonus = 6;
+ if (sameCase === count) {
+ bonus += 2;
+ }
+ if (start) {
+ bonus += 3;
+ }
+ if (end) {
+ bonus += 1;
+ }
+ if (count === len) {
+ if (start) {
+ if (sameCase === len) {
+ sz += 2;
+ } else {
+ sz += 1;
+ }
+ }
+ if (end) {
+ bonus += 1;
+ }
+ }
+ return sameCase + sz * (sz + bonus);
+ };
+
+ exports.scoreCharacter = scoreCharacter = function(i, j, start, acro_score, csc_score) {
+ var posBonus;
+ posBonus = scorePosition(i);
+ if (start) {
+ return posBonus + wm * ((acro_score > csc_score ? acro_score : csc_score) + 10);
+ }
+ return posBonus + wm * csc_score;
+ };
+
+ exports.scoreConsecutives = scoreConsecutives = function(subject, subject_lw, query, query_lw, i, j, start) {
+ var k, m, mi, n, nj, sameCase, startPos, sz;
+ m = subject.length;
+ n = query.length;
+ mi = m - i;
+ nj = n - j;
+ k = mi < nj ? mi : nj;
+ startPos = i;
+ sameCase = 0;
+ sz = 0;
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ while (++sz < k && query_lw[++j] === subject_lw[++i]) {
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ }
+ if (sz === 1) {
+ return 1 + 2 * sameCase;
+ }
+ return scorePattern(sz, n, sameCase, start, isWordEnd(i, subject, subject_lw, m));
+ };
+
+ exports.scoreExactMatch = scoreExactMatch = function(subject, subject_lw, query, query_lw, pos, n, m) {
+ var end, i, pos2, sameCase, start;
+ start = isWordStart(pos, subject, subject_lw);
+ if (!start) {
+ pos2 = subject_lw.indexOf(query_lw, pos + 1);
+ if (pos2 > -1) {
+ start = isWordStart(pos2, subject, subject_lw);
+ if (start) {
+ pos = pos2;
+ }
+ }
+ }
+ i = -1;
+ sameCase = 0;
+ while (++i < n) {
+ if (query[pos + i] === subject[i]) {
+ sameCase++;
+ }
+ }
+ end = isWordEnd(pos + n - 1, subject, subject_lw, m);
+ return scoreExact(n, m, scorePattern(n, n, sameCase, start, end), pos);
+ };
+
+ AcronymResult = (function() {
+ function AcronymResult(score, pos, count) {
+ this.score = score;
+ this.pos = pos;
+ this.count = count;
+ }
+
+ return AcronymResult;
+
+ })();
+
+ emptyAcronymResult = new AcronymResult(0, 0.1, 0);
+
+ exports.scoreAcronyms = scoreAcronyms = function(subject, subject_lw, query, query_lw) {
+ var count, i, j, m, n, pos, qj_lw, sameCase, score;
+ m = subject.length;
+ n = query.length;
+ if (!(m > 1 && n > 1)) {
+ return emptyAcronymResult;
+ }
+ count = 0;
+ pos = 0;
+ sameCase = 0;
+ i = -1;
+ j = -1;
+ while (++j < n) {
+ qj_lw = query_lw[j];
+ while (++i < m) {
+ if (qj_lw === subject_lw[i] && isWordStart(i, subject, subject_lw)) {
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ pos += i;
+ count++;
+ break;
+ }
+ }
+ if (i === m) {
+ break;
+ }
+ }
+ if (count < 2) {
+ return emptyAcronymResult;
+ }
+ score = scorePattern(count, n, sameCase, true, false);
+ return new AcronymResult(score, pos / count, count);
+ };
+
+ basenameScore = function(subject, subject_lw, prepQuery, fullPathScore) {
+ var alpha, basePathScore, basePos, depth, end;
+ if (fullPathScore === 0) {
+ return 0;
+ }
+ end = subject.length - 1;
+ while (subject[end] === PathSeparator) {
+ end--;
+ }
+ basePos = subject.lastIndexOf(PathSeparator, end);
+ if (basePos === -1) {
+ return fullPathScore;
+ }
+ depth = prepQuery.depth;
+ while (depth-- > 0) {
+ basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+ if (basePos === -1) {
+ return fullPathScore;
+ }
+ }
+ basePos++;
+ end++;
+ basePathScore = doScore(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery);
+ alpha = 0.5 * tau_depth / (tau_depth + countDir(subject, end + 1));
+ return alpha * basePathScore + (1 - alpha) * fullPathScore * scoreSize(0, file_coeff * (end - basePos));
+ };
+
+ exports.countDir = countDir = function(path, end) {
+ var count, i;
+ if (end < 1) {
+ return 0;
+ }
+ count = 0;
+ i = -1;
+ while (++i < end && path[i] === PathSeparator) {
+ continue;
+ }
+ while (++i < end) {
+ if (path[i] === PathSeparator) {
+ count++;
+ while (++i < end && path[i] === PathSeparator) {
+ continue;
+ }
+ }
+ }
+ return count;
+ };
+
+ truncatedUpperCase = function(str) {
+ var char, upper, _i, _len;
+ upper = "";
+ for (_i = 0, _len = str.length; _i < _len; _i++) {
+ char = str[_i];
+ upper += char.toUpperCase()[0];
+ }
+ return upper;
+ };
+
+}).call(this);
+
+},{"path":7}],7:[function(require,module,exports){
+(function (process){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length - 1; i >= 0; i--) {
+ var last = parts[i];
+ if (last === '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up--; up) {
+ parts.unshift('..');
+ }
+ }
+
+ return parts;
+}
+
+// Split a filename into [root, dir, basename, ext], unix version
+// 'root' is just a slash, or nothing.
+var splitPathRe =
+ /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+var splitPath = function(filename) {
+ return splitPathRe.exec(filename).slice(1);
+};
+
+// path.resolve([from ...], to)
+// posix version
+exports.resolve = function() {
+ var resolvedPath = '',
+ resolvedAbsolute = false;
+
+ for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+ var path = (i >= 0) ? arguments[i] : process.cwd();
+
+ // Skip empty and invalid entries
+ if (typeof path !== 'string') {
+ throw new TypeError('Arguments to path.resolve must be strings');
+ } else if (!path) {
+ continue;
+ }
+
+ resolvedPath = path + '/' + resolvedPath;
+ resolvedAbsolute = path.charAt(0) === '/';
+ }
+
+ // At this point the path should be resolved to a full absolute path, but
+ // handle relative paths to be safe (might happen when process.cwd() fails)
+
+ // Normalize the path
+ resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+ return !!p;
+ }), !resolvedAbsolute).join('/');
+
+ return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+};
+
+// path.normalize(path)
+// posix version
+exports.normalize = function(path) {
+ var isAbsolute = exports.isAbsolute(path),
+ trailingSlash = substr(path, -1) === '/';
+
+ // Normalize the path
+ path = normalizeArray(filter(path.split('/'), function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ if (path && trailingSlash) {
+ path += '/';
+ }
+
+ return (isAbsolute ? '/' : '') + path;
+};
+
+// posix version
+exports.isAbsolute = function(path) {
+ return path.charAt(0) === '/';
+};
+
+// posix version
+exports.join = function() {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return exports.normalize(filter(paths, function(p, index) {
+ if (typeof p !== 'string') {
+ throw new TypeError('Arguments to path.join must be strings');
+ }
+ return p;
+ }).join('/'));
+};
+
+
+// path.relative(from, to)
+// posix version
+exports.relative = function(from, to) {
+ from = exports.resolve(from).substr(1);
+ to = exports.resolve(to).substr(1);
+
+ function trim(arr) {
+ var start = 0;
+ for (; start < arr.length; start++) {
+ if (arr[start] !== '') break;
+ }
+
+ var end = arr.length - 1;
+ for (; end >= 0; end--) {
+ if (arr[end] !== '') break;
+ }
+
+ if (start > end) return [];
+ return arr.slice(start, end - start + 1);
+ }
+
+ var fromParts = trim(from.split('/'));
+ var toParts = trim(to.split('/'));
+
+ var length = Math.min(fromParts.length, toParts.length);
+ var samePartsLength = length;
+ for (var i = 0; i < length; i++) {
+ if (fromParts[i] !== toParts[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+
+ var outputParts = [];
+ for (var i = samePartsLength; i < fromParts.length; i++) {
+ outputParts.push('..');
+ }
+
+ outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+ return outputParts.join('/');
+};
+
+exports.sep = '/';
+exports.delimiter = ':';
+
+exports.dirname = function(path) {
+ var result = splitPath(path),
+ root = result[0],
+ dir = result[1];
+
+ if (!root && !dir) {
+ // No dirname whatsoever
+ return '.';
+ }
+
+ if (dir) {
+ // It has a dirname, strip trailing slash
+ dir = dir.substr(0, dir.length - 1);
+ }
+
+ return root + dir;
+};
+
+
+exports.basename = function(path, ext) {
+ var f = splitPath(path)[2];
+ // TODO: make this comparison case-insensitive on windows?
+ if (ext && f.substr(-1 * ext.length) === ext) {
+ f = f.substr(0, f.length - ext.length);
+ }
+ return f;
+};
+
+
+exports.extname = function(path) {
+ return splitPath(path)[3];
+};
+
+function filter (xs, f) {
+ if (xs.filter) return xs.filter(f);
+ var res = [];
+ for (var i = 0; i < xs.length; i++) {
+ if (f(xs[i], i, xs)) res.push(xs[i]);
+ }
+ return res;
+}
+
+// String.prototype.substr - negative index don't work in IE8
+var substr = 'ab'.substr(-1) === 'b'
+ ? function (str, start, len) { return str.substr(start, len) }
+ : function (str, start, len) {
+ if (start < 0) start = str.length + start;
+ return str.substr(start, len);
+ }
+;
+
+}).call(this,require('_process'))
+},{"_process":8}],8:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+var queue = [];
+var draining = false;
+var currentQueue;
+var queueIndex = -1;
+
+function cleanUpNextTick() {
+ draining = false;
+ if (currentQueue.length) {
+ queue = currentQueue.concat(queue);
+ } else {
+ queueIndex = -1;
+ }
+ if (queue.length) {
+ drainQueue();
+ }
+}
+
+function drainQueue() {
+ if (draining) {
+ return;
+ }
+ var timeout = setTimeout(cleanUpNextTick);
+ draining = true;
+
+ var len = queue.length;
+ while(len) {
+ currentQueue = queue;
+ queue = [];
+ while (++queueIndex < len) {
+ if (currentQueue) {
+ currentQueue[queueIndex].run();
+ }
+ }
+ queueIndex = -1;
+ len = queue.length;
+ }
+ currentQueue = null;
+ draining = false;
+ clearTimeout(timeout);
+}
+
+process.nextTick = function (fun) {
+ var args = new Array(arguments.length - 1);
+ if (arguments.length > 1) {
+ for (var i = 1; i < arguments.length; i++) {
+ args[i - 1] = arguments[i];
+ }
+ }
+ queue.push(new Item(fun, args));
+ if (queue.length === 1 && !draining) {
+ setTimeout(drainQueue, 0);
+ }
+};
+
+// v8 likes predictible objects
+function Item(fun, array) {
+ this.fun = fun;
+ this.array = array;
+}
+Item.prototype.run = function () {
+ this.fun.apply(null, this.array);
+};
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+};
+
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}]},{},[1]);
diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.min.js b/vendor/assets/javascripts/fuzzaldrin-plus.min.js
deleted file mode 100644
index 3f25c2d8373..00000000000
--- a/vendor/assets/javascripts/fuzzaldrin-plus.min.js
+++ /dev/null
@@ -1 +0,0 @@
-(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){(function(){var PathSeparator,legacy_scorer,pluckCandidates,scorer,sortCandidates;scorer=require('./scorer');legacy_scorer=require('./legacy');pluckCandidates=function(a){return a.candidate};sortCandidates=function(a,b){return b.score-a.score};PathSeparator=require('path').sep;module.exports=function(candidates,query,arg){var allowErrors,bAllowErrors,bKey,candidate,coreQuery,i,j,key,legacy,len,len1,maxInners,maxResults,prepQuery,queryHasSlashes,ref,score,scoredCandidates,spotLeft,string;ref=arg!=null?arg:{},key=ref.key,maxResults=ref.maxResults,maxInners=ref.maxInners,allowErrors=ref.allowErrors,legacy=ref.legacy;scoredCandidates=[];spotLeft=(maxInners!=null)&&maxInners>0?maxInners:candidates.length;bAllowErrors=!!allowErrors;bKey=key!=null;prepQuery=scorer.prepQuery(query);if(!legacy){for(i=0,len=candidates.length;i<len;i++){candidate=candidates[i];string=bKey?candidate[key]:candidate;if(!string){continue}score=scorer.score(string,query,prepQuery,bAllowErrors);if(score>0){scoredCandidates.push({candidate:candidate,score:score});if(!--spotLeft){break}}}}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;for(j=0,len1=candidates.length;j<len1;j++){candidate=candidates[j];string=key!=null?candidate[key]:candidate;if(!string){continue}score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}if(score>0){scoredCandidates.push({candidate:candidate,score:score})}}}scoredCandidates.sort(sortCandidates);candidates=scoredCandidates.map(pluckCandidates);if(maxResults!=null){candidates=candidates.slice(0,maxResults)}return candidates}}).call(this)},{"./legacy":4,"./scorer":6,"path":7}],2:[function(require,module,exports){(function(){var PathSeparator,filter,legacy_scorer,matcher,prepQueryCache,scorer;scorer=require('./scorer');legacy_scorer=require('./legacy');filter=require('./filter');matcher=require('./matcher');PathSeparator=require('path').sep;prepQueryCache=null;module.exports={filter:function(candidates,query,options){if(!((query!=null?query.length:void 0)&&(candidates!=null?candidates.length:void 0))){return[]}return filter(candidates,query,options)},prepQuery:function(query){return scorer.prepQuery(query)},score:function(string,query,prepQuery,arg){var allowErrors,coreQuery,legacy,queryHasSlashes,ref,score;ref=arg!=null?arg:{},allowErrors=ref.allowErrors,legacy=ref.legacy;if(!((string!=null?string.length:void 0)&&(query!=null?query.length:void 0))){return 0}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!legacy){score=scorer.score(string,query,prepQuery,!!allowErrors)}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}}return score},match:function(string,query,prepQuery,arg){var allowErrors,baseMatches,i,matches,query_lw,ref,results,string_lw;allowErrors=(arg!=null?arg:{}).allowErrors;if(!string){return[]}if(!query){return[]}if(string===query){return(function(){results=[];for(var i=0,ref=string.length;0<=ref?i<ref:i>ref;0<=ref?i++:i--){results.push(i)}return results}).apply(this)}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!(allowErrors||scorer.isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return[]}string_lw=string.toLowerCase();query_lw=prepQuery.query_lw;matches=matcher.match(string,string_lw,prepQuery);if(matches.length===0){return matches}if(string.indexOf(PathSeparator)>-1){baseMatches=matcher.basenameMatch(string,string_lw,prepQuery);matches=matcher.mergeMatches(matches,baseMatches)}return matches}}}).call(this)},{"./filter":1,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],3:[function(require,module,exports){fuzzaldrinPlus=require('./fuzzaldrin')},{"./fuzzaldrin":2}],4:[function(require,module,exports){(function(){var PathSeparator,queryIsLastPathSegment;PathSeparator=require('path').sep;exports.basenameScore=function(string,query,score){var base,depth,index,lastCharacter,segmentCount,slashCount;index=string.length-1;while(string[index]===PathSeparator){index--}slashCount=0;lastCharacter=index;base=null;while(index>=0){if(string[index]===PathSeparator){slashCount++;if(base==null){base=string.substring(index+1,lastCharacter+1)}}else if(index===0){if(lastCharacter<string.length-1){if(base==null){base=string.substring(0,lastCharacter+1)}}else{if(base==null){base=string}}}index--}if(base===string){score*=2}else if(base){score+=exports.score(base,query)}segmentCount=slashCount+1;depth=Math.max(1,10-segmentCount);score*=depth*0.01;return score};exports.score=function(string,query){var character,characterScore,indexInQuery,indexInString,lowerCaseIndex,minIndex,queryLength,queryScore,ref,stringLength,totalCharacterScore,upperCaseIndex;if(string===query){return 1}if(queryIsLastPathSegment(string,query)){return 1}totalCharacterScore=0;queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;while(indexInQuery<queryLength){character=query[indexInQuery++];lowerCaseIndex=string.indexOf(character.toLowerCase());upperCaseIndex=string.indexOf(character.toUpperCase());minIndex=Math.min(lowerCaseIndex,upperCaseIndex);if(minIndex===-1){minIndex=Math.max(lowerCaseIndex,upperCaseIndex)}indexInString=minIndex;if(indexInString===-1){return 0}characterScore=0.1;if(string[indexInString]===character){characterScore+=0.1}if(indexInString===0||string[indexInString-1]===PathSeparator){characterScore+=0.8}else if((ref=string[indexInString-1])==='-'||ref==='_'||ref===' '){characterScore+=0.7}string=string.substring(indexInString+1,stringLength);totalCharacterScore+=characterScore}queryScore=totalCharacterScore/queryLength;return((queryScore*(queryLength/stringLength))+queryScore)/2};queryIsLastPathSegment=function(string,query){if(string[string.length-query.length-1]===PathSeparator){return string.lastIndexOf(query)===string.length-query.length}};exports.match=function(string,query,stringOffset){var character,i,indexInQuery,indexInString,lowerCaseIndex,matches,minIndex,queryLength,ref,results,stringLength,upperCaseIndex;if(stringOffset==null){stringOffset=0}if(string===query){return(function(){results=[];for(var i=stringOffset,ref=stringOffset+string.length;stringOffset<=ref?i<ref:i>ref;stringOffset<=ref?i++:i--){results.push(i)}return results}).apply(this)}queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;matches=[];while(indexInQuery<queryLength){character=query[indexInQuery++];lowerCaseIndex=string.indexOf(character.toLowerCase());upperCaseIndex=string.indexOf(character.toUpperCase());minIndex=Math.min(lowerCaseIndex,upperCaseIndex);if(minIndex===-1){minIndex=Math.max(lowerCaseIndex,upperCaseIndex)}indexInString=minIndex;if(indexInString===-1){return[]}matches.push(stringOffset+indexInString);stringOffset+=indexInString+1;string=string.substring(indexInString+1,stringLength)}return matches}}).call(this)},{"path":7}],5:[function(require,module,exports){(function(){var PathSeparator,scorer;PathSeparator=require('path').sep;scorer=require('./scorer');exports.basenameMatch=function(subject,subject_lw,prepQuery){var basePos,depth,end;end=subject.length-1;while(subject[end]===PathSeparator){end--}basePos=subject.lastIndexOf(PathSeparator,end);if(basePos===-1){return[]}depth=prepQuery.depth;while(depth-->0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return[]}}basePos++;end++;return exports.match(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery,basePos)};exports.mergeMatches=function(a,b){var ai,bj,i,j,m,n,out;m=a.length;n=b.length;if(n===0){return a.slice()}if(m===0){return b.slice()}i=-1;j=0;bj=b[j];out=[];while(++i<m){ai=a[i];while(bj<=ai&&++j<n){if(bj<ai){out.push(bj)}bj=b[j]}out.push(ai)}while(j<n){out.push(b[j++])}return out};exports.match=function(subject,subject_lw,prepQuery,offset){var DIAGONAL,LEFT,STOP,UP,acro_score,align,backtrack,csc_diag,csc_row,csc_score,i,j,m,matches,move,n,pos,query,query_lw,score,score_diag,score_row,score_up,si_lw,start,trace;if(offset==null){offset=0}query=prepQuery.query;query_lw=prepQuery.query_lw;m=subject.length;n=query.length;acro_score=scorer.scoreAcronyms(subject,subject_lw,query,query_lw).score;score_row=new Array(n);csc_row=new Array(n);STOP=0;UP=1;LEFT=2;DIAGONAL=3;trace=new Array(m*n);pos=-1;j=-1;while(++j<n){score_row[j]=0;csc_row[j]=0}i=-1;while(++i<m){score=0;score_up=0;csc_diag=0;si_lw=subject_lw[i];j=-1;while(++j<n){csc_score=0;align=0;score_diag=score_up;if(query_lw[j]===si_lw){start=scorer.isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scorer.scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scorer.scoreCharacter(i,j,start,acro_score,csc_score)}score_up=score_row[j];csc_diag=csc_row[j];if(score>score_up){move=LEFT}else{score=score_up;move=UP}if(align>score){score=align;move=DIAGONAL}else{csc_score=0}score_row[j]=score;csc_row[j]=csc_score;trace[++pos]=score>0?move:STOP}}i=m-1;j=n-1;pos=i*n+j;backtrack=true;matches=[];while(backtrack&&i>=0&&j>=0){switch(trace[pos]){case UP:i--;pos-=n;break;case LEFT:j--;pos--;break;case DIAGONAL:matches.push(i+offset);j--;i--;pos-=n+1;break;default:backtrack=false}}matches.reverse();return matches}}).call(this)},{"./scorer":6,"path":7}],6:[function(require,module,exports){(function(){var AcronymResult,PathSeparator,Query,basenameScore,coreChars,countDir,doScore,emptyAcronymResult,file_coeff,isMatch,isSeparator,isWordEnd,isWordStart,miss_coeff,opt_char_re,pos_bonus,scoreAcronyms,scoreCharacter,scoreConsecutives,scoreExact,scoreExactMatch,scorePattern,scorePosition,scoreSize,tau_depth,tau_size,truncatedUpperCase,wm;PathSeparator=require('path').sep;wm=150;pos_bonus=20;tau_depth=13;tau_size=85;file_coeff=1.2;miss_coeff=0.75;opt_char_re=/[ _\-:\/\\]/g;exports.coreChars=coreChars=function(query){return query.replace(opt_char_re,'')};exports.score=function(string,query,prepQuery,allowErrors){var score,string_lw;if(prepQuery==null){prepQuery=new Query(query)}if(allowErrors==null){allowErrors=false}if(!(allowErrors||isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return 0}string_lw=string.toLowerCase();score=doScore(string,string_lw,prepQuery);return Math.ceil(basenameScore(string,string_lw,prepQuery,score))};Query=(function(){function Query(query){if(!(query!=null?query.length:void 0)){return null}this.query=query;this.query_lw=query.toLowerCase();this.core=coreChars(query);this.core_lw=this.core.toLowerCase();this.core_up=truncatedUpperCase(this.core);this.depth=countDir(query,query.length)}return Query})();exports.prepQuery=function(query){return new Query(query)};exports.isMatch=isMatch=function(subject,query_lw,query_up){var i,j,m,n,qj_lw,qj_up,si;m=subject.length;n=query_lw.length;if(!m||!n||n>m){return false}i=-1;j=-1;while(++j<n){qj_lw=query_lw[j];qj_up=query_up[j];while(++i<m){si=subject[i];if(si===qj_lw||si===qj_up){break}}if(i===m){return false}}return true};doScore=function(subject,subject_lw,prepQuery){var acro,acro_score,align,csc_diag,csc_row,csc_score,i,j,m,miss_budget,miss_left,mm,n,pos,query,query_lw,record_miss,score,score_diag,score_row,score_up,si_lw,start,sz;query=prepQuery.query;query_lw=prepQuery.query_lw;m=subject.length;n=query.length;acro=scoreAcronyms(subject,subject_lw,query,query_lw);acro_score=acro.score;if(acro.count===n){return scoreExact(n,m,acro_score,acro.pos)}pos=subject_lw.indexOf(query_lw);if(pos>-1){return scoreExactMatch(subject,subject_lw,query,query_lw,pos,n,m)}score_row=new Array(n);csc_row=new Array(n);sz=scoreSize(n,m);miss_budget=Math.ceil(miss_coeff*n)+5;miss_left=miss_budget;j=-1;while(++j<n){score_row[j]=0;csc_row[j]=0}i=subject_lw.indexOf(query_lw[0]);if(i>-1){i--}mm=subject_lw.lastIndexOf(query_lw[n-1],m);if(mm>i){m=mm+1}while(++i<m){score=0;score_diag=0;csc_diag=0;si_lw=subject_lw[i];record_miss=true;j=-1;while(++j<n){score_up=score_row[j];if(score_up>score){score=score_up}csc_score=0;if(query_lw[j]===si_lw){start=isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scoreCharacter(i,j,start,acro_score,csc_score);if(align>score){score=align;miss_left=miss_budget}else{if(record_miss&&--miss_left<=0){return score_row[n-1]*sz}record_miss=false}}score_diag=score_up;csc_diag=csc_row[j];csc_row[j]=csc_score;score_row[j]=score}}return score*sz};exports.isWordStart=isWordStart=function(pos,subject,subject_lw){var curr_s,prev_s;if(pos===0){return true}curr_s=subject[pos];prev_s=subject[pos-1];return isSeparator(curr_s)||isSeparator(prev_s)||(curr_s!==subject_lw[pos]&&prev_s===subject_lw[pos-1])};exports.isWordEnd=isWordEnd=function(pos,subject,subject_lw,len){var curr_s,next_s;if(pos===len-1){return true}curr_s=subject[pos];next_s=subject[pos+1];return isSeparator(curr_s)||isSeparator(next_s)||(curr_s===subject_lw[pos]&&next_s!==subject_lw[pos+1])};isSeparator=function(c){return c===' '||c==='.'||c==='-'||c==='_'||c==='/'||c==='\\'};scorePosition=function(pos){var sc;if(pos<pos_bonus){sc=pos_bonus-pos;return 100+sc*sc}else{return Math.max(100+pos_bonus-pos,0)}};scoreSize=function(n,m){return tau_size/(tau_size+Math.abs(m-n))};scoreExact=function(n,m,quality,pos){return 2*n*(wm*quality+scorePosition(pos))*scoreSize(n,m)};exports.scorePattern=scorePattern=function(count,len,sameCase,start,end){var bonus,sz;sz=count;bonus=6;if(sameCase===count){bonus+=2}if(start){bonus+=3}if(end){bonus+=1}if(count===len){if(start){if(sameCase===len){sz+=2}else{sz+=1}}if(end){bonus+=1}}return sameCase+sz*(sz+bonus)};exports.scoreCharacter=scoreCharacter=function(i,j,start,acro_score,csc_score){var posBonus;posBonus=scorePosition(i);if(start){return posBonus+wm*((acro_score>csc_score?acro_score:csc_score)+10)}return posBonus+wm*csc_score};exports.scoreConsecutives=scoreConsecutives=function(subject,subject_lw,query,query_lw,i,j,start){var k,m,mi,n,nj,sameCase,startPos,sz;m=subject.length;n=query.length;mi=m-i;nj=n-j;k=mi<nj?mi:nj;startPos=i;sameCase=0;sz=0;if(query[j]===subject[i]){sameCase++}while(++sz<k&&query_lw[++j]===subject_lw[++i]){if(query[j]===subject[i]){sameCase++}}if(sz===1){return 1+2*sameCase}return scorePattern(sz,n,sameCase,start,isWordEnd(i,subject,subject_lw,m))};exports.scoreExactMatch=scoreExactMatch=function(subject,subject_lw,query,query_lw,pos,n,m){var end,i,pos2,sameCase,start;start=isWordStart(pos,subject,subject_lw);if(!start){pos2=subject_lw.indexOf(query_lw,pos+1);if(pos2>-1){start=isWordStart(pos2,subject,subject_lw);if(start){pos=pos2}}}i=-1;sameCase=0;while(++i<n){if(query[pos+i]===subject[i]){sameCase++}}end=isWordEnd(pos+n-1,subject,subject_lw,m);return scoreExact(n,m,scorePattern(n,n,sameCase,start,end),pos)};AcronymResult=(function(){function AcronymResult(score1,pos1,count1){this.score=score1;this.pos=pos1;this.count=count1}return AcronymResult})();emptyAcronymResult=new AcronymResult(0,0.1,0);exports.scoreAcronyms=scoreAcronyms=function(subject,subject_lw,query,query_lw){var count,i,j,m,n,pos,qj_lw,sameCase,score;m=subject.length;n=query.length;if(!(m>1&&n>1)){return emptyAcronymResult}count=0;pos=0;sameCase=0;i=-1;j=-1;while(++j<n){qj_lw=query_lw[j];while(++i<m){if(qj_lw===subject_lw[i]&&isWordStart(i,subject,subject_lw)){if(query[j]===subject[i]){sameCase++}pos+=i;count++;break}}if(i===m){break}}if(count<2){return emptyAcronymResult}score=scorePattern(count,n,sameCase,true,false);return new AcronymResult(score,pos/count,count)};basenameScore=function(subject,subject_lw,prepQuery,fullPathScore){var alpha,basePathScore,basePos,depth,end;if(fullPathScore===0){return 0}end=subject.length-1;while(subject[end]===PathSeparator){end--}basePos=subject.lastIndexOf(PathSeparator,end);if(basePos===-1){return fullPathScore}depth=prepQuery.depth;while(depth-->0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return fullPathScore}}basePos++;end++;basePathScore=doScore(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery);alpha=0.5*tau_depth/(tau_depth+countDir(subject,end+1));return alpha*basePathScore+(1-alpha)*fullPathScore*scoreSize(0,file_coeff*(end-basePos))};exports.countDir=countDir=function(path,end){var count,i;if(end<1){return 0}count=0;i=-1;while(++i<end&&path[i]===PathSeparator){continue}while(++i<end){if(path[i]===PathSeparator){count++;while(++i<end&&path[i]===PathSeparator){continue}}}return count};truncatedUpperCase=function(str){var char,l,len1,upper;upper="";for(l=0,len1=str.length;l<len1;l++){char=str[l];upper+=char.toUpperCase()[0]}return upper}}).call(this)},{"path":7}],7:[function(require,module,exports){(function(process){function normalizeArray(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==='.'){parts.splice(i,1)}else if(last==='..'){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift('..')}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath='',resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=(i>=0)?arguments[i]:process.cwd();if(typeof path!=='string'){throw new TypeError('Arguments to path.resolve must be strings');}else if(!path){continue}resolvedPath=path+'/'+resolvedPath;resolvedAbsolute=path.charAt(0)==='/'}resolvedPath=normalizeArray(filter(resolvedPath.split('/'),function(p){return!!p}),!resolvedAbsolute).join('/');return((resolvedAbsolute?'/':'')+resolvedPath)||'.'};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==='/';path=normalizeArray(filter(path.split('/'),function(p){return!!p}),!isAbsolute).join('/');if(!path&&!isAbsolute){path='.'}if(path&&trailingSlash){path+='/'}return(isAbsolute?'/':'')+path};exports.isAbsolute=function(path){return path.charAt(0)==='/'};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=='string'){throw new TypeError('Arguments to path.join must be strings');}return p}).join('/'))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=='')break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=='')break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split('/'));var toParts=trim(to.split('/'));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push('..')}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join('/')};exports.sep='/';exports.delimiter=':';exports.dirname=function(path){var result=splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return'.'}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir};exports.basename=function(path,ext){var f=splitPath(path)[2];if(ext&&f.substr(-1*ext.length)===ext){f=f.substr(0,f.length-ext.length)}return f};exports.extname=function(path){return splitPath(path)[3]};function filter(xs,f){if(xs.filter)return xs.filter(f);var res=[];for(var i=0;i<xs.length;i++){if(f(xs[i],i,xs))res.push(xs[i])}return res}var substr='ab'.substr(-1)==='b'?function(str,start,len){return str.substr(start,len)}:function(str,start,len){if(start<0)start=str.length+start;return str.substr(start,len)}}).call(this,require('_process'))},{"_process":8}],8:[function(require,module,exports){var process=module.exports={};var queue=[];var draining=false;var currentQueue;var queueIndex=-1;function cleanUpNextTick(){draining=false;if(currentQueue.length){queue=currentQueue.concat(queue)}else{queueIndex=-1}if(queue.length){drainQueue()}}function drainQueue(){if(draining){return}var timeout=setTimeout(cleanUpNextTick);draining=true;var len=queue.length;while(len){currentQueue=queue;queue=[];while(++queueIndex<len){if(currentQueue){currentQueue[queueIndex].run()}}queueIndex=-1;len=queue.length}currentQueue=null;draining=false;clearTimeout(timeout)}process.nextTick=function(fun){var args=new Array(arguments.length-1);if(arguments.length>1){for(var i=1;i<arguments.length;i++){args[i-1]=arguments[i]}}queue.push(new Item(fun,args));if(queue.length===1&&!draining){setTimeout(drainQueue,0)}};function Item(fun,array){this.fun=fun;this.array=array}Item.prototype.run=function(){this.fun.apply(null,this.array)};process.title='browser';process.browser=true;process.env={};process.argv=[];process.version='';process.versions={};function noop(){}process.on=noop;process.addListener=noop;process.once=noop;process.off=noop;process.removeListener=noop;process.removeAllListeners=noop;process.emit=noop;process.binding=function(name){throw new Error('process.binding is not supported');};process.cwd=function(){return'/'};process.chdir=function(dir){throw new Error('process.chdir is not supported');};process.umask=function(){return 0}},{}]},{},[3]);
diff --git a/vendor/assets/javascripts/g.bar-min.js b/vendor/assets/javascripts/g.bar-min.js
deleted file mode 100644
index 7620dabda74..00000000000
--- a/vendor/assets/javascripts/g.bar-min.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/*!
- * g.Raphael 0.5 - Charting library, based on Raphaël
- *
- * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
- * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
- * From: https://github.com/jhurt/g.raphael/blob/master/g.bar.js
- */
-(function(){function c(c,d,e,f,g,h,i,j){var k,l={round:"round",sharp:"sharp",soft:"soft",square:"square"};if(g&&!f||!g&&!e)return i?"":j.path();switch(h=l[h]||"square",f=Math.round(f),e=Math.round(e),c=Math.round(c),d=Math.round(d),h){case"round":if(g)m=~~(e/2),m>f?(m=f,k=["M",c-~~(e/2),d,"l",0,0,"a",~~(e/2),m,0,0,1,e,0,"l",0,0,"z"]):k=["M",c-m,d,"l",0,m-f,"a",m,m,0,1,1,e,0,"l",0,f-m,"z"];else{var m=~~(f/2);m>e?(m=e,k=["M",c+.5,d+.5-~~(f/2),"l",0,0,"a",m,~~(f/2),0,0,1,0,f,"l",0,0,"z"]):k=["M",c+.5,d+.5-m,"l",e-m,0,"a",m,m,0,1,1,0,f,"l",m-e,0,"z"]}break;case"sharp":if(g)n=~~(e/2),k=["M",c+n,d,"l",-e,0,0,-b(f-n,0),n,-a(n,f),n,a(n,f),n,"z"];else{var n=~~(f/2);k=["M",c,d+n,"l",0,-f,b(e-n,0),0,a(n,e),n,-a(n,e),n+(f>2*n),"z"]}break;case"square":k=g?["M",c+~~(e/2),d,"l",1-e,0,0,-f,e-1,0,"z"]:["M",c,d+~~(f/2),"l",0,-f,e,0,0,f,"z"];break;case"soft":g?(m=a(Math.round(e/5),f),k=["M",c-~~(e/2),d,"l",0,m-f,"a",m,m,0,0,1,m,-m,"l",e-2*m,0,"a",m,m,0,0,1,m,m,"l",0,f-m,"z"]):(m=a(e,Math.round(f/5)),k=["M",c+.5,d+.5-~~(f/2),"l",e-m,0,"a",m,m,0,0,1,m,m,"l",0,f-2*m,"a",m,m,0,0,1,-m,m,"l",m-e,0,"z"])}return i?k.join(","):j.path(k)}function d(a,b,d,e,f,g,h){h=h||{};var i=this,j=h.type||"square",k=parseFloat(h.gutter||"20%"),l=a.set(),m=a.set(),n=a.set(),o=a.set(),p=Math.max.apply(Math,g),q=[],r=0,s=h.colors||i.colors,t=g.length;if(Raphael.is(g[0],"array")){p=[],r=t,t=0;for(var u=g.length;u--;)m.push(a.set()),p.push(Math.max.apply(Math,g[u])),t=Math.max(t,g[u].length);if(h.stacked)for(var u=t;u--;){for(var v=0,w=g.length;w--;)v+=+g[w][u]||0;q.push(v)}for(var u=g.length;u--;)if(t>g[u].length)for(var w=t;w--;)g[u].push(0);p=Math.max.apply(Math,h.stacked?q:p)}p=h.to||p;var x=100*(e/(t*(100+k)+k)),y=x*k/100,z=null==h.vgutter?20:h.vgutter,A=[],B=b+y,C=(f-2*z)/p;h.stretch||(y=Math.round(y),x=Math.floor(x)),!h.stacked&&(x/=r||1);for(var u=0;t>u;u++){A=[];for(var w=0;(r||1)>w;w++){var D=Math.round((r?g[w][u]:g[u])*C),E=d+f-z-D,F=c(Math.round(B+x/2),E+D,x,D,!0,j,null,a).attr({stroke:"none",fill:s[r?w:u]});r?m[w].push(F):m.push(F),F.y=E,F.x=Math.round(B+x/2),F.w=x,F.h=D,F.value=r?g[w][u]:g[u],h.stacked?A.push(F):B+=x}if(h.stacked){var G;o.push(G=a.rect(A[0].x-A[0].w/2,d,x,f).attr(i.shim)),G.bars=a.set();for(var H=0,I=A.length;I--;)A[I].toFront();for(var I=0,J=A.length;J>I;I++){var K,F=A[I],D=(H+F.value)*C,L=c(F.x,d+f-z-.5*!!H,x,D,!0,j,1,a);G.bars.push(F),H&&F.attr({path:L}),F.h=D,F.y=d+f-z-.5*!!H-D,n.push(K=a.rect(F.x-F.w/2,F.y,x,F.value*C).attr(i.shim)),K.bar=F,K.value=F.value,H+=F.value}B+=x}B+=y}if(o.toFront(),B=b+y,!h.stacked)for(var u=0;t>u;u++){for(var w=0;(r||1)>w;w++){var K;n.push(K=a.rect(Math.round(B),d+z,x,f-z).attr(i.shim)),K.bar=r?m[w][u]:m[u],K.value=K.bar.value,B+=x}B+=y}return l.label=function(b,c){b=b||[],this.labels=a.set();var e,j=-1/0;if(h.stacked){for(var k=0;t>k;k++)for(var l=0,o=0;(r||1)>o;o++)if(l+=r?g[o][k]:g[k],o==r-1){var q=i.labelise(b[k],l,p);e=a.text(m[o][k].x,d+f-z/2,q).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[k*(r||1)+o]);var s=e.getBBox();j>s.x-7?e.remove():(this.labels.push(e),j=s.x+s.width)}}else for(var k=0;t>k;k++)for(var o=0;(r||1)>o;o++){var q=i.labelise(r?b[o]&&b[o][k]:b[k],r?g[o][k]:g[k],p);e=a.text(m[o][k].x-x/2,c?d+f-z/2:m[o][k].y-10,q).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[k*(r||1)+o]);var s=e.getBBox();e.translate((x-s.width)/2,1),j>s.x-7?e.remove():(this.labels.push(e),j=s.x+s.width)}return this},l.hover=function(a,b){return o.hide(),n.show(),n.mouseover(a).mouseout(b),this},l.hoverColumn=function(a,b){return n.hide(),o.show(),b=b||function(){},o.mouseover(a).mouseout(b),this},l.click=function(a){return o.hide(),n.show(),n.click(a),this},l.each=function(a){if(!Raphael.is(a,"function"))return this;for(var b=n.length;b--;)a.call(n[b]);return this},l.eachColumn=function(a){if(!Raphael.is(a,"function"))return this;for(var b=o.length;b--;)a.call(o[b]);return this},l.clickColumn=function(a){return n.hide(),o.show(),o.click(a),this},l.push(m,n,o),l.bars=m,l.covers=n,l}function e(a,b,d,e,f,g,h){h=h||{};var i=this,j=h.type||"square",k=parseFloat(h.gutter||"20%"),l=a.set(),m=a.set(),n=a.set(),o=a.set(),p=Math.max.apply(Math,g),q=[],r=0,s=h.colors||i.colors,t=g.length;if(Raphael.is(g[0],"array")){p=[],r=t,t=0;for(var u=g.length;u--;)m.push(a.set()),p.push(Math.max.apply(Math,g[u])),t=Math.max(t,g[u].length);if(h.stacked)for(var u=t;u--;){for(var v=0,w=g.length;w--;)v+=+g[w][u]||0;q.push(v)}for(var u=g.length;u--;)if(t>g[u].length)for(var w=t;w--;)g[u].push(0);p=Math.max.apply(Math,h.stacked?q:p)}p=h.to||p;var x=Math.floor(100*(f/(t*(100+k)+k))),y=Math.floor(x*k/100),z=[],A=d+y,B=(e-1)/p;!h.stacked&&(x/=r||1);for(var u=0;t>u;u++){z=[];for(var w=0;(r||1)>w;w++){var C=r?g[w][u]:g[u],D=c(b,A+x/2,Math.round(C*B),x-1,!1,j,null,a).attr({stroke:"none",fill:s[r?w:u]});r?m[w].push(D):m.push(D),D.x=b+Math.round(C*B),D.y=A+x/2,D.w=Math.round(C*B),D.h=x,D.value=+C,h.stacked?z.push(D):A+=x}if(h.stacked){var E=a.rect(b,z[0].y-z[0].h/2,e,x).attr(i.shim);o.push(E),E.bars=a.set();for(var F=0,G=z.length;G--;)z[G].toFront();for(var G=0,H=z.length;H>G;G++){var I,D=z[G],C=Math.round((F+D.value)*B),J=c(b,D.y,C,x-1,!1,j,1,a);E.bars.push(D),F&&D.attr({path:J}),D.w=C,D.x=b+C,n.push(I=a.rect(b+F*B,D.y-D.h/2,D.value*B,x).attr(i.shim)),I.bar=D,F+=D.value}A+=x}A+=y}if(o.toFront(),A=d+y,!h.stacked)for(var u=0;t>u;u++){for(var w=0;(r||1)>w;w++){var I=a.rect(b,A,e,x).attr(i.shim);n.push(I),I.bar=r?m[w][u]:m[u],I.value=I.bar.value,A+=x}A+=y}return l.label=function(c,d){c=c||[],this.labels=a.set();for(var e=0;t>e;e++)for(var f=0;r>f;f++){var o,j=i.labelise(r?c[f]&&c[f][e]:c[e],r?g[f][e]:g[e],p),k=d?m[f][e].x-x/2+3:b+5;this.labels.push(o=a.text(k,m[f][e].y,j).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[0])),b+5>o.getBBox().x?o.attr({x:b+5,"text-anchor":"start"}):m[f][e].label=o}return this},l.hover=function(a,b){return o.hide(),n.show(),b=b||function(){},n.mouseover(a).mouseout(b),this},l.hoverColumn=function(a,b){return n.hide(),o.show(),b=b||function(){},o.mouseover(a).mouseout(b),this},l.each=function(a){if(!Raphael.is(a,"function"))return this;for(var b=n.length;b--;)a.call(n[b]);return this},l.eachColumn=function(a){if(!Raphael.is(a,"function"))return this;for(var b=o.length;b--;)a.call(o[b]);return this},l.click=function(a){return o.hide(),n.show(),n.click(a),this},l.clickColumn=function(a){return n.hide(),o.show(),o.click(a),this},l.push(m,n,o),l.bars=m,l.covers=n,l}var a=Math.min,b=Math.max,f=function(){};f.prototype=Raphael.g,e.prototype=d.prototype=new f,Raphael.fn.hbarchart=function(a,b,c,d,f,g){return new e(this,a,b,c,d,f,g)},Raphael.fn.barchart=function(a,b,c,e,f,g){return new d(this,a,b,c,e,f,g)}})(); \ No newline at end of file
diff --git a/vendor/assets/javascripts/g.bar.js b/vendor/assets/javascripts/g.bar.js
new file mode 100644
index 00000000000..166bd654d6e
--- /dev/null
+++ b/vendor/assets/javascripts/g.bar.js
@@ -0,0 +1,674 @@
+/*!
+ * g.Raphael 0.51 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+(function () {
+ var mmin = Math.min,
+ mmax = Math.max;
+
+ function finger(x, y, width, height, dir, ending, isPath, paper) {
+ var path,
+ ends = { round: 'round', sharp: 'sharp', soft: 'soft', square: 'square' };
+
+ // dir 0 for horizontal and 1 for vertical
+ if ((dir && !height) || (!dir && !width)) {
+ return isPath ? "" : paper.path();
+ }
+
+ ending = ends[ending] || "square";
+ height = Math.round(height);
+ width = Math.round(width);
+ x = Math.round(x);
+ y = Math.round(y);
+
+ switch (ending) {
+ case "round":
+ if (!dir) {
+ var r = ~~(height / 2);
+
+ if (width < r) {
+ r = width;
+ path = [
+ "M", x + .5, y + .5 - ~~(height / 2),
+ "l", 0, 0,
+ "a", r, ~~(height / 2), 0, 0, 1, 0, height,
+ "l", 0, 0,
+ "z"
+ ];
+ } else {
+ path = [
+ "M", x + .5, y + .5 - r,
+ "l", width - r, 0,
+ "a", r, r, 0, 1, 1, 0, height,
+ "l", r - width, 0,
+ "z"
+ ];
+ }
+ } else {
+ r = ~~(width / 2);
+
+ if (height < r) {
+ r = height;
+ path = [
+ "M", x - ~~(width / 2), y,
+ "l", 0, 0,
+ "a", ~~(width / 2), r, 0, 0, 1, width, 0,
+ "l", 0, 0,
+ "z"
+ ];
+ } else {
+ path = [
+ "M", x - r, y,
+ "l", 0, r - height,
+ "a", r, r, 0, 1, 1, width, 0,
+ "l", 0, height - r,
+ "z"
+ ];
+ }
+ }
+ break;
+ case "sharp":
+ if (!dir) {
+ var half = ~~(height / 2);
+
+ path = [
+ "M", x, y + half,
+ "l", 0, -height, mmax(width - half, 0), 0, mmin(half, width), half, -mmin(half, width), half + (half * 2 < height),
+ "z"
+ ];
+ } else {
+ half = ~~(width / 2);
+ path = [
+ "M", x + half, y,
+ "l", -width, 0, 0, -mmax(height - half, 0), half, -mmin(half, height), half, mmin(half, height), half,
+ "z"
+ ];
+ }
+ break;
+ case "square":
+ if (!dir) {
+ path = [
+ "M", x, y + ~~(height / 2),
+ "l", 0, -height, width, 0, 0, height,
+ "z"
+ ];
+ } else {
+ path = [
+ "M", x + ~~(width / 2), y,
+ "l", 1 - width, 0, 0, -height, width - 1, 0,
+ "z"
+ ];
+ }
+ break;
+ case "soft":
+ if (!dir) {
+ r = mmin(width, Math.round(height / 5));
+ path = [
+ "M", x + .5, y + .5 - ~~(height / 2),
+ "l", width - r, 0,
+ "a", r, r, 0, 0, 1, r, r,
+ "l", 0, height - r * 2,
+ "a", r, r, 0, 0, 1, -r, r,
+ "l", r - width, 0,
+ "z"
+ ];
+ } else {
+ r = mmin(Math.round(width / 5), height);
+ path = [
+ "M", x - ~~(width / 2), y,
+ "l", 0, r - height,
+ "a", r, r, 0, 0, 1, r, -r,
+ "l", width - 2 * r, 0,
+ "a", r, r, 0, 0, 1, r, r,
+ "l", 0, height - r,
+ "z"
+ ];
+ }
+ }
+
+ if (isPath) {
+ return path.join(",");
+ } else {
+ return paper.path(path);
+ }
+ }
+
+/*\
+ * Paper.vbarchart
+ [ method ]
+ **
+ * Creates a vertical bar chart
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the chart
+ - y (number) y coordinate of the chart
+ - width (number) width of the chart (respected by all elements in the set)
+ - height (number) height of the chart (respected by all elements in the set)
+ - values (array) values
+ - opts (object) options for the chart
+ o {
+ o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
+ o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
+ o vgutter (number)
+ o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
+ o stacked (boolean) whether or not to tread values as in a stacked bar chart
+ o to
+ o stretch (boolean)
+ o }
+ **
+ = (object) path element of the popup
+ > Usage
+ | r.vbarchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
+ \*/
+
+ function VBarchart(paper, x, y, width, height, values, opts) {
+ opts = opts || {};
+
+ var chartinst = this,
+ type = opts.type || "square",
+ gutter = parseFloat(opts.gutter || "20%"),
+ chart = paper.set(),
+ bars = paper.set(),
+ covers = paper.set(),
+ covers2 = paper.set(),
+ total = Math.max.apply(Math, values),
+ stacktotal = [],
+ multi = 0,
+ colors = opts.colors || chartinst.colors,
+ len = values.length;
+
+ if (Raphael.is(values[0], "array")) {
+ total = [];
+ multi = len;
+ len = 0;
+
+ for (var i = values.length; i--;) {
+ bars.push(paper.set());
+ total.push(Math.max.apply(Math, values[i]));
+ len = Math.max(len, values[i].length);
+ }
+
+ if (opts.stacked) {
+ for (var i = len; i--;) {
+ var tot = 0;
+
+ for (var j = values.length; j--;) {
+ tot +=+ values[j][i] || 0;
+ }
+
+ stacktotal.push(tot);
+ }
+ }
+
+ for (var i = values.length; i--;) {
+ if (values[i].length < len) {
+ for (var j = len; j--;) {
+ values[i].push(0);
+ }
+ }
+ }
+
+ total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+ }
+
+ total = (opts.to) || total;
+
+ var barwidth = width / (len * (100 + gutter) + gutter) * 100,
+ barhgutter = barwidth * gutter / 100,
+ barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
+ stack = [],
+ X = x + barhgutter,
+ Y = (height - 2 * barvgutter) / total;
+
+ if (!opts.stretch) {
+ barhgutter = Math.round(barhgutter);
+ barwidth = Math.floor(barwidth);
+ }
+
+ !opts.stacked && (barwidth /= multi || 1);
+
+ for (var i = 0; i < len; i++) {
+ stack = [];
+
+ for (var j = 0; j < (multi || 1); j++) {
+ var h = Math.round((multi ? values[j][i] : values[i]) * Y),
+ top = y + height - barvgutter - h,
+ bar = finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type, null, paper).attr({ stroke: "none", fill: colors[multi ? j : i] });
+
+ if (multi) {
+ bars[j].push(bar);
+ } else {
+ bars.push(bar);
+ }
+
+ bar.y = top;
+ bar.x = Math.round(X + barwidth / 2);
+ bar.w = barwidth;
+ bar.h = h;
+ bar.value = multi ? values[j][i] : values[i];
+
+ if (!opts.stacked) {
+ X += barwidth;
+ } else {
+ stack.push(bar);
+ }
+ }
+
+ if (opts.stacked) {
+ var cvr;
+
+ covers2.push(cvr = paper.rect(stack[0].x - stack[0].w / 2, y, barwidth, height).attr(chartinst.shim));
+ cvr.bars = paper.set();
+
+ var size = 0;
+
+ for (var s = stack.length; s--;) {
+ stack[s].toFront();
+ }
+
+ for (var s = 0, ss = stack.length; s < ss; s++) {
+ var bar = stack[s],
+ cover,
+ h = (size + bar.value) * Y,
+ path = finger(bar.x, y + height - barvgutter - !!size * .5, barwidth, h, true, type, 1, paper);
+
+ cvr.bars.push(bar);
+ size && bar.attr({path: path});
+ bar.h = h;
+ bar.y = y + height - barvgutter - !!size * .5 - h;
+ covers.push(cover = paper.rect(bar.x - bar.w / 2, bar.y, barwidth, bar.value * Y).attr(chartinst.shim));
+ cover.bar = bar;
+ cover.value = bar.value;
+ size += bar.value;
+ }
+
+ X += barwidth;
+ }
+
+ X += barhgutter;
+ }
+
+ covers2.toFront();
+ X = x + barhgutter;
+
+ if (!opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var cover;
+
+ covers.push(cover = paper.rect(Math.round(X), y + barvgutter, barwidth, height - barvgutter).attr(chartinst.shim));
+ cover.bar = multi ? bars[j][i] : bars[i];
+ cover.value = cover.bar.value;
+ X += barwidth;
+ }
+
+ X += barhgutter;
+ }
+ }
+
+ chart.label = function (labels, isBottom) {
+ labels = labels || [];
+ this.labels = paper.set();
+
+ var L, l = -Infinity;
+
+ if (opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ var tot = 0;
+
+ for (var j = 0; j < (multi || 1); j++) {
+ tot += multi ? values[j][i] : values[i];
+
+ if (j == multi - 1) {
+ var label = paper.labelise(labels[i], tot, total);
+
+ L = paper.text(bars[i * (multi || 1) + j].x, y + height - barvgutter / 2, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
+
+ var bb = L.getBBox();
+
+ if (bb.x - 7 < l) {
+ L.remove();
+ } else {
+ this.labels.push(L);
+ l = bb.x + bb.width;
+ }
+ }
+ }
+ }
+ } else {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
+
+ L = paper.text(bars[i * (multi || 1) + j].x, isBottom ? y + height - barvgutter / 2 : bars[i * (multi || 1) + j].y - 10, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
+
+ var bb = L.getBBox();
+
+ if (bb.x - 7 < l) {
+ L.remove();
+ } else {
+ this.labels.push(L);
+ l = bb.x + bb.width;
+ }
+ }
+ }
+ }
+ return this;
+ };
+
+ chart.hover = function (fin, fout) {
+ covers2.hide();
+ covers.show();
+ covers.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.hoverColumn = function (fin, fout) {
+ covers.hide();
+ covers2.show();
+ fout = fout || function () {};
+ covers2.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.click = function (f) {
+ covers2.hide();
+ covers.show();
+ covers.click(f);
+ return this;
+ };
+
+ chart.each = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers.length; i--;) {
+ f.call(covers[i]);
+ }
+ return this;
+ };
+
+ chart.eachColumn = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers2.length; i--;) {
+ f.call(covers2[i]);
+ }
+ return this;
+ };
+
+ chart.clickColumn = function (f) {
+ covers.hide();
+ covers2.show();
+ covers2.click(f);
+ return this;
+ };
+
+ chart.push(bars, covers, covers2);
+ chart.bars = bars;
+ chart.covers = covers;
+ return chart;
+ };
+
+ //inheritance
+ var F = function() {};
+ F.prototype = Raphael.g;
+ HBarchart.prototype = VBarchart.prototype = new F; //prototype reused by hbarchart
+
+ Raphael.fn.barchart = function(x, y, width, height, values, opts) {
+ return new VBarchart(this, x, y, width, height, values, opts);
+ };
+
+/*\
+ * Paper.barchart
+ [ method ]
+ **
+ * Creates a horizontal bar chart
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the chart
+ - y (number) y coordinate of the chart
+ - width (number) width of the chart (respected by all elements in the set)
+ - height (number) height of the chart (respected by all elements in the set)
+ - values (array) values
+ - opts (object) options for the chart
+ o {
+ o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
+ o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
+ o vgutter (number)
+ o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
+ o stacked (boolean) whether or not to tread values as in a stacked bar chart
+ o to
+ o stretch (boolean)
+ o }
+ **
+ = (object) path element of the popup
+ > Usage
+ | r.barchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
+ \*/
+
+ function HBarchart(paper, x, y, width, height, values, opts) {
+ opts = opts || {};
+
+ var chartinst = this,
+ type = opts.type || "square",
+ gutter = parseFloat(opts.gutter || "20%"),
+ chart = paper.set(),
+ bars = paper.set(),
+ covers = paper.set(),
+ covers2 = paper.set(),
+ total = Math.max.apply(Math, values),
+ stacktotal = [],
+ multi = 0,
+ colors = opts.colors || chartinst.colors,
+ len = values.length;
+
+ if (Raphael.is(values[0], "array")) {
+ total = [];
+ multi = len;
+ len = 0;
+
+ for (var i = values.length; i--;) {
+ bars.push(paper.set());
+ total.push(Math.max.apply(Math, values[i]));
+ len = Math.max(len, values[i].length);
+ }
+
+ if (opts.stacked) {
+ for (var i = len; i--;) {
+ var tot = 0;
+ for (var j = values.length; j--;) {
+ tot +=+ values[j][i] || 0;
+ }
+ stacktotal.push(tot);
+ }
+ }
+
+ for (var i = values.length; i--;) {
+ if (values[i].length < len) {
+ for (var j = len; j--;) {
+ values[i].push(0);
+ }
+ }
+ }
+
+ total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+ }
+
+ total = (opts.to) || total;
+
+ var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
+ bargutter = Math.floor(barheight * gutter / 100),
+ stack = [],
+ Y = y + bargutter,
+ X = (width - 1) / total;
+
+ !opts.stacked && (barheight /= multi || 1);
+
+ for (var i = 0; i < len; i++) {
+ stack = [];
+
+ for (var j = 0; j < (multi || 1); j++) {
+ var val = multi ? values[j][i] : values[i],
+ bar = finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type, null, paper).attr({stroke: "none", fill: colors[multi ? j : i]});
+
+ if (multi) {
+ bars[j].push(bar);
+ } else {
+ bars.push(bar);
+ }
+
+ bar.x = x + Math.round(val * X);
+ bar.y = Y + barheight / 2;
+ bar.w = Math.round(val * X);
+ bar.h = barheight;
+ bar.value = +val;
+
+ if (!opts.stacked) {
+ Y += barheight;
+ } else {
+ stack.push(bar);
+ }
+ }
+
+ if (opts.stacked) {
+ var cvr = paper.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(chartinst.shim);
+
+ covers2.push(cvr);
+ cvr.bars = paper.set();
+
+ var size = 0;
+
+ for (var s = stack.length; s--;) {
+ stack[s].toFront();
+ }
+
+ for (var s = 0, ss = stack.length; s < ss; s++) {
+ var bar = stack[s],
+ cover,
+ val = Math.round((size + bar.value) * X),
+ path = finger(x, bar.y, val, barheight - 1, false, type, 1, paper);
+
+ cvr.bars.push(bar);
+ size && bar.attr({ path: path });
+ bar.w = val;
+ bar.x = x + val;
+ covers.push(cover = paper.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(chartinst.shim));
+ cover.bar = bar;
+ size += bar.value;
+ }
+
+ Y += barheight;
+ }
+
+ Y += bargutter;
+ }
+
+ covers2.toFront();
+ Y = y + bargutter;
+
+ if (!opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var cover = paper.rect(x, Y, width, barheight).attr(chartinst.shim);
+
+ covers.push(cover);
+ cover.bar = multi ? bars[j][i] : bars[i];
+ cover.value = cover.bar.value;
+ Y += barheight;
+ }
+
+ Y += bargutter;
+ }
+ }
+
+ chart.label = function (labels, isRight) {
+ labels = labels || [];
+ this.labels = paper.set();
+
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < multi; j++) {
+ var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
+ X = isRight ? bars[i * (multi || 1) + j].x - barheight / 2 + 3 : x + 5,
+ A = isRight ? "end" : "start",
+ L;
+
+ this.labels.push(L = paper.text(X, bars[i * (multi || 1) + j].y, label).attr(txtattr).attr({ "text-anchor": A }).insertBefore(covers[0]));
+
+ if (L.getBBox().x < x + 5) {
+ L.attr({x: x + 5, "text-anchor": "start"});
+ } else {
+ bars[i * (multi || 1) + j].label = L;
+ }
+ }
+ }
+
+ return this;
+ };
+
+ chart.hover = function (fin, fout) {
+ covers2.hide();
+ covers.show();
+ fout = fout || function () {};
+ covers.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.hoverColumn = function (fin, fout) {
+ covers.hide();
+ covers2.show();
+ fout = fout || function () {};
+ covers2.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.each = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers.length; i--;) {
+ f.call(covers[i]);
+ }
+ return this;
+ };
+
+ chart.eachColumn = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers2.length; i--;) {
+ f.call(covers2[i]);
+ }
+ return this;
+ };
+
+ chart.click = function (f) {
+ covers2.hide();
+ covers.show();
+ covers.click(f);
+ return this;
+ };
+
+ chart.clickColumn = function (f) {
+ covers.hide();
+ covers2.show();
+ covers2.click(f);
+ return this;
+ };
+
+ chart.push(bars, covers, covers2);
+ chart.bars = bars;
+ chart.covers = covers;
+ return chart;
+ };
+
+ Raphael.fn.hbarchart = function(x, y, width, height, values, opts) {
+ return new HBarchart(this, x, y, width, height, values, opts);
+ };
+
+})();
diff --git a/vendor/assets/javascripts/g.raphael-min.js b/vendor/assets/javascripts/g.raphael-min.js
deleted file mode 100644
index f8b381c623b..00000000000
--- a/vendor/assets/javascripts/g.raphael-min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * g.Raphael 0.51 - Charting library, based on Raphaël
- *
- * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
- * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
- */
-Raphael.el.popup=function(a,b,c,d){var f,g,h,i,j,e=this.paper||this[0].paper;if(e){switch(this.type){case"text":case"circle":case"ellipse":h=!0;break;default:h=!1}a=null==a?"up":a,b=b||5,f=this.getBBox(),c="number"==typeof c?c:h?f.x+f.width/2:f.x,d="number"==typeof d?d:h?f.y+f.height/2:f.y,i=Math.max(f.width/2-b,0),j=Math.max(f.height/2-b,0),this.translate(c-f.x-(h?f.width/2:0),d-f.y-(h?f.height/2:0)),f=this.getBBox();var k={up:["M",c,d,"l",-b,-b,-i,0,"a",b,b,0,0,1,-b,-b,"l",0,-f.height,"a",b,b,0,0,1,b,-b,"l",2*b+2*i,0,"a",b,b,0,0,1,b,b,"l",0,f.height,"a",b,b,0,0,1,-b,b,"l",-i,0,"z"].join(","),down:["M",c,d,"l",b,b,i,0,"a",b,b,0,0,1,b,b,"l",0,f.height,"a",b,b,0,0,1,-b,b,"l",-(2*b+2*i),0,"a",b,b,0,0,1,-b,-b,"l",0,-f.height,"a",b,b,0,0,1,b,-b,"l",i,0,"z"].join(","),left:["M",c,d,"l",-b,b,0,j,"a",b,b,0,0,1,-b,b,"l",-f.width,0,"a",b,b,0,0,1,-b,-b,"l",0,-(2*b+2*j),"a",b,b,0,0,1,b,-b,"l",f.width,0,"a",b,b,0,0,1,b,b,"l",0,j,"z"].join(","),right:["M",c,d,"l",b,-b,0,-j,"a",b,b,0,0,1,b,-b,"l",f.width,0,"a",b,b,0,0,1,b,b,"l",0,2*b+2*j,"a",b,b,0,0,1,-b,b,"l",-f.width,0,"a",b,b,0,0,1,-b,-b,"l",0,-j,"z"].join(",")};return g={up:{x:-!h*(f.width/2),y:2*-b-(h?f.height/2:f.height)},down:{x:-!h*(f.width/2),y:2*b+(h?f.height/2:f.height)},left:{x:2*-b-(h?f.width/2:f.width),y:-!h*(f.height/2)},right:{x:2*b+(h?f.width/2:f.width),y:-!h*(f.height/2)}}[a],this.translate(g.x,g.y),e.path(k[a]).attr({fill:"#000",stroke:"none"}).insertBefore(this.node?this:this[0])}},Raphael.el.tag=function(a,b,c,d){var e=3,f=this.paper||this[0].paper;if(f){var i,j,k,g=f.path().attr({fill:"#000",stroke:"#000"}),h=this.getBBox();switch(this.type){case"text":case"circle":case"ellipse":k=!0;break;default:k=!1}return a=a||0,c="number"==typeof c?c:k?h.x+h.width/2:h.x,d="number"==typeof d?d:k?h.y+h.height/2:h.y,b=null==b?5:b,j=.5522*b,h.height>=2*b?g.attr({path:["M",c,d+b,"a",b,b,0,1,1,0,2*-b,b,b,0,1,1,0,2*b,"m",0,2*-b-e,"a",b+e,b+e,0,1,0,0,2*(b+e),"L",c+b+e,d+h.height/2+e,"l",h.width+2*e,0,0,-h.height-2*e,-h.width-2*e,0,"L",c,d-b-e].join(",")}):(i=Math.sqrt(Math.pow(b+e,2)-Math.pow(h.height/2+e,2)),g.attr({path:["M",c,d+b,"c",-j,0,-b,j-b,-b,-b,0,-j,b-j,-b,b,-b,j,0,b,b-j,b,b,0,j,j-b,b,-b,b,"M",c+i,d-h.height/2-e,"a",b+e,b+e,0,1,0,0,h.height+2*e,"l",b+e-i+h.width+2*e,0,0,-h.height-2*e,"L",c+i,d-h.height/2-e].join(",")})),a=360-a,g.rotate(a,c,d),this.attrs?(this.attr(this.attrs.x?"x":"cx",c+b+e+(k?h.width/2:"text"==this.type?h.width:0)).attr("y",k?d:d-h.height/2),this.rotate(a,c,d),a>90&&270>a&&this.attr(this.attrs.x?"x":"cx",c-b-e-(k?h.width/2:h.width)).rotate(180,c,d)):a>90&&270>a?(this.translate(c-h.x-h.width-b-e,d-h.y-h.height/2),this.rotate(a-180,h.x+h.width+b+e,h.y+h.height/2)):(this.translate(c-h.x+b+e,d-h.y-h.height/2),this.rotate(a,h.x-b-e,h.y+h.height/2)),g.insertBefore(this.node?this:this[0])}},Raphael.el.drop=function(a,b,c){var f,g,h,i,j,d=this.getBBox(),e=this.paper||this[0].paper;if(e){switch(this.type){case"text":case"circle":case"ellipse":f=!0;break;default:f=!1}return a=a||0,b="number"==typeof b?b:f?d.x+d.width/2:d.x,c="number"==typeof c?c:f?d.y+d.height/2:d.y,g=Math.max(d.width,d.height)+Math.min(d.width,d.height),h=e.path(["M",b,c,"l",g,0,"A",.4*g,.4*g,0,1,0,b+.7*g,c-.7*g,"z"]).attr({fill:"#000",stroke:"none"}).rotate(22.5-a,b,c),a=(a+90)*Math.PI/180,i=b+g*Math.sin(a)-(f?0:d.width/2),j=c+g*Math.cos(a)-(f?0:d.height/2),this.attrs?this.attr(this.attrs.x?"x":"cx",i).attr(this.attrs.y?"y":"cy",j):this.translate(i-d.x,j-d.y),h.insertBefore(this.node?this:this[0])}},Raphael.el.flag=function(a,b,c){var d=3,e=this.paper||this[0].paper;if(e){var i,f=e.path().attr({fill:"#000",stroke:"#000"}),g=this.getBBox(),h=g.height/2;switch(this.type){case"text":case"circle":case"ellipse":i=!0;break;default:i=!1}return a=a||0,b="number"==typeof b?b:i?g.x+g.width/2:g.x,c="number"==typeof c?c:i?g.y+g.height/2:g.y,f.attr({path:["M",b,c,"l",h+d,-h-d,g.width+2*d,0,0,g.height+2*d,-g.width-2*d,0,"z"].join(",")}),a=360-a,f.rotate(a,b,c),this.attrs?(this.attr(this.attrs.x?"x":"cx",b+h+d+(i?g.width/2:"text"==this.type?g.width:0)).attr("y",i?c:c-g.height/2),this.rotate(a,b,c),a>90&&270>a&&this.attr(this.attrs.x?"x":"cx",b-h-d-(i?g.width/2:g.width)).rotate(180,b,c)):a>90&&270>a?(this.translate(b-g.x-g.width-h-d,c-g.y-g.height/2),this.rotate(a-180,g.x+g.width+h+d,g.y+g.height/2)):(this.translate(b-g.x+h+d,c-g.y-g.height/2),this.rotate(a,g.x-h-d,g.y+g.height/2)),f.insertBefore(this.node?this:this[0])}},Raphael.el.label=function(){var a=this.getBBox(),b=this.paper||this[0].paper,c=Math.min(20,a.width+10,a.height+10)/2;if(b)return b.rect(a.x-c/2,a.y-c/2,a.width+c,a.height+c,c).attr({stroke:"none",fill:"#000"}).insertBefore(this.node?this:this[0])},Raphael.el.blob=function(a,b,c){var g,h,i,d=this.getBBox(),e=Math.PI/180,f=this.paper||this[0].paper;if(f){switch(this.type){case"text":case"circle":case"ellipse":h=!0;break;default:h=!1}g=f.path().attr({fill:"#000",stroke:"none"}),a=(+a+1?a:45)+90,i=Math.min(d.height,d.width),b="number"==typeof b?b:h?d.x+d.width/2:d.x,c="number"==typeof c?c:h?d.y+d.height/2:d.y;var j=Math.max(d.width+i,25*i/12),k=Math.max(d.height+i,25*i/12),l=b+i*Math.sin((a-22.5)*e),m=c+i*Math.cos((a-22.5)*e),n=b+i*Math.sin((a+22.5)*e),o=c+i*Math.cos((a+22.5)*e),p=(n-l)/2,q=(o-m)/2,r=j/2,s=k/2,t=-Math.sqrt(Math.abs(r*r*s*s-r*r*q*q-s*s*p*p)/(r*r*q*q+s*s*p*p)),u=t*r*q/s+(n+l)/2,v=t*-s*p/r+(o+m)/2;return g.attr({x:u,y:v,path:["M",b,c,"L",n,o,"A",r,s,0,1,1,l,m,"z"].join(",")}),this.translate(u-d.x-d.width/2,v-d.y-d.height/2),g.insertBefore(this.node?this:this[0])}},Raphael.fn.label=function(a,b,c){var d=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),d.push(c.label(),c)},Raphael.fn.popup=function(a,b,c,d,e){var f=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),f.push(c.popup(d,e),c)},Raphael.fn.tag=function(a,b,c,d,e){var f=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),f.push(c.tag(d,e),c)},Raphael.fn.flag=function(a,b,c,d){var e=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),e.push(c.flag(d),c)},Raphael.fn.drop=function(a,b,c,d){var e=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),e.push(c.drop(d),c)},Raphael.fn.blob=function(a,b,c,d){var e=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),e.push(c.blob(d),c)},Raphael.el.lighter=function(a){a=a||2;var b=[this.attrs.fill,this.attrs.stroke];return this.fs=this.fs||[b[0],b[1]],b[0]=Raphael.rgb2hsb(Raphael.getRGB(b[0]).hex),b[1]=Raphael.rgb2hsb(Raphael.getRGB(b[1]).hex),b[0].b=Math.min(b[0].b*a,1),b[0].s=b[0].s/a,b[1].b=Math.min(b[1].b*a,1),b[1].s=b[1].s/a,this.attr({fill:"hsb("+[b[0].h,b[0].s,b[0].b]+")",stroke:"hsb("+[b[1].h,b[1].s,b[1].b]+")"}),this},Raphael.el.darker=function(a){a=a||2;var b=[this.attrs.fill,this.attrs.stroke];return this.fs=this.fs||[b[0],b[1]],b[0]=Raphael.rgb2hsb(Raphael.getRGB(b[0]).hex),b[1]=Raphael.rgb2hsb(Raphael.getRGB(b[1]).hex),b[0].s=Math.min(b[0].s*a,1),b[0].b=b[0].b/a,b[1].s=Math.min(b[1].s*a,1),b[1].b=b[1].b/a,this.attr({fill:"hsb("+[b[0].h,b[0].s,b[0].b]+")",stroke:"hsb("+[b[1].h,b[1].s,b[1].b]+")"}),this},Raphael.el.resetBrightness=function(){return this.fs&&(this.attr({fill:this.fs[0],stroke:this.fs[1]}),delete this.fs),this},function(){var a=["lighter","darker","resetBrightness"],b=["popup","tag","flag","label","drop","blob"];for(var c in b)(function(a){Raphael.st[a]=function(){return Raphael.el[a].apply(this,arguments)}})(b[c]);for(var c in a)(function(a){Raphael.st[a]=function(){for(var b=0;this.length>b;b++)this[b][a].apply(this[b],arguments);return this}})(a[c])}(),Raphael.g={shim:{stroke:"none",fill:"#000","fill-opacity":0},txtattr:{font:"12px Arial, sans-serif",fill:"#fff"},colors:function(){for(var a=[.6,.2,.05,.1333,.75,0],b=[],c=0;10>c;c++)a.length>c?b.push("hsb("+a[c]+",.75, .75)"):b.push("hsb("+a[c-a.length]+", 1, .5)");return b}(),snapEnds:function(a,b,c){function f(a){return.25>Math.abs(a-.5)?~~a+.5:Math.round(a)}var d=a,e=b;if(d==e)return{from:d,to:e,power:0};var g=(e-d)/c,h=~~g,i=h,j=0;if(h){for(;i;)j--,i=~~(g*Math.pow(10,j))/Math.pow(10,j);j++}else{if(0!=g&&isFinite(g))for(;!h;)j=j||1,h=~~(g*Math.pow(10,j))/Math.pow(10,j),j++;else j=1;j&&j--}return e=f(b*Math.pow(10,j))/Math.pow(10,j),b>e&&(e=f((b+.5)*Math.pow(10,j))/Math.pow(10,j)),d=f((a-(j>0?0:.5))*Math.pow(10,j))/Math.pow(10,j),{from:d,to:e,power:j}},axis:function(a,b,c,d,e,f,g,h,i,j,k){j=null==j?2:j,i=i||"t",f=f||10,k=arguments[arguments.length-1];var t,l="|"==i||" "==i?["M",a+.5,b,"l",0,.001]:1==g||3==g?["M",a+.5,b,"l",0,-c]:["M",a,b+.5,"l",c,0],m=this.snapEnds(d,e,f),n=m.from,o=m.to,p=m.power,q=0,r={font:"11px 'Fontin Sans', Fontin-Sans, sans-serif"},s=k.set();t=(o-n)/f;var u=n,v=p>0?p:0;if(z=c/f,1==+g||3==+g){for(var w=b,x=(g-1?1:-1)*(j+3+!!(g-1));w>=b-c;)"-"!=i&&" "!=i&&(l=l.concat(["M",a-("+"==i||"|"==i?j:2*!(g-1)*j),w+.5,"l",2*j+1,0])),s.push(k.text(a+x,w,h&&h[q++]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r).attr({"text-anchor":g-1?"start":"end"})),u+=t,w-=z;Math.round(w+z-(b-c))&&("-"!=i&&" "!=i&&(l=l.concat(["M",a-("+"==i||"|"==i?j:2*!(g-1)*j),b-c+.5,"l",2*j+1,0])),s.push(k.text(a+x,b-c,h&&h[q]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r).attr({"text-anchor":g-1?"start":"end"})))}else{u=n,v=(p>0)*p,x=(g?-1:1)*(j+9+!g);for(var y=a,z=c/f,A=0,B=0;a+c>=y;){"-"!=i&&" "!=i&&(l=l.concat(["M",y+.5,b-("+"==i?j:2*!!g*j),"l",0,2*j+1])),s.push(A=k.text(y,b+x,h&&h[q++]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r));var C=A.getBBox();B>=C.x-5?s.pop(s.length-1).remove():B=C.x+C.width,u+=t,y+=z}Math.round(y-z-a-c)&&("-"!=i&&" "!=i&&(l=l.concat(["M",a+c+.5,b-("+"==i?j:2*!!g*j),"l",0,2*j+1])),s.push(k.text(a+c,b+x,h&&h[q]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r)))}var D=k.path(l);return D.text=s,D.all=k.set([D,s]),D.remove=function(){this.text.remove(),this.constructor.prototype.remove.call(this)},D},labelise:function(a,b,c){return a?(a+"").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g,function(a,d,e){return d?(+b).toFixed(d.replace(/^#+\.?/g,"").length):e?(100*b/c).toFixed(e.replace(/^%+\.?/g,"").length)+"%":void 0}):(+b).toFixed(0)}}; \ No newline at end of file
diff --git a/vendor/assets/javascripts/g.raphael.js b/vendor/assets/javascripts/g.raphael.js
new file mode 100644
index 00000000000..27f27caf9f2
--- /dev/null
+++ b/vendor/assets/javascripts/g.raphael.js
@@ -0,0 +1,861 @@
+/*!
+ * g.Raphael 0.51 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+
+/*
+ * Tooltips on Element prototype
+ */
+/*\
+ * Element.popup
+ [ method ]
+ **
+ * Puts the context Element in a 'popup' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - dir (string) location of Element relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
+ - size (number) amount of bevel/padding around the Element, as well as half the width and height of the tail [default: `5`]
+ - x (number) x coordinate of the popup's tail [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the popup's tail [default: Element's `y` or `cy`]
+ **
+ = (object) path element of the popup
+ \*/
+Raphael.el.popup = function (dir, size, x, y) {
+ var paper = this.paper || this[0].paper,
+ bb, xy, center, cw, ch;
+
+ if (!paper) return;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ dir = dir == null ? 'up' : dir;
+ size = size || 5;
+ bb = this.getBBox();
+
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+ cw = Math.max(bb.width / 2 - size, 0);
+ ch = Math.max(bb.height / 2 - size, 0);
+
+ this.translate(x - bb.x - (center ? bb.width / 2 : 0), y - bb.y - (center ? bb.height / 2 : 0));
+ bb = this.getBBox();
+
+ var paths = {
+ up: [
+ 'M', x, y,
+ 'l', -size, -size, -cw, 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -bb.height,
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', size * 2 + cw * 2, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, bb.height,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -cw, 0,
+ 'z'
+ ].join(','),
+ down: [
+ 'M', x, y,
+ 'l', size, size, cw, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, bb.height,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -(size * 2 + cw * 2), 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -bb.height,
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', cw, 0,
+ 'z'
+ ].join(','),
+ left: [
+ 'M', x, y,
+ 'l', -size, size, 0, ch,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -bb.width, 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -(size * 2 + ch * 2),
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', bb.width, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, ch,
+ 'z'
+ ].join(','),
+ right: [
+ 'M', x, y,
+ 'l', size, -size, 0, -ch,
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', bb.width, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, size * 2 + ch * 2,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -bb.width, 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -ch,
+ 'z'
+ ].join(',')
+ };
+
+ xy = {
+ up: { x: -!center * (bb.width / 2), y: -size * 2 - (center ? bb.height / 2 : bb.height) },
+ down: { x: -!center * (bb.width / 2), y: size * 2 + (center ? bb.height / 2 : bb.height) },
+ left: { x: -size * 2 - (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) },
+ right: { x: size * 2 + (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) }
+ }[dir];
+
+ this.translate(xy.x, xy.y);
+ return paper.path(paths[dir]).attr({ fill: "#000", stroke: "none" }).insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.tag
+ [ method ]
+ **
+ * Puts the context Element in a 'tag' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - r (number) radius of the loop [default: `5`]
+ - x (number) x coordinate of the center of the tag loop [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the center of the tag loop [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the tag
+ \*/
+Raphael.el.tag = function (angle, r, x, y) {
+ var d = 3,
+ paper = this.paper || this[0].paper;
+
+ if (!paper) return;
+
+ var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
+ bb = this.getBBox(),
+ dx, R, center, tmp;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ angle = angle || 0;
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+ r = r == null ? 5 : r;
+ R = .5522 * r;
+
+ if (bb.height >= r * 2) {
+ p.attr({
+ path: [
+ "M", x, y + r,
+ "a", r, r, 0, 1, 1, 0, -r * 2, r, r, 0, 1, 1, 0, r * 2,
+ "m", 0, -r * 2 -d,
+ "a", r + d, r + d, 0, 1, 0, 0, (r + d) * 2,
+ "L", x + r + d, y + bb.height / 2 + d,
+ "l", bb.width + 2 * d, 0, 0, -bb.height - 2 * d, -bb.width - 2 * d, 0,
+ "L", x, y - r - d
+ ].join(",")
+ });
+ } else {
+ dx = Math.sqrt(Math.pow(r + d, 2) - Math.pow(bb.height / 2 + d, 2));
+ p.attr({
+ path: [
+ "M", x, y + r,
+ "c", -R, 0, -r, R - r, -r, -r, 0, -R, r - R, -r, r, -r, R, 0, r, r - R, r, r, 0, R, R - r, r, -r, r,
+ "M", x + dx, y - bb.height / 2 - d,
+ "a", r + d, r + d, 0, 1, 0, 0, bb.height + 2 * d,
+ "l", r + d - dx + bb.width + 2 * d, 0, 0, -bb.height - 2 * d,
+ "L", x + dx, y - bb.height / 2 - d
+ ].join(",")
+ });
+ }
+
+ angle = 360 - angle;
+ p.rotate(angle, x, y);
+
+ if (this.attrs) {
+ //elements
+ this.attr(this.attrs.x ? 'x' : 'cx', x + r + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
+ this.rotate(angle, x, y);
+ angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - r - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
+ } else {
+ //sets
+ if (angle > 90 && angle < 270) {
+ this.translate(x - bb.x - bb.width - r - d, y - bb.y - bb.height / 2);
+ this.rotate(angle - 180, bb.x + bb.width + r + d, bb.y + bb.height / 2);
+ } else {
+ this.translate(x - bb.x + r + d, y - bb.y - bb.height / 2);
+ this.rotate(angle, bb.x - r - d, bb.y + bb.height / 2);
+ }
+ }
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.drop
+ [ method ]
+ **
+ * Puts the context Element in a 'drop' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the drop's point [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the drop's point [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the drop
+ \*/
+Raphael.el.drop = function (angle, x, y) {
+ var bb = this.getBBox(),
+ paper = this.paper || this[0].paper,
+ center, size, p, dx, dy;
+
+ if (!paper) return;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ angle = angle || 0;
+
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+ size = Math.max(bb.width, bb.height) + Math.min(bb.width, bb.height);
+ p = paper.path([
+ "M", x, y,
+ "l", size, 0,
+ "A", size * .4, size * .4, 0, 1, 0, x + size * .7, y - size * .7,
+ "z"
+ ]).attr({fill: "#000", stroke: "none"}).rotate(22.5 - angle, x, y);
+
+ angle = (angle + 90) * Math.PI / 180;
+ dx = (x + size * Math.sin(angle)) - (center ? 0 : bb.width / 2);
+ dy = (y + size * Math.cos(angle)) - (center ? 0 : bb.height / 2);
+
+ this.attrs ?
+ this.attr(this.attrs.x ? 'x' : 'cx', dx).attr(this.attrs.y ? 'y' : 'cy', dy) :
+ this.translate(dx - bb.x, dy - bb.y);
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.flag
+ [ method ]
+ **
+ * Puts the context Element in a 'flag' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the flag's point [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the flag's point [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the flag
+ \*/
+Raphael.el.flag = function (angle, x, y) {
+ var d = 3,
+ paper = this.paper || this[0].paper;
+
+ if (!paper) return;
+
+ var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
+ bb = this.getBBox(),
+ h = bb.height / 2,
+ center;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ angle = angle || 0;
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2: bb.y);
+
+ p.attr({
+ path: [
+ "M", x, y,
+ "l", h + d, -h - d, bb.width + 2 * d, 0, 0, bb.height + 2 * d, -bb.width - 2 * d, 0,
+ "z"
+ ].join(",")
+ });
+
+ angle = 360 - angle;
+ p.rotate(angle, x, y);
+
+ if (this.attrs) {
+ //elements
+ this.attr(this.attrs.x ? 'x' : 'cx', x + h + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
+ this.rotate(angle, x, y);
+ angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - h - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
+ } else {
+ //sets
+ if (angle > 90 && angle < 270) {
+ this.translate(x - bb.x - bb.width - h - d, y - bb.y - bb.height / 2);
+ this.rotate(angle - 180, bb.x + bb.width + h + d, bb.y + bb.height / 2);
+ } else {
+ this.translate(x - bb.x + h + d, y - bb.y - bb.height / 2);
+ this.rotate(angle, bb.x - h - d, bb.y + bb.height / 2);
+ }
+ }
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.label
+ [ method ]
+ **
+ * Puts the context Element in a 'label' tooltip. Can also be used on sets.
+ **
+ = (object) path element of the label.
+ \*/
+Raphael.el.label = function () {
+ var bb = this.getBBox(),
+ paper = this.paper || this[0].paper,
+ r = Math.min(20, bb.width + 10, bb.height + 10) / 2;
+
+ if (!paper) return;
+
+ return paper.rect(bb.x - r / 2, bb.y - r / 2, bb.width + r, bb.height + r, r).attr({ stroke: 'none', fill: '#000' }).insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.blob
+ [ method ]
+ **
+ * Puts the context Element in a 'blob' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the blob's tail [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the blob's tail [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the blob
+ \*/
+Raphael.el.blob = function (angle, x, y) {
+ var bb = this.getBBox(),
+ rad = Math.PI / 180,
+ paper = this.paper || this[0].paper,
+ p, center, size;
+
+ if (!paper) return;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ p = paper.path().attr({ fill: "#000", stroke: "none" });
+ angle = (+angle + 1 ? angle : 45) + 90;
+ size = Math.min(bb.height, bb.width);
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+
+ var w = Math.max(bb.width + size, size * 25 / 12),
+ h = Math.max(bb.height + size, size * 25 / 12),
+ x2 = x + size * Math.sin((angle - 22.5) * rad),
+ y2 = y + size * Math.cos((angle - 22.5) * rad),
+ x1 = x + size * Math.sin((angle + 22.5) * rad),
+ y1 = y + size * Math.cos((angle + 22.5) * rad),
+ dx = (x1 - x2) / 2,
+ dy = (y1 - y2) / 2,
+ rx = w / 2,
+ ry = h / 2,
+ k = -Math.sqrt(Math.abs(rx * rx * ry * ry - rx * rx * dy * dy - ry * ry * dx * dx) / (rx * rx * dy * dy + ry * ry * dx * dx)),
+ cx = k * rx * dy / ry + (x1 + x2) / 2,
+ cy = k * -ry * dx / rx + (y1 + y2) / 2;
+
+ p.attr({
+ x: cx,
+ y: cy,
+ path: [
+ "M", x, y,
+ "L", x1, y1,
+ "A", rx, ry, 0, 1, 1, x2, y2,
+ "z"
+ ].join(",")
+ });
+
+ this.translate(cx - bb.x - bb.width / 2, cy - bb.y - bb.height / 2);
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*
+ * Tooltips on Paper prototype
+ */
+/*\
+ * Paper.label
+ [ method ]
+ **
+ * Puts the given `text` into a 'label' tooltip. The text is given a default style according to @g.txtattr. See @Element.label
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the center of the label
+ - y (number) y coordinate of the center of the label
+ - text (string) text to place inside the label
+ **
+ = (object) set containing the label path and the text element
+ > Usage
+ | paper.label(50, 50, "$9.99");
+ \*/
+Raphael.fn.label = function (x, y, text) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.label(), text);
+};
+
+/*\
+ * Paper.popup
+ [ method ]
+ **
+ * Puts the given `text` into a 'popup' tooltip. The text is given a default style according to @g.txtattr. See @Element.popup
+ *
+ * Note: The `dir` parameter has changed from g.Raphael 0.4.1 to 0.5. The options `0`, `1`, `2`, and `3` has been changed to `'down'`, `'left'`, `'up'`, and `'right'` respectively.
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the popup's tail
+ - y (number) y coordinate of the popup's tail
+ - text (string) text to place inside the popup
+ - dir (string) location of the text relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
+ - size (number) amount of padding around the Element [default: `5`]
+ **
+ = (object) set containing the popup path and the text element
+ > Usage
+ | paper.popup(50, 50, "$9.99", 'down');
+ \*/
+Raphael.fn.popup = function (x, y, text, dir, size) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.popup(dir, size), text);
+};
+
+/*\
+ * Paper.tag
+ [ method ]
+ **
+ * Puts the given text into a 'tag' tooltip. The text is given a default style according to @g.txtattr. See @Element.tag
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the center of the tag loop
+ - y (number) y coordinate of the center of the tag loop
+ - text (string) text to place inside the tag
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - r (number) radius of the loop [default: `5`]
+ **
+ = (object) set containing the tag path and the text element
+ > Usage
+ | paper.tag(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.tag = function (x, y, text, angle, r) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.tag(angle, r), text);
+};
+
+/*\
+ * Paper.flag
+ [ method ]
+ **
+ * Puts the given `text` into a 'flag' tooltip. The text is given a default style according to @g.txtattr. See @Element.flag
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the flag's point
+ - y (number) y coordinate of the flag's point
+ - text (string) text to place inside the flag
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the flag path and the text element
+ > Usage
+ | paper.flag(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.flag = function (x, y, text, angle) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.flag(angle), text);
+};
+
+/*\
+ * Paper.drop
+ [ method ]
+ **
+ * Puts the given text into a 'drop' tooltip. The text is given a default style according to @g.txtattr. See @Element.drop
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the drop's point
+ - y (number) y coordinate of the drop's point
+ - text (string) text to place inside the drop
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the drop path and the text element
+ > Usage
+ | paper.drop(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.drop = function (x, y, text, angle) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.drop(angle), text);
+};
+
+/*\
+ * Paper.blob
+ [ method ]
+ **
+ * Puts the given text into a 'blob' tooltip. The text is given a default style according to @g.txtattr. See @Element.blob
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the blob's tail
+ - y (number) y coordinate of the blob's tail
+ - text (string) text to place inside the blob
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the blob path and the text element
+ > Usage
+ | paper.blob(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.blob = function (x, y, text, angle) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.blob(angle), text);
+};
+
+/**
+ * Brightness functions on the Element prototype
+ */
+/*\
+ * Element.lighter
+ [ method ]
+ **
+ * Makes the context element lighter by increasing the brightness and reducing the saturation by a given factor. Can be called on Sets.
+ **
+ > Parameters
+ **
+ - times (number) adjustment factor [default: `2`]
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ | fill: "#ff0000",
+ | stroke: "#fff",
+ | "stroke-width": 2
+ | }).lighter(6);
+ \*/
+Raphael.el.lighter = function (times) {
+ times = times || 2;
+
+ var fs = [this.attrs.fill, this.attrs.stroke];
+
+ this.fs = this.fs || [fs[0], fs[1]];
+
+ fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
+ fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
+ fs[0].b = Math.min(fs[0].b * times, 1);
+ fs[0].s = fs[0].s / times;
+ fs[1].b = Math.min(fs[1].b * times, 1);
+ fs[1].s = fs[1].s / times;
+
+ this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
+ return this;
+};
+
+/*\
+ * Element.darker
+ [ method ]
+ **
+ * Makes the context element darker by decreasing the brightness and increasing the saturation by a given factor. Can be called on Sets.
+ **
+ > Parameters
+ **
+ - times (number) adjustment factor [default: `2`]
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ | fill: "#ff0000",
+ | stroke: "#fff",
+ | "stroke-width": 2
+ | }).darker(6);
+ \*/
+Raphael.el.darker = function (times) {
+ times = times || 2;
+
+ var fs = [this.attrs.fill, this.attrs.stroke];
+
+ this.fs = this.fs || [fs[0], fs[1]];
+
+ fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
+ fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
+ fs[0].s = Math.min(fs[0].s * times, 1);
+ fs[0].b = fs[0].b / times;
+ fs[1].s = Math.min(fs[1].s * times, 1);
+ fs[1].b = fs[1].b / times;
+
+ this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
+ return this;
+};
+
+/*\
+ * Element.resetBrightness
+ [ method ]
+ **
+ * Resets brightness and saturation levels to their original values. See @Element.lighter and @Element.darker. Can be called on Sets.
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ | fill: "#ff0000",
+ | stroke: "#fff",
+ | "stroke-width": 2
+ | }).lighter(6).resetBrightness();
+ \*/
+Raphael.el.resetBrightness = function () {
+ if (this.fs) {
+ this.attr({ fill: this.fs[0], stroke: this.fs[1] });
+ delete this.fs;
+ }
+ return this;
+};
+
+//alias to set prototype
+(function () {
+ var brightness = ['lighter', 'darker', 'resetBrightness'],
+ tooltips = ['popup', 'tag', 'flag', 'label', 'drop', 'blob'];
+
+ for (var f in tooltips) (function (name) {
+ Raphael.st[name] = function () {
+ return Raphael.el[name].apply(this, arguments);
+ };
+ })(tooltips[f]);
+
+ for (var f in brightness) (function (name) {
+ Raphael.st[name] = function () {
+ for (var i = 0; i < this.length; i++) {
+ this[i][name].apply(this[i], arguments);
+ }
+
+ return this;
+ };
+ })(brightness[f]);
+})();
+
+//chart prototype for storing common functions
+Raphael.g = {
+ /*\
+ * g.shim
+ [ object ]
+ **
+ * An attribute object that charts will set on all generated shims (shims being the invisible objects that mouse events are bound to)
+ **
+ > Default value
+ | { stroke: 'none', fill: '#000', 'fill-opacity': 0 }
+ \*/
+ shim: { stroke: 'none', fill: '#000', 'fill-opacity': 0 },
+
+ /*\
+ * g.txtattr
+ [ object ]
+ **
+ * An attribute object that charts and tooltips will set on any generated text
+ **
+ > Default value
+ | { font: '12px Arial, sans-serif', fill: '#fff' }
+ \*/
+ txtattr: { font: '12px Arial, sans-serif', fill: '#fff' },
+
+ /*\
+ * g.colors
+ [ array ]
+ **
+ * An array of color values that charts will iterate through when drawing chart data values.
+ **
+ \*/
+ colors: (function () {
+ var hues = [.6, .2, .05, .1333, .75, 0],
+ colors = [];
+
+ for (var i = 0; i < 10; i++) {
+ if (i < hues.length) {
+ colors.push('hsb(' + hues[i] + ',.75, .75)');
+ } else {
+ colors.push('hsb(' + hues[i - hues.length] + ', 1, .5)');
+ }
+ }
+
+ return colors;
+ })(),
+
+ snapEnds: function(from, to, steps) {
+ var f = from,
+ t = to;
+
+ if (f == t) {
+ return {from: f, to: t, power: 0};
+ }
+
+ function round(a) {
+ return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
+ }
+
+ var d = (t - f) / steps,
+ r = ~~(d),
+ R = r,
+ i = 0;
+
+ if (r) {
+ while (R) {
+ i--;
+ R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
+ }
+
+ i ++;
+ } else {
+ if(d == 0 || !isFinite(d)) {
+ i = 1;
+ } else {
+ while (!r) {
+ i = i || 1;
+ r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
+ i++;
+ }
+ }
+
+ i && i--;
+ }
+
+ t = round(to * Math.pow(10, i)) / Math.pow(10, i);
+
+ if (t < to) {
+ t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
+ }
+
+ f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
+ return { from: f, to: t, power: i };
+ },
+
+ axis: function (x, y, length, from, to, steps, orientation, labels, type, dashsize, paper) {
+ dashsize = dashsize == null ? 2 : dashsize;
+ type = type || "t";
+ steps = steps || 10;
+ paper = arguments[arguments.length-1] //paper is always last argument
+
+ var path = type == "|" || type == " " ? ["M", x + .5, y, "l", 0, .001] : orientation == 1 || orientation == 3 ? ["M", x + .5, y, "l", 0, -length] : ["M", x, y + .5, "l", length, 0],
+ ends = this.snapEnds(from, to, steps),
+ f = ends.from,
+ t = ends.to,
+ i = ends.power,
+ j = 0,
+ txtattr = { font: "11px 'Fontin Sans', Fontin-Sans, sans-serif" },
+ text = paper.set(),
+ d;
+
+ d = (t - f) / steps;
+
+ var label = f,
+ rnd = i > 0 ? i : 0;
+ dx = length / steps;
+
+ if (+orientation == 1 || +orientation == 3) {
+ var Y = y,
+ addon = (orientation - 1 ? 1 : -1) * (dashsize + 3 + !!(orientation - 1));
+
+ while (Y >= y - length) {
+ type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), Y + .5, "l", dashsize * 2 + 1, 0]));
+ text.push(paper.text(x + addon, Y, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
+ label += d;
+ Y -= dx;
+ }
+
+ if (Math.round(Y + dx - (y - length))) {
+ type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), y - length + .5, "l", dashsize * 2 + 1, 0]));
+ text.push(paper.text(x + addon, y - length, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
+ }
+ } else {
+ label = f;
+ rnd = (i > 0) * i;
+ addon = (orientation ? -1 : 1) * (dashsize + 9 + !orientation);
+
+ var X = x,
+ dx = length / steps,
+ txt = 0,
+ prev = 0;
+
+ while (X <= x + length) {
+ type != "-" && type != " " && (path = path.concat(["M", X + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
+ text.push(txt = paper.text(X, y + addon, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
+
+ var bb = txt.getBBox();
+
+ if (prev >= bb.x - 5) {
+ text.pop(text.length - 1).remove();
+ } else {
+ prev = bb.x + bb.width;
+ }
+
+ label += d;
+ X += dx;
+ }
+
+ if (Math.round(X - dx - x - length)) {
+ type != "-" && type != " " && (path = path.concat(["M", x + length + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
+ text.push(paper.text(x + length, y + addon, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
+ }
+ }
+
+ var res = paper.path(path);
+
+ res.text = text;
+ res.all = paper.set([res, text]);
+ res.remove = function () {
+ this.text.remove();
+ this.constructor.prototype.remove.call(this);
+ };
+
+ return res;
+ },
+
+ labelise: function(label, val, total) {
+ if (label) {
+ return (label + "").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g, function (all, value, percent) {
+ if (value) {
+ return (+val).toFixed(value.replace(/^#+\.?/g, "").length);
+ }
+ if (percent) {
+ return (val * 100 / total).toFixed(percent.replace(/^%+\.?/g, "").length) + "%";
+ }
+ });
+ } else {
+ return (+val).toFixed(0);
+ }
+ }
+}
diff --git a/vendor/assets/javascripts/jquery.nicescroll.js b/vendor/assets/javascripts/jquery.nicescroll.js
new file mode 100644
index 00000000000..7653f25df4b
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.nicescroll.js
@@ -0,0 +1,3634 @@
+/* jquery.nicescroll
+-- version 3.6.0
+-- copyright 2014-11-21 InuYaksa*2014
+-- licensed under the MIT
+--
+-- http://nicescroll.areaaperta.com/
+-- https://github.com/inuyaksa/jquery.nicescroll
+--
+*/
+
+(function(factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals.
+ factory(jQuery);
+ }
+}(function(jQuery) {
+ "use strict";
+
+ // globals
+ var domfocus = false;
+ var mousefocus = false;
+ var tabindexcounter = 0;
+ var ascrailcounter = 2000;
+ var globalmaxzindex = 0;
+
+ var $ = jQuery; // sandbox
+
+ // http://stackoverflow.com/questions/2161159/get-script-path
+ function getScriptPath() {
+ var scripts = document.getElementsByTagName('script');
+ var path = scripts[scripts.length - 1].src.split('?')[0];
+ return (path.split('/').length > 0) ? path.split('/').slice(0, -1).join('/') + '/' : '';
+ }
+
+ var vendors = ['webkit','ms','moz','o'];
+
+ var setAnimationFrame = window.requestAnimationFrame || false;
+ var clearAnimationFrame = window.cancelAnimationFrame || false;
+
+ if (!setAnimationFrame) { // legacy detection
+ for (var vx in vendors) {
+ var v = vendors[vx];
+ if (!setAnimationFrame) setAnimationFrame = window[v + 'RequestAnimationFrame'];
+ if (!clearAnimationFrame) clearAnimationFrame = window[v + 'CancelAnimationFrame'] || window[v + 'CancelRequestAnimationFrame'];
+ }
+ }
+
+ var ClsMutationObserver = window.MutationObserver || window.WebKitMutationObserver || false;
+
+ var _globaloptions = {
+ zindex: "auto",
+ cursoropacitymin: 0,
+ cursoropacitymax: 1,
+ cursorcolor: "#424242",
+ cursorwidth: "5px",
+ cursorborder: "1px solid #fff",
+ cursorborderradius: "5px",
+ scrollspeed: 60,
+ mousescrollstep: 8 * 3,
+ touchbehavior: false,
+ hwacceleration: true,
+ usetransition: true,
+ boxzoom: false,
+ dblclickzoom: true,
+ gesturezoom: true,
+ grabcursorenabled: true,
+ autohidemode: true,
+ background: "",
+ iframeautoresize: true,
+ cursorminheight: 32,
+ preservenativescrolling: true,
+ railoffset: false,
+ railhoffset: false,
+ bouncescroll: true,
+ spacebarenabled: true,
+ railpadding: {
+ top: 0,
+ right: 0,
+ left: 0,
+ bottom: 0
+ },
+ disableoutline: true,
+ horizrailenabled: true,
+ railalign: "right",
+ railvalign: "bottom",
+ enabletranslate3d: true,
+ enablemousewheel: true,
+ enablekeyboard: true,
+ smoothscroll: true,
+ sensitiverail: true,
+ enablemouselockapi: true,
+ // cursormaxheight:false,
+ cursorfixedheight: false,
+ directionlockdeadzone: 6,
+ hidecursordelay: 400,
+ nativeparentscrolling: true,
+ enablescrollonselection: true,
+ overflowx: true,
+ overflowy: true,
+ cursordragspeed: 0.3,
+ rtlmode: "auto",
+ cursordragontouch: false,
+ oneaxismousemode: "auto",
+ scriptpath: getScriptPath(),
+ preventmultitouchscrolling: true
+ };
+
+ var browserdetected = false;
+
+ var getBrowserDetection = function() {
+
+ if (browserdetected) return browserdetected;
+
+ var _el = document.createElement('DIV'),
+ _style = _el.style,
+ _agent = navigator.userAgent,
+ _platform = navigator.platform,
+ d = {};
+
+ d.haspointerlock = "pointerLockElement" in document || "webkitPointerLockElement" in document || "mozPointerLockElement" in document;
+
+ d.isopera = ("opera" in window); // 12-
+ d.isopera12 = (d.isopera && ("getUserMedia" in navigator));
+ d.isoperamini = (Object.prototype.toString.call(window.operamini) === "[object OperaMini]");
+
+ d.isie = (("all" in document) && ("attachEvent" in _el) && !d.isopera); //IE10-
+ d.isieold = (d.isie && !("msInterpolationMode" in _style)); // IE6 and older
+ d.isie7 = d.isie && !d.isieold && (!("documentMode" in document) || (document.documentMode == 7));
+ d.isie8 = d.isie && ("documentMode" in document) && (document.documentMode == 8);
+ d.isie9 = d.isie && ("performance" in window) && (document.documentMode >= 9);
+ d.isie10 = d.isie && ("performance" in window) && (document.documentMode == 10);
+ d.isie11 = ("msRequestFullscreen" in _el) && (document.documentMode >= 11); // IE11+
+
+ d.isie9mobile = /iemobile.9/i.test(_agent); //wp 7.1 mango
+ if (d.isie9mobile) d.isie9 = false;
+ d.isie7mobile = (!d.isie9mobile && d.isie7) && /iemobile/i.test(_agent); //wp 7.0
+
+ d.ismozilla = ("MozAppearance" in _style);
+
+ d.iswebkit = ("WebkitAppearance" in _style);
+
+ d.ischrome = ("chrome" in window);
+ d.ischrome22 = (d.ischrome && d.haspointerlock);
+ d.ischrome26 = (d.ischrome && ("transition" in _style)); // issue with transform detection (maintain prefix)
+
+ d.cantouch = ("ontouchstart" in document.documentElement) || ("ontouchstart" in window); // detection for Chrome Touch Emulation
+ d.hasmstouch = (window.MSPointerEvent || false); // IE10 pointer events
+ d.hasw3ctouch = (window.PointerEvent || false); //IE11 pointer events, following W3C Pointer Events spec
+
+ d.ismac = /^mac$/i.test(_platform);
+
+ d.isios = (d.cantouch && /iphone|ipad|ipod/i.test(_platform));
+ d.isios4 = ((d.isios) && !("seal" in Object));
+ d.isios7 = ((d.isios)&&("webkitHidden" in document)); //iOS 7+
+
+ d.isandroid = (/android/i.test(_agent));
+
+ d.haseventlistener = ("addEventListener" in _el);
+
+ d.trstyle = false;
+ d.hastransform = false;
+ d.hastranslate3d = false;
+ d.transitionstyle = false;
+ d.hastransition = false;
+ d.transitionend = false;
+
+ var a;
+ var check = ['transform', 'msTransform', 'webkitTransform', 'MozTransform', 'OTransform'];
+ for (a = 0; a < check.length; a++) {
+ if (typeof _style[check[a]] != "undefined") {
+ d.trstyle = check[a];
+ break;
+ }
+ }
+ d.hastransform = (!!d.trstyle);
+ if (d.hastransform) {
+ _style[d.trstyle] = "translate3d(1px,2px,3px)";
+ d.hastranslate3d = /translate3d/.test(_style[d.trstyle]);
+ }
+
+ d.transitionstyle = false;
+ d.prefixstyle = '';
+ d.transitionend = false;
+ check = ['transition', 'webkitTransition', 'msTransition', 'MozTransition', 'OTransition', 'OTransition', 'KhtmlTransition'];
+ var prefix = ['', '-webkit-', '-ms-', '-moz-', '-o-', '-o', '-khtml-'];
+ var evs = ['transitionend', 'webkitTransitionEnd', 'msTransitionEnd', 'transitionend', 'otransitionend', 'oTransitionEnd', 'KhtmlTransitionEnd'];
+ for (a = 0; a < check.length; a++) {
+ if (check[a] in _style) {
+ d.transitionstyle = check[a];
+ d.prefixstyle = prefix[a];
+ d.transitionend = evs[a];
+ break;
+ }
+ }
+ if (d.ischrome26) { // always use prefix
+ d.prefixstyle = prefix[1];
+ }
+
+ d.hastransition = (d.transitionstyle);
+
+ function detectCursorGrab() {
+ var lst = ['-webkit-grab', '-moz-grab', 'grab'];
+ if ((d.ischrome && !d.ischrome22) || d.isie) lst = []; // force setting for IE returns false positive and chrome cursor bug
+ for (var a = 0; a < lst.length; a++) {
+ var p = lst[a];
+ _style.cursor = p;
+ if (_style.cursor == p) return p;
+ }
+ return 'url(//mail.google.com/mail/images/2/openhand.cur),n-resize'; // thank you google for custom cursor!
+ }
+ d.cursorgrabvalue = detectCursorGrab();
+
+ d.hasmousecapture = ("setCapture" in _el);
+
+ d.hasMutationObserver = (ClsMutationObserver !== false);
+
+ _el = null; //memory released
+
+ browserdetected = d;
+
+ return d;
+ };
+
+ var NiceScrollClass = function(myopt, me) {
+
+ var self = this;
+
+ this.version = '3.6.0';
+ this.name = 'nicescroll';
+
+ this.me = me;
+
+ this.opt = {
+ doc: $("body"),
+ win: false
+ };
+
+ $.extend(this.opt, _globaloptions); // clone opts
+
+ // Options for internal use
+ this.opt.snapbackspeed = 80;
+
+ if (myopt || false) {
+ for (var a in self.opt) {
+ if (typeof myopt[a] != "undefined") self.opt[a] = myopt[a];
+ }
+ }
+
+ this.doc = self.opt.doc;
+ this.iddoc = (this.doc && this.doc[0]) ? this.doc[0].id || '' : '';
+ this.ispage = /^BODY|HTML/.test((self.opt.win) ? self.opt.win[0].nodeName : this.doc[0].nodeName);
+ this.haswrapper = (self.opt.win !== false);
+ this.win = self.opt.win || (this.ispage ? $(window) : this.doc);
+ this.docscroll = (this.ispage && !this.haswrapper) ? $(window) : this.win;
+ this.body = $("body");
+ this.viewport = false;
+
+ this.isfixed = false;
+
+ this.iframe = false;
+ this.isiframe = ((this.doc[0].nodeName == 'IFRAME') && (this.win[0].nodeName == 'IFRAME'));
+
+ this.istextarea = (this.win[0].nodeName == 'TEXTAREA');
+
+ this.forcescreen = false; //force to use screen position on events
+
+ this.canshowonmouseevent = (self.opt.autohidemode != "scroll");
+
+ // Events jump table
+ this.onmousedown = false;
+ this.onmouseup = false;
+ this.onmousemove = false;
+ this.onmousewheel = false;
+ this.onkeypress = false;
+ this.ongesturezoom = false;
+ this.onclick = false;
+
+ // Nicescroll custom events
+ this.onscrollstart = false;
+ this.onscrollend = false;
+ this.onscrollcancel = false;
+
+ this.onzoomin = false;
+ this.onzoomout = false;
+
+ // Let's start!
+ this.view = false;
+ this.page = false;
+
+ this.scroll = {
+ x: 0,
+ y: 0
+ };
+ this.scrollratio = {
+ x: 0,
+ y: 0
+ };
+ this.cursorheight = 20;
+ this.scrollvaluemax = 0;
+
+ this.isrtlmode = (this.opt.rtlmode == "auto") ? ((this.win[0] == window ? this.body : this.win).css("direction") == "rtl") : (this.opt.rtlmode === true);
+ // this.checkrtlmode = false;
+
+ this.scrollrunning = false;
+
+ this.scrollmom = false;
+
+ this.observer = false; // observer div changes
+ this.observerremover = false; // observer on parent for remove detection
+ this.observerbody = false; // observer on body for position change
+
+ do {
+ this.id = "ascrail" + (ascrailcounter++);
+ } while (document.getElementById(this.id));
+
+ this.rail = false;
+ this.cursor = false;
+ this.cursorfreezed = false;
+ this.selectiondrag = false;
+
+ this.zoom = false;
+ this.zoomactive = false;
+
+ this.hasfocus = false;
+ this.hasmousefocus = false;
+
+ this.visibility = true;
+ this.railslocked = false; // locked by resize
+ this.locked = false; // prevent lost of locked status sets by user
+ this.hidden = false; // rails always hidden
+ this.cursoractive = true; // user can interact with cursors
+
+ this.wheelprevented = false; //prevent mousewheel event
+
+ this.overflowx = self.opt.overflowx;
+ this.overflowy = self.opt.overflowy;
+
+ this.nativescrollingarea = false;
+ this.checkarea = 0;
+
+ this.events = []; // event list for unbind
+
+ this.saved = {}; // style saved
+
+ this.delaylist = {};
+ this.synclist = {};
+
+ this.lastdeltax = 0;
+ this.lastdeltay = 0;
+
+ this.detected = getBrowserDetection();
+
+ var cap = $.extend({}, this.detected);
+
+ this.canhwscroll = (cap.hastransform && self.opt.hwacceleration);
+ this.ishwscroll = (this.canhwscroll && self.haswrapper);
+
+ this.hasreversehr = (this.isrtlmode&&!cap.iswebkit); //RTL mode with reverse horizontal axis
+
+ this.istouchcapable = false; // desktop devices with touch screen support
+
+ //## Check WebKit-based desktop with touch support
+ //## + Firefox 18 nightly build (desktop) false positive (or desktop with touch support)
+ if (cap.cantouch && !cap.isios && !cap.isandroid && (cap.iswebkit || cap.ismozilla)) {
+ this.istouchcapable = true;
+ cap.cantouch = false; // parse normal desktop events
+ }
+
+ //## disable MouseLock API on user request
+ if (!self.opt.enablemouselockapi) {
+ cap.hasmousecapture = false;
+ cap.haspointerlock = false;
+ }
+
+/* deprecated
+ this.delayed = function(name, fn, tm, lazy) {
+ };
+*/
+
+ this.debounced = function(name, fn, tm) {
+ var dd = self.delaylist[name];
+ self.delaylist[name] = fn;
+ if (!dd) {
+ setTimeout(function() {
+ var fn = self.delaylist[name];
+ self.delaylist[name] = false;
+ fn.call(self);
+ }, tm);
+ }
+ };
+
+ var _onsync = false;
+
+ this.synched = function(name, fn) {
+
+ function requestSync() {
+ if (_onsync) return;
+ setAnimationFrame(function() {
+ _onsync = false;
+ for (var nn in self.synclist) {
+ var fn = self.synclist[nn];
+ if (fn) fn.call(self);
+ self.synclist[nn] = false;
+ }
+ });
+ _onsync = true;
+ }
+
+ self.synclist[name] = fn;
+ requestSync();
+ return name;
+ };
+
+ this.unsynched = function(name) {
+ if (self.synclist[name]) self.synclist[name] = false;
+ };
+
+ this.css = function(el, pars) { // save & set
+ for (var n in pars) {
+ self.saved.css.push([el, n, el.css(n)]);
+ el.css(n, pars[n]);
+ }
+ };
+
+ this.scrollTop = function(val) {
+ return (typeof val == "undefined") ? self.getScrollTop() : self.setScrollTop(val);
+ };
+
+ this.scrollLeft = function(val) {
+ return (typeof val == "undefined") ? self.getScrollLeft() : self.setScrollLeft(val);
+ };
+
+ // derived by by Dan Pupius www.pupius.net
+ var BezierClass = function(st, ed, spd, p1, p2, p3, p4) {
+
+ this.st = st;
+ this.ed = ed;
+ this.spd = spd;
+
+ this.p1 = p1 || 0;
+ this.p2 = p2 || 1;
+ this.p3 = p3 || 0;
+ this.p4 = p4 || 1;
+
+ this.ts = (new Date()).getTime();
+ this.df = this.ed - this.st;
+ };
+ BezierClass.prototype = {
+ B2: function(t) {
+ return 3 * t * t * (1 - t);
+ },
+ B3: function(t) {
+ return 3 * t * (1 - t) * (1 - t);
+ },
+ B4: function(t) {
+ return (1 - t) * (1 - t) * (1 - t);
+ },
+ getNow: function() {
+ var nw = (new Date()).getTime();
+ var pc = 1 - ((nw - this.ts) / this.spd);
+ var bz = this.B2(pc) + this.B3(pc) + this.B4(pc);
+ return (pc < 0) ? this.ed : this.st + Math.round(this.df * bz);
+ },
+ update: function(ed, spd) {
+ this.st = this.getNow();
+ this.ed = ed;
+ this.spd = spd;
+ this.ts = (new Date()).getTime();
+ this.df = this.ed - this.st;
+ return this;
+ }
+ };
+
+ //derived from http://stackoverflow.com/questions/11236090/
+ function getMatrixValues() {
+ var tr = self.doc.css(cap.trstyle);
+ if (tr && (tr.substr(0, 6) == "matrix")) {
+ return tr.replace(/^.*\((.*)\)$/g, "$1").replace(/px/g, '').split(/, +/);
+ }
+ return false;
+ }
+
+ if (this.ishwscroll) {
+ // hw accelerated scroll
+ this.doc.translate = {
+ x: 0,
+ y: 0,
+ tx: "0px",
+ ty: "0px"
+ };
+
+ //this one can help to enable hw accel on ios6 http://indiegamr.com/ios6-html-hardware-acceleration-changes-and-how-to-fix-them/
+ if (cap.hastranslate3d && cap.isios) this.doc.css("-webkit-backface-visibility", "hidden"); // prevent flickering http://stackoverflow.com/questions/3461441/
+
+ this.getScrollTop = function(last) {
+ if (!last) {
+ var mtx = getMatrixValues();
+ if (mtx) return (mtx.length == 16) ? -mtx[13] : -mtx[5]; //matrix3d 16 on IE10
+ if (self.timerscroll && self.timerscroll.bz) return self.timerscroll.bz.getNow();
+ }
+ return self.doc.translate.y;
+ };
+
+ this.getScrollLeft = function(last) {
+ if (!last) {
+ var mtx = getMatrixValues();
+ if (mtx) return (mtx.length == 16) ? -mtx[12] : -mtx[4]; //matrix3d 16 on IE10
+ if (self.timerscroll && self.timerscroll.bh) return self.timerscroll.bh.getNow();
+ }
+ return self.doc.translate.x;
+ };
+
+ this.notifyScrollEvent = function(el) {
+ var e = document.createEvent("UIEvents");
+ e.initUIEvent("scroll", false, true, window, 1);
+ e.niceevent = true;
+ el.dispatchEvent(e);
+ };
+
+ var cxscrollleft = (this.isrtlmode) ? 1 : -1;
+
+ if (cap.hastranslate3d && self.opt.enabletranslate3d) {
+ this.setScrollTop = function(val, silent) {
+ self.doc.translate.y = val;
+ self.doc.translate.ty = (val * -1) + "px";
+ self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ this.setScrollLeft = function(val, silent) {
+ self.doc.translate.x = val;
+ self.doc.translate.tx = (val * cxscrollleft) + "px";
+ self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ } else {
+ this.setScrollTop = function(val, silent) {
+ self.doc.translate.y = val;
+ self.doc.translate.ty = (val * -1) + "px";
+ self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ this.setScrollLeft = function(val, silent) {
+ self.doc.translate.x = val;
+ self.doc.translate.tx = (val * cxscrollleft) + "px";
+ self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ }
+ } else {
+ // native scroll
+ this.getScrollTop = function() {
+ return self.docscroll.scrollTop();
+ };
+ this.setScrollTop = function(val) {
+ return self.docscroll.scrollTop(val);
+ };
+ this.getScrollLeft = function() {
+ if (self.detected.ismozilla && self.isrtlmode)
+ return Math.abs(self.docscroll.scrollLeft());
+ return self.docscroll.scrollLeft();
+ };
+ this.setScrollLeft = function(val) {
+ return self.docscroll.scrollLeft((self.detected.ismozilla && self.isrtlmode) ? -val : val);
+ };
+ }
+
+ this.getTarget = function(e) {
+ if (!e) return false;
+ if (e.target) return e.target;
+ if (e.srcElement) return e.srcElement;
+ return false;
+ };
+
+ this.hasParent = function(e, id) {
+ if (!e) return false;
+ var el = e.target || e.srcElement || e || false;
+ while (el && el.id != id) {
+ el = el.parentNode || false;
+ }
+ return (el !== false);
+ };
+
+ function getZIndex() {
+ var dom = self.win;
+ if ("zIndex" in dom) return dom.zIndex(); // use jQuery UI method when available
+ while (dom.length > 0) {
+ if (dom[0].nodeType == 9) return false;
+ var zi = dom.css('zIndex');
+ if (!isNaN(zi) && zi != 0) return parseInt(zi);
+ dom = dom.parent();
+ }
+ return false;
+ }
+
+ //inspired by http://forum.jquery.com/topic/width-includes-border-width-when-set-to-thin-medium-thick-in-ie
+ var _convertBorderWidth = {
+ "thin": 1,
+ "medium": 3,
+ "thick": 5
+ };
+
+ function getWidthToPixel(dom, prop, chkheight) {
+ var wd = dom.css(prop);
+ var px = parseFloat(wd);
+ if (isNaN(px)) {
+ px = _convertBorderWidth[wd] || 0;
+ var brd = (px == 3) ? ((chkheight) ? (self.win.outerHeight() - self.win.innerHeight()) : (self.win.outerWidth() - self.win.innerWidth())) : 1; //DON'T TRUST CSS
+ if (self.isie8 && px) px += 1;
+ return (brd) ? px : 0;
+ }
+ return px;
+ }
+
+ this.getDocumentScrollOffset = function() {
+ return {top:window.pageYOffset||document.documentElement.scrollTop,
+ left:window.pageXOffset||document.documentElement.scrollLeft};
+ }
+
+ this.getOffset = function() {
+ if (self.isfixed) {
+ var ofs = self.win.offset(); // fix Chrome auto issue (when right/bottom props only)
+ var scrl = self.getDocumentScrollOffset();
+ ofs.top-=scrl.top;
+ ofs.left-=scrl.left;
+ return ofs;
+ }
+ var ww = self.win.offset();
+ if (!self.viewport) return ww;
+ var vp = self.viewport.offset();
+ return {
+ top: ww.top - vp.top,// + self.viewport.scrollTop(),
+ left: ww.left - vp.left // + self.viewport.scrollLeft()
+ };
+ };
+
+ this.updateScrollBar = function(len) {
+ if (self.ishwscroll) {
+ self.rail.css({ //**
+ height: self.win.innerHeight() - (self.opt.railpadding.top + self.opt.railpadding.bottom)
+ });
+ if (self.railh) self.railh.css({ //**
+ width: self.win.innerWidth() - (self.opt.railpadding.left + self.opt.railpadding.right)
+ });
+
+ } else {
+ var wpos = self.getOffset();
+ var pos = {
+ top: wpos.top,
+ left: wpos.left - (self.opt.railpadding.left + self.opt.railpadding.right)
+ };
+ pos.top += getWidthToPixel(self.win, 'border-top-width', true);
+ pos.left += (self.rail.align) ? self.win.outerWidth() - getWidthToPixel(self.win, 'border-right-width') - self.rail.width : getWidthToPixel(self.win, 'border-left-width');
+
+ var off = self.opt.railoffset;
+ if (off) {
+ if (off.top) pos.top += off.top;
+ if (self.rail.align && off.left) pos.left += off.left;
+ }
+
+ if (!self.railslocked) self.rail.css({
+ top: pos.top,
+ left: pos.left,
+ height: ((len) ? len.h : self.win.innerHeight()) - (self.opt.railpadding.top + self.opt.railpadding.bottom)
+ });
+
+ if (self.zoom) {
+ self.zoom.css({
+ top: pos.top + 1,
+ left: (self.rail.align == 1) ? pos.left - 20 : pos.left + self.rail.width + 4
+ });
+ }
+
+ if (self.railh && !self.railslocked) {
+ var pos = {
+ top: wpos.top,
+ left: wpos.left
+ };
+ var off = self.opt.railhoffset;
+ if (!!off) {
+ if (!!off.top) pos.top += off.top;
+ if (!!off.left) pos.left += off.left;
+ }
+ var y = (self.railh.align) ? pos.top + getWidthToPixel(self.win, 'border-top-width', true) + self.win.innerHeight() - self.railh.height : pos.top + getWidthToPixel(self.win, 'border-top-width', true);
+ var x = pos.left + getWidthToPixel(self.win, 'border-left-width');
+ self.railh.css({
+ top: y - (self.opt.railpadding.top + self.opt.railpadding.bottom),
+ left: x,
+ width: self.railh.width
+ });
+ }
+
+
+ }
+ };
+
+ this.doRailClick = function(e, dbl, hr) {
+ var fn, pg, cur, pos;
+
+ if (self.railslocked) return;
+ self.cancelEvent(e);
+
+ if (dbl) {
+ fn = (hr) ? self.doScrollLeft : self.doScrollTop;
+ cur = (hr) ? ((e.pageX - self.railh.offset().left - (self.cursorwidth / 2)) * self.scrollratio.x) : ((e.pageY - self.rail.offset().top - (self.cursorheight / 2)) * self.scrollratio.y);
+ fn(cur);
+ } else {
+ fn = (hr) ? self.doScrollLeftBy : self.doScrollBy;
+ cur = (hr) ? self.scroll.x : self.scroll.y;
+ pos = (hr) ? e.pageX - self.railh.offset().left : e.pageY - self.rail.offset().top;
+ pg = (hr) ? self.view.w : self.view.h;
+ fn((cur >= pos) ? pg: -pg);// (cur >= pos) ? fn(pg): fn(-pg);
+ }
+
+ };
+
+ self.hasanimationframe = (setAnimationFrame);
+ self.hascancelanimationframe = (clearAnimationFrame);
+
+ if (!self.hasanimationframe) {
+ setAnimationFrame = function(fn) {
+ return setTimeout(fn, 15 - Math.floor((+new Date()) / 1000) % 16);
+ }; // 1000/60)};
+ clearAnimationFrame = clearInterval;
+ } else if (!self.hascancelanimationframe) clearAnimationFrame = function() {
+ self.cancelAnimationFrame = true;
+ };
+
+ this.init = function() {
+
+ self.saved.css = [];
+
+ if (cap.isie7mobile) return true; // SORRY, DO NOT WORK!
+ if (cap.isoperamini) return true; // SORRY, DO NOT WORK!
+
+ if (cap.hasmstouch) self.css((self.ispage) ? $("html") : self.win, {
+ '-ms-touch-action': 'none'
+ });
+
+ self.zindex = "auto";
+ if (!self.ispage && self.opt.zindex == "auto") {
+ self.zindex = getZIndex() || "auto";
+ } else {
+ self.zindex = self.opt.zindex;
+ }
+
+ if (!self.ispage && self.zindex != "auto") {
+ if (self.zindex > globalmaxzindex) globalmaxzindex = self.zindex;
+ }
+
+ if (self.isie && self.zindex == 0 && self.opt.zindex == "auto") { // fix IE auto == 0
+ self.zindex = "auto";
+ }
+
+ if (!self.ispage || (!cap.cantouch && !cap.isieold && !cap.isie9mobile)) {
+
+ var cont = self.docscroll;
+ if (self.ispage) cont = (self.haswrapper) ? self.win : self.doc;
+
+ if (!cap.isie9mobile) self.css(cont, {
+ 'overflow-y': 'hidden'
+ });
+
+ if (self.ispage && cap.isie7) {
+ if (self.doc[0].nodeName == 'BODY') self.css($("html"), {
+ 'overflow-y': 'hidden'
+ }); //IE7 double scrollbar issue
+ else if (self.doc[0].nodeName == 'HTML') self.css($("body"), {
+ 'overflow-y': 'hidden'
+ }); //IE7 double scrollbar issue
+ }
+
+ if (cap.isios && !self.ispage && !self.haswrapper) self.css($("body"), {
+ "-webkit-overflow-scrolling": "touch"
+ }); //force hw acceleration
+
+ var cursor = $(document.createElement('div'));
+ cursor.css({
+ position: "relative",
+ top: 0,
+ "float": "right",
+ width: self.opt.cursorwidth,
+ height: "0px",
+ 'background-color': self.opt.cursorcolor,
+ border: self.opt.cursorborder,
+ 'background-clip': 'padding-box',
+ '-webkit-border-radius': self.opt.cursorborderradius,
+ '-moz-border-radius': self.opt.cursorborderradius,
+ 'border-radius': self.opt.cursorborderradius
+ });
+
+ cursor.hborder = parseFloat(cursor.outerHeight() - cursor.innerHeight());
+
+ cursor.addClass('nicescroll-cursors');
+
+ self.cursor = cursor;
+
+ var rail = $(document.createElement('div'));
+ rail.attr('id', self.id);
+ rail.addClass('nicescroll-rails nicescroll-rails-vr');
+
+ var v, a, kp = ["left","right","top","bottom"]; //**
+ for (var n in kp) {
+ a = kp[n];
+ v = self.opt.railpadding[a];
+ (v) ? rail.css("padding-"+a,v+"px") : self.opt.railpadding[a] = 0;
+ }
+
+ rail.append(cursor);
+
+ rail.width = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerWidth());
+ rail.css({
+ width: rail.width + "px",
+ 'zIndex': self.zindex,
+ "background": self.opt.background,
+ cursor: "default"
+ });
+
+ rail.visibility = true;
+ rail.scrollable = true;
+
+ rail.align = (self.opt.railalign == "left") ? 0 : 1;
+
+ self.rail = rail;
+
+ self.rail.drag = false;
+
+ var zoom = false;
+ if (self.opt.boxzoom && !self.ispage && !cap.isieold) {
+ zoom = document.createElement('div');
+
+ self.bind(zoom, "click", self.doZoom);
+ self.bind(zoom, "mouseenter", function() {
+ self.zoom.css('opacity', self.opt.cursoropacitymax);
+ });
+ self.bind(zoom, "mouseleave", function() {
+ self.zoom.css('opacity', self.opt.cursoropacitymin);
+ });
+
+ self.zoom = $(zoom);
+ self.zoom.css({
+ "cursor": "pointer",
+ 'z-index': self.zindex,
+ 'backgroundImage': 'url(' + self.opt.scriptpath + 'zoomico.png)',
+ 'height': 18,
+ 'width': 18,
+ 'backgroundPosition': '0px 0px'
+ });
+ if (self.opt.dblclickzoom) self.bind(self.win, "dblclick", self.doZoom);
+ if (cap.cantouch && self.opt.gesturezoom) {
+ self.ongesturezoom = function(e) {
+ if (e.scale > 1.5) self.doZoomIn(e);
+ if (e.scale < 0.8) self.doZoomOut(e);
+ return self.cancelEvent(e);
+ };
+ self.bind(self.win, "gestureend", self.ongesturezoom);
+ }
+ }
+
+ // init HORIZ
+
+ self.railh = false;
+ var railh;
+
+ if (self.opt.horizrailenabled) {
+
+ self.css(cont, {
+ 'overflow-x': 'hidden'
+ });
+
+ var cursor = $(document.createElement('div'));
+ cursor.css({
+ position: "absolute",
+ top: 0,
+ height: self.opt.cursorwidth,
+ width: "0px",
+ 'background-color': self.opt.cursorcolor,
+ border: self.opt.cursorborder,
+ 'background-clip': 'padding-box',
+ '-webkit-border-radius': self.opt.cursorborderradius,
+ '-moz-border-radius': self.opt.cursorborderradius,
+ 'border-radius': self.opt.cursorborderradius
+ });
+
+ if (cap.isieold) cursor.css({'overflow':'hidden'}); //IE6 horiz scrollbar issue
+
+ cursor.wborder = parseFloat(cursor.outerWidth() - cursor.innerWidth());
+
+ cursor.addClass('nicescroll-cursors');
+
+ self.cursorh = cursor;
+
+ railh = $(document.createElement('div'));
+ railh.attr('id', self.id + '-hr');
+ railh.addClass('nicescroll-rails nicescroll-rails-hr');
+ railh.height = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerHeight());
+ railh.css({
+ height: railh.height + "px",
+ 'zIndex': self.zindex,
+ "background": self.opt.background
+ });
+
+ railh.append(cursor);
+
+ railh.visibility = true;
+ railh.scrollable = true;
+
+ railh.align = (self.opt.railvalign == "top") ? 0 : 1;
+
+ self.railh = railh;
+
+ self.railh.drag = false;
+
+ }
+
+ //
+
+ if (self.ispage) {
+ rail.css({
+ position: "fixed",
+ top: "0px",
+ height: "100%"
+ });
+ (rail.align) ? rail.css({
+ right: "0px"
+ }): rail.css({
+ left: "0px"
+ });
+ self.body.append(rail);
+ if (self.railh) {
+ railh.css({
+ position: "fixed",
+ left: "0px",
+ width: "100%"
+ });
+ (railh.align) ? railh.css({
+ bottom: "0px"
+ }): railh.css({
+ top: "0px"
+ });
+ self.body.append(railh);
+ }
+ } else {
+ if (self.ishwscroll) {
+ if (self.win.css('position') == 'static') self.css(self.win, {
+ 'position': 'relative'
+ });
+ var bd = (self.win[0].nodeName == 'HTML') ? self.body : self.win;
+ $(bd).scrollTop(0).scrollLeft(0); // fix rail position if content already scrolled
+ if (self.zoom) {
+ self.zoom.css({
+ position: "absolute",
+ top: 1,
+ right: 0,
+ "margin-right": rail.width + 4
+ });
+ bd.append(self.zoom);
+ }
+ rail.css({
+ position: "absolute",
+ top: 0
+ });
+ (rail.align) ? rail.css({
+ right: 0
+ }): rail.css({
+ left: 0
+ });
+ bd.append(rail);
+ if (railh) {
+ railh.css({
+ position: "absolute",
+ left: 0,
+ bottom: 0
+ });
+ (railh.align) ? railh.css({
+ bottom: 0
+ }): railh.css({
+ top: 0
+ });
+ bd.append(railh);
+ }
+ } else {
+ self.isfixed = (self.win.css("position") == "fixed");
+ var rlpos = (self.isfixed) ? "fixed" : "absolute";
+
+ if (!self.isfixed) self.viewport = self.getViewport(self.win[0]);
+ if (self.viewport) {
+ self.body = self.viewport;
+ if ((/fixed|absolute/.test(self.viewport.css("position"))) == false) self.css(self.viewport, {
+ "position": "relative"
+ });
+ }
+
+ rail.css({
+ position: rlpos
+ });
+ if (self.zoom) self.zoom.css({
+ position: rlpos
+ });
+ self.updateScrollBar();
+ self.body.append(rail);
+ if (self.zoom) self.body.append(self.zoom);
+ if (self.railh) {
+ railh.css({
+ position: rlpos
+ });
+ self.body.append(railh);
+ }
+ }
+
+ if (cap.isios) self.css(self.win, {
+ '-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
+ '-webkit-touch-callout': 'none'
+ }); // prevent grey layer on click
+
+ if (cap.isie && self.opt.disableoutline) self.win.attr("hideFocus", "true"); // IE, prevent dotted rectangle on focused div
+ if (cap.iswebkit && self.opt.disableoutline) self.win.css({"outline": "none"}); // Webkit outline
+ //if (cap.isopera&&self.opt.disableoutline) self.win.css({"outline":"0"}); // Opera 12- to test [TODO]
+
+ }
+
+ if (self.opt.autohidemode === false) {
+ self.autohidedom = false;
+ self.rail.css({
+ opacity: self.opt.cursoropacitymax
+ });
+ if (self.railh) self.railh.css({
+ opacity: self.opt.cursoropacitymax
+ });
+ } else if ((self.opt.autohidemode === true) || (self.opt.autohidemode === "leave")) {
+ self.autohidedom = $().add(self.rail);
+ if (cap.isie8) self.autohidedom = self.autohidedom.add(self.cursor);
+ if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
+ if (self.railh && cap.isie8) self.autohidedom = self.autohidedom.add(self.cursorh);
+ } else if (self.opt.autohidemode == "scroll") {
+ self.autohidedom = $().add(self.rail);
+ if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
+ } else if (self.opt.autohidemode == "cursor") {
+ self.autohidedom = $().add(self.cursor);
+ if (self.railh) self.autohidedom = self.autohidedom.add(self.cursorh);
+ } else if (self.opt.autohidemode == "hidden") {
+ self.autohidedom = false;
+ self.hide();
+ self.railslocked = false;
+ }
+
+ if (cap.isie9mobile) {
+
+ self.scrollmom = new ScrollMomentumClass2D(self);
+
+ self.onmangotouch = function() {
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+
+ if ((py == self.scrollmom.lastscrolly) && (px == self.scrollmom.lastscrollx)) return true;
+
+ var dfy = py - self.mangotouch.sy;
+ var dfx = px - self.mangotouch.sx;
+ var df = Math.round(Math.sqrt(Math.pow(dfx, 2) + Math.pow(dfy, 2)));
+ if (df == 0) return;
+
+ var dry = (dfy < 0) ? -1 : 1;
+ var drx = (dfx < 0) ? -1 : 1;
+
+ var tm = +new Date();
+ if (self.mangotouch.lazy) clearTimeout(self.mangotouch.lazy);
+
+ if (((tm - self.mangotouch.tm) > 80) || (self.mangotouch.dry != dry) || (self.mangotouch.drx != drx)) {
+ self.scrollmom.stop();
+ self.scrollmom.reset(px, py);
+ self.mangotouch.sy = py;
+ self.mangotouch.ly = py;
+ self.mangotouch.sx = px;
+ self.mangotouch.lx = px;
+ self.mangotouch.dry = dry;
+ self.mangotouch.drx = drx;
+ self.mangotouch.tm = tm;
+ } else {
+
+ self.scrollmom.stop();
+ self.scrollmom.update(self.mangotouch.sx - dfx, self.mangotouch.sy - dfy);
+ self.mangotouch.tm = tm;
+
+ var ds = Math.max(Math.abs(self.mangotouch.ly - py), Math.abs(self.mangotouch.lx - px));
+ self.mangotouch.ly = py;
+ self.mangotouch.lx = px;
+
+ if (ds > 2) {
+ self.mangotouch.lazy = setTimeout(function() {
+ self.mangotouch.lazy = false;
+ self.mangotouch.dry = 0;
+ self.mangotouch.drx = 0;
+ self.mangotouch.tm = 0;
+ self.scrollmom.doMomentum(30);
+ }, 100);
+ }
+ }
+ };
+
+ var top = self.getScrollTop();
+ var lef = self.getScrollLeft();
+ self.mangotouch = {
+ sy: top,
+ ly: top,
+ dry: 0,
+ sx: lef,
+ lx: lef,
+ drx: 0,
+ lazy: false,
+ tm: 0
+ };
+
+ self.bind(self.docscroll, "scroll", self.onmangotouch);
+
+ } else {
+
+ if (cap.cantouch || self.istouchcapable || self.opt.touchbehavior || cap.hasmstouch) {
+
+ self.scrollmom = new ScrollMomentumClass2D(self);
+
+ self.ontouchstart = function(e) {
+ if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+
+ self.hasmoving = false;
+
+ if (!self.railslocked) {
+
+ var tg;
+ if (cap.hasmstouch) {
+ tg = (e.target) ? e.target : false;
+ while (tg) {
+ var nc = $(tg).getNiceScroll();
+ if ((nc.length > 0) && (nc[0].me == self.me)) break;
+ if (nc.length > 0) return false;
+ if ((tg.nodeName == 'DIV') && (tg.id == self.id)) break;
+ tg = (tg.parentNode) ? tg.parentNode : false;
+ }
+ }
+
+ self.cancelScroll();
+
+ tg = self.getTarget(e);
+
+ if (tg) {
+ var skp = (/INPUT/i.test(tg.nodeName)) && (/range/i.test(tg.type));
+ if (skp) return self.stopPropagation(e);
+ }
+
+ if (!("clientX" in e) && ("changedTouches" in e)) {
+ e.clientX = e.changedTouches[0].clientX;
+ e.clientY = e.changedTouches[0].clientY;
+ }
+
+ if (self.forcescreen) {
+ var le = e;
+ e = {
+ "original": (e.original) ? e.original : e
+ };
+ e.clientX = le.screenX;
+ e.clientY = le.screenY;
+ }
+
+ self.rail.drag = {
+ x: e.clientX,
+ y: e.clientY,
+ sx: self.scroll.x,
+ sy: self.scroll.y,
+ st: self.getScrollTop(),
+ sl: self.getScrollLeft(),
+ pt: 2,
+ dl: false
+ };
+
+ if (self.ispage || !self.opt.directionlockdeadzone) {
+ self.rail.drag.dl = "f";
+ } else {
+
+ var view = {
+ w: $(window).width(),
+ h: $(window).height()
+ };
+
+ var page = {
+ w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
+ h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
+ };
+
+ var maxh = Math.max(0, page.h - view.h);
+ var maxw = Math.max(0, page.w - view.w);
+
+ if (!self.rail.scrollable && self.railh.scrollable) self.rail.drag.ck = (maxh > 0) ? "v" : false;
+ else if (self.rail.scrollable && !self.railh.scrollable) self.rail.drag.ck = (maxw > 0) ? "h" : false;
+ else self.rail.drag.ck = false;
+ if (!self.rail.drag.ck) self.rail.drag.dl = "f";
+ }
+
+ if (self.opt.touchbehavior && self.isiframe && cap.isie) {
+ var wp = self.win.position();
+ self.rail.drag.x += wp.left;
+ self.rail.drag.y += wp.top;
+ }
+
+ self.hasmoving = false;
+ self.lastmouseup = false;
+ self.scrollmom.reset(e.clientX, e.clientY);
+
+ if (!cap.cantouch && !this.istouchcapable && !e.pointerType) {
+
+ var ip = (tg) ? /INPUT|SELECT|TEXTAREA/i.test(tg.nodeName) : false;
+ if (!ip) {
+ if (!self.ispage && cap.hasmousecapture) tg.setCapture();
+ if (self.opt.touchbehavior) {
+ if (tg.onclick && !(tg._onclick || false)) { // intercept DOM0 onclick event
+ tg._onclick = tg.onclick;
+ tg.onclick = function(e) {
+ if (self.hasmoving) return false;
+ tg._onclick.call(this, e);
+ };
+ }
+ return self.cancelEvent(e);
+ }
+ return self.stopPropagation(e);
+ }
+
+ if (/SUBMIT|CANCEL|BUTTON/i.test($(tg).attr('type'))) {
+ pc = {
+ "tg": tg,
+ "click": false
+ };
+ self.preventclick = pc;
+ }
+
+ }
+ }
+
+ };
+
+ self.ontouchend = function(e) {
+ if (!self.rail.drag) return true;
+ if (self.rail.drag.pt == 2) {
+ if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+ self.scrollmom.doMomentum();
+ self.rail.drag = false;
+ if (self.hasmoving) {
+ self.lastmouseup = true;
+ self.hideCursor();
+ if (cap.hasmousecapture) document.releaseCapture();
+ if (!cap.cantouch) return self.cancelEvent(e);
+ }
+ }
+ else if (self.rail.drag.pt == 1) {
+ return self.onmouseup(e);
+ }
+
+ };
+
+ var moveneedoffset = (self.opt.touchbehavior && self.isiframe && !cap.hasmousecapture);
+
+ self.ontouchmove = function(e, byiframe) {
+
+ if (!self.rail.drag) return false;
+
+ if (e.targetTouches && self.opt.preventmultitouchscrolling) {
+ if (e.targetTouches.length > 1) return false; // multitouch
+ }
+
+ if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+
+ if (self.rail.drag.pt == 2) {
+ if (cap.cantouch && (cap.isios) && (typeof e.original == "undefined")) return true; // prevent ios "ghost" events by clickable elements
+
+ self.hasmoving = true;
+
+ if (self.preventclick && !self.preventclick.click) {
+ self.preventclick.click = self.preventclick.tg.onclick || false;
+ self.preventclick.tg.onclick = self.onpreventclick;
+ }
+
+ var ev = $.extend({
+ "original": e
+ }, e);
+ e = ev;
+
+ if (("changedTouches" in e)) {
+ e.clientX = e.changedTouches[0].clientX;
+ e.clientY = e.changedTouches[0].clientY;
+ }
+
+ if (self.forcescreen) {
+ var le = e;
+ e = {
+ "original": (e.original) ? e.original : e
+ };
+ e.clientX = le.screenX;
+ e.clientY = le.screenY;
+ }
+
+ var ofy,ofx;
+ ofx = ofy = 0;
+
+ if (moveneedoffset && !byiframe) {
+ var wp = self.win.position();
+ ofx = -wp.left;
+ ofy = -wp.top;
+ }
+
+ var fy = e.clientY + ofy;
+ var my = (fy - self.rail.drag.y);
+ var fx = e.clientX + ofx;
+ var mx = (fx - self.rail.drag.x);
+
+ var ny = self.rail.drag.st - my;
+
+ if (self.ishwscroll && self.opt.bouncescroll) {
+ if (ny < 0) {
+ ny = Math.round(ny / 2);
+ // fy = 0;
+ } else if (ny > self.page.maxh) {
+ ny = self.page.maxh + Math.round((ny - self.page.maxh) / 2);
+ // fy = 0;
+ }
+ } else {
+ if (ny < 0) {
+ ny = 0;
+ fy = 0;
+ }
+ if (ny > self.page.maxh) {
+ ny = self.page.maxh;
+ fy = 0;
+ }
+ }
+
+ var nx;
+ if (self.railh && self.railh.scrollable) {
+ nx = (self.isrtlmode) ? mx - self.rail.drag.sl : self.rail.drag.sl - mx;
+
+ if (self.ishwscroll && self.opt.bouncescroll) {
+ if (nx < 0) {
+ nx = Math.round(nx / 2);
+ // fx = 0;
+ } else if (nx > self.page.maxw) {
+ nx = self.page.maxw + Math.round((nx - self.page.maxw) / 2);
+ // fx = 0;
+ }
+ } else {
+ if (nx < 0) {
+ nx = 0;
+ fx = 0;
+ }
+ if (nx > self.page.maxw) {
+ nx = self.page.maxw;
+ fx = 0;
+ }
+ }
+
+ }
+
+ var grabbed = false;
+ if (self.rail.drag.dl) {
+ grabbed = true;
+ if (self.rail.drag.dl == "v") nx = self.rail.drag.sl;
+ else if (self.rail.drag.dl == "h") ny = self.rail.drag.st;
+ } else {
+ var ay = Math.abs(my);
+ var ax = Math.abs(mx);
+ var dz = self.opt.directionlockdeadzone;
+ if (self.rail.drag.ck == "v") {
+ if (ay > dz && (ax <= (ay * 0.3))) {
+ self.rail.drag = false;
+ return true;
+ } else if (ax > dz) {
+ self.rail.drag.dl = "f";
+ $("body").scrollTop($("body").scrollTop()); // stop iOS native scrolling (when active javascript has blocked)
+ }
+ } else if (self.rail.drag.ck == "h") {
+ if (ax > dz && (ay <= (ax * 0.3))) {
+ self.rail.drag = false;
+ return true;
+ } else if (ay > dz) {
+ self.rail.drag.dl = "f";
+ $("body").scrollLeft($("body").scrollLeft()); // stop iOS native scrolling (when active javascript has blocked)
+ }
+ }
+ }
+
+ self.synched("touchmove", function() {
+ if (self.rail.drag && (self.rail.drag.pt == 2)) {
+ if (self.prepareTransition) self.prepareTransition(0);
+ if (self.rail.scrollable) self.setScrollTop(ny);
+ self.scrollmom.update(fx, fy);
+ if (self.railh && self.railh.scrollable) {
+ self.setScrollLeft(nx);
+ self.showCursor(ny, nx);
+ } else {
+ self.showCursor(ny);
+ }
+ if (cap.isie10) document.selection.clear();
+ }
+ });
+
+ if (cap.ischrome && self.istouchcapable) grabbed = false; //chrome touch emulation doesn't like!
+ if (grabbed) return self.cancelEvent(e);
+ }
+ else if (self.rail.drag.pt == 1) { // drag on cursor
+ return self.onmousemove(e);
+ }
+
+ };
+
+ }
+
+ self.onmousedown = function(e, hronly) {
+ if (self.rail.drag && self.rail.drag.pt != 1) return;
+ if (self.railslocked) return self.cancelEvent(e);
+ self.cancelScroll();
+ self.rail.drag = {
+ x: e.clientX,
+ y: e.clientY,
+ sx: self.scroll.x,
+ sy: self.scroll.y,
+ pt: 1,
+ hr: (!!hronly)
+ };
+ var tg = self.getTarget(e);
+ if (!self.ispage && cap.hasmousecapture) tg.setCapture();
+ if (self.isiframe && !cap.hasmousecapture) {
+ self.saved.csspointerevents = self.doc.css("pointer-events");
+ self.css(self.doc, {
+ "pointer-events": "none"
+ });
+ }
+ self.hasmoving = false;
+ return self.cancelEvent(e);
+ };
+
+ self.onmouseup = function(e) {
+ if (self.rail.drag) {
+ if (self.rail.drag.pt != 1) return true;
+ if (cap.hasmousecapture) document.releaseCapture();
+ if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents);
+ self.rail.drag = false;
+ //if (!self.rail.active) self.hideCursor();
+ if (self.hasmoving) self.triggerScrollEnd(); // TODO - check &&!self.scrollrunning
+ return self.cancelEvent(e);
+ }
+ };
+
+ self.onmousemove = function(e) {
+ if (self.rail.drag) {
+ if (self.rail.drag.pt != 1) return;
+
+ if (cap.ischrome && e.which == 0) return self.onmouseup(e);
+
+ self.cursorfreezed = true;
+ self.hasmoving = true;
+
+ if (self.rail.drag.hr) {
+ self.scroll.x = self.rail.drag.sx + (e.clientX - self.rail.drag.x);
+ if (self.scroll.x < 0) self.scroll.x = 0;
+ var mw = self.scrollvaluemaxw;
+ if (self.scroll.x > mw) self.scroll.x = mw;
+ } else {
+ self.scroll.y = self.rail.drag.sy + (e.clientY - self.rail.drag.y);
+ if (self.scroll.y < 0) self.scroll.y = 0;
+ var my = self.scrollvaluemax;
+ if (self.scroll.y > my) self.scroll.y = my;
+ }
+
+ self.synched('mousemove', function() {
+ if (self.rail.drag && (self.rail.drag.pt == 1)) {
+ self.showCursor();
+ if (self.rail.drag.hr) {
+ if (self.hasreversehr) {
+ self.doScrollLeft(self.scrollvaluemaxw-Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
+ } else {
+ self.doScrollLeft(Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
+ }
+ }
+ else self.doScrollTop(Math.round(self.scroll.y * self.scrollratio.y), self.opt.cursordragspeed);
+ }
+ });
+
+ return self.cancelEvent(e);
+ }
+ /*
+ else {
+ self.checkarea = true;
+ }
+*/
+ };
+
+ if (cap.cantouch || self.opt.touchbehavior) {
+
+ self.onpreventclick = function(e) {
+ if (self.preventclick) {
+ self.preventclick.tg.onclick = self.preventclick.click;
+ self.preventclick = false;
+ return self.cancelEvent(e);
+ }
+ }
+
+ self.bind(self.win, "mousedown", self.ontouchstart); // control content dragging
+
+ self.onclick = (cap.isios) ? false : function(e) {
+ if (self.lastmouseup) {
+ self.lastmouseup = false;
+ return self.cancelEvent(e);
+ } else {
+ return true;
+ }
+ };
+
+ if (self.opt.grabcursorenabled && cap.cursorgrabvalue) {
+ self.css((self.ispage) ? self.doc : self.win, {
+ 'cursor': cap.cursorgrabvalue
+ });
+ self.css(self.rail, {
+ 'cursor': cap.cursorgrabvalue
+ });
+ }
+
+ } else {
+
+ var checkSelectionScroll = function(e) {
+ if (!self.selectiondrag) return;
+
+ if (e) {
+ var ww = self.win.outerHeight();
+ var df = (e.pageY - self.selectiondrag.top);
+ if (df > 0 && df < ww) df = 0;
+ if (df >= ww) df -= ww;
+ self.selectiondrag.df = df;
+ }
+ if (self.selectiondrag.df == 0) return;
+
+ var rt = -Math.floor(self.selectiondrag.df / 6) * 2;
+ self.doScrollBy(rt);
+
+ self.debounced("doselectionscroll", function() {
+ checkSelectionScroll()
+ }, 50);
+ };
+
+ if ("getSelection" in document) { // A grade - Major browsers
+ self.hasTextSelected = function() {
+ return (document.getSelection().rangeCount > 0);
+ };
+ } else if ("selection" in document) { //IE9-
+ self.hasTextSelected = function() {
+ return (document.selection.type != "None");
+ };
+ } else {
+ self.hasTextSelected = function() { // no support
+ return false;
+ };
+ }
+
+ self.onselectionstart = function(e) {
+/* More testing - severe chrome issues
+ if (!self.haswrapper&&(e.which&&e.which==2)) { // fool browser to manage middle button scrolling
+ self.win.css({'overflow':'auto'});
+ setTimeout(function(){
+ self.win.css({'overflow':''});
+ },10);
+ return true;
+ }
+*/
+ if (self.ispage) return;
+ self.selectiondrag = self.win.offset();
+ };
+
+ self.onselectionend = function(e) {
+ self.selectiondrag = false;
+ };
+ self.onselectiondrag = function(e) {
+ if (!self.selectiondrag) return;
+ if (self.hasTextSelected()) self.debounced("selectionscroll", function() {
+ checkSelectionScroll(e)
+ }, 250);
+ };
+
+
+ }
+
+ if (cap.hasw3ctouch) { //IE11+
+ self.css(self.rail, {
+ 'touch-action': 'none'
+ });
+ self.css(self.cursor, {
+ 'touch-action': 'none'
+ });
+ self.bind(self.win, "pointerdown", self.ontouchstart);
+ self.bind(document, "pointerup", self.ontouchend);
+ self.bind(document, "pointermove", self.ontouchmove);
+ } else if (cap.hasmstouch) { //IE10
+ self.css(self.rail, {
+ '-ms-touch-action': 'none'
+ });
+ self.css(self.cursor, {
+ '-ms-touch-action': 'none'
+ });
+ self.bind(self.win, "MSPointerDown", self.ontouchstart);
+ self.bind(document, "MSPointerUp", self.ontouchend);
+ self.bind(document, "MSPointerMove", self.ontouchmove);
+ self.bind(self.cursor, "MSGestureHold", function(e) {
+ e.preventDefault()
+ });
+ self.bind(self.cursor, "contextmenu", function(e) {
+ e.preventDefault()
+ });
+ } else if (this.istouchcapable) { //desktop with screen touch enabled
+ self.bind(self.win, "touchstart", self.ontouchstart);
+ self.bind(document, "touchend", self.ontouchend);
+ self.bind(document, "touchcancel", self.ontouchend);
+ self.bind(document, "touchmove", self.ontouchmove);
+ }
+
+
+ if (self.opt.cursordragontouch || (!cap.cantouch && !self.opt.touchbehavior)) {
+
+ self.rail.css({
+ "cursor": "default"
+ });
+ self.railh && self.railh.css({
+ "cursor": "default"
+ });
+
+ self.jqbind(self.rail, "mouseenter", function() {
+ if (!self.ispage && !self.win.is(":visible")) return false;
+ if (self.canshowonmouseevent) self.showCursor();
+ self.rail.active = true;
+ });
+ self.jqbind(self.rail, "mouseleave", function() {
+ self.rail.active = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+
+ if (self.opt.sensitiverail) {
+ self.bind(self.rail, "click", function(e) {
+ self.doRailClick(e, false, false)
+ });
+ self.bind(self.rail, "dblclick", function(e) {
+ self.doRailClick(e, true, false)
+ });
+ self.bind(self.cursor, "click", function(e) {
+ self.cancelEvent(e)
+ });
+ self.bind(self.cursor, "dblclick", function(e) {
+ self.cancelEvent(e)
+ });
+ }
+
+ if (self.railh) {
+ self.jqbind(self.railh, "mouseenter", function() {
+ if (!self.ispage && !self.win.is(":visible")) return false;
+ if (self.canshowonmouseevent) self.showCursor();
+ self.rail.active = true;
+ });
+ self.jqbind(self.railh, "mouseleave", function() {
+ self.rail.active = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+
+ if (self.opt.sensitiverail) {
+ self.bind(self.railh, "click", function(e) {
+ self.doRailClick(e, false, true)
+ });
+ self.bind(self.railh, "dblclick", function(e) {
+ self.doRailClick(e, true, true)
+ });
+ self.bind(self.cursorh, "click", function(e) {
+ self.cancelEvent(e)
+ });
+ self.bind(self.cursorh, "dblclick", function(e) {
+ self.cancelEvent(e)
+ });
+ }
+
+ }
+
+ }
+
+ if (!cap.cantouch && !self.opt.touchbehavior) {
+
+ self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.onmouseup);
+ self.bind(document, "mousemove", self.onmousemove);
+ if (self.onclick) self.bind(document, "click", self.onclick);
+
+ self.bind(self.cursor, "mousedown", self.onmousedown);
+ self.bind(self.cursor, "mouseup", self.onmouseup);
+
+ if (self.railh) {
+ self.bind(self.cursorh, "mousedown", function(e) {
+ self.onmousedown(e, true)
+ });
+ self.bind(self.cursorh, "mouseup", self.onmouseup);
+ }
+
+ if (!self.ispage && self.opt.enablescrollonselection) {
+ self.bind(self.win[0], "mousedown", self.onselectionstart);
+ self.bind(document, "mouseup", self.onselectionend);
+ self.bind(self.cursor, "mouseup", self.onselectionend);
+ if (self.cursorh) self.bind(self.cursorh, "mouseup", self.onselectionend);
+ self.bind(document, "mousemove", self.onselectiondrag);
+ }
+
+ if (self.zoom) {
+ self.jqbind(self.zoom, "mouseenter", function() {
+ if (self.canshowonmouseevent) self.showCursor();
+ self.rail.active = true;
+ });
+ self.jqbind(self.zoom, "mouseleave", function() {
+ self.rail.active = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+ }
+
+ } else {
+
+ self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.ontouchend);
+ self.bind(document, "mousemove", self.ontouchmove);
+ if (self.onclick) self.bind(document, "click", self.onclick);
+
+ if (self.opt.cursordragontouch) {
+ self.bind(self.cursor, "mousedown", self.onmousedown);
+ self.bind(self.cursor, "mouseup", self.onmouseup);
+ //self.bind(self.cursor, "mousemove", self.onmousemove);
+ self.cursorh && self.bind(self.cursorh, "mousedown", function(e) {
+ self.onmousedown(e, true)
+ });
+ //self.cursorh && self.bind(self.cursorh, "mousemove", self.onmousemove);
+ self.cursorh && self.bind(self.cursorh, "mouseup", self.onmouseup);
+ }
+
+ }
+
+ if (self.opt.enablemousewheel) {
+ if (!self.isiframe) self.bind((cap.isie && self.ispage) ? document : self.win /*self.docscroll*/ , "mousewheel", self.onmousewheel);
+ self.bind(self.rail, "mousewheel", self.onmousewheel);
+ if (self.railh) self.bind(self.railh, "mousewheel", self.onmousewheelhr);
+ }
+
+ if (!self.ispage && !cap.cantouch && !(/HTML|^BODY/.test(self.win[0].nodeName))) {
+ if (!self.win.attr("tabindex")) self.win.attr({
+ "tabindex": tabindexcounter++
+ });
+
+ self.jqbind(self.win, "focus", function(e) {
+ domfocus = (self.getTarget(e)).id || true;
+ self.hasfocus = true;
+ if (self.canshowonmouseevent) self.noticeCursor();
+ });
+ self.jqbind(self.win, "blur", function(e) {
+ domfocus = false;
+ self.hasfocus = false;
+ });
+
+ self.jqbind(self.win, "mouseenter", function(e) {
+ mousefocus = (self.getTarget(e)).id || true;
+ self.hasmousefocus = true;
+ if (self.canshowonmouseevent) self.noticeCursor();
+ });
+ self.jqbind(self.win, "mouseleave", function() {
+ mousefocus = false;
+ self.hasmousefocus = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+
+ }
+
+ } // !ie9mobile
+
+ //Thanks to http://www.quirksmode.org !!
+ self.onkeypress = function(e) {
+ if (self.railslocked && self.page.maxh == 0) return true;
+
+ e = (e) ? e : window.e;
+ var tg = self.getTarget(e);
+ if (tg && /INPUT|TEXTAREA|SELECT|OPTION/.test(tg.nodeName)) {
+ var tp = tg.getAttribute('type') || tg.type || false;
+ if ((!tp) || !(/submit|button|cancel/i.tp)) return true;
+ }
+
+ if ($(tg).attr('contenteditable')) return true;
+
+ if (self.hasfocus || (self.hasmousefocus && !domfocus) || (self.ispage && !domfocus && !mousefocus)) {
+ var key = e.keyCode;
+
+ if (self.railslocked && key != 27) return self.cancelEvent(e);
+
+ var ctrl = e.ctrlKey || false;
+ var shift = e.shiftKey || false;
+
+ var ret = false;
+ switch (key) {
+ case 38:
+ case 63233: //safari
+ self.doScrollBy(24 * 3);
+ ret = true;
+ break;
+ case 40:
+ case 63235: //safari
+ self.doScrollBy(-24 * 3);
+ ret = true;
+ break;
+ case 37:
+ case 63232: //safari
+ if (self.railh) {
+ (ctrl) ? self.doScrollLeft(0): self.doScrollLeftBy(24 * 3);
+ ret = true;
+ }
+ break;
+ case 39:
+ case 63234: //safari
+ if (self.railh) {
+ (ctrl) ? self.doScrollLeft(self.page.maxw): self.doScrollLeftBy(-24 * 3);
+ ret = true;
+ }
+ break;
+ case 33:
+ case 63276: // safari
+ self.doScrollBy(self.view.h);
+ ret = true;
+ break;
+ case 34:
+ case 63277: // safari
+ self.doScrollBy(-self.view.h);
+ ret = true;
+ break;
+ case 36:
+ case 63273: // safari
+ (self.railh && ctrl) ? self.doScrollPos(0, 0): self.doScrollTo(0);
+ ret = true;
+ break;
+ case 35:
+ case 63275: // safari
+ (self.railh && ctrl) ? self.doScrollPos(self.page.maxw, self.page.maxh): self.doScrollTo(self.page.maxh);
+ ret = true;
+ break;
+ case 32:
+ if (self.opt.spacebarenabled) {
+ (shift) ? self.doScrollBy(self.view.h): self.doScrollBy(-self.view.h);
+ ret = true;
+ }
+ break;
+ case 27: // ESC
+ if (self.zoomactive) {
+ self.doZoom();
+ ret = true;
+ }
+ break;
+ }
+ if (ret) return self.cancelEvent(e);
+ }
+ };
+
+ if (self.opt.enablekeyboard) self.bind(document, (cap.isopera && !cap.isopera12) ? "keypress" : "keydown", self.onkeypress);
+
+ self.bind(document, "keydown", function(e) {
+ var ctrl = e.ctrlKey || false;
+ if (ctrl) self.wheelprevented = true;
+ });
+ self.bind(document, "keyup", function(e) {
+ var ctrl = e.ctrlKey || false;
+ if (!ctrl) self.wheelprevented = false;
+ });
+ self.bind(window,"blur",function(e){
+ self.wheelprevented = false;
+ });
+
+ self.bind(window, 'resize', self.lazyResize);
+ self.bind(window, 'orientationchange', self.lazyResize);
+
+ self.bind(window, "load", self.lazyResize);
+
+ if (cap.ischrome && !self.ispage && !self.haswrapper) { //chrome void scrollbar bug - it persists in version 26
+ var tmp = self.win.attr("style");
+ var ww = parseFloat(self.win.css("width")) + 1;
+ self.win.css('width', ww);
+ self.synched("chromefix", function() {
+ self.win.attr("style", tmp)
+ });
+ }
+
+
+ // Trying a cross-browser implementation - good luck!
+
+ self.onAttributeChange = function(e) {
+ self.lazyResize(self.isieold ? 250 : 30);
+ };
+
+ if (ClsMutationObserver !== false) {
+ self.observerbody = new ClsMutationObserver(function(mutations) {
+ mutations.forEach(function(mut){
+ if (mut.type=="attributes") {
+ return ($("body").hasClass("modal-open")) ? self.hide() : self.show(); // Support for Bootstrap modal
+ }
+ });
+ if (document.body.scrollHeight!=self.page.maxh) return self.lazyResize(30);
+ });
+ self.observerbody.observe(document.body, {
+ childList: true,
+ subtree: true,
+ characterData: false,
+ attributes: true,
+ attributeFilter: ['class']
+ });
+ }
+
+ if (!self.ispage && !self.haswrapper) {
+ // redesigned MutationObserver for Chrome18+/Firefox14+/iOS6+ with support for: remove div, add/remove content
+ if (ClsMutationObserver !== false) {
+ self.observer = new ClsMutationObserver(function(mutations) {
+ mutations.forEach(self.onAttributeChange);
+ });
+ self.observer.observe(self.win[0], {
+ childList: true,
+ characterData: false,
+ attributes: true,
+ subtree: false
+ });
+ self.observerremover = new ClsMutationObserver(function(mutations) {
+ mutations.forEach(function(mo) {
+ if (mo.removedNodes.length > 0) {
+ for (var dd in mo.removedNodes) {
+ if (!!self && (mo.removedNodes[dd] == self.win[0])) return self.remove();
+ }
+ }
+ });
+ });
+ self.observerremover.observe(self.win[0].parentNode, {
+ childList: true,
+ characterData: false,
+ attributes: false,
+ subtree: false
+ });
+ } else {
+ self.bind(self.win, (cap.isie && !cap.isie9) ? "propertychange" : "DOMAttrModified", self.onAttributeChange);
+ if (cap.isie9) self.win[0].attachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
+ self.bind(self.win, "DOMNodeRemoved", function(e) {
+ if (e.target == self.win[0]) self.remove();
+ });
+ }
+ }
+
+ //
+
+ if (!self.ispage && self.opt.boxzoom) self.bind(window, "resize", self.resizeZoom);
+ if (self.istextarea) self.bind(self.win, "mouseup", self.lazyResize);
+
+ // self.checkrtlmode = true;
+ self.lazyResize(30);
+
+ }
+
+ if (this.doc[0].nodeName == 'IFRAME') {
+ var oniframeload = function() {
+ self.iframexd = false;
+ var doc;
+ try {
+ doc = 'contentDocument' in this ? this.contentDocument : this.contentWindow.document;
+ var a = doc.domain;
+ } catch (e) {
+ self.iframexd = true;
+ doc = false
+ }
+
+ if (self.iframexd) {
+ if ("console" in window) console.log('NiceScroll error: policy restriced iframe');
+ return true; //cross-domain - I can't manage this
+ }
+
+ self.forcescreen = true;
+
+ if (self.isiframe) {
+ self.iframe = {
+ "doc": $(doc),
+ "html": self.doc.contents().find('html')[0],
+ "body": self.doc.contents().find('body')[0]
+ };
+ self.getContentSize = function() {
+ return {
+ w: Math.max(self.iframe.html.scrollWidth, self.iframe.body.scrollWidth),
+ h: Math.max(self.iframe.html.scrollHeight, self.iframe.body.scrollHeight)
+ };
+ };
+ self.docscroll = $(self.iframe.body); //$(this.contentWindow);
+ }
+
+ if (!cap.isios && self.opt.iframeautoresize && !self.isiframe) {
+ self.win.scrollTop(0); // reset position
+ self.doc.height(""); //reset height to fix browser bug
+ var hh = Math.max(doc.getElementsByTagName('html')[0].scrollHeight, doc.body.scrollHeight);
+ self.doc.height(hh);
+ }
+ self.lazyResize(30);
+
+ if (cap.isie7) self.css($(self.iframe.html), {
+ 'overflow-y': 'hidden'
+ });
+ self.css($(self.iframe.body), {
+ 'overflow-y': 'hidden'
+ });
+
+ if (cap.isios && self.haswrapper) {
+ self.css($(doc.body), {
+ '-webkit-transform': 'translate3d(0,0,0)'
+ }); // avoid iFrame content clipping - thanks to http://blog.derraab.com/2012/04/02/avoid-iframe-content-clipping-with-css-transform-on-ios/
+ }
+
+ if ('contentWindow' in this) {
+ self.bind(this.contentWindow, "scroll", self.onscroll); //IE8 & minor
+ } else {
+ self.bind(doc, "scroll", self.onscroll);
+ }
+
+ if (self.opt.enablemousewheel) {
+ self.bind(doc, "mousewheel", self.onmousewheel);
+ }
+
+ if (self.opt.enablekeyboard) self.bind(doc, (cap.isopera) ? "keypress" : "keydown", self.onkeypress);
+
+ if (cap.cantouch || self.opt.touchbehavior) {
+ self.bind(doc, "mousedown", self.ontouchstart);
+ self.bind(doc, "mousemove", function(e) {
+ return self.ontouchmove(e, true)
+ });
+ if (self.opt.grabcursorenabled && cap.cursorgrabvalue) self.css($(doc.body), {
+ 'cursor': cap.cursorgrabvalue
+ });
+ }
+
+ self.bind(doc, "mouseup", self.ontouchend);
+
+ if (self.zoom) {
+ if (self.opt.dblclickzoom) self.bind(doc, 'dblclick', self.doZoom);
+ if (self.ongesturezoom) self.bind(doc, "gestureend", self.ongesturezoom);
+ }
+ };
+
+ if (this.doc[0].readyState && this.doc[0].readyState == "complete") {
+ setTimeout(function() {
+ oniframeload.call(self.doc[0], false)
+ }, 500);
+ }
+ self.bind(this.doc, "load", oniframeload);
+
+ }
+
+ };
+
+ this.showCursor = function(py, px) {
+ if (self.cursortimeout) {
+ clearTimeout(self.cursortimeout);
+ self.cursortimeout = 0;
+ }
+ if (!self.rail) return;
+ if (self.autohidedom) {
+ self.autohidedom.stop().css({
+ opacity: self.opt.cursoropacitymax
+ });
+ self.cursoractive = true;
+ }
+
+ if (!self.rail.drag || self.rail.drag.pt != 1) {
+ if ((typeof py != "undefined") && (py !== false)) {
+ self.scroll.y = Math.round(py * 1 / self.scrollratio.y);
+ }
+ if (typeof px != "undefined") {
+ self.scroll.x = Math.round(px * 1 / self.scrollratio.x);
+ }
+ }
+
+ self.cursor.css({
+ height: self.cursorheight,
+ top: self.scroll.y
+ });
+ if (self.cursorh) {
+ var lx = (self.hasreversehr) ? self.scrollvaluemaxw-self.scroll.x : self.scroll.x;
+ (!self.rail.align && self.rail.visibility) ? self.cursorh.css({
+ width: self.cursorwidth,
+ left: lx + self.rail.width
+ }): self.cursorh.css({
+ width: self.cursorwidth,
+ left: lx
+ });
+ self.cursoractive = true;
+ }
+
+ if (self.zoom) self.zoom.stop().css({
+ opacity: self.opt.cursoropacitymax
+ });
+ };
+
+ this.hideCursor = function(tm) {
+ if (self.cursortimeout) return;
+ if (!self.rail) return;
+ if (!self.autohidedom) return;
+ if (self.hasmousefocus && self.opt.autohidemode == "leave") return;
+ self.cursortimeout = setTimeout(function() {
+ if (!self.rail.active || !self.showonmouseevent) {
+ self.autohidedom.stop().animate({
+ opacity: self.opt.cursoropacitymin
+ });
+ if (self.zoom) self.zoom.stop().animate({
+ opacity: self.opt.cursoropacitymin
+ });
+ self.cursoractive = false;
+ }
+ self.cursortimeout = 0;
+ }, tm || self.opt.hidecursordelay);
+ };
+
+ this.noticeCursor = function(tm, py, px) {
+ self.showCursor(py, px);
+ if (!self.rail.active) self.hideCursor(tm);
+ };
+
+ this.getContentSize =
+ (self.ispage) ?
+ function() {
+ return {
+ w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
+ h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
+ }
+ } : (self.haswrapper) ?
+ function() {
+ return {
+ w: self.doc.outerWidth() + parseInt(self.win.css('paddingLeft')) + parseInt(self.win.css('paddingRight')),
+ h: self.doc.outerHeight() + parseInt(self.win.css('paddingTop')) + parseInt(self.win.css('paddingBottom'))
+ }
+ } : function() {
+ return {
+ w: self.docscroll[0].scrollWidth,
+ h: self.docscroll[0].scrollHeight
+ }
+ };
+
+ this.onResize = function(e, page) {
+
+ if (!self || !self.win) return false;
+
+ if (!self.haswrapper && !self.ispage) {
+ if (self.win.css('display') == 'none') {
+ if (self.visibility) self.hideRail().hideRailHr();
+ return false;
+ } else {
+ if (!self.hidden && !self.visibility) self.showRail().showRailHr();
+ }
+ }
+
+ var premaxh = self.page.maxh;
+ var premaxw = self.page.maxw;
+
+ var preview = {
+ h: self.view.h,
+ w: self.view.w
+ };
+
+ self.view = {
+ w: (self.ispage) ? self.win.width() : parseInt(self.win[0].clientWidth),
+ h: (self.ispage) ? self.win.height() : parseInt(self.win[0].clientHeight)
+ };
+
+ self.page = (page) ? page : self.getContentSize();
+
+ self.page.maxh = Math.max(0, self.page.h - self.view.h);
+ self.page.maxw = Math.max(0, self.page.w - self.view.w);
+
+ if ((self.page.maxh == premaxh) && (self.page.maxw == premaxw) && (self.view.w == preview.w) && (self.view.h == preview.h)) {
+ // test position
+ if (!self.ispage) {
+ var pos = self.win.offset();
+ if (self.lastposition) {
+ var lst = self.lastposition;
+ if ((lst.top == pos.top) && (lst.left == pos.left)) return self; //nothing to do
+ }
+ self.lastposition = pos;
+ } else {
+ return self; //nothing to do
+ }
+ }
+
+ if (self.page.maxh == 0) {
+ self.hideRail();
+ self.scrollvaluemax = 0;
+ self.scroll.y = 0;
+ self.scrollratio.y = 0;
+ self.cursorheight = 0;
+ self.setScrollTop(0);
+ self.rail.scrollable = false;
+ } else {
+ self.page.maxh -= (self.opt.railpadding.top + self.opt.railpadding.bottom); //**
+ self.rail.scrollable = true;
+ }
+
+ if (self.page.maxw == 0) {
+ self.hideRailHr();
+ self.scrollvaluemaxw = 0;
+ self.scroll.x = 0;
+ self.scrollratio.x = 0;
+ self.cursorwidth = 0;
+ self.setScrollLeft(0);
+ self.railh.scrollable = false;
+ } else {
+ self.page.maxw -= (self.opt.railpadding.left + self.opt.railpadding.right); //**
+ self.railh.scrollable = true;
+ }
+
+ self.railslocked = (self.locked) || ((self.page.maxh == 0) && (self.page.maxw == 0));
+ if (self.railslocked) {
+ if (!self.ispage) self.updateScrollBar(self.view);
+ return false;
+ }
+
+ if (!self.hidden && !self.visibility) {
+ self.showRail().showRailHr();
+ }
+ else if (!self.hidden && !self.railh.visibility) self.showRailHr();
+
+ if (self.istextarea && self.win.css('resize') && self.win.css('resize') != 'none') self.view.h -= 20;
+
+ self.cursorheight = Math.min(self.view.h, Math.round(self.view.h * (self.view.h / self.page.h)));
+ self.cursorheight = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorheight);
+
+ self.cursorwidth = Math.min(self.view.w, Math.round(self.view.w * (self.view.w / self.page.w)));
+ self.cursorwidth = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorwidth);
+
+ self.scrollvaluemax = self.view.h - self.cursorheight - self.cursor.hborder - (self.opt.railpadding.top + self.opt.railpadding.bottom); //**
+
+ if (self.railh) {
+ self.railh.width = (self.page.maxh > 0) ? (self.view.w - self.rail.width) : self.view.w;
+ self.scrollvaluemaxw = self.railh.width - self.cursorwidth - self.cursorh.wborder - (self.opt.railpadding.left + self.opt.railpadding.right); //**
+ }
+
+ /*
+ if (self.checkrtlmode&&self.railh) {
+ self.checkrtlmode = false;
+ if (self.opt.rtlmode&&self.scroll.x==0) self.setScrollLeft(self.page.maxw);
+ }
+*/
+
+ if (!self.ispage) self.updateScrollBar(self.view);
+
+ self.scrollratio = {
+ x: (self.page.maxw / self.scrollvaluemaxw),
+ y: (self.page.maxh / self.scrollvaluemax)
+ };
+
+ var sy = self.getScrollTop();
+ if (sy > self.page.maxh) {
+ self.doScrollTop(self.page.maxh);
+ } else {
+ self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+ self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
+ if (self.cursoractive) self.noticeCursor();
+ }
+
+ if (self.scroll.y && (self.getScrollTop() == 0)) self.doScrollTo(Math.floor(self.scroll.y * self.scrollratio.y));
+
+ return self;
+ };
+
+ this.resize = self.onResize;
+
+ this.lazyResize = function(tm) { // event debounce
+ tm = (isNaN(tm)) ? 30 : tm;
+ self.debounced('resize', self.resize, tm);
+ return self;
+ };
+
+ // modified by MDN https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/wheel
+ function _modernWheelEvent(dom, name, fn, bubble) {
+ self._bind(dom, name, function(e) {
+ var e = (e) ? e : window.event;
+ var event = {
+ original: e,
+ target: e.target || e.srcElement,
+ type: "wheel",
+ deltaMode: e.type == "MozMousePixelScroll" ? 0 : 1,
+ deltaX: 0,
+ deltaZ: 0,
+ preventDefault: function() {
+ e.preventDefault ? e.preventDefault() : e.returnValue = false;
+ return false;
+ },
+ stopImmediatePropagation: function() {
+ (e.stopImmediatePropagation) ? e.stopImmediatePropagation(): e.cancelBubble = true;
+ }
+ };
+
+ if (name == "mousewheel") {
+ event.deltaY = -1 / 40 * e.wheelDelta;
+ e.wheelDeltaX && (event.deltaX = -1 / 40 * e.wheelDeltaX);
+ } else {
+ event.deltaY = e.detail;
+ }
+
+ return fn.call(dom, event);
+ }, bubble);
+ };
+
+
+
+ this.jqbind = function(dom, name, fn) { // use jquery bind for non-native events (mouseenter/mouseleave)
+ self.events.push({
+ e: dom,
+ n: name,
+ f: fn,
+ q: true
+ });
+ $(dom).bind(name, fn);
+ };
+
+ this.bind = function(dom, name, fn, bubble) { // touch-oriented & fixing jquery bind
+ var el = ("jquery" in dom) ? dom[0] : dom;
+
+ if (name == 'mousewheel') {
+ if (window.addEventListener||'onwheel' in document) { // modern brosers & IE9 detection fix
+ self._bind(el, "wheel", fn, bubble || false);
+ } else {
+ var wname = (typeof document.onmousewheel != "undefined") ? "mousewheel" : "DOMMouseScroll"; // older IE/Firefox
+ _modernWheelEvent(el, wname, fn, bubble || false);
+ if (wname == "DOMMouseScroll") _modernWheelEvent(el, "MozMousePixelScroll", fn, bubble || false); // Firefox legacy
+ }
+ } else if (el.addEventListener) {
+ if (cap.cantouch && /mouseup|mousedown|mousemove/.test(name)) { // touch device support
+ var tt = (name == 'mousedown') ? 'touchstart' : (name == 'mouseup') ? 'touchend' : 'touchmove';
+ self._bind(el, tt, function(e) {
+ if (e.touches) {
+ if (e.touches.length < 2) {
+ var ev = (e.touches.length) ? e.touches[0] : e;
+ ev.original = e;
+ fn.call(this, ev);
+ }
+ } else if (e.changedTouches) {
+ var ev = e.changedTouches[0];
+ ev.original = e;
+ fn.call(this, ev);
+ } //blackberry
+ }, bubble || false);
+ }
+ self._bind(el, name, fn, bubble || false);
+ if (cap.cantouch && name == "mouseup") self._bind(el, "touchcancel", fn, bubble || false);
+ } else {
+ self._bind(el, name, function(e) {
+ e = e || window.event || false;
+ if (e) {
+ if (e.srcElement) e.target = e.srcElement;
+ }
+ if (!("pageY" in e)) {
+ e.pageX = e.clientX + document.documentElement.scrollLeft;
+ e.pageY = e.clientY + document.documentElement.scrollTop;
+ }
+ return ((fn.call(el, e) === false) || bubble === false) ? self.cancelEvent(e) : true;
+ });
+ }
+ };
+
+ if (cap.haseventlistener) { // W3C standard model
+ this._bind = function(el, name, fn, bubble) { // primitive bind
+ self.events.push({
+ e: el,
+ n: name,
+ f: fn,
+ b: bubble,
+ q: false
+ });
+ el.addEventListener(name, fn, bubble || false);
+ };
+ this.cancelEvent = function(e) {
+ if (!e) return false;
+ var e = (e.original) ? e.original : e;
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.preventManipulation) e.preventManipulation(); //IE10
+ return false;
+ };
+ this.stopPropagation = function(e) {
+ if (!e) return false;
+ var e = (e.original) ? e.original : e;
+ e.stopPropagation();
+ return false;
+ };
+ this._unbind = function(el, name, fn, bub) { // primitive unbind
+ el.removeEventListener(name, fn, bub);
+ };
+ } else { // old IE model
+ this._bind = function(el, name, fn, bubble) { // primitive bind
+ self.events.push({
+ e: el,
+ n: name,
+ f: fn,
+ b: bubble,
+ q: false
+ });
+ if (el.attachEvent) {
+ el.attachEvent("on" + name, fn);
+ } else {
+ el["on" + name] = fn;
+ }
+ };
+ // Thanks to http://www.switchonthecode.com !!
+ this.cancelEvent = function(e) {
+ var e = window.event || false;
+ if (!e) return false;
+ e.cancelBubble = true;
+ e.cancel = true;
+ e.returnValue = false;
+ return false;
+ };
+ this.stopPropagation = function(e) {
+ var e = window.event || false;
+ if (!e) return false;
+ e.cancelBubble = true;
+ return false;
+ };
+ this._unbind = function(el, name, fn, bub) { // primitive unbind IE old
+ if (el.detachEvent) {
+ el.detachEvent('on' + name, fn);
+ } else {
+ el['on' + name] = false;
+ }
+ };
+ }
+
+ this.unbindAll = function() {
+ for (var a = 0; a < self.events.length; a++) {
+ var r = self.events[a];
+ (r.q) ? r.e.unbind(r.n, r.f): self._unbind(r.e, r.n, r.f, r.b);
+ }
+ };
+
+ this.showRail = function() {
+ if ((self.page.maxh != 0) && (self.ispage || self.win.css('display') != 'none')) {
+ self.visibility = true;
+ self.rail.visibility = true;
+ self.rail.css('display', 'block');
+ }
+ return self;
+ };
+
+ this.showRailHr = function() {
+ if (!self.railh) return self;
+ if ((self.page.maxw != 0) && (self.ispage || self.win.css('display') != 'none')) {
+ self.railh.visibility = true;
+ self.railh.css('display', 'block');
+ }
+ return self;
+ };
+
+ this.hideRail = function() {
+ self.visibility = false;
+ self.rail.visibility = false;
+ self.rail.css('display', 'none');
+ return self;
+ };
+
+ this.hideRailHr = function() {
+ if (!self.railh) return self;
+ self.railh.visibility = false;
+ self.railh.css('display', 'none');
+ return self;
+ };
+
+ this.show = function() {
+ self.hidden = false;
+ self.railslocked = false;
+ return self.showRail().showRailHr();
+ };
+
+ this.hide = function() {
+ self.hidden = true;
+ self.railslocked = true;
+ return self.hideRail().hideRailHr();
+ };
+
+ this.toggle = function() {
+ return (self.hidden) ? self.show() : self.hide();
+ };
+
+ this.remove = function() {
+ self.stop();
+ if (self.cursortimeout) clearTimeout(self.cursortimeout);
+ self.doZoomOut();
+ self.unbindAll();
+
+ if (cap.isie9) self.win[0].detachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
+
+ if (self.observer !== false) self.observer.disconnect();
+ if (self.observerremover !== false) self.observerremover.disconnect();
+ if (self.observerbody !== false) self.observerbody.disconnect();
+
+ self.events = null;
+
+ if (self.cursor) {
+ self.cursor.remove();
+ }
+ if (self.cursorh) {
+ self.cursorh.remove();
+ }
+ if (self.rail) {
+ self.rail.remove();
+ }
+ if (self.railh) {
+ self.railh.remove();
+ }
+ if (self.zoom) {
+ self.zoom.remove();
+ }
+ for (var a = 0; a < self.saved.css.length; a++) {
+ var d = self.saved.css[a];
+ d[0].css(d[1], (typeof d[2] == "undefined") ? '' : d[2]);
+ }
+ self.saved = false;
+ self.me.data('__nicescroll', ''); //erase all traces
+
+ // memory leak fixed by GianlucaGuarini - thanks a lot!
+ // remove the current nicescroll from the $.nicescroll array & normalize array
+ var lst = $.nicescroll;
+ lst.each(function(i) {
+ if (!this) return;
+ if (this.id === self.id) {
+ delete lst[i];
+ for (var b = ++i; b < lst.length; b++, i++) lst[i] = lst[b];
+ lst.length--;
+ if (lst.length) delete lst[lst.length];
+ }
+ });
+
+ for (var i in self) {
+ self[i] = null;
+ delete self[i];
+ }
+
+ self = null;
+
+ };
+
+ this.scrollstart = function(fn) {
+ this.onscrollstart = fn;
+ return self;
+ };
+ this.scrollend = function(fn) {
+ this.onscrollend = fn;
+ return self;
+ };
+ this.scrollcancel = function(fn) {
+ this.onscrollcancel = fn;
+ return self;
+ };
+
+ this.zoomin = function(fn) {
+ this.onzoomin = fn;
+ return self;
+ };
+ this.zoomout = function(fn) {
+ this.onzoomout = fn;
+ return self;
+ };
+
+ this.isScrollable = function(e) {
+ var dom = (e.target) ? e.target : e;
+ if (dom.nodeName == 'OPTION') return true;
+ while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
+ var dd = $(dom);
+ var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
+ if (/scroll|auto/.test(ov)) return (dom.clientHeight != dom.scrollHeight);
+ dom = (dom.parentNode) ? dom.parentNode : false;
+ }
+ return false;
+ };
+
+ this.getViewport = function(me) {
+ var dom = (me && me.parentNode) ? me.parentNode : false;
+ while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
+ var dd = $(dom);
+ if (/fixed|absolute/.test(dd.css("position"))) return dd;
+ var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
+ if ((/scroll|auto/.test(ov)) && (dom.clientHeight != dom.scrollHeight)) return dd;
+ if (dd.getNiceScroll().length > 0) return dd;
+ dom = (dom.parentNode) ? dom.parentNode : false;
+ }
+ return false; //(dom) ? $(dom) : false;
+ };
+
+ this.triggerScrollEnd = function() {
+ if (!self.onscrollend) return;
+
+ var px = self.getScrollLeft();
+ var py = self.getScrollTop();
+
+ var info = {
+ "type": "scrollend",
+ "current": {
+ "x": px,
+ "y": py
+ },
+ "end": {
+ "x": px,
+ "y": py
+ }
+ };
+ self.onscrollend.call(self, info);
+ }
+
+ function execScrollWheel(e, hr, chkscroll) {
+ var px, py;
+
+ if (e.deltaMode == 0) { // PIXEL
+ px = -Math.floor(e.deltaX * (self.opt.mousescrollstep / (18 * 3)));
+ py = -Math.floor(e.deltaY * (self.opt.mousescrollstep / (18 * 3)));
+ } else if (e.deltaMode == 1) { // LINE
+ px = -Math.floor(e.deltaX * self.opt.mousescrollstep);
+ py = -Math.floor(e.deltaY * self.opt.mousescrollstep);
+ }
+
+ if (hr && self.opt.oneaxismousemode && (px == 0) && py) { // classic vertical-only mousewheel + browser with x/y support
+ px = py;
+ py = 0;
+
+ if (chkscroll) {
+ var hrend = (px < 0) ? (self.getScrollLeft() >= self.page.maxw) : (self.getScrollLeft() <= 0);
+ if (hrend) { // preserve vertical scrolling
+ py = px;
+ px = 0;
+ }
+ }
+
+ }
+
+ if (px) {
+ if (self.scrollmom) {
+ self.scrollmom.stop()
+ }
+ self.lastdeltax += px;
+ self.debounced("mousewheelx", function() {
+ var dt = self.lastdeltax;
+ self.lastdeltax = 0;
+ if (!self.rail.drag) {
+ self.doScrollLeftBy(dt)
+ }
+ }, 15);
+ }
+ if (py) {
+ if (self.opt.nativeparentscrolling && chkscroll && !self.ispage && !self.zoomactive) {
+ if (py < 0) {
+ if (self.getScrollTop() >= self.page.maxh) return true;
+ } else {
+ if (self.getScrollTop() <= 0) return true;
+ }
+ }
+ if (self.scrollmom) {
+ self.scrollmom.stop()
+ }
+ self.lastdeltay += py;
+ self.debounced("mousewheely", function() {
+ var dt = self.lastdeltay;
+ self.lastdeltay = 0;
+ if (!self.rail.drag) {
+ self.doScrollBy(dt)
+ }
+ }, 15);
+ }
+
+ e.stopImmediatePropagation();
+ return e.preventDefault();
+ };
+
+ this.onmousewheel = function(e) {
+ if (self.wheelprevented) return;
+ if (self.railslocked) {
+ self.debounced("checkunlock", self.resize, 250);
+ return true;
+ }
+ if (self.rail.drag) return self.cancelEvent(e);
+
+ if (self.opt.oneaxismousemode == "auto" && e.deltaX != 0) self.opt.oneaxismousemode = false; // check two-axis mouse support (not very elegant)
+
+ if (self.opt.oneaxismousemode && e.deltaX == 0) {
+ if (!self.rail.scrollable) {
+ if (self.railh && self.railh.scrollable) {
+ return self.onmousewheelhr(e);
+ } else {
+ return true;
+ }
+ }
+ }
+
+ var nw = +(new Date());
+ var chk = false;
+ if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
+ self.nativescrollingarea = self.isScrollable(e);
+ chk = true;
+ }
+ self.checkarea = nw;
+ if (self.nativescrollingarea) return true; // this isn't my business
+ var ret = execScrollWheel(e, false, chk);
+ if (ret) self.checkarea = 0;
+ return ret;
+ };
+
+ this.onmousewheelhr = function(e) {
+ if (self.wheelprevented) return;
+ if (self.railslocked || !self.railh.scrollable) return true;
+ if (self.rail.drag) return self.cancelEvent(e);
+
+ var nw = +(new Date());
+ var chk = false;
+ if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
+ self.nativescrollingarea = self.isScrollable(e);
+ chk = true;
+ }
+ self.checkarea = nw;
+ if (self.nativescrollingarea) return true; // this isn't my business
+ if (self.railslocked) return self.cancelEvent(e);
+
+ return execScrollWheel(e, true, chk);
+ };
+
+ this.stop = function() {
+ self.cancelScroll();
+ if (self.scrollmon) self.scrollmon.stop();
+ self.cursorfreezed = false;
+ self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+ self.noticeCursor();
+ return self;
+ };
+
+ this.getTransitionSpeed = function(dif) {
+ var sp = Math.round(self.opt.scrollspeed * 10);
+ var ex = Math.min(sp, Math.round((dif / 20) * self.opt.scrollspeed));
+ return (ex > 20) ? ex : 0;
+ };
+
+ if (!self.opt.smoothscroll) {
+ this.doScrollLeft = function(x, spd) { //direct
+ var y = self.getScrollTop();
+ self.doScrollPos(x, y, spd);
+ };
+ this.doScrollTop = function(y, spd) { //direct
+ var x = self.getScrollLeft();
+ self.doScrollPos(x, y, spd);
+ };
+ this.doScrollPos = function(x, y, spd) { //direct
+ var nx = (x > self.page.maxw) ? self.page.maxw : x;
+ if (nx < 0) nx = 0;
+ var ny = (y > self.page.maxh) ? self.page.maxh : y;
+ if (ny < 0) ny = 0;
+ self.synched('scroll', function() {
+ self.setScrollTop(ny);
+ self.setScrollLeft(nx);
+ });
+ };
+ this.cancelScroll = function() {}; // direct
+ } else if (self.ishwscroll && cap.hastransition && self.opt.usetransition && !!self.opt.smoothscroll) {
+ this.prepareTransition = function(dif, istime) {
+ var ex = (istime) ? ((dif > 20) ? dif : 0) : self.getTransitionSpeed(dif);
+ var trans = (ex) ? cap.prefixstyle + 'transform ' + ex + 'ms ease-out' : '';
+ if (!self.lasttransitionstyle || self.lasttransitionstyle != trans) {
+ self.lasttransitionstyle = trans;
+ self.doc.css(cap.transitionstyle, trans);
+ }
+ return ex;
+ };
+
+ this.doScrollLeft = function(x, spd) { //trans
+ var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollTop = function(y, spd) { //trans
+ var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollPos = function(x, y, spd) { //trans
+
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+
+ if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection
+
+ if (self.opt.bouncescroll == false) {
+ if (y < 0) y = 0;
+ else if (y > self.page.maxh) y = self.page.maxh;
+ if (x < 0) x = 0;
+ else if (x > self.page.maxw) x = self.page.maxw;
+ }
+
+ if (self.scrollrunning && x == self.newscrollx && y == self.newscrolly) return false;
+
+ self.newscrolly = y;
+ self.newscrollx = x;
+
+ self.newscrollspeed = spd || false;
+
+ if (self.timer) return false;
+
+ self.timer = setTimeout(function() {
+
+ var top = self.getScrollTop();
+ var lft = self.getScrollLeft();
+
+ var dst = {};
+ dst.x = x - lft;
+ dst.y = y - top;
+ dst.px = lft;
+ dst.py = top;
+
+ var dd = Math.round(Math.sqrt(Math.pow(dst.x, 2) + Math.pow(dst.y, 2)));
+ var ms = (self.newscrollspeed && self.newscrollspeed > 1) ? self.newscrollspeed : self.getTransitionSpeed(dd);
+ if (self.newscrollspeed && self.newscrollspeed <= 1) ms *= self.newscrollspeed;
+
+ self.prepareTransition(ms, true);
+
+ if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+
+ if (ms > 0) {
+
+ if (!self.scrollrunning && self.onscrollstart) {
+ var info = {
+ "type": "scrollstart",
+ "current": {
+ "x": lft,
+ "y": top
+ },
+ "request": {
+ "x": x,
+ "y": y
+ },
+ "end": {
+ "x": self.newscrollx,
+ "y": self.newscrolly
+ },
+ "speed": ms
+ };
+ self.onscrollstart.call(self, info);
+ }
+
+ if (cap.transitionend) {
+ if (!self.scrollendtrapped) {
+ self.scrollendtrapped = true;
+ self.bind(self.doc, cap.transitionend, self.onScrollTransitionEnd, false); //I have got to do something usefull!!
+ }
+ } else {
+ if (self.scrollendtrapped) clearTimeout(self.scrollendtrapped);
+ self.scrollendtrapped = setTimeout(self.onScrollTransitionEnd, ms); // simulate transitionend event
+ }
+
+ var py = top;
+ var px = lft;
+ self.timerscroll = {
+ bz: new BezierClass(py, self.newscrolly, ms, 0, 0, 0.58, 1),
+ bh: new BezierClass(px, self.newscrollx, ms, 0, 0, 0.58, 1)
+ };
+ if (!self.cursorfreezed) self.timerscroll.tm = setInterval(function() {
+ self.showCursor(self.getScrollTop(), self.getScrollLeft())
+ }, 60);
+
+ }
+
+ self.synched("doScroll-set", function() {
+ self.timer = 0;
+ if (self.scrollendtrapped) self.scrollrunning = true;
+ self.setScrollTop(self.newscrolly);
+ self.setScrollLeft(self.newscrollx);
+ if (!self.scrollendtrapped) self.onScrollTransitionEnd();
+ });
+
+
+ }, 50);
+
+ };
+
+ this.cancelScroll = function() {
+ if (!self.scrollendtrapped) return true;
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+ self.scrollrunning = false;
+ if (!cap.transitionend) clearTimeout(cap.transitionend);
+ self.scrollendtrapped = false;
+ self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
+ self.prepareTransition(0);
+ self.setScrollTop(py); // fire event onscroll
+ if (self.railh) self.setScrollLeft(px);
+ if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+ self.timerscroll = false;
+
+ self.cursorfreezed = false;
+
+ self.showCursor(py, px);
+ return self;
+ };
+ this.onScrollTransitionEnd = function() {
+ if (self.scrollendtrapped) self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
+ self.scrollendtrapped = false;
+ self.prepareTransition(0);
+ if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+ self.timerscroll = false;
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+ self.setScrollTop(py); // fire event onscroll
+ if (self.railh) self.setScrollLeft(px); // fire event onscroll left
+
+ self.noticeCursor(false, py, px);
+
+ self.cursorfreezed = false;
+
+ if (py < 0) py = 0
+ else if (py > self.page.maxh) py = self.page.maxh;
+ if (px < 0) px = 0
+ else if (px > self.page.maxw) px = self.page.maxw;
+ if ((py != self.newscrolly) || (px != self.newscrollx)) return self.doScrollPos(px, py, self.opt.snapbackspeed);
+
+ if (self.onscrollend && self.scrollrunning) {
+ self.triggerScrollEnd();
+ }
+ self.scrollrunning = false;
+
+ };
+
+ } else {
+
+ this.doScrollLeft = function(x, spd) { //no-trans
+ var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollTop = function(y, spd) { //no-trans
+ var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollPos = function(x, y, spd) { //no-trans
+ var y = ((typeof y == "undefined") || (y === false)) ? self.getScrollTop(true) : y;
+
+ if ((self.timer) && (self.newscrolly == y) && (self.newscrollx == x)) return true;
+
+ if (self.timer) clearAnimationFrame(self.timer);
+ self.timer = 0;
+
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+
+ if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection
+
+ self.newscrolly = y;
+ self.newscrollx = x;
+
+ if (!self.bouncescroll || !self.rail.visibility) {
+ if (self.newscrolly < 0) {
+ self.newscrolly = 0;
+ } else if (self.newscrolly > self.page.maxh) {
+ self.newscrolly = self.page.maxh;
+ }
+ }
+ if (!self.bouncescroll || !self.railh.visibility) {
+ if (self.newscrollx < 0) {
+ self.newscrollx = 0;
+ } else if (self.newscrollx > self.page.maxw) {
+ self.newscrollx = self.page.maxw;
+ }
+ }
+
+ self.dst = {};
+ self.dst.x = x - px;
+ self.dst.y = y - py;
+ self.dst.px = px;
+ self.dst.py = py;
+
+ var dst = Math.round(Math.sqrt(Math.pow(self.dst.x, 2) + Math.pow(self.dst.y, 2)));
+
+ self.dst.ax = self.dst.x / dst;
+ self.dst.ay = self.dst.y / dst;
+
+ var pa = 0;
+ var pe = dst;
+
+ if (self.dst.x == 0) {
+ pa = py;
+ pe = y;
+ self.dst.ay = 1;
+ self.dst.py = 0;
+ } else if (self.dst.y == 0) {
+ pa = px;
+ pe = x;
+ self.dst.ax = 1;
+ self.dst.px = 0;
+ }
+
+ var ms = self.getTransitionSpeed(dst);
+ if (spd && spd <= 1) ms *= spd;
+ if (ms > 0) {
+ self.bzscroll = (self.bzscroll) ? self.bzscroll.update(pe, ms) : new BezierClass(pa, pe, ms, 0, 1, 0, 1);
+ } else {
+ self.bzscroll = false;
+ }
+
+ if (self.timer) return;
+
+ if ((py == self.page.maxh && y >= self.page.maxh) || (px == self.page.maxw && x >= self.page.maxw)) self.checkContentSize();
+
+ var sync = 1;
+
+ function scrolling() {
+ if (self.cancelAnimationFrame) return true;
+
+ self.scrollrunning = true;
+
+ sync = 1 - sync;
+ if (sync) return (self.timer = setAnimationFrame(scrolling) || 1);
+
+ var done = 0;
+ var sx, sy;
+
+ var sc = sy = self.getScrollTop();
+ if (self.dst.ay) {
+ sc = (self.bzscroll) ? self.dst.py + (self.bzscroll.getNow() * self.dst.ay) : self.newscrolly;
+ var dr = sc - sy;
+ if ((dr < 0 && sc < self.newscrolly) || (dr > 0 && sc > self.newscrolly)) sc = self.newscrolly;
+ self.setScrollTop(sc);
+ if (sc == self.newscrolly) done = 1;
+ } else {
+ done = 1;
+ }
+
+ var scx = sx = self.getScrollLeft();
+ if (self.dst.ax) {
+ scx = (self.bzscroll) ? self.dst.px + (self.bzscroll.getNow() * self.dst.ax) : self.newscrollx;
+ var dr = scx - sx;
+ if ((dr < 0 && scx < self.newscrollx) || (dr > 0 && scx > self.newscrollx)) scx = self.newscrollx;
+ self.setScrollLeft(scx);
+ if (scx == self.newscrollx) done += 1;
+ } else {
+ done += 1;
+ }
+
+ if (done == 2) {
+ self.timer = 0;
+ self.cursorfreezed = false;
+ self.bzscroll = false;
+ self.scrollrunning = false;
+ if (sc < 0) sc = 0;
+ else if (sc > self.page.maxh) sc = self.page.maxh;
+ if (scx < 0) scx = 0;
+ else if (scx > self.page.maxw) scx = self.page.maxw;
+ if ((scx != self.newscrollx) || (sc != self.newscrolly)) self.doScrollPos(scx, sc);
+ else {
+ if (self.onscrollend) {
+ self.triggerScrollEnd();
+ }
+ }
+ } else {
+ self.timer = setAnimationFrame(scrolling) || 1;
+ }
+ };
+ self.cancelAnimationFrame = false;
+ self.timer = 1;
+
+ if (self.onscrollstart && !self.scrollrunning) {
+ var info = {
+ "type": "scrollstart",
+ "current": {
+ "x": px,
+ "y": py
+ },
+ "request": {
+ "x": x,
+ "y": y
+ },
+ "end": {
+ "x": self.newscrollx,
+ "y": self.newscrolly
+ },
+ "speed": ms
+ };
+ self.onscrollstart.call(self, info);
+ }
+
+ scrolling();
+
+ if ((py == self.page.maxh && y >= py) || (px == self.page.maxw && x >= px)) self.checkContentSize();
+
+ self.noticeCursor();
+ };
+
+ this.cancelScroll = function() {
+ if (self.timer) clearAnimationFrame(self.timer);
+ self.timer = 0;
+ self.bzscroll = false;
+ self.scrollrunning = false;
+ return self;
+ };
+
+ }
+
+ this.doScrollBy = function(stp, relative) {
+ var ny = 0;
+ if (relative) {
+ ny = Math.floor((self.scroll.y - stp) * self.scrollratio.y)
+ } else {
+ var sy = (self.timer) ? self.newscrolly : self.getScrollTop(true);
+ ny = sy - stp;
+ }
+ if (self.bouncescroll) {
+ var haf = Math.round(self.view.h / 2);
+ if (ny < -haf) ny = -haf
+ else if (ny > (self.page.maxh + haf)) ny = (self.page.maxh + haf);
+ }
+ self.cursorfreezed = false;
+
+ var py = self.getScrollTop(true);
+ if (ny < 0 && py <= 0) return self.noticeCursor();
+ else if (ny > self.page.maxh && py >= self.page.maxh) {
+ self.checkContentSize();
+ return self.noticeCursor();
+ }
+
+ self.doScrollTop(ny);
+ };
+
+ this.doScrollLeftBy = function(stp, relative) {
+ var nx = 0;
+ if (relative) {
+ nx = Math.floor((self.scroll.x - stp) * self.scrollratio.x)
+ } else {
+ var sx = (self.timer) ? self.newscrollx : self.getScrollLeft(true);
+ nx = sx - stp;
+ }
+ if (self.bouncescroll) {
+ var haf = Math.round(self.view.w / 2);
+ if (nx < -haf) nx = -haf;
+ else if (nx > (self.page.maxw + haf)) nx = (self.page.maxw + haf);
+ }
+ self.cursorfreezed = false;
+
+ var px = self.getScrollLeft(true);
+ if (nx < 0 && px <= 0) return self.noticeCursor();
+ else if (nx > self.page.maxw && px >= self.page.maxw) return self.noticeCursor();
+
+ self.doScrollLeft(nx);
+ };
+
+ this.doScrollTo = function(pos, relative) {
+ var ny = (relative) ? Math.round(pos * self.scrollratio.y) : pos;
+ if (ny < 0) ny = 0;
+ else if (ny > self.page.maxh) ny = self.page.maxh;
+ self.cursorfreezed = false;
+ self.doScrollTop(pos);
+ };
+
+ this.checkContentSize = function() {
+ var pg = self.getContentSize();
+ if ((pg.h != self.page.h) || (pg.w != self.page.w)) self.resize(false, pg);
+ };
+
+ self.onscroll = function(e) {
+ if (self.rail.drag) return;
+ if (!self.cursorfreezed) {
+ self.synched('scroll', function() {
+ self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+ if (self.railh) self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
+ self.noticeCursor();
+ });
+ }
+ };
+ self.bind(self.docscroll, "scroll", self.onscroll);
+
+ this.doZoomIn = function(e) {
+ if (self.zoomactive) return;
+ self.zoomactive = true;
+
+ self.zoomrestore = {
+ style: {}
+ };
+ var lst = ['position', 'top', 'left', 'zIndex', 'backgroundColor', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight'];
+ var win = self.win[0].style;
+ for (var a in lst) {
+ var pp = lst[a];
+ self.zoomrestore.style[pp] = (typeof win[pp] != "undefined") ? win[pp] : '';
+ }
+
+ self.zoomrestore.style.width = self.win.css('width');
+ self.zoomrestore.style.height = self.win.css('height');
+
+ self.zoomrestore.padding = {
+ w: self.win.outerWidth() - self.win.width(),
+ h: self.win.outerHeight() - self.win.height()
+ };
+
+ if (cap.isios4) {
+ self.zoomrestore.scrollTop = $(window).scrollTop();
+ $(window).scrollTop(0);
+ }
+
+ self.win.css({
+ "position": (cap.isios4) ? "absolute" : "fixed",
+ "top": 0,
+ "left": 0,
+ "z-index": globalmaxzindex + 100,
+ "margin": "0px"
+ });
+ var bkg = self.win.css("backgroundColor");
+ if (bkg == "" || /transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(bkg)) self.win.css("backgroundColor", "#fff");
+ self.rail.css({
+ "z-index": globalmaxzindex + 101
+ });
+ self.zoom.css({
+ "z-index": globalmaxzindex + 102
+ });
+ self.zoom.css('backgroundPosition', '0px -18px');
+ self.resizeZoom();
+
+ if (self.onzoomin) self.onzoomin.call(self);
+
+ return self.cancelEvent(e);
+ };
+
+ this.doZoomOut = function(e) {
+ if (!self.zoomactive) return;
+ self.zoomactive = false;
+
+ self.win.css("margin", "");
+ self.win.css(self.zoomrestore.style);
+
+ if (cap.isios4) {
+ $(window).scrollTop(self.zoomrestore.scrollTop);
+ }
+
+ self.rail.css({
+ "z-index": self.zindex
+ });
+ self.zoom.css({
+ "z-index": self.zindex
+ });
+ self.zoomrestore = false;
+ self.zoom.css('backgroundPosition', '0px 0px');
+ self.onResize();
+
+ if (self.onzoomout) self.onzoomout.call(self);
+
+ return self.cancelEvent(e);
+ };
+
+ this.doZoom = function(e) {
+ return (self.zoomactive) ? self.doZoomOut(e) : self.doZoomIn(e);
+ };
+
+ this.resizeZoom = function() {
+ if (!self.zoomactive) return;
+
+ var py = self.getScrollTop(); //preserve scrolling position
+ self.win.css({
+ width: $(window).width() - self.zoomrestore.padding.w + "px",
+ height: $(window).height() - self.zoomrestore.padding.h + "px"
+ });
+ self.onResize();
+
+ self.setScrollTop(Math.min(self.page.maxh, py));
+ };
+
+ this.init();
+
+ $.nicescroll.push(this);
+
+ };
+
+ // Inspired by the work of Kin Blas
+ // http://webpro.host.adobe.com/people/jblas/momentum/includes/jquery.momentum.0.7.js
+
+
+ var ScrollMomentumClass2D = function(nc) {
+ var self = this;
+ this.nc = nc;
+
+ this.lastx = 0;
+ this.lasty = 0;
+ this.speedx = 0;
+ this.speedy = 0;
+ this.lasttime = 0;
+ this.steptime = 0;
+ this.snapx = false;
+ this.snapy = false;
+ this.demulx = 0;
+ this.demuly = 0;
+
+ this.lastscrollx = -1;
+ this.lastscrolly = -1;
+
+ this.chkx = 0;
+ this.chky = 0;
+
+ this.timer = 0;
+
+ this.time = function() {
+ return +new Date(); //beautifull hack
+ };
+
+ this.reset = function(px, py) {
+ self.stop();
+ var now = self.time();
+ self.steptime = 0;
+ self.lasttime = now;
+ self.speedx = 0;
+ self.speedy = 0;
+ self.lastx = px;
+ self.lasty = py;
+ self.lastscrollx = -1;
+ self.lastscrolly = -1;
+ };
+
+ this.update = function(px, py) {
+ var now = self.time();
+ self.steptime = now - self.lasttime;
+ self.lasttime = now;
+ var dy = py - self.lasty;
+ var dx = px - self.lastx;
+ var sy = self.nc.getScrollTop();
+ var sx = self.nc.getScrollLeft();
+ var newy = sy + dy;
+ var newx = sx + dx;
+ self.snapx = (newx < 0) || (newx > self.nc.page.maxw);
+ self.snapy = (newy < 0) || (newy > self.nc.page.maxh);
+ self.speedx = dx;
+ self.speedy = dy;
+ self.lastx = px;
+ self.lasty = py;
+ };
+
+ this.stop = function() {
+ self.nc.unsynched("domomentum2d");
+ if (self.timer) clearTimeout(self.timer);
+ self.timer = 0;
+ self.lastscrollx = -1;
+ self.lastscrolly = -1;
+ };
+
+ this.doSnapy = function(nx, ny) {
+ var snap = false;
+
+ if (ny < 0) {
+ ny = 0;
+ snap = true;
+ } else if (ny > self.nc.page.maxh) {
+ ny = self.nc.page.maxh;
+ snap = true;
+ }
+
+ if (nx < 0) {
+ nx = 0;
+ snap = true;
+ } else if (nx > self.nc.page.maxw) {
+ nx = self.nc.page.maxw;
+ snap = true;
+ }
+
+ (snap) ? self.nc.doScrollPos(nx, ny, self.nc.opt.snapbackspeed): self.nc.triggerScrollEnd();
+ };
+
+ this.doMomentum = function(gp) {
+ var t = self.time();
+ var l = (gp) ? t + gp : self.lasttime;
+
+ var sl = self.nc.getScrollLeft();
+ var st = self.nc.getScrollTop();
+
+ var pageh = self.nc.page.maxh;
+ var pagew = self.nc.page.maxw;
+
+ self.speedx = (pagew > 0) ? Math.min(60, self.speedx) : 0;
+ self.speedy = (pageh > 0) ? Math.min(60, self.speedy) : 0;
+
+ var chk = l && (t - l) <= 60;
+
+ if ((st < 0) || (st > pageh) || (sl < 0) || (sl > pagew)) chk = false;
+
+ var sy = (self.speedy && chk) ? self.speedy : false;
+ var sx = (self.speedx && chk) ? self.speedx : false;
+
+ if (sy || sx) {
+ var tm = Math.max(16, self.steptime); //timeout granularity
+
+ if (tm > 50) { // do smooth
+ var xm = tm / 50;
+ self.speedx *= xm;
+ self.speedy *= xm;
+ tm = 50;
+ }
+
+ self.demulxy = 0;
+
+ self.lastscrollx = self.nc.getScrollLeft();
+ self.chkx = self.lastscrollx;
+ self.lastscrolly = self.nc.getScrollTop();
+ self.chky = self.lastscrolly;
+
+ var nx = self.lastscrollx;
+ var ny = self.lastscrolly;
+
+ var onscroll = function() {
+ var df = ((self.time() - t) > 600) ? 0.04 : 0.02;
+
+ if (self.speedx) {
+ nx = Math.floor(self.lastscrollx - (self.speedx * (1 - self.demulxy)));
+ self.lastscrollx = nx;
+ if ((nx < 0) || (nx > pagew)) df = 0.10;
+ }
+
+ if (self.speedy) {
+ ny = Math.floor(self.lastscrolly - (self.speedy * (1 - self.demulxy)));
+ self.lastscrolly = ny;
+ if ((ny < 0) || (ny > pageh)) df = 0.10;
+ }
+
+ self.demulxy = Math.min(1, self.demulxy + df);
+
+ self.nc.synched("domomentum2d", function() {
+
+ if (self.speedx) {
+ var scx = self.nc.getScrollLeft();
+ if (scx != self.chkx) self.stop();
+ self.chkx = nx;
+ self.nc.setScrollLeft(nx);
+ }
+
+ if (self.speedy) {
+ var scy = self.nc.getScrollTop();
+ if (scy != self.chky) self.stop();
+ self.chky = ny;
+ self.nc.setScrollTop(ny);
+ }
+
+ if (!self.timer) {
+ self.nc.hideCursor();
+ self.doSnapy(nx, ny);
+ }
+
+ });
+
+ if (self.demulxy < 1) {
+ self.timer = setTimeout(onscroll, tm);
+ } else {
+ self.stop();
+ self.nc.hideCursor();
+ self.doSnapy(nx, ny);
+ }
+ };
+
+ onscroll();
+
+ } else {
+ self.doSnapy(self.nc.getScrollLeft(), self.nc.getScrollTop());
+ }
+
+ }
+
+ };
+
+
+ // override jQuery scrollTop
+
+ var _scrollTop = jQuery.fn.scrollTop; // preserve original function
+
+ jQuery.cssHooks["pageYOffset"] = {
+ get: function(elem, computed, extra) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(elem);
+ },
+ set: function(elem, value) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call(elem, value);
+ return this;
+ }
+ };
+
+ /*
+ $.fx.step["scrollTop"] = function(fx){
+ $.cssHooks["scrollTop"].set( fx.elem, fx.now + fx.unit );
+ };
+*/
+
+ jQuery.fn.scrollTop = function(value) {
+ if (typeof value == "undefined") {
+ var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
+ return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(this);
+ } else {
+ return this.each(function() {
+ var nice = $.data(this, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call($(this), value);
+ });
+ }
+ };
+
+ // override jQuery scrollLeft
+
+ var _scrollLeft = jQuery.fn.scrollLeft; // preserve original function
+
+ $.cssHooks.pageXOffset = {
+ get: function(elem, computed, extra) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(elem);
+ },
+ set: function(elem, value) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call(elem, value);
+ return this;
+ }
+ };
+
+ /*
+ $.fx.step["scrollLeft"] = function(fx){
+ $.cssHooks["scrollLeft"].set( fx.elem, fx.now + fx.unit );
+ };
+*/
+
+ jQuery.fn.scrollLeft = function(value) {
+ if (typeof value == "undefined") {
+ var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
+ return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(this);
+ } else {
+ return this.each(function() {
+ var nice = $.data(this, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call($(this), value);
+ });
+ }
+ };
+
+ var NiceScrollArray = function(doms) {
+ var self = this;
+ this.length = 0;
+ this.name = "nicescrollarray";
+
+ this.each = function(fn) {
+ for (var a = 0, i = 0; a < self.length; a++) fn.call(self[a], i++);
+ return self;
+ };
+
+ this.push = function(nice) {
+ self[self.length] = nice;
+ self.length++;
+ };
+
+ this.eq = function(idx) {
+ return self[idx];
+ };
+
+ if (doms) {
+ for (var a = 0; a < doms.length; a++) {
+ var nice = $.data(doms[a], '__nicescroll') || false;
+ if (nice) {
+ this[this.length] = nice;
+ this.length++;
+ }
+ };
+ }
+
+ return this;
+ };
+
+ function mplex(el, lst, fn) {
+ for (var a = 0; a < lst.length; a++) fn(el, lst[a]);
+ };
+ mplex(
+ NiceScrollArray.prototype, ['show', 'hide', 'toggle', 'onResize', 'resize', 'remove', 'stop', 'doScrollPos'],
+ function(e, n) {
+ e[n] = function() {
+ var args = arguments;
+ return this.each(function() {
+ this[n].apply(this, args);
+ });
+ };
+ }
+ );
+
+ jQuery.fn.getNiceScroll = function(index) {
+ if (typeof index == "undefined") {
+ return new NiceScrollArray(this);
+ } else {
+ var nice = this[index] && $.data(this[index], '__nicescroll') || false;
+ return nice;
+ }
+ };
+
+ jQuery.extend(jQuery.expr[':'], {
+ nicescroll: function(a) {
+ return ($.data(a, '__nicescroll')) ? true : false;
+ }
+ });
+
+ $.fn.niceScroll = function(wrapper, opt) {
+ if (typeof opt == "undefined") {
+ if ((typeof wrapper == "object") && !("jquery" in wrapper)) {
+ opt = wrapper;
+ wrapper = false;
+ }
+ }
+ opt = $.extend({},opt); // cloning
+ var ret = new NiceScrollArray();
+ if (typeof opt == "undefined") opt = {};
+
+ if (wrapper || false) {
+ opt.doc = $(wrapper);
+ opt.win = $(this);
+ }
+ var docundef = !("doc" in opt);
+ if (!docundef && !("win" in opt)) opt.win = $(this);
+
+ this.each(function() {
+ var nice = $(this).data('__nicescroll') || false;
+ if (!nice) {
+ opt.doc = (docundef) ? $(this) : opt.doc;
+ nice = new NiceScrollClass(opt, $(this));
+ $(this).data('__nicescroll', nice);
+ }
+ ret.push(nice);
+ });
+ return (ret.length == 1) ? ret[0] : ret;
+ };
+
+ window.NiceScroll = {
+ getjQuery: function() {
+ return jQuery
+ }
+ };
+
+ if (!$.nicescroll) {
+ $.nicescroll = new NiceScrollArray();
+ $.nicescroll.options = _globaloptions;
+ }
+
+})); \ No newline at end of file
diff --git a/vendor/assets/javascripts/jquery.nicescroll.min.js b/vendor/assets/javascripts/jquery.nicescroll.min.js
deleted file mode 100644
index 5440b6a0da0..00000000000
--- a/vendor/assets/javascripts/jquery.nicescroll.min.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/* jquery.nicescroll 3.6.0 InuYaksa*2014 MIT http://nicescroll.areaaperta.com */(function(f){"function"===typeof define&&define.amd?define(["jquery"],f):f(jQuery)})(function(f){var y=!1,D=!1,N=0,O=2E3,x=0,H=["webkit","ms","moz","o"],s=window.requestAnimationFrame||!1,t=window.cancelAnimationFrame||!1;if(!s)for(var P in H){var E=H[P];s||(s=window[E+"RequestAnimationFrame"]);t||(t=window[E+"CancelAnimationFrame"]||window[E+"CancelRequestAnimationFrame"])}var v=window.MutationObserver||window.WebKitMutationObserver||!1,I={zindex:"auto",cursoropacitymin:0,cursoropacitymax:1,cursorcolor:"#424242",
-cursorwidth:"5px",cursorborder:"1px solid #fff",cursorborderradius:"5px",scrollspeed:60,mousescrollstep:24,touchbehavior:!1,hwacceleration:!0,usetransition:!0,boxzoom:!1,dblclickzoom:!0,gesturezoom:!0,grabcursorenabled:!0,autohidemode:!0,background:"",iframeautoresize:!0,cursorminheight:32,preservenativescrolling:!0,railoffset:!1,railhoffset:!1,bouncescroll:!0,spacebarenabled:!0,railpadding:{top:0,right:0,left:0,bottom:0},disableoutline:!0,horizrailenabled:!0,railalign:"right",railvalign:"bottom",
-enabletranslate3d:!0,enablemousewheel:!0,enablekeyboard:!0,smoothscroll:!0,sensitiverail:!0,enablemouselockapi:!0,cursorfixedheight:!1,directionlockdeadzone:6,hidecursordelay:400,nativeparentscrolling:!0,enablescrollonselection:!0,overflowx:!0,overflowy:!0,cursordragspeed:.3,rtlmode:"auto",cursordragontouch:!1,oneaxismousemode:"auto",scriptpath:function(){var f=document.getElementsByTagName("script"),f=f[f.length-1].src.split("?")[0];return 0<f.split("/").length?f.split("/").slice(0,-1).join("/")+
-"/":""}(),preventmultitouchscrolling:!0},F=!1,Q=function(){if(F)return F;var f=document.createElement("DIV"),c=f.style,h=navigator.userAgent,m=navigator.platform,d={haspointerlock:"pointerLockElement"in document||"webkitPointerLockElement"in document||"mozPointerLockElement"in document};d.isopera="opera"in window;d.isopera12=d.isopera&&"getUserMedia"in navigator;d.isoperamini="[object OperaMini]"===Object.prototype.toString.call(window.operamini);d.isie="all"in document&&"attachEvent"in f&&!d.isopera;
-d.isieold=d.isie&&!("msInterpolationMode"in c);d.isie7=d.isie&&!d.isieold&&(!("documentMode"in document)||7==document.documentMode);d.isie8=d.isie&&"documentMode"in document&&8==document.documentMode;d.isie9=d.isie&&"performance"in window&&9<=document.documentMode;d.isie10=d.isie&&"performance"in window&&10==document.documentMode;d.isie11="msRequestFullscreen"in f&&11<=document.documentMode;d.isie9mobile=/iemobile.9/i.test(h);d.isie9mobile&&(d.isie9=!1);d.isie7mobile=!d.isie9mobile&&d.isie7&&/iemobile/i.test(h);
-d.ismozilla="MozAppearance"in c;d.iswebkit="WebkitAppearance"in c;d.ischrome="chrome"in window;d.ischrome22=d.ischrome&&d.haspointerlock;d.ischrome26=d.ischrome&&"transition"in c;d.cantouch="ontouchstart"in document.documentElement||"ontouchstart"in window;d.hasmstouch=window.MSPointerEvent||!1;d.hasw3ctouch=window.PointerEvent||!1;d.ismac=/^mac$/i.test(m);d.isios=d.cantouch&&/iphone|ipad|ipod/i.test(m);d.isios4=d.isios&&!("seal"in Object);d.isios7=d.isios&&"webkitHidden"in document;d.isandroid=/android/i.test(h);
-d.haseventlistener="addEventListener"in f;d.trstyle=!1;d.hastransform=!1;d.hastranslate3d=!1;d.transitionstyle=!1;d.hastransition=!1;d.transitionend=!1;m=["transform","msTransform","webkitTransform","MozTransform","OTransform"];for(h=0;h<m.length;h++)if("undefined"!=typeof c[m[h]]){d.trstyle=m[h];break}d.hastransform=!!d.trstyle;d.hastransform&&(c[d.trstyle]="translate3d(1px,2px,3px)",d.hastranslate3d=/translate3d/.test(c[d.trstyle]));d.transitionstyle=!1;d.prefixstyle="";d.transitionend=!1;for(var m=
-"transition webkitTransition msTransition MozTransition OTransition OTransition KhtmlTransition".split(" "),n=" -webkit- -ms- -moz- -o- -o -khtml-".split(" "),p="transitionend webkitTransitionEnd msTransitionEnd transitionend otransitionend oTransitionEnd KhtmlTransitionEnd".split(" "),h=0;h<m.length;h++)if(m[h]in c){d.transitionstyle=m[h];d.prefixstyle=n[h];d.transitionend=p[h];break}d.ischrome26&&(d.prefixstyle=n[1]);d.hastransition=d.transitionstyle;a:{h=["-webkit-grab","-moz-grab","grab"];if(d.ischrome&&
-!d.ischrome22||d.isie)h=[];for(m=0;m<h.length;m++)if(n=h[m],c.cursor=n,c.cursor==n){c=n;break a}c="url(//mail.google.com/mail/images/2/openhand.cur),n-resize"}d.cursorgrabvalue=c;d.hasmousecapture="setCapture"in f;d.hasMutationObserver=!1!==v;return F=d},R=function(k,c){function h(){var b=a.doc.css(e.trstyle);return b&&"matrix"==b.substr(0,6)?b.replace(/^.*\((.*)\)$/g,"$1").replace(/px/g,"").split(/, +/):!1}function m(){var b=a.win;if("zIndex"in b)return b.zIndex();for(;0<b.length&&9!=b[0].nodeType;){var g=
-b.css("zIndex");if(!isNaN(g)&&0!=g)return parseInt(g);b=b.parent()}return!1}function d(b,g,q){g=b.css(g);b=parseFloat(g);return isNaN(b)?(b=w[g]||0,q=3==b?q?a.win.outerHeight()-a.win.innerHeight():a.win.outerWidth()-a.win.innerWidth():1,a.isie8&&b&&(b+=1),q?b:0):b}function n(b,g,q,c){a._bind(b,g,function(a){a=a?a:window.event;var c={original:a,target:a.target||a.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==a.type?0:1,deltaX:0,deltaZ:0,preventDefault:function(){a.preventDefault?a.preventDefault():
-a.returnValue=!1;return!1},stopImmediatePropagation:function(){a.stopImmediatePropagation?a.stopImmediatePropagation():a.cancelBubble=!0}};"mousewheel"==g?(c.deltaY=-.025*a.wheelDelta,a.wheelDeltaX&&(c.deltaX=-.025*a.wheelDeltaX)):c.deltaY=a.detail;return q.call(b,c)},c)}function p(b,g,c){var d,e;0==b.deltaMode?(d=-Math.floor(a.opt.mousescrollstep/54*b.deltaX),e=-Math.floor(a.opt.mousescrollstep/54*b.deltaY)):1==b.deltaMode&&(d=-Math.floor(b.deltaX*a.opt.mousescrollstep),e=-Math.floor(b.deltaY*a.opt.mousescrollstep));
-g&&a.opt.oneaxismousemode&&0==d&&e&&(d=e,e=0,c&&(0>d?a.getScrollLeft()>=a.page.maxw:0>=a.getScrollLeft())&&(e=d,d=0));d&&(a.scrollmom&&a.scrollmom.stop(),a.lastdeltax+=d,a.debounced("mousewheelx",function(){var b=a.lastdeltax;a.lastdeltax=0;a.rail.drag||a.doScrollLeftBy(b)},15));if(e){if(a.opt.nativeparentscrolling&&c&&!a.ispage&&!a.zoomactive)if(0>e){if(a.getScrollTop()>=a.page.maxh)return!0}else if(0>=a.getScrollTop())return!0;a.scrollmom&&a.scrollmom.stop();a.lastdeltay+=e;a.debounced("mousewheely",
-function(){var b=a.lastdeltay;a.lastdeltay=0;a.rail.drag||a.doScrollBy(b)},15)}b.stopImmediatePropagation();return b.preventDefault()}var a=this;this.version="3.6.0";this.name="nicescroll";this.me=c;this.opt={doc:f("body"),win:!1};f.extend(this.opt,I);this.opt.snapbackspeed=80;if(k)for(var G in a.opt)"undefined"!=typeof k[G]&&(a.opt[G]=k[G]);this.iddoc=(this.doc=a.opt.doc)&&this.doc[0]?this.doc[0].id||"":"";this.ispage=/^BODY|HTML/.test(a.opt.win?a.opt.win[0].nodeName:this.doc[0].nodeName);this.haswrapper=
-!1!==a.opt.win;this.win=a.opt.win||(this.ispage?f(window):this.doc);this.docscroll=this.ispage&&!this.haswrapper?f(window):this.win;this.body=f("body");this.iframe=this.isfixed=this.viewport=!1;this.isiframe="IFRAME"==this.doc[0].nodeName&&"IFRAME"==this.win[0].nodeName;this.istextarea="TEXTAREA"==this.win[0].nodeName;this.forcescreen=!1;this.canshowonmouseevent="scroll"!=a.opt.autohidemode;this.page=this.view=this.onzoomout=this.onzoomin=this.onscrollcancel=this.onscrollend=this.onscrollstart=this.onclick=
-this.ongesturezoom=this.onkeypress=this.onmousewheel=this.onmousemove=this.onmouseup=this.onmousedown=!1;this.scroll={x:0,y:0};this.scrollratio={x:0,y:0};this.cursorheight=20;this.scrollvaluemax=0;this.isrtlmode="auto"==this.opt.rtlmode?"rtl"==(this.win[0]==window?this.body:this.win).css("direction"):!0===this.opt.rtlmode;this.observerbody=this.observerremover=this.observer=this.scrollmom=this.scrollrunning=!1;do this.id="ascrail"+O++;while(document.getElementById(this.id));this.hasmousefocus=this.hasfocus=
-this.zoomactive=this.zoom=this.selectiondrag=this.cursorfreezed=this.cursor=this.rail=!1;this.visibility=!0;this.hidden=this.locked=this.railslocked=!1;this.cursoractive=!0;this.wheelprevented=!1;this.overflowx=a.opt.overflowx;this.overflowy=a.opt.overflowy;this.nativescrollingarea=!1;this.checkarea=0;this.events=[];this.saved={};this.delaylist={};this.synclist={};this.lastdeltay=this.lastdeltax=0;this.detected=Q();var e=f.extend({},this.detected);this.ishwscroll=(this.canhwscroll=e.hastransform&&
-a.opt.hwacceleration)&&a.haswrapper;this.hasreversehr=this.isrtlmode&&!e.iswebkit;this.istouchcapable=!1;!e.cantouch||e.isios||e.isandroid||!e.iswebkit&&!e.ismozilla||(this.istouchcapable=!0,e.cantouch=!1);a.opt.enablemouselockapi||(e.hasmousecapture=!1,e.haspointerlock=!1);this.debounced=function(b,g,c){var d=a.delaylist[b];a.delaylist[b]=g;d||setTimeout(function(){var g=a.delaylist[b];a.delaylist[b]=!1;g.call(a)},c)};var r=!1;this.synched=function(b,g){a.synclist[b]=g;(function(){r||(s(function(){r=
-!1;for(var b in a.synclist){var g=a.synclist[b];g&&g.call(a);a.synclist[b]=!1}}),r=!0)})();return b};this.unsynched=function(b){a.synclist[b]&&(a.synclist[b]=!1)};this.css=function(b,g){for(var c in g)a.saved.css.push([b,c,b.css(c)]),b.css(c,g[c])};this.scrollTop=function(b){return"undefined"==typeof b?a.getScrollTop():a.setScrollTop(b)};this.scrollLeft=function(b){return"undefined"==typeof b?a.getScrollLeft():a.setScrollLeft(b)};var A=function(a,g,c,d,e,f,h){this.st=a;this.ed=g;this.spd=c;this.p1=
-d||0;this.p2=e||1;this.p3=f||0;this.p4=h||1;this.ts=(new Date).getTime();this.df=this.ed-this.st};A.prototype={B2:function(a){return 3*a*a*(1-a)},B3:function(a){return 3*a*(1-a)*(1-a)},B4:function(a){return(1-a)*(1-a)*(1-a)},getNow:function(){var a=1-((new Date).getTime()-this.ts)/this.spd,g=this.B2(a)+this.B3(a)+this.B4(a);return 0>a?this.ed:this.st+Math.round(this.df*g)},update:function(a,g){this.st=this.getNow();this.ed=a;this.spd=g;this.ts=(new Date).getTime();this.df=this.ed-this.st;return this}};
-if(this.ishwscroll){this.doc.translate={x:0,y:0,tx:"0px",ty:"0px"};e.hastranslate3d&&e.isios&&this.doc.css("-webkit-backface-visibility","hidden");this.getScrollTop=function(b){if(!b){if(b=h())return 16==b.length?-b[13]:-b[5];if(a.timerscroll&&a.timerscroll.bz)return a.timerscroll.bz.getNow()}return a.doc.translate.y};this.getScrollLeft=function(b){if(!b){if(b=h())return 16==b.length?-b[12]:-b[4];if(a.timerscroll&&a.timerscroll.bh)return a.timerscroll.bh.getNow()}return a.doc.translate.x};this.notifyScrollEvent=
-function(a){var g=document.createEvent("UIEvents");g.initUIEvent("scroll",!1,!0,window,1);g.niceevent=!0;a.dispatchEvent(g)};var K=this.isrtlmode?1:-1;e.hastranslate3d&&a.opt.enabletranslate3d?(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate3d("+a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate3d("+
-a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])}):(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])})}else this.getScrollTop=
-function(){return a.docscroll.scrollTop()},this.setScrollTop=function(b){return a.docscroll.scrollTop(b)},this.getScrollLeft=function(){return a.detected.ismozilla&&a.isrtlmode?Math.abs(a.docscroll.scrollLeft()):a.docscroll.scrollLeft()},this.setScrollLeft=function(b){return a.docscroll.scrollLeft(a.detected.ismozilla&&a.isrtlmode?-b:b)};this.getTarget=function(a){return a?a.target?a.target:a.srcElement?a.srcElement:!1:!1};this.hasParent=function(a,g){if(!a)return!1;for(var c=a.target||a.srcElement||
-a||!1;c&&c.id!=g;)c=c.parentNode||!1;return!1!==c};var w={thin:1,medium:3,thick:5};this.getDocumentScrollOffset=function(){return{top:window.pageYOffset||document.documentElement.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft}};this.getOffset=function(){if(a.isfixed){var b=a.win.offset(),g=a.getDocumentScrollOffset();b.top-=g.top;b.left-=g.left;return b}b=a.win.offset();if(!a.viewport)return b;g=a.viewport.offset();return{top:b.top-g.top,left:b.left-g.left}};this.updateScrollBar=
-function(b){if(a.ishwscroll)a.rail.css({height:a.win.innerHeight()-(a.opt.railpadding.top+a.opt.railpadding.bottom)}),a.railh&&a.railh.css({width:a.win.innerWidth()-(a.opt.railpadding.left+a.opt.railpadding.right)});else{var g=a.getOffset(),c=g.top,e=g.left-(a.opt.railpadding.left+a.opt.railpadding.right),c=c+d(a.win,"border-top-width",!0),e=e+(a.rail.align?a.win.outerWidth()-d(a.win,"border-right-width")-a.rail.width:d(a.win,"border-left-width")),f=a.opt.railoffset;f&&(f.top&&(c+=f.top),a.rail.align&&
-f.left&&(e+=f.left));a.railslocked||a.rail.css({top:c,left:e,height:(b?b.h:a.win.innerHeight())-(a.opt.railpadding.top+a.opt.railpadding.bottom)});a.zoom&&a.zoom.css({top:c+1,left:1==a.rail.align?e-20:e+a.rail.width+4});if(a.railh&&!a.railslocked){c=g.top;e=g.left;if(f=a.opt.railhoffset)f.top&&(c+=f.top),f.left&&(e+=f.left);b=a.railh.align?c+d(a.win,"border-top-width",!0)+a.win.innerHeight()-a.railh.height:c+d(a.win,"border-top-width",!0);e+=d(a.win,"border-left-width");a.railh.css({top:b-(a.opt.railpadding.top+
-a.opt.railpadding.bottom),left:e,width:a.railh.width})}}};this.doRailClick=function(b,g,c){var e;a.railslocked||(a.cancelEvent(b),g?(g=c?a.doScrollLeft:a.doScrollTop,e=c?(b.pageX-a.railh.offset().left-a.cursorwidth/2)*a.scrollratio.x:(b.pageY-a.rail.offset().top-a.cursorheight/2)*a.scrollratio.y,g(e)):(g=c?a.doScrollLeftBy:a.doScrollBy,e=c?a.scroll.x:a.scroll.y,b=c?b.pageX-a.railh.offset().left:b.pageY-a.rail.offset().top,c=c?a.view.w:a.view.h,g(e>=b?c:-c)))};a.hasanimationframe=s;a.hascancelanimationframe=
-t;a.hasanimationframe?a.hascancelanimationframe||(t=function(){a.cancelAnimationFrame=!0}):(s=function(a){return setTimeout(a,15-Math.floor(+new Date/1E3)%16)},t=clearInterval);this.init=function(){a.saved.css=[];if(e.isie7mobile||e.isoperamini)return!0;e.hasmstouch&&a.css(a.ispage?f("html"):a.win,{"-ms-touch-action":"none"});a.zindex="auto";a.zindex=a.ispage||"auto"!=a.opt.zindex?a.opt.zindex:m()||"auto";!a.ispage&&"auto"!=a.zindex&&a.zindex>x&&(x=a.zindex);a.isie&&0==a.zindex&&"auto"==a.opt.zindex&&
-(a.zindex="auto");if(!a.ispage||!e.cantouch&&!e.isieold&&!e.isie9mobile){var b=a.docscroll;a.ispage&&(b=a.haswrapper?a.win:a.doc);e.isie9mobile||a.css(b,{"overflow-y":"hidden"});a.ispage&&e.isie7&&("BODY"==a.doc[0].nodeName?a.css(f("html"),{"overflow-y":"hidden"}):"HTML"==a.doc[0].nodeName&&a.css(f("body"),{"overflow-y":"hidden"}));!e.isios||a.ispage||a.haswrapper||a.css(f("body"),{"-webkit-overflow-scrolling":"touch"});var g=f(document.createElement("div"));g.css({position:"relative",top:0,"float":"right",
-width:a.opt.cursorwidth,height:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius});g.hborder=parseFloat(g.outerHeight()-g.innerHeight());g.addClass("nicescroll-cursors");a.cursor=g;var c=f(document.createElement("div"));c.attr("id",a.id);c.addClass("nicescroll-rails nicescroll-rails-vr");var d,h,k=["left","right",
-"top","bottom"],J;for(J in k)h=k[J],(d=a.opt.railpadding[h])?c.css("padding-"+h,d+"px"):a.opt.railpadding[h]=0;c.append(g);c.width=Math.max(parseFloat(a.opt.cursorwidth),g.outerWidth());c.css({width:c.width+"px",zIndex:a.zindex,background:a.opt.background,cursor:"default"});c.visibility=!0;c.scrollable=!0;c.align="left"==a.opt.railalign?0:1;a.rail=c;g=a.rail.drag=!1;!a.opt.boxzoom||a.ispage||e.isieold||(g=document.createElement("div"),a.bind(g,"click",a.doZoom),a.bind(g,"mouseenter",function(){a.zoom.css("opacity",
-a.opt.cursoropacitymax)}),a.bind(g,"mouseleave",function(){a.zoom.css("opacity",a.opt.cursoropacitymin)}),a.zoom=f(g),a.zoom.css({cursor:"pointer","z-index":a.zindex,backgroundImage:"url("+a.opt.scriptpath+"zoomico.png)",height:18,width:18,backgroundPosition:"0px 0px"}),a.opt.dblclickzoom&&a.bind(a.win,"dblclick",a.doZoom),e.cantouch&&a.opt.gesturezoom&&(a.ongesturezoom=function(b){1.5<b.scale&&a.doZoomIn(b);.8>b.scale&&a.doZoomOut(b);return a.cancelEvent(b)},a.bind(a.win,"gestureend",a.ongesturezoom)));
-a.railh=!1;var l;a.opt.horizrailenabled&&(a.css(b,{"overflow-x":"hidden"}),g=f(document.createElement("div")),g.css({position:"absolute",top:0,height:a.opt.cursorwidth,width:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius}),e.isieold&&g.css({overflow:"hidden"}),g.wborder=parseFloat(g.outerWidth()-g.innerWidth()),
-g.addClass("nicescroll-cursors"),a.cursorh=g,l=f(document.createElement("div")),l.attr("id",a.id+"-hr"),l.addClass("nicescroll-rails nicescroll-rails-hr"),l.height=Math.max(parseFloat(a.opt.cursorwidth),g.outerHeight()),l.css({height:l.height+"px",zIndex:a.zindex,background:a.opt.background}),l.append(g),l.visibility=!0,l.scrollable=!0,l.align="top"==a.opt.railvalign?0:1,a.railh=l,a.railh.drag=!1);a.ispage?(c.css({position:"fixed",top:"0px",height:"100%"}),c.align?c.css({right:"0px"}):c.css({left:"0px"}),
-a.body.append(c),a.railh&&(l.css({position:"fixed",left:"0px",width:"100%"}),l.align?l.css({bottom:"0px"}):l.css({top:"0px"}),a.body.append(l))):(a.ishwscroll?("static"==a.win.css("position")&&a.css(a.win,{position:"relative"}),b="HTML"==a.win[0].nodeName?a.body:a.win,f(b).scrollTop(0).scrollLeft(0),a.zoom&&(a.zoom.css({position:"absolute",top:1,right:0,"margin-right":c.width+4}),b.append(a.zoom)),c.css({position:"absolute",top:0}),c.align?c.css({right:0}):c.css({left:0}),b.append(c),l&&(l.css({position:"absolute",
-left:0,bottom:0}),l.align?l.css({bottom:0}):l.css({top:0}),b.append(l))):(a.isfixed="fixed"==a.win.css("position"),b=a.isfixed?"fixed":"absolute",a.isfixed||(a.viewport=a.getViewport(a.win[0])),a.viewport&&(a.body=a.viewport,0==/fixed|absolute/.test(a.viewport.css("position"))&&a.css(a.viewport,{position:"relative"})),c.css({position:b}),a.zoom&&a.zoom.css({position:b}),a.updateScrollBar(),a.body.append(c),a.zoom&&a.body.append(a.zoom),a.railh&&(l.css({position:b}),a.body.append(l))),e.isios&&a.css(a.win,
-{"-webkit-tap-highlight-color":"rgba(0,0,0,0)","-webkit-touch-callout":"none"}),e.isie&&a.opt.disableoutline&&a.win.attr("hideFocus","true"),e.iswebkit&&a.opt.disableoutline&&a.win.css({outline:"none"}));!1===a.opt.autohidemode?(a.autohidedom=!1,a.rail.css({opacity:a.opt.cursoropacitymax}),a.railh&&a.railh.css({opacity:a.opt.cursoropacitymax})):!0===a.opt.autohidemode||"leave"===a.opt.autohidemode?(a.autohidedom=f().add(a.rail),e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursor)),a.railh&&(a.autohidedom=
-a.autohidedom.add(a.railh)),a.railh&&e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"scroll"==a.opt.autohidemode?(a.autohidedom=f().add(a.rail),a.railh&&(a.autohidedom=a.autohidedom.add(a.railh))):"cursor"==a.opt.autohidemode?(a.autohidedom=f().add(a.cursor),a.railh&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"hidden"==a.opt.autohidemode&&(a.autohidedom=!1,a.hide(),a.railslocked=!1);if(e.isie9mobile)a.scrollmom=new L(a),a.onmangotouch=function(){var b=a.getScrollTop(),c=a.getScrollLeft();
-if(b==a.scrollmom.lastscrolly&&c==a.scrollmom.lastscrollx)return!0;var g=b-a.mangotouch.sy,e=c-a.mangotouch.sx;if(0!=Math.round(Math.sqrt(Math.pow(e,2)+Math.pow(g,2)))){var d=0>g?-1:1,f=0>e?-1:1,q=+new Date;a.mangotouch.lazy&&clearTimeout(a.mangotouch.lazy);80<q-a.mangotouch.tm||a.mangotouch.dry!=d||a.mangotouch.drx!=f?(a.scrollmom.stop(),a.scrollmom.reset(c,b),a.mangotouch.sy=b,a.mangotouch.ly=b,a.mangotouch.sx=c,a.mangotouch.lx=c,a.mangotouch.dry=d,a.mangotouch.drx=f,a.mangotouch.tm=q):(a.scrollmom.stop(),
-a.scrollmom.update(a.mangotouch.sx-e,a.mangotouch.sy-g),a.mangotouch.tm=q,g=Math.max(Math.abs(a.mangotouch.ly-b),Math.abs(a.mangotouch.lx-c)),a.mangotouch.ly=b,a.mangotouch.lx=c,2<g&&(a.mangotouch.lazy=setTimeout(function(){a.mangotouch.lazy=!1;a.mangotouch.dry=0;a.mangotouch.drx=0;a.mangotouch.tm=0;a.scrollmom.doMomentum(30)},100)))}},c=a.getScrollTop(),l=a.getScrollLeft(),a.mangotouch={sy:c,ly:c,dry:0,sx:l,lx:l,drx:0,lazy:!1,tm:0},a.bind(a.docscroll,"scroll",a.onmangotouch);else{if(e.cantouch||
-a.istouchcapable||a.opt.touchbehavior||e.hasmstouch){a.scrollmom=new L(a);a.ontouchstart=function(b){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;a.hasmoving=!1;if(!a.railslocked){var c;if(e.hasmstouch)for(c=b.target?b.target:!1;c;){var g=f(c).getNiceScroll();if(0<g.length&&g[0].me==a.me)break;if(0<g.length)return!1;if("DIV"==c.nodeName&&c.id==a.id)break;c=c.parentNode?c.parentNode:!1}a.cancelScroll();if((c=a.getTarget(b))&&/INPUT/i.test(c.nodeName)&&/range/i.test(c.type))return a.stopPropagation(b);
-!("clientX"in b)&&"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);a.forcescreen&&(g=b,b={original:b.original?b.original:b},b.clientX=g.screenX,b.clientY=g.screenY);a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,st:a.getScrollTop(),sl:a.getScrollLeft(),pt:2,dl:!1};if(a.ispage||!a.opt.directionlockdeadzone)a.rail.drag.dl="f";else{var g=f(window).width(),d=f(window).height(),q=Math.max(document.body.scrollWidth,document.documentElement.scrollWidth),
-h=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight),d=Math.max(0,h-d),g=Math.max(0,q-g);a.rail.drag.ck=!a.rail.scrollable&&a.railh.scrollable?0<d?"v":!1:a.rail.scrollable&&!a.railh.scrollable?0<g?"h":!1:!1;a.rail.drag.ck||(a.rail.drag.dl="f")}a.opt.touchbehavior&&a.isiframe&&e.isie&&(g=a.win.position(),a.rail.drag.x+=g.left,a.rail.drag.y+=g.top);a.hasmoving=!1;a.lastmouseup=!1;a.scrollmom.reset(b.clientX,b.clientY);if(!e.cantouch&&!this.istouchcapable&&!b.pointerType){if(!c||
-!/INPUT|SELECT|TEXTAREA/i.test(c.nodeName))return!a.ispage&&e.hasmousecapture&&c.setCapture(),a.opt.touchbehavior?(c.onclick&&!c._onclick&&(c._onclick=c.onclick,c.onclick=function(b){if(a.hasmoving)return!1;c._onclick.call(this,b)}),a.cancelEvent(b)):a.stopPropagation(b);/SUBMIT|CANCEL|BUTTON/i.test(f(c).attr("type"))&&(pc={tg:c,click:!1},a.preventclick=pc)}}};a.ontouchend=function(b){if(!a.rail.drag)return!0;if(2==a.rail.drag.pt){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;
-a.scrollmom.doMomentum();a.rail.drag=!1;if(a.hasmoving&&(a.lastmouseup=!0,a.hideCursor(),e.hasmousecapture&&document.releaseCapture(),!e.cantouch))return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmouseup(b)};var n=a.opt.touchbehavior&&a.isiframe&&!e.hasmousecapture;a.ontouchmove=function(b,c){if(!a.rail.drag||b.targetTouches&&a.opt.preventmultitouchscrolling&&1<b.targetTouches.length||b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;if(2==a.rail.drag.pt){if(e.cantouch&&
-e.isios&&"undefined"==typeof b.original)return!0;a.hasmoving=!0;a.preventclick&&!a.preventclick.click&&(a.preventclick.click=a.preventclick.tg.onclick||!1,a.preventclick.tg.onclick=a.onpreventclick);b=f.extend({original:b},b);"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);if(a.forcescreen){var g=b;b={original:b.original?b.original:b};b.clientX=g.screenX;b.clientY=g.screenY}var d,g=d=0;n&&!c&&(d=a.win.position(),g=-d.left,d=-d.top);var q=b.clientY+
-d;d=q-a.rail.drag.y;var h=b.clientX+g,u=h-a.rail.drag.x,k=a.rail.drag.st-d;a.ishwscroll&&a.opt.bouncescroll?0>k?k=Math.round(k/2):k>a.page.maxh&&(k=a.page.maxh+Math.round((k-a.page.maxh)/2)):(0>k&&(q=k=0),k>a.page.maxh&&(k=a.page.maxh,q=0));var l;a.railh&&a.railh.scrollable&&(l=a.isrtlmode?u-a.rail.drag.sl:a.rail.drag.sl-u,a.ishwscroll&&a.opt.bouncescroll?0>l?l=Math.round(l/2):l>a.page.maxw&&(l=a.page.maxw+Math.round((l-a.page.maxw)/2)):(0>l&&(h=l=0),l>a.page.maxw&&(l=a.page.maxw,h=0)));g=!1;if(a.rail.drag.dl)g=
-!0,"v"==a.rail.drag.dl?l=a.rail.drag.sl:"h"==a.rail.drag.dl&&(k=a.rail.drag.st);else{d=Math.abs(d);var u=Math.abs(u),z=a.opt.directionlockdeadzone;if("v"==a.rail.drag.ck){if(d>z&&u<=.3*d)return a.rail.drag=!1,!0;u>z&&(a.rail.drag.dl="f",f("body").scrollTop(f("body").scrollTop()))}else if("h"==a.rail.drag.ck){if(u>z&&d<=.3*u)return a.rail.drag=!1,!0;d>z&&(a.rail.drag.dl="f",f("body").scrollLeft(f("body").scrollLeft()))}}a.synched("touchmove",function(){a.rail.drag&&2==a.rail.drag.pt&&(a.prepareTransition&&
-a.prepareTransition(0),a.rail.scrollable&&a.setScrollTop(k),a.scrollmom.update(h,q),a.railh&&a.railh.scrollable?(a.setScrollLeft(l),a.showCursor(k,l)):a.showCursor(k),e.isie10&&document.selection.clear())});e.ischrome&&a.istouchcapable&&(g=!1);if(g)return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmousemove(b)}}a.onmousedown=function(b,c){if(!a.rail.drag||1==a.rail.drag.pt){if(a.railslocked)return a.cancelEvent(b);a.cancelScroll();a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,
-pt:1,hr:!!c};var g=a.getTarget(b);!a.ispage&&e.hasmousecapture&&g.setCapture();a.isiframe&&!e.hasmousecapture&&(a.saved.csspointerevents=a.doc.css("pointer-events"),a.css(a.doc,{"pointer-events":"none"}));a.hasmoving=!1;return a.cancelEvent(b)}};a.onmouseup=function(b){if(a.rail.drag){if(1!=a.rail.drag.pt)return!0;e.hasmousecapture&&document.releaseCapture();a.isiframe&&!e.hasmousecapture&&a.doc.css("pointer-events",a.saved.csspointerevents);a.rail.drag=!1;a.hasmoving&&a.triggerScrollEnd();return a.cancelEvent(b)}};
-a.onmousemove=function(b){if(a.rail.drag&&1==a.rail.drag.pt){if(e.ischrome&&0==b.which)return a.onmouseup(b);a.cursorfreezed=!0;a.hasmoving=!0;if(a.rail.drag.hr){a.scroll.x=a.rail.drag.sx+(b.clientX-a.rail.drag.x);0>a.scroll.x&&(a.scroll.x=0);var c=a.scrollvaluemaxw;a.scroll.x>c&&(a.scroll.x=c)}else a.scroll.y=a.rail.drag.sy+(b.clientY-a.rail.drag.y),0>a.scroll.y&&(a.scroll.y=0),c=a.scrollvaluemax,a.scroll.y>c&&(a.scroll.y=c);a.synched("mousemove",function(){a.rail.drag&&1==a.rail.drag.pt&&(a.showCursor(),
-a.rail.drag.hr?a.hasreversehr?a.doScrollLeft(a.scrollvaluemaxw-Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollLeft(Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollTop(Math.round(a.scroll.y*a.scrollratio.y),a.opt.cursordragspeed))});return a.cancelEvent(b)}};if(e.cantouch||a.opt.touchbehavior)a.onpreventclick=function(b){if(a.preventclick)return a.preventclick.tg.onclick=a.preventclick.click,a.preventclick=!1,a.cancelEvent(b)},a.bind(a.win,"mousedown",
-a.ontouchstart),a.onclick=e.isios?!1:function(b){return a.lastmouseup?(a.lastmouseup=!1,a.cancelEvent(b)):!0},a.opt.grabcursorenabled&&e.cursorgrabvalue&&(a.css(a.ispage?a.doc:a.win,{cursor:e.cursorgrabvalue}),a.css(a.rail,{cursor:e.cursorgrabvalue}));else{var p=function(b){if(a.selectiondrag){if(b){var c=a.win.outerHeight();b=b.pageY-a.selectiondrag.top;0<b&&b<c&&(b=0);b>=c&&(b-=c);a.selectiondrag.df=b}0!=a.selectiondrag.df&&(a.doScrollBy(2*-Math.floor(a.selectiondrag.df/6)),a.debounced("doselectionscroll",
-function(){p()},50))}};a.hasTextSelected="getSelection"in document?function(){return 0<document.getSelection().rangeCount}:"selection"in document?function(){return"None"!=document.selection.type}:function(){return!1};a.onselectionstart=function(b){a.ispage||(a.selectiondrag=a.win.offset())};a.onselectionend=function(b){a.selectiondrag=!1};a.onselectiondrag=function(b){a.selectiondrag&&a.hasTextSelected()&&a.debounced("selectionscroll",function(){p(b)},250)}}e.hasw3ctouch?(a.css(a.rail,{"touch-action":"none"}),
-a.css(a.cursor,{"touch-action":"none"}),a.bind(a.win,"pointerdown",a.ontouchstart),a.bind(document,"pointerup",a.ontouchend),a.bind(document,"pointermove",a.ontouchmove)):e.hasmstouch?(a.css(a.rail,{"-ms-touch-action":"none"}),a.css(a.cursor,{"-ms-touch-action":"none"}),a.bind(a.win,"MSPointerDown",a.ontouchstart),a.bind(document,"MSPointerUp",a.ontouchend),a.bind(document,"MSPointerMove",a.ontouchmove),a.bind(a.cursor,"MSGestureHold",function(a){a.preventDefault()}),a.bind(a.cursor,"contextmenu",
-function(a){a.preventDefault()})):this.istouchcapable&&(a.bind(a.win,"touchstart",a.ontouchstart),a.bind(document,"touchend",a.ontouchend),a.bind(document,"touchcancel",a.ontouchend),a.bind(document,"touchmove",a.ontouchmove));if(a.opt.cursordragontouch||!e.cantouch&&!a.opt.touchbehavior)a.rail.css({cursor:"default"}),a.railh&&a.railh.css({cursor:"default"}),a.jqbind(a.rail,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),
-a.jqbind(a.rail,"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.rail,"click",function(b){a.doRailClick(b,!1,!1)}),a.bind(a.rail,"dblclick",function(b){a.doRailClick(b,!0,!1)}),a.bind(a.cursor,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursor,"dblclick",function(b){a.cancelEvent(b)})),a.railh&&(a.jqbind(a.railh,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.railh,
-"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.railh,"click",function(b){a.doRailClick(b,!1,!0)}),a.bind(a.railh,"dblclick",function(b){a.doRailClick(b,!0,!0)}),a.bind(a.cursorh,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursorh,"dblclick",function(b){a.cancelEvent(b)})));e.cantouch||a.opt.touchbehavior?(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.ontouchend),a.bind(document,"mousemove",a.ontouchmove),a.onclick&&a.bind(document,"click",
-a.onclick),a.opt.cursordragontouch&&(a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.cursorh&&a.bind(a.cursorh,"mousedown",function(b){a.onmousedown(b,!0)}),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onmouseup))):(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.onmouseup),a.bind(document,"mousemove",a.onmousemove),a.onclick&&a.bind(document,"click",a.onclick),a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.railh&&(a.bind(a.cursorh,
-"mousedown",function(b){a.onmousedown(b,!0)}),a.bind(a.cursorh,"mouseup",a.onmouseup)),!a.ispage&&a.opt.enablescrollonselection&&(a.bind(a.win[0],"mousedown",a.onselectionstart),a.bind(document,"mouseup",a.onselectionend),a.bind(a.cursor,"mouseup",a.onselectionend),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onselectionend),a.bind(document,"mousemove",a.onselectiondrag)),a.zoom&&(a.jqbind(a.zoom,"mouseenter",function(){a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.zoom,"mouseleave",
-function(){a.rail.active=!1;a.rail.drag||a.hideCursor()})));a.opt.enablemousewheel&&(a.isiframe||a.bind(e.isie&&a.ispage?document:a.win,"mousewheel",a.onmousewheel),a.bind(a.rail,"mousewheel",a.onmousewheel),a.railh&&a.bind(a.railh,"mousewheel",a.onmousewheelhr));a.ispage||e.cantouch||/HTML|^BODY/.test(a.win[0].nodeName)||(a.win.attr("tabindex")||a.win.attr({tabindex:N++}),a.jqbind(a.win,"focus",function(b){y=a.getTarget(b).id||!0;a.hasfocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,
-"blur",function(b){y=!1;a.hasfocus=!1}),a.jqbind(a.win,"mouseenter",function(b){D=a.getTarget(b).id||!0;a.hasmousefocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,"mouseleave",function(){D=!1;a.hasmousefocus=!1;a.rail.drag||a.hideCursor()}))}a.onkeypress=function(b){if(a.railslocked&&0==a.page.maxh)return!0;b=b?b:window.e;var c=a.getTarget(b);if(c&&/INPUT|TEXTAREA|SELECT|OPTION/.test(c.nodeName)&&(!c.getAttribute("type")&&!c.type||!/submit|button|cancel/i.tp)||f(c).attr("contenteditable"))return!0;
-if(a.hasfocus||a.hasmousefocus&&!y||a.ispage&&!y&&!D){c=b.keyCode;if(a.railslocked&&27!=c)return a.cancelEvent(b);var g=b.ctrlKey||!1,d=b.shiftKey||!1,e=!1;switch(c){case 38:case 63233:a.doScrollBy(72);e=!0;break;case 40:case 63235:a.doScrollBy(-72);e=!0;break;case 37:case 63232:a.railh&&(g?a.doScrollLeft(0):a.doScrollLeftBy(72),e=!0);break;case 39:case 63234:a.railh&&(g?a.doScrollLeft(a.page.maxw):a.doScrollLeftBy(-72),e=!0);break;case 33:case 63276:a.doScrollBy(a.view.h);e=!0;break;case 34:case 63277:a.doScrollBy(-a.view.h);
-e=!0;break;case 36:case 63273:a.railh&&g?a.doScrollPos(0,0):a.doScrollTo(0);e=!0;break;case 35:case 63275:a.railh&&g?a.doScrollPos(a.page.maxw,a.page.maxh):a.doScrollTo(a.page.maxh);e=!0;break;case 32:a.opt.spacebarenabled&&(d?a.doScrollBy(a.view.h):a.doScrollBy(-a.view.h),e=!0);break;case 27:a.zoomactive&&(a.doZoom(),e=!0)}if(e)return a.cancelEvent(b)}};a.opt.enablekeyboard&&a.bind(document,e.isopera&&!e.isopera12?"keypress":"keydown",a.onkeypress);a.bind(document,"keydown",function(b){b.ctrlKey&&
-(a.wheelprevented=!0)});a.bind(document,"keyup",function(b){b.ctrlKey||(a.wheelprevented=!1)});a.bind(window,"blur",function(b){a.wheelprevented=!1});a.bind(window,"resize",a.lazyResize);a.bind(window,"orientationchange",a.lazyResize);a.bind(window,"load",a.lazyResize);if(e.ischrome&&!a.ispage&&!a.haswrapper){var r=a.win.attr("style"),c=parseFloat(a.win.css("width"))+1;a.win.css("width",c);a.synched("chromefix",function(){a.win.attr("style",r)})}a.onAttributeChange=function(b){a.lazyResize(a.isieold?
-250:30)};!1!==v&&(a.observerbody=new v(function(b){b.forEach(function(b){if("attributes"==b.type)return f("body").hasClass("modal-open")?a.hide():a.show()});if(document.body.scrollHeight!=a.page.maxh)return a.lazyResize(30)}),a.observerbody.observe(document.body,{childList:!0,subtree:!0,characterData:!1,attributes:!0,attributeFilter:["class"]}));a.ispage||a.haswrapper||(!1!==v?(a.observer=new v(function(b){b.forEach(a.onAttributeChange)}),a.observer.observe(a.win[0],{childList:!0,characterData:!1,
-attributes:!0,subtree:!1}),a.observerremover=new v(function(b){b.forEach(function(b){if(0<b.removedNodes.length)for(var c in b.removedNodes)if(a&&b.removedNodes[c]==a.win[0])return a.remove()})}),a.observerremover.observe(a.win[0].parentNode,{childList:!0,characterData:!1,attributes:!1,subtree:!1})):(a.bind(a.win,e.isie&&!e.isie9?"propertychange":"DOMAttrModified",a.onAttributeChange),e.isie9&&a.win[0].attachEvent("onpropertychange",a.onAttributeChange),a.bind(a.win,"DOMNodeRemoved",function(b){b.target==
-a.win[0]&&a.remove()})));!a.ispage&&a.opt.boxzoom&&a.bind(window,"resize",a.resizeZoom);a.istextarea&&a.bind(a.win,"mouseup",a.lazyResize);a.lazyResize(30)}if("IFRAME"==this.doc[0].nodeName){var M=function(){a.iframexd=!1;var b;try{b="contentDocument"in this?this.contentDocument:this.contentWindow.document}catch(c){a.iframexd=!0,b=!1}if(a.iframexd)return"console"in window&&console.log("NiceScroll error: policy restriced iframe"),!0;a.forcescreen=!0;a.isiframe&&(a.iframe={doc:f(b),html:a.doc.contents().find("html")[0],
-body:a.doc.contents().find("body")[0]},a.getContentSize=function(){return{w:Math.max(a.iframe.html.scrollWidth,a.iframe.body.scrollWidth),h:Math.max(a.iframe.html.scrollHeight,a.iframe.body.scrollHeight)}},a.docscroll=f(a.iframe.body));if(!e.isios&&a.opt.iframeautoresize&&!a.isiframe){a.win.scrollTop(0);a.doc.height("");var g=Math.max(b.getElementsByTagName("html")[0].scrollHeight,b.body.scrollHeight);a.doc.height(g)}a.lazyResize(30);e.isie7&&a.css(f(a.iframe.html),{"overflow-y":"hidden"});a.css(f(a.iframe.body),
-{"overflow-y":"hidden"});e.isios&&a.haswrapper&&a.css(f(b.body),{"-webkit-transform":"translate3d(0,0,0)"});"contentWindow"in this?a.bind(this.contentWindow,"scroll",a.onscroll):a.bind(b,"scroll",a.onscroll);a.opt.enablemousewheel&&a.bind(b,"mousewheel",a.onmousewheel);a.opt.enablekeyboard&&a.bind(b,e.isopera?"keypress":"keydown",a.onkeypress);if(e.cantouch||a.opt.touchbehavior)a.bind(b,"mousedown",a.ontouchstart),a.bind(b,"mousemove",function(b){return a.ontouchmove(b,!0)}),a.opt.grabcursorenabled&&
-e.cursorgrabvalue&&a.css(f(b.body),{cursor:e.cursorgrabvalue});a.bind(b,"mouseup",a.ontouchend);a.zoom&&(a.opt.dblclickzoom&&a.bind(b,"dblclick",a.doZoom),a.ongesturezoom&&a.bind(b,"gestureend",a.ongesturezoom))};this.doc[0].readyState&&"complete"==this.doc[0].readyState&&setTimeout(function(){M.call(a.doc[0],!1)},500);a.bind(this.doc,"load",M)}};this.showCursor=function(b,c){a.cursortimeout&&(clearTimeout(a.cursortimeout),a.cursortimeout=0);if(a.rail){a.autohidedom&&(a.autohidedom.stop().css({opacity:a.opt.cursoropacitymax}),
-a.cursoractive=!0);a.rail.drag&&1==a.rail.drag.pt||("undefined"!=typeof b&&!1!==b&&(a.scroll.y=Math.round(1*b/a.scrollratio.y)),"undefined"!=typeof c&&(a.scroll.x=Math.round(1*c/a.scrollratio.x)));a.cursor.css({height:a.cursorheight,top:a.scroll.y});if(a.cursorh){var d=a.hasreversehr?a.scrollvaluemaxw-a.scroll.x:a.scroll.x;!a.rail.align&&a.rail.visibility?a.cursorh.css({width:a.cursorwidth,left:d+a.rail.width}):a.cursorh.css({width:a.cursorwidth,left:d});a.cursoractive=!0}a.zoom&&a.zoom.stop().css({opacity:a.opt.cursoropacitymax})}};
-this.hideCursor=function(b){a.cursortimeout||!a.rail||!a.autohidedom||a.hasmousefocus&&"leave"==a.opt.autohidemode||(a.cursortimeout=setTimeout(function(){a.rail.active&&a.showonmouseevent||(a.autohidedom.stop().animate({opacity:a.opt.cursoropacitymin}),a.zoom&&a.zoom.stop().animate({opacity:a.opt.cursoropacitymin}),a.cursoractive=!1);a.cursortimeout=0},b||a.opt.hidecursordelay))};this.noticeCursor=function(b,c,d){a.showCursor(c,d);a.rail.active||a.hideCursor(b)};this.getContentSize=a.ispage?function(){return{w:Math.max(document.body.scrollWidth,
-document.documentElement.scrollWidth),h:Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}}:a.haswrapper?function(){return{w:a.doc.outerWidth()+parseInt(a.win.css("paddingLeft"))+parseInt(a.win.css("paddingRight")),h:a.doc.outerHeight()+parseInt(a.win.css("paddingTop"))+parseInt(a.win.css("paddingBottom"))}}:function(){return{w:a.docscroll[0].scrollWidth,h:a.docscroll[0].scrollHeight}};this.onResize=function(b,c){if(!a||!a.win)return!1;if(!a.haswrapper&&!a.ispage){if("none"==
-a.win.css("display"))return a.visibility&&a.hideRail().hideRailHr(),!1;a.hidden||a.visibility||a.showRail().showRailHr()}var d=a.page.maxh,e=a.page.maxw,f=a.view.h,h=a.view.w;a.view={w:a.ispage?a.win.width():parseInt(a.win[0].clientWidth),h:a.ispage?a.win.height():parseInt(a.win[0].clientHeight)};a.page=c?c:a.getContentSize();a.page.maxh=Math.max(0,a.page.h-a.view.h);a.page.maxw=Math.max(0,a.page.w-a.view.w);if(a.page.maxh==d&&a.page.maxw==e&&a.view.w==h&&a.view.h==f){if(a.ispage)return a;d=a.win.offset();
-if(a.lastposition&&(e=a.lastposition,e.top==d.top&&e.left==d.left))return a;a.lastposition=d}0==a.page.maxh?(a.hideRail(),a.scrollvaluemax=0,a.scroll.y=0,a.scrollratio.y=0,a.cursorheight=0,a.setScrollTop(0),a.rail.scrollable=!1):(a.page.maxh-=a.opt.railpadding.top+a.opt.railpadding.bottom,a.rail.scrollable=!0);0==a.page.maxw?(a.hideRailHr(),a.scrollvaluemaxw=0,a.scroll.x=0,a.scrollratio.x=0,a.cursorwidth=0,a.setScrollLeft(0),a.railh.scrollable=!1):(a.page.maxw-=a.opt.railpadding.left+a.opt.railpadding.right,
-a.railh.scrollable=!0);a.railslocked=a.locked||0==a.page.maxh&&0==a.page.maxw;if(a.railslocked)return a.ispage||a.updateScrollBar(a.view),!1;a.hidden||a.visibility?a.hidden||a.railh.visibility||a.showRailHr():a.showRail().showRailHr();a.istextarea&&a.win.css("resize")&&"none"!=a.win.css("resize")&&(a.view.h-=20);a.cursorheight=Math.min(a.view.h,Math.round(a.view.h/a.page.h*a.view.h));a.cursorheight=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorheight);a.cursorwidth=
-Math.min(a.view.w,Math.round(a.view.w/a.page.w*a.view.w));a.cursorwidth=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorwidth);a.scrollvaluemax=a.view.h-a.cursorheight-a.cursor.hborder-(a.opt.railpadding.top+a.opt.railpadding.bottom);a.railh&&(a.railh.width=0<a.page.maxh?a.view.w-a.rail.width:a.view.w,a.scrollvaluemaxw=a.railh.width-a.cursorwidth-a.cursorh.wborder-(a.opt.railpadding.left+a.opt.railpadding.right));a.ispage||a.updateScrollBar(a.view);a.scrollratio=
-{x:a.page.maxw/a.scrollvaluemaxw,y:a.page.maxh/a.scrollvaluemax};a.getScrollTop()>a.page.maxh?a.doScrollTop(a.page.maxh):(a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y)),a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)),a.cursoractive&&a.noticeCursor());a.scroll.y&&0==a.getScrollTop()&&a.doScrollTo(Math.floor(a.scroll.y*a.scrollratio.y));return a};this.resize=a.onResize;this.lazyResize=function(b){b=isNaN(b)?30:b;a.debounced("resize",a.resize,b);return a};this.jqbind=function(b,
-c,d){a.events.push({e:b,n:c,f:d,q:!0});f(b).bind(c,d)};this.bind=function(b,c,d,f){var h="jquery"in b?b[0]:b;"mousewheel"==c?window.addEventListener||"onwheel"in document?a._bind(h,"wheel",d,f||!1):(b="undefined"!=typeof document.onmousewheel?"mousewheel":"DOMMouseScroll",n(h,b,d,f||!1),"DOMMouseScroll"==b&&n(h,"MozMousePixelScroll",d,f||!1)):h.addEventListener?(e.cantouch&&/mouseup|mousedown|mousemove/.test(c)&&a._bind(h,"mousedown"==c?"touchstart":"mouseup"==c?"touchend":"touchmove",function(a){if(a.touches){if(2>
-a.touches.length){var b=a.touches.length?a.touches[0]:a;b.original=a;d.call(this,b)}}else a.changedTouches&&(b=a.changedTouches[0],b.original=a,d.call(this,b))},f||!1),a._bind(h,c,d,f||!1),e.cantouch&&"mouseup"==c&&a._bind(h,"touchcancel",d,f||!1)):a._bind(h,c,function(b){(b=b||window.event||!1)&&b.srcElement&&(b.target=b.srcElement);"pageY"in b||(b.pageX=b.clientX+document.documentElement.scrollLeft,b.pageY=b.clientY+document.documentElement.scrollTop);return!1===d.call(h,b)||!1===f?a.cancelEvent(b):
-!0})};e.haseventlistener?(this._bind=function(b,c,d,e){a.events.push({e:b,n:c,f:d,b:e,q:!1});b.addEventListener(c,d,e||!1)},this.cancelEvent=function(a){if(!a)return!1;a=a.original?a.original:a;a.preventDefault();a.stopPropagation();a.preventManipulation&&a.preventManipulation();return!1},this.stopPropagation=function(a){if(!a)return!1;a=a.original?a.original:a;a.stopPropagation();return!1},this._unbind=function(a,c,d,e){a.removeEventListener(c,d,e)}):(this._bind=function(b,c,d,e){a.events.push({e:b,
-n:c,f:d,b:e,q:!1});b.attachEvent?b.attachEvent("on"+c,d):b["on"+c]=d},this.cancelEvent=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;a.cancel=!0;return a.returnValue=!1},this.stopPropagation=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;return!1},this._unbind=function(a,c,d,e){a.detachEvent?a.detachEvent("on"+c,d):a["on"+c]=!1});this.unbindAll=function(){for(var b=0;b<a.events.length;b++){var c=a.events[b];c.q?c.e.unbind(c.n,c.f):a._unbind(c.e,c.n,c.f,c.b)}};this.showRail=
-function(){0==a.page.maxh||!a.ispage&&"none"==a.win.css("display")||(a.visibility=!0,a.rail.visibility=!0,a.rail.css("display","block"));return a};this.showRailHr=function(){if(!a.railh)return a;0==a.page.maxw||!a.ispage&&"none"==a.win.css("display")||(a.railh.visibility=!0,a.railh.css("display","block"));return a};this.hideRail=function(){a.visibility=!1;a.rail.visibility=!1;a.rail.css("display","none");return a};this.hideRailHr=function(){if(!a.railh)return a;a.railh.visibility=!1;a.railh.css("display",
-"none");return a};this.show=function(){a.hidden=!1;a.railslocked=!1;return a.showRail().showRailHr()};this.hide=function(){a.hidden=!0;a.railslocked=!0;return a.hideRail().hideRailHr()};this.toggle=function(){return a.hidden?a.show():a.hide()};this.remove=function(){a.stop();a.cursortimeout&&clearTimeout(a.cursortimeout);a.doZoomOut();a.unbindAll();e.isie9&&a.win[0].detachEvent("onpropertychange",a.onAttributeChange);!1!==a.observer&&a.observer.disconnect();!1!==a.observerremover&&a.observerremover.disconnect();
-!1!==a.observerbody&&a.observerbody.disconnect();a.events=null;a.cursor&&a.cursor.remove();a.cursorh&&a.cursorh.remove();a.rail&&a.rail.remove();a.railh&&a.railh.remove();a.zoom&&a.zoom.remove();for(var b=0;b<a.saved.css.length;b++){var c=a.saved.css[b];c[0].css(c[1],"undefined"==typeof c[2]?"":c[2])}a.saved=!1;a.me.data("__nicescroll","");var d=f.nicescroll;d.each(function(b){if(this&&this.id===a.id){delete d[b];for(var c=++b;c<d.length;c++,b++)d[b]=d[c];d.length--;d.length&&delete d[d.length]}});
-for(var h in a)a[h]=null,delete a[h];a=null};this.scrollstart=function(b){this.onscrollstart=b;return a};this.scrollend=function(b){this.onscrollend=b;return a};this.scrollcancel=function(b){this.onscrollcancel=b;return a};this.zoomin=function(b){this.onzoomin=b;return a};this.zoomout=function(b){this.onzoomout=b;return a};this.isScrollable=function(a){a=a.target?a.target:a;if("OPTION"==a.nodeName)return!0;for(;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a),c=c.css("overflowY")||c.css("overflowX")||
-c.css("overflow")||"";if(/scroll|auto/.test(c))return a.clientHeight!=a.scrollHeight;a=a.parentNode?a.parentNode:!1}return!1};this.getViewport=function(a){for(a=a&&a.parentNode?a.parentNode:!1;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a);if(/fixed|absolute/.test(c.css("position")))return c;var d=c.css("overflowY")||c.css("overflowX")||c.css("overflow")||"";if(/scroll|auto/.test(d)&&a.clientHeight!=a.scrollHeight||0<c.getNiceScroll().length)return c;a=a.parentNode?a.parentNode:!1}return!1};
-this.triggerScrollEnd=function(){if(a.onscrollend){var b=a.getScrollLeft(),c=a.getScrollTop();a.onscrollend.call(a,{type:"scrollend",current:{x:b,y:c},end:{x:b,y:c}})}};this.onmousewheel=function(b){if(!a.wheelprevented){if(a.railslocked)return a.debounced("checkunlock",a.resize,250),!0;if(a.rail.drag)return a.cancelEvent(b);"auto"==a.opt.oneaxismousemode&&0!=b.deltaX&&(a.opt.oneaxismousemode=!1);if(a.opt.oneaxismousemode&&0==b.deltaX&&!a.rail.scrollable)return a.railh&&a.railh.scrollable?a.onmousewheelhr(b):
-!0;var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;if(a.nativescrollingarea)return!0;if(b=p(b,!1,d))a.checkarea=0;return b}};this.onmousewheelhr=function(b){if(!a.wheelprevented){if(a.railslocked||!a.railh.scrollable)return!0;if(a.rail.drag)return a.cancelEvent(b);var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;return a.nativescrollingarea?
-!0:a.railslocked?a.cancelEvent(b):p(b,!0,d)}};this.stop=function(){a.cancelScroll();a.scrollmon&&a.scrollmon.stop();a.cursorfreezed=!1;a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.noticeCursor();return a};this.getTransitionSpeed=function(b){var c=Math.round(10*a.opt.scrollspeed);b=Math.min(c,Math.round(b/20*a.opt.scrollspeed));return 20<b?b:0};a.opt.smoothscroll?a.ishwscroll&&e.hastransition&&a.opt.usetransition&&a.opt.smoothscroll?(this.prepareTransition=function(b,c){var d=c?20<
-b?b:0:a.getTransitionSpeed(b),f=d?e.prefixstyle+"transform "+d+"ms ease-out":"";a.lasttransitionstyle&&a.lasttransitionstyle==f||(a.lasttransitionstyle=f,a.doc.css(e.transitionstyle,f));return d},this.doScrollLeft=function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-
-f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();0==a.opt.bouncescroll&&(0>c?c=0:c>a.page.maxh&&(c=a.page.maxh),0>b?b=0:b>a.page.maxw&&(b=a.page.maxw));if(a.scrollrunning&&b==a.newscrollx&&c==a.newscrolly)return!1;a.newscrolly=c;a.newscrollx=b;a.newscrollspeed=d||!1;if(a.timer)return!1;a.timer=setTimeout(function(){var d=a.getScrollTop(),f=a.getScrollLeft(),h,k;h=b-f;k=c-d;h=Math.round(Math.sqrt(Math.pow(h,2)+Math.pow(k,2)));h=a.newscrollspeed&&1<a.newscrollspeed?a.newscrollspeed:a.getTransitionSpeed(h);
-a.newscrollspeed&&1>=a.newscrollspeed&&(h*=a.newscrollspeed);a.prepareTransition(h,!0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);0<h&&(!a.scrollrunning&&a.onscrollstart&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:f,y:d},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:h}),e.transitionend?a.scrollendtrapped||(a.scrollendtrapped=!0,a.bind(a.doc,e.transitionend,a.onScrollTransitionEnd,!1)):(a.scrollendtrapped&&clearTimeout(a.scrollendtrapped),a.scrollendtrapped=
-setTimeout(a.onScrollTransitionEnd,h)),a.timerscroll={bz:new A(d,a.newscrolly,h,0,0,.58,1),bh:new A(f,a.newscrollx,h,0,0,.58,1)},a.cursorfreezed||(a.timerscroll.tm=setInterval(function(){a.showCursor(a.getScrollTop(),a.getScrollLeft())},60)));a.synched("doScroll-set",function(){a.timer=0;a.scrollendtrapped&&(a.scrollrunning=!0);a.setScrollTop(a.newscrolly);a.setScrollLeft(a.newscrollx);if(!a.scrollendtrapped)a.onScrollTransitionEnd()})},50)},this.cancelScroll=function(){if(!a.scrollendtrapped)return!0;
-var b=a.getScrollTop(),c=a.getScrollLeft();a.scrollrunning=!1;e.transitionend||clearTimeout(e.transitionend);a.scrollendtrapped=!1;a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);a.prepareTransition(0);a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;a.cursorfreezed=!1;a.showCursor(b,c);return a},this.onScrollTransitionEnd=function(){a.scrollendtrapped&&a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);
-a.scrollendtrapped=!1;a.prepareTransition(0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;var b=a.getScrollTop(),c=a.getScrollLeft();a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.noticeCursor(!1,b,c);a.cursorfreezed=!1;0>b?b=0:b>a.page.maxh&&(b=a.page.maxh);0>c?c=0:c>a.page.maxw&&(c=a.page.maxw);if(b!=a.newscrolly||c!=a.newscrollx)return a.doScrollPos(c,b,a.opt.snapbackspeed);a.onscrollend&&a.scrollrunning&&a.triggerScrollEnd();a.scrollrunning=!1}):(this.doScrollLeft=
-function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){function e(){if(a.cancelAnimationFrame)return!0;a.scrollrunning=!0;if(n=1-n)return a.timer=s(e)||1;var b=0,c,d,g=d=a.getScrollTop();if(a.dst.ay){g=a.bzscroll?a.dst.py+a.bzscroll.getNow()*a.dst.ay:a.newscrolly;c=g-d;if(0>c&&g<a.newscrolly||0<c&&g>a.newscrolly)g=a.newscrolly;
-a.setScrollTop(g);g==a.newscrolly&&(b=1)}else b=1;d=c=a.getScrollLeft();if(a.dst.ax){d=a.bzscroll?a.dst.px+a.bzscroll.getNow()*a.dst.ax:a.newscrollx;c=d-c;if(0>c&&d<a.newscrollx||0<c&&d>a.newscrollx)d=a.newscrollx;a.setScrollLeft(d);d==a.newscrollx&&(b+=1)}else b+=1;2==b?(a.timer=0,a.cursorfreezed=!1,a.bzscroll=!1,a.scrollrunning=!1,0>g?g=0:g>a.page.maxh&&(g=a.page.maxh),0>d?d=0:d>a.page.maxw&&(d=a.page.maxw),d!=a.newscrollx||g!=a.newscrolly?a.doScrollPos(d,g):a.onscrollend&&a.triggerScrollEnd()):
-a.timer=s(e)||1}c="undefined"==typeof c||!1===c?a.getScrollTop(!0):c;if(a.timer&&a.newscrolly==c&&a.newscrollx==b)return!0;a.timer&&t(a.timer);a.timer=0;var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();a.newscrolly=c;a.newscrollx=b;a.bouncescroll&&a.rail.visibility||(0>a.newscrolly?a.newscrolly=0:a.newscrolly>a.page.maxh&&(a.newscrolly=a.page.maxh));a.bouncescroll&&a.railh.visibility||(0>a.newscrollx?a.newscrollx=0:a.newscrollx>a.page.maxw&&
-(a.newscrollx=a.page.maxw));a.dst={};a.dst.x=b-h;a.dst.y=c-f;a.dst.px=h;a.dst.py=f;var k=Math.round(Math.sqrt(Math.pow(a.dst.x,2)+Math.pow(a.dst.y,2)));a.dst.ax=a.dst.x/k;a.dst.ay=a.dst.y/k;var l=0,m=k;0==a.dst.x?(l=f,m=c,a.dst.ay=1,a.dst.py=0):0==a.dst.y&&(l=h,m=b,a.dst.ax=1,a.dst.px=0);k=a.getTransitionSpeed(k);d&&1>=d&&(k*=d);a.bzscroll=0<k?a.bzscroll?a.bzscroll.update(m,k):new A(l,m,k,0,1,0,1):!1;if(!a.timer){(f==a.page.maxh&&c>=a.page.maxh||h==a.page.maxw&&b>=a.page.maxw)&&a.checkContentSize();
-var n=1;a.cancelAnimationFrame=!1;a.timer=1;a.onscrollstart&&!a.scrollrunning&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:h,y:f},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:k});e();(f==a.page.maxh&&c>=f||h==a.page.maxw&&b>=h)&&a.checkContentSize();a.noticeCursor()}},this.cancelScroll=function(){a.timer&&t(a.timer);a.timer=0;a.bzscroll=!1;a.scrollrunning=!1;return a}):(this.doScrollLeft=function(b,c){var d=a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,
-c){var d=a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var e=b>a.page.maxw?a.page.maxw:b;0>e&&(e=0);var f=c>a.page.maxh?a.page.maxh:c;0>f&&(f=0);a.synched("scroll",function(){a.setScrollTop(f);a.setScrollLeft(e)})},this.cancelScroll=function(){});this.doScrollBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.y-b)*a.scrollratio.y):(a.timer?a.newscrolly:a.getScrollTop(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.h/2);d<-e?d=-e:d>a.page.maxh+e&&(d=a.page.maxh+e)}a.cursorfreezed=
-!1;e=a.getScrollTop(!0);if(0>d&&0>=e)return a.noticeCursor();if(d>a.page.maxh&&e>=a.page.maxh)return a.checkContentSize(),a.noticeCursor();a.doScrollTop(d)};this.doScrollLeftBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.x-b)*a.scrollratio.x):(a.timer?a.newscrollx:a.getScrollLeft(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.w/2);d<-e?d=-e:d>a.page.maxw+e&&(d=a.page.maxw+e)}a.cursorfreezed=!1;e=a.getScrollLeft(!0);if(0>d&&0>=e||d>a.page.maxw&&e>=a.page.maxw)return a.noticeCursor();a.doScrollLeft(d)};
-this.doScrollTo=function(b,c){c&&Math.round(b*a.scrollratio.y);a.cursorfreezed=!1;a.doScrollTop(b)};this.checkContentSize=function(){var b=a.getContentSize();b.h==a.page.h&&b.w==a.page.w||a.resize(!1,b)};a.onscroll=function(b){a.rail.drag||a.cursorfreezed||a.synched("scroll",function(){a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.railh&&(a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)));a.noticeCursor()})};a.bind(a.docscroll,"scroll",a.onscroll);this.doZoomIn=function(b){if(!a.zoomactive){a.zoomactive=
-!0;a.zoomrestore={style:{}};var c="position top left zIndex backgroundColor marginTop marginBottom marginLeft marginRight".split(" "),d=a.win[0].style,h;for(h in c){var k=c[h];a.zoomrestore.style[k]="undefined"!=typeof d[k]?d[k]:""}a.zoomrestore.style.width=a.win.css("width");a.zoomrestore.style.height=a.win.css("height");a.zoomrestore.padding={w:a.win.outerWidth()-a.win.width(),h:a.win.outerHeight()-a.win.height()};e.isios4&&(a.zoomrestore.scrollTop=f(window).scrollTop(),f(window).scrollTop(0));
-a.win.css({position:e.isios4?"absolute":"fixed",top:0,left:0,"z-index":x+100,margin:"0px"});c=a.win.css("backgroundColor");(""==c||/transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(c))&&a.win.css("backgroundColor","#fff");a.rail.css({"z-index":x+101});a.zoom.css({"z-index":x+102});a.zoom.css("backgroundPosition","0px -18px");a.resizeZoom();a.onzoomin&&a.onzoomin.call(a);return a.cancelEvent(b)}};this.doZoomOut=function(b){if(a.zoomactive)return a.zoomactive=!1,a.win.css("margin",""),a.win.css(a.zoomrestore.style),
-e.isios4&&f(window).scrollTop(a.zoomrestore.scrollTop),a.rail.css({"z-index":a.zindex}),a.zoom.css({"z-index":a.zindex}),a.zoomrestore=!1,a.zoom.css("backgroundPosition","0px 0px"),a.onResize(),a.onzoomout&&a.onzoomout.call(a),a.cancelEvent(b)};this.doZoom=function(b){return a.zoomactive?a.doZoomOut(b):a.doZoomIn(b)};this.resizeZoom=function(){if(a.zoomactive){var b=a.getScrollTop();a.win.css({width:f(window).width()-a.zoomrestore.padding.w+"px",height:f(window).height()-a.zoomrestore.padding.h+"px"});
-a.onResize();a.setScrollTop(Math.min(a.page.maxh,b))}};this.init();f.nicescroll.push(this)},L=function(f){var c=this;this.nc=f;this.steptime=this.lasttime=this.speedy=this.speedx=this.lasty=this.lastx=0;this.snapy=this.snapx=!1;this.demuly=this.demulx=0;this.lastscrolly=this.lastscrollx=-1;this.timer=this.chky=this.chkx=0;this.time=function(){return+new Date};this.reset=function(f,k){c.stop();var d=c.time();c.steptime=0;c.lasttime=d;c.speedx=0;c.speedy=0;c.lastx=f;c.lasty=k;c.lastscrollx=-1;c.lastscrolly=
--1};this.update=function(f,k){var d=c.time();c.steptime=d-c.lasttime;c.lasttime=d;var d=k-c.lasty,n=f-c.lastx,p=c.nc.getScrollTop(),a=c.nc.getScrollLeft(),p=p+d,a=a+n;c.snapx=0>a||a>c.nc.page.maxw;c.snapy=0>p||p>c.nc.page.maxh;c.speedx=n;c.speedy=d;c.lastx=f;c.lasty=k};this.stop=function(){c.nc.unsynched("domomentum2d");c.timer&&clearTimeout(c.timer);c.timer=0;c.lastscrollx=-1;c.lastscrolly=-1};this.doSnapy=function(f,k){var d=!1;0>k?(k=0,d=!0):k>c.nc.page.maxh&&(k=c.nc.page.maxh,d=!0);0>f?(f=0,d=
-!0):f>c.nc.page.maxw&&(f=c.nc.page.maxw,d=!0);d?c.nc.doScrollPos(f,k,c.nc.opt.snapbackspeed):c.nc.triggerScrollEnd()};this.doMomentum=function(f){var k=c.time(),d=f?k+f:c.lasttime;f=c.nc.getScrollLeft();var n=c.nc.getScrollTop(),p=c.nc.page.maxh,a=c.nc.page.maxw;c.speedx=0<a?Math.min(60,c.speedx):0;c.speedy=0<p?Math.min(60,c.speedy):0;d=d&&60>=k-d;if(0>n||n>p||0>f||f>a)d=!1;f=c.speedx&&d?c.speedx:!1;if(c.speedy&&d&&c.speedy||f){var s=Math.max(16,c.steptime);50<s&&(f=s/50,c.speedx*=f,c.speedy*=f,s=
-50);c.demulxy=0;c.lastscrollx=c.nc.getScrollLeft();c.chkx=c.lastscrollx;c.lastscrolly=c.nc.getScrollTop();c.chky=c.lastscrolly;var e=c.lastscrollx,r=c.lastscrolly,t=function(){var d=600<c.time()-k?.04:.02;c.speedx&&(e=Math.floor(c.lastscrollx-c.speedx*(1-c.demulxy)),c.lastscrollx=e,0>e||e>a)&&(d=.1);c.speedy&&(r=Math.floor(c.lastscrolly-c.speedy*(1-c.demulxy)),c.lastscrolly=r,0>r||r>p)&&(d=.1);c.demulxy=Math.min(1,c.demulxy+d);c.nc.synched("domomentum2d",function(){c.speedx&&(c.nc.getScrollLeft()!=
-c.chkx&&c.stop(),c.chkx=e,c.nc.setScrollLeft(e));c.speedy&&(c.nc.getScrollTop()!=c.chky&&c.stop(),c.chky=r,c.nc.setScrollTop(r));c.timer||(c.nc.hideCursor(),c.doSnapy(e,r))});1>c.demulxy?c.timer=setTimeout(t,s):(c.stop(),c.nc.hideCursor(),c.doSnapy(e,r))};t()}else c.doSnapy(c.nc.getScrollLeft(),c.nc.getScrollTop())}},w=f.fn.scrollTop;f.cssHooks.pageYOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollTop():w.call(k)},set:function(k,c){var h=f.data(k,"__nicescroll")||
-!1;h&&h.ishwscroll?h.setScrollTop(parseInt(c)):w.call(k,c);return this}};f.fn.scrollTop=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollTop():w.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollTop(parseInt(k)):w.call(f(this),k)})};var B=f.fn.scrollLeft;f.cssHooks.pageXOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollLeft():B.call(k)},
-set:function(k,c){var h=f.data(k,"__nicescroll")||!1;h&&h.ishwscroll?h.setScrollLeft(parseInt(c)):B.call(k,c);return this}};f.fn.scrollLeft=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollLeft():B.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollLeft(parseInt(k)):B.call(f(this),k)})};var C=function(k){var c=this;this.length=0;this.name="nicescrollarray";this.each=function(d){for(var f=
-0,h=0;f<c.length;f++)d.call(c[f],h++);return c};this.push=function(d){c[c.length]=d;c.length++};this.eq=function(d){return c[d]};if(k)for(var h=0;h<k.length;h++){var m=f.data(k[h],"__nicescroll")||!1;m&&(this[this.length]=m,this.length++)}return this};(function(f,c,h){for(var m=0;m<c.length;m++)h(f,c[m])})(C.prototype,"show hide toggle onResize resize remove stop doScrollPos".split(" "),function(f,c){f[c]=function(){var f=arguments;return this.each(function(){this[c].apply(this,f)})}});f.fn.getNiceScroll=
-function(k){return"undefined"==typeof k?new C(this):this[k]&&f.data(this[k],"__nicescroll")||!1};f.extend(f.expr[":"],{nicescroll:function(k){return f.data(k,"__nicescroll")?!0:!1}});f.fn.niceScroll=function(k,c){"undefined"!=typeof c||"object"!=typeof k||"jquery"in k||(c=k,k=!1);c=f.extend({},c);var h=new C;"undefined"==typeof c&&(c={});k&&(c.doc=f(k),c.win=f(this));var m=!("doc"in c);m||"win"in c||(c.win=f(this));this.each(function(){var d=f(this).data("__nicescroll")||!1;d||(c.doc=m?f(this):c.doc,
-d=new R(c,f(this)),f(this).data("__nicescroll",d));h.push(d)});return 1==h.length?h[0]:h};window.NiceScroll={getjQuery:function(){return f}};f.nicescroll||(f.nicescroll=new C,f.nicescroll.options=I)});