Commit ad68bc63 authored by James Lopez's avatar James Lopez

Merge branches 'feature/project-export' and 'feature/project-import' of...

Merge branches 'feature/project-export' and 'feature/project-import' of gitlab.com:gitlab-org/gitlab-ce into feature/project-import

# Conflicts:
#	app/models/project.rb
#	db/schema.rb
#	lib/gitlab/import_export/import_export_reader.rb
parents 41c06c31 cc322603

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

We’re closing our issue tracker on GitHub so we can focus on the GitLab.com project and respond to issues more quickly.
We encourage you to open an issue on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues). You can log into GitLab.com using your GitHub account.
Thank you for taking the time to contribute back to GitLab!
Please open a merge request [on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests), we look forward to reviewing your contribution! You can log into GitLab.com using your GitHub account.
......@@ -4,46 +4,46 @@
.bundle
.chef
.directory
.envrc
.gitlab_shell_secret
/.envrc
/.gitlab_shell_secret
.idea
.rbenv-version
/.rbenv-version
.rbx/
.ruby-gemset
.ruby-version
.rvmrc
/.ruby-gemset
/.ruby-version
/.rvmrc
.sass-cache/
.secret
.vagrant
.byebug_history
Vagrantfile
backups/*
config/aws.yml
config/database.yml
config/gitlab.yml
config/gitlab_ci.yml
config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb
config/initializers/relative_url.rb
config/resque.yml
config/unicorn.rb
config/secrets.yml
config/sidekiq.yml
coverage/*
db/*.sqlite3
db/*.sqlite3-journal
db/data.yml
doc/code/*
dump.rdb
log/*.log*
nohup.out
public/assets/
public/uploads.*
public/uploads/
shared/artifacts/
rails_best_practices_output.html
/.secret
/.vagrant
/.byebug_history
/Vagrantfile
/backups/*
/config/aws.yml
/config/database.yml
/config/gitlab.yml
/config/gitlab_ci.yml
/config/initializers/rack_attack.rb
/config/initializers/smtp_settings.rb
/config/initializers/relative_url.rb
/config/resque.yml
/config/unicorn.rb
/config/secrets.yml
/config/sidekiq.yml
/coverage/*
/db/*.sqlite3
/db/*.sqlite3-journal
/db/data.yml
/doc/code/*
/dump.rdb
/log/*.log*
/nohup.out
/public/assets/
/public/uploads.*
/public/uploads/
/shared/artifacts/
/rails_best_practices_output.html
/tags
tmp/
vendor/bundle/*
builds/*
shared/*
/tmp/*
/vendor/bundle/*
/builds/*
/shared/*
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -96,7 +96,7 @@ The designs are made using Antetype (`.atype` files). You can use the
[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
(the PNG is 1:1).
The current designs can be found in the [`gitlab1.atype` file].
The current designs can be found in the [`gitlab8.atype` file].
### UI development kit
......@@ -308,16 +308,14 @@ tests are least likely to receive timely feedback. The workflow to make a merge
request is as follows:
1. Fork the project into your personal space on GitLab.com
1. Create a feature branch
1. Create a feature branch, branch away from `master`.
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are changing the README, some documentation or other things which
have no effect on the tests, add `[ci skip]` somewhere in the commit message
and make sure to read the [documentation styleguide][doc-styleguide]
1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by
[squashing them][git-squash]
1. Push the commit(s) to your fork
1. Submit a merge request (MR) to the master branch
1. Submit a merge request (MR) to the `master` branch
1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you
used to achieve it, see the [merge request description format]
......@@ -407,6 +405,7 @@ description area. Copy-paste it to retain the markdown format.
entire line to follow it. This prevents linting tools from generating warnings.
- Don't touch neighbouring lines. As an exception, automatic mass
refactoring modifications may leave style non-compliant.
1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.), they should conform to our [Licensing guidelines][license-finder-doc]. See the instructions in that document for help if your MR fails the "license-finder" test with a "Dependencies that need approval" error.
## Changes for Stable Releases
......@@ -532,4 +531,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
[license-finder-doc]: doc/development/licensing.md
......@@ -18,9 +18,8 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.4'
gem 'devise', '~> 4.0'
gem 'doorkeeper', '~> 3.1'
gem 'devise-async', '~> 0.9.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
......@@ -36,18 +35,20 @@ gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt'
# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
gem 'devise-two-factor', '~> 3.0.0'
gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 1.3.4'
gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1'
# Browser detection
gem "browser", '~> 1.0.0'
gem "browser", '~> 2.0.3'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
......@@ -72,7 +73,7 @@ gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination
gem "kaminari", "~> 0.16.3"
gem "kaminari", "~> 0.17.0"
# HAML
gem "haml-rails", '~> 0.9.0'
......@@ -83,8 +84,15 @@ gem "carrierwave", '~> 0.10.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
gem 'fog-aws', '~> 0.9'
gem 'fog-azure', '~> 0.0'
gem 'fog-core', '~> 1.40'
gem 'fog-local', '~> 0.3'
gem 'fog-google', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
# for aws storage
gem "fog", "~> 1.36.0"
gem "unf", '~> 0.1.4'
# Authorization
......@@ -104,7 +112,7 @@ gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
gem 'rouge', '~> 1.10.1'
gem 'rouge', '~> 1.11'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
......@@ -120,7 +128,7 @@ group :unicorn do
end
# State machine
gem "state_machines-activerecord", '~> 0.3.0'
gem "state_machines-activerecord", '~> 0.4.0'
# Run events after state machine commits
gem 'after_commit_queue'
......@@ -137,7 +145,7 @@ gem 'redis-namespace'
gem "httparty", '~> 0.13.3'
# Colored output to console
gem "colorize", '~> 0.7.0'
gem "rainbow", '~> 2.1.0'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
......@@ -177,9 +185,6 @@ gem 'ruby-fogbugz', '~> 0.2.1'
# d3
gem 'd3_rails', '~> 3.5.0'
#cal-heatmap
gem 'cal-heatmap-rails', '~> 3.6.0'
# underscore-rails
gem "underscore-rails", "~> 1.8.0"
......@@ -197,7 +202,7 @@ gem 'licensee', '~> 8.0.0'
gem "rack-attack", '~> 4.3.1'
# Ace editor
gem 'ace-rails-ap', '~> 2.0.1'
gem 'ace-rails-ap', '~> 4.0.2'
# Keyboard shortcuts
gem 'mousetrap-rails', '~> 1.4.6'
......@@ -218,13 +223,13 @@ gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
......@@ -242,7 +247,6 @@ group :development do
gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.7.0"
gem 'letter_opener_web', '~> 1.3.0'
gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0'
......@@ -293,15 +297,19 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.38.0', require: false
gem 'rubocop', '~> 0.40.0', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.11.0', require: false
gem 'flog', require: false
gem 'flay', require: false
gem 'bundler-audit', require: false
gem 'benchmark-ips', require: false
gem "license_finder", require: false
gem 'knapsack'
end
group :test do
......@@ -325,8 +333,7 @@ gem "mail_room", "~> 0.7"
gem 'email_reply_parser', '~> 0.5.8'
## CI
gem 'activerecord-deprecated_finders', '~> 1.0.3'
gem 'activerecord-session_store', '~> 0.1.0'
gem 'activerecord-session_store', '~> 1.0.0'
gem "nested_form", '~> 0.3.2'
# OAuth
......@@ -334,3 +341,6 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion
gem "paranoia", "~> 2.0"
# Health check
gem 'health_check', '~> 1.5.1'
This diff is collapsed.
# GitLab
[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.svg?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
## Canonical source
......@@ -35,11 +33,11 @@ There are two editions of GitLab:
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
- [Subscriptions](https://about.gitlab.com/subscription/)
- [Subscriptions](https://about.gitlab.com/pricing/)
- [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab Enterprise Edition](https://about.gitlab.com/features/#enterprise) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Requirements
......
......@@ -8,3 +8,5 @@ relative_url_conf = File.expand_path('../config/initializers/relative_url', __FI
require relative_url_conf if File.exist?("#{relative_url_conf}.rb")
Gitlab::Application.load_tasks
Knapsack.load_tasks if defined?(Knapsack)
8.8.0-pre
8.9.0-pre
class @LabelManager
errorMessage: 'Unable to update label prioritization at this time'
constructor: (opts = {}) ->
# Defaults
{
@togglePriorityButton = $('.js-toggle-priority')
@prioritizedLabels = $('.js-prioritized-labels')
@otherLabels = $('.js-other-labels')
} = opts
@prioritizedLabels.sortable(
items: 'li'
placeholder: 'list-placeholder'
axis: 'y'
update: @onPrioritySortUpdate.bind(@)
)
@bindEvents()
bindEvents: ->
@togglePriorityButton.on 'click', @, @onTogglePriorityClick
onTogglePriorityClick: (e) ->
e.preventDefault()
_this = e.data
$btn = $(e.currentTarget)
$label = $("##{$btn.data('domId')}")
action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
_this.toggleLabelPriority($label, action)
toggleLabelPriority: ($label, action, persistState = true) ->
_this = @
url = $label.find('.js-toggle-priority').data 'url'
$target = @prioritizedLabels
$from = @otherLabels
# Optimistic update
if action is 'remove'
$target = @otherLabels
$from = @prioritizedLabels
if $from.find('li').length is 1
$from.find('.empty-message').show()
if not $target.find('li').length
$target.find('.empty-message').hide()
$label.detach().appendTo($target)
# Return if we are not persisting state
return unless persistState
if action is 'remove'
xhr = $.ajax url: url, type: 'DELETE'
else
xhr = @savePrioritySort($label, action)
xhr.fail @rollbackLabelPosition.bind(@, $label, action)
onPrioritySortUpdate: ->
xhr = @savePrioritySort()
xhr.fail ->
new Flash(@errorMessage, 'alert')
savePrioritySort: () ->
$.post
url: @prioritizedLabels.data('url')
data:
label_ids: @getSortedLabelsIds()
rollbackLabelPosition: ($label, originalAction)->
action = if originalAction is 'remove' then 'add' else 'remove'
@toggleLabelPriority($label, action, false)
new Flash(@errorMessage, 'alert')
getSortedLabelsIds: ->
sortedIds = []
@prioritizedLabels.find('li').each ->
sortedIds.push $(@).data 'id'
sortedIds
class @Activities
constructor: ->
Pager.init 20, true
Pager.init 20, true, false, @updateTooltips
$(".event-filter-link").on "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
updateTooltips: ->
gl.utils.localTimeAgo($('.js-timeago', '#activity'))
reloadActivities: ->
$(".content_list").html ''
Pager.init 20, true
......
@Api =
groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.json"
group_projects_path: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json"
labels_path: "/api/:version/projects/:id/labels"
license_path: "/api/:version/licenses/:key"
groupsPath: "/api/:version/groups.json"
groupPath: "/api/:version/groups/:id.json"
namespacesPath: "/api/:version/namespaces.json"
groupProjectsPath: "/api/:version/groups/:id/projects.json"
projectsPath: "/api/:version/projects.json"
labelsPath: "/api/:version/projects/:id/labels"
licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
url = Api.buildUrl(Api.groupPath)
url = url.replace(':id', group_id)
$.ajax(
......@@ -22,7 +23,7 @@
# Return groups list. Filtered by query
# Only active groups retrieved
groups: (query, skip_ldap, callback) ->
url = Api.buildUrl(Api.groups_path)
url = Api.buildUrl(Api.groupsPath)
$.ajax(
url: url
......@@ -36,7 +37,7 @@
# Return namespaces list. Filtered by query
namespaces: (query, callback) ->
url = Api.buildUrl(Api.namespaces_path)
url = Api.buildUrl(Api.namespacesPath)
$.ajax(
url: url
......@@ -50,7 +51,7 @@
# Return projects list. Filtered by query
projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path)
url = Api.buildUrl(Api.projectsPath)
$.ajax(
url: url
......@@ -64,7 +65,7 @@
callback(projects)
newLabel: (project_id, data, callback) ->
url = Api.buildUrl(Api.labels_path)
url = Api.buildUrl(Api.labelsPath)
url = url.replace(':id', project_id)
data.private_token = gon.api_token
......@@ -80,7 +81,7 @@
# Return group projects list. Filtered by query
groupProjects: (group_id, query, callback) ->
url = Api.buildUrl(Api.group_projects_path)
url = Api.buildUrl(Api.groupProjectsPath)
url = url.replace(':id', group_id)
$.ajax(
......@@ -95,7 +96,7 @@
# Return text for a specific license
licenseText: (key, data, callback) ->
url = Api.buildUrl(Api.license_path).replace(':key', key)
url = Api.buildUrl(Api.licensePath).replace(':key', key)
$.ajax(
url: url
......@@ -103,6 +104,12 @@
).done (license) ->
callback(license)
gitignoreText: (key, callback) ->
url = Api.buildUrl(Api.gitignorePath).replace(':key', key)
$.get url, (gitignore) ->
callback(gitignore)
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
......@@ -4,7 +4,7 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require jquery
#= require jquery2
#= require jquery-ui/autocomplete
#= require jquery-ui/datepicker
#= require jquery-ui/draggable
......@@ -18,8 +18,6 @@
#= require jquery.atwho
#= require jquery.scrollTo
#= require jquery.turbolinks
#= require d3
#= require cal-heatmap
#= require turbolinks
#= require autosave
#= require bootstrap/affix
......@@ -37,7 +35,6 @@
#= require raphael
#= require g.raphael
#= require g.bar
#= require Chart
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
......@@ -52,9 +49,17 @@
#= require shortcuts_network
#= require jquery.nicescroll
#= require date.format
#= require_tree .
#= require_directory ./behaviors
#= require_directory ./blob
#= require_directory ./ci
#= require_directory ./commit
#= require_directory ./extensions
#= require_directory ./lib
#= require_directory ./u2f
#= require_directory .
#= require fuzzaldrin-plus
#= require cropper
#= require u2f
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......@@ -157,19 +162,6 @@ $ ->
$el.data('placement') || 'bottom'
)
$('.header-logo .home').tooltip(
placement: (_, el) ->
$el = $(el)
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
container: 'body'
)
$('.page-with-sidebar').tooltip(
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
placement: 'right'
container: 'body'
)
# Form submitter
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
......@@ -202,8 +194,10 @@ $ ->
$('.navbar-toggle').on 'click', ->
$('.header-content .title').toggle()
$('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
......@@ -219,6 +213,10 @@ $ ->
form = btn.closest("form")
new ConfirmDangerModal(form, text)
$(document).on 'click', 'button', ->
$(this).blur()
$('input[type="search"]').each ->
$this = $(this)
$this.attr 'value', $this.val()
......@@ -231,7 +229,6 @@ $ ->
$this.attr 'value', $this.val()
$sidebarGutterToggle = $('.js-sidebar-toggle')
$navIconToggle = $('.toggle-nav-collapse')
$(document)
.off 'breakpoint:change'
......@@ -241,42 +238,6 @@ $ ->
if $gutterIcon.hasClass('fa-angle-double-right')
$sidebarGutterToggle.trigger('click')
$navIcon = $navIconToggle.find('.fa')
if $navIcon.hasClass('fa-angle-left')
$navIconToggle.trigger('click')
$(document)
.off 'click', '.js-sidebar-toggle'
.on 'click', '.js-sidebar-toggle', (e, triggered) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
$allGutterToggleIcons = $('.js-sidebar-toggle i')
if $thisIcon.hasClass('fa-angle-double-right')
$allGutterToggleIcons
.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
$('aside.right-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
$allGutterToggleIcons
.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
$('aside.right-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
if not triggered
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize()
......@@ -289,9 +250,10 @@ $ ->
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
.off "resize"
.on "resize", (e) ->
.off "resize.app"
.on "resize.app", (e) ->
fitSidebarForSize()
gl.awardsHandler = new AwardsHandler()
checkInitialSidebarSize()
new Aside()
class @BlobGitignoreSelector
constructor: (opts) ->
{
@dropdown
@editor
@$wrapper = @dropdown.closest('.gitignore-selector')
@$filenameInput = $('#file_name')
@data = @dropdown.data('filenames')
} = opts
@dropdown.glDropdown(
data: @data,
filterable: true,
selectable: true,
search:
fields: ['name']
clicked: @onClick
text: (gitignore) ->
gitignore.name
)
@toggleGitignoreSelector()
@bindEvents()
bindEvents: ->
@$filenameInput
.on 'keyup blur', (e) =>
@toggleGitignoreSelector()
toggleGitignoreSelector: ->
filename = @$filenameInput.val() or $('.editor-file-name').text().trim()
@$wrapper.toggleClass 'hidden', filename isnt '.gitignore'
onClick: (item, el, e) =>
e.preventDefault()
@requestIgnoreFile(item.name)
requestIgnoreFile: (name) ->
Api.gitignoreText name, @requestIgnoreFileSuccess.bind(@)
requestIgnoreFileSuccess: (gitignore) ->
@editor.setValue(gitignore.content, 1)
@editor.focus()
class @BlobGitignoreSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitignore-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobGitignoreSelector(
dropdown: $dropdown,
editor: @editor
)
......@@ -13,6 +13,7 @@ class @EditBlob
@initModePanesAndLinks()
new BlobLicenseSelector(@editor)
new BlobGitignoreSelectors(editor: @editor)
initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane")
......
class @Calendar
constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
cal = new CalHeatMap()
cal.init
itemName: ["contribution"]
data: timestamps
start: new Date(starting_year, starting_month)
domainLabelFormat: "%b"
id: "cal-heatmap"
domain: "month"
subDomain: "day"
range: 12
tooltip: true
label:
position: "top"
legend: [
0
10
20
30
]
legendCellPadding: 3
cellSize: $('.user-calendar').width() / 73
onClick: (date, count) ->
formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
$.ajax
url: calendar_activities_path
data:
date: formated_date
cache: false
dataType: "html"
success: (data) ->
$(".user-calendar-activities").html data
# This is a manifest file that'll be compiled into application.js, which will include all the files
# listed below.
#
# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
#
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
# GO AFTER THE REQUIRES BELOW.
#
#= require pager
#= require jquery_nested_form
#= require_tree .
#
$(document).on 'click', '.edit-runner-link', (event) ->
event.preventDefault()
descr = $(this).closest('.runner-description').first()
descr.addClass('hide')
form = descr.next('.runner-description-form')
descrInput = form.find('input.description')
originalValue = descrInput.val()
form.removeClass('hide')
form.find('.cancel').on 'click', (event) ->
event.preventDefault()
form.addClass('hide')
descrInput.val(originalValue)
descr.removeClass('hide')
$(document).on 'click', '.assign-all-runner', ->
$(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..')
......
class CiBuild
class @CiBuild
@interval: null
@state: null
constructor: (build_url, build_status) ->
constructor: (@build_url, @build_status, @state) ->
clearInterval(CiBuild.interval)
@initScrollButtonAffix()
# Init breakpoint checker
@bp = Breakpoints.get()
@hideSidebar()
$('.js-build-sidebar').niceScroll()
$(document)
.off 'click', '.js-sidebar-build-toggle'
.on 'click', '.js-sidebar-build-toggle', @toggleSidebar
if build_status == "running" || build_status == "pending"
$(window)
.off 'resize.build'
.on 'resize.build', @hideSidebar
if $('#build-trace').length
@getInitialBuildTrace()
@initScrollButtonAffix()
if @build_status is "running" or @build_status is "pending"
#
# Bind autoscroll button to follow build output
#
$("#autoscroll-button").bind "click", ->
$('#autoscroll-button').on 'click', ->
state = $(this).data("state")
if "enabled" is state
$(this).data "state", "disabled"
......@@ -24,19 +39,37 @@ class CiBuild
# Only valid for runnig build when output changes during time
#
CiBuild.interval = setInterval =>
if window.location.href.split("#").first() is build_url
$.ajax
url: build_url
dataType: "json"
success: (build) =>
if build.status == "running"
$('#build-trace code').html build.trace_html
$('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
@checkAutoscroll()
else if build.status != build_status
Turbolinks.visit build_url
if window.location.href.split("#").first() is @build_url
@getBuildTrace()
, 4000
getInitialBuildTrace: ->
$.ajax
url: @build_url
dataType: 'json'
success: (build_data) ->
$('.js-build-output').html build_data.trace_html
if build_data.status is 'success' or build_data.status is 'failed'
$('.js-build-refresh').remove()
getBuildTrace: ->
$.ajax
url: "#{@build_url}/trace.json?state=#{encodeURIComponent(@state)}"
dataType: "json"
success: (log) =>
if log.state
@state = log.state
if log.status is "running"
if log.append
$('.js-build-output').append log.html
else
$('.js-build-output').html log.html
@checkAutoscroll()
else if log.status isnt @build_status
Turbolinks.visit @build_url
checkAutoscroll: ->
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
......@@ -51,4 +84,22 @@ class CiBuild
$body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
)
@CiBuild = CiBuild
shouldHideSidebar: ->
bootstrapBreakpoint = @bp.getBreakpointSize()
bootstrapBreakpoint is 'xs' or bootstrapBreakpoint is 'sm'
toggleSidebar: =>
if @shouldHideSidebar()
$('.js-build-sidebar')
.toggleClass 'right-sidebar-expanded right-sidebar-collapsed'
hideSidebar: =>
if @shouldHideSidebar()
$('.js-build-sidebar')
.removeClass 'right-sidebar-expanded'
.addClass 'right-sidebar-collapsed'
else
$('.js-build-sidebar')
.removeClass 'right-sidebar-collapsed'
.addClass 'right-sidebar-expanded'
......@@ -16,8 +16,8 @@ class Dispatcher
shortcut_handler = null
switch page
when 'projects:issues:index'
Issues.init()
Issuable.init()
new IssuableBulkActions()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
......@@ -98,6 +98,8 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
when 'projects:labels:index'
new LabelManager() if $('.prioritized-labels').length
when 'projects:network:show'
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
......@@ -119,7 +121,7 @@ class Dispatcher
new UsersSelect()
when 'projects'
new NamespaceSelect()
when 'dashboard'
when 'dashboard', 'root'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles'
new Profile()
......
......@@ -11,6 +11,7 @@ class @DueDateSelect
$block = $dropdown.closest('.block')
$selectbox = $dropdown.closest('.selectbox')
$value = $block.find('.value')
$valueContent = $block.find('.value-content')
$sidebarValue = $('.js-due-date-sidebar-value', $block)
fieldName = $dropdown.data('field-name')
......@@ -20,14 +21,18 @@ class @DueDateSelect
$dropdown.glDropdown(
hidden: ->
$selectbox.hide()
$value.removeAttr('style')
$value.css('display', '')
)
addDueDate = ->
addDueDate = (isDropdown) ->
# Create the post date
value = $("input[name='#{fieldName}']").val()
date = new Date value.replace(new RegExp('-', 'g'), ',')
mediumDate = $.datepicker.formatDate 'M d, yy', date
if value isnt ''
date = new Date value.replace(new RegExp('-', 'g'), ',')
mediumDate = $.datepicker.formatDate 'M d, yy', date
else
mediumDate = 'None'
data = {}
data[abilityName] = {}
......@@ -37,25 +42,38 @@ class @DueDateSelect
type: 'PUT'
url: issueUpdateURL
data: data
dataType: 'json'
beforeSend: ->
$loading.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$selectbox.hide()
$value.removeAttr('style')
if isDropdown
$dropdown.trigger('loading.gl.dropdown')
$selectbox.hide()
$value.css('display', '')
$value.html(mediumDate)
$valueContent.html(mediumDate)
$sidebarValue.html(mediumDate)
if value isnt ''
$('.js-remove-due-date-holder').removeClass 'hidden'
else
$('.js-remove-due-date-holder').addClass 'hidden'
).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$dropdown.dropdown('toggle')
if isDropdown
$dropdown.trigger('loaded.gl.dropdown')
$dropdown.dropdown('toggle')
$loading.fadeOut()
$block.on 'click', '.js-remove-due-date', (e) ->
e.preventDefault()
$("input[name='#{fieldName}']").val ''
addDueDate(false)
$datePicker.datepicker(
dateFormat: 'yy-mm-dd',
defaultDate: $("input[name='#{fieldName}']").val()
altField: "input[name='#{fieldName}']"
onSelect: ->
addDueDate()
addDueDate(true)
)
$(document)
......
class @Flash
constructor: (message, type)->
constructor: (message, type = 'alert')->
@flash = $(".flash-container")
@flash.html("")
......
......@@ -3,6 +3,7 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
dataLoading: false
dataLoaded: false
dataSource: ''
......@@ -18,6 +19,28 @@ GitLab.GfmAutoComplete =
Issues:
template: '<li><small>${id}</small> ${title}</li>'
# Milestones
Milestones:
template: '<li>${title}</li>'
Loading:
template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
DefaultOptions:
sorter: (query, items, searchKey) ->
return items if items[0].name? and items[0].name is 'loading'
$.fn.atwho.default.callbacks.sorter(query, items, searchKey)
filter: (query, data, searchKey) ->
return data if data[0] is 'loading'
$.fn.atwho.default.callbacks.filter(query, data, searchKey)
beforeInsert: (value) ->
if not GitLab.GfmAutoComplete.dataLoaded
@at
else
value
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: (wrap) ->
@input = $('.js-gfm-input')
......@@ -49,18 +72,37 @@ GitLab.GfmAutoComplete =
# Emoji
@input.atwho
at: ':'
displayTpl: @Emoji.template
displayTpl: (value) =>
if value.path?
@Emoji.template
else
@Loading.template
insertTpl: ':${name}:'
data: ['loading']
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
# Team Members
@input.atwho
at: '@'
displayTpl: @Members.template
displayTpl: (value) =>
if value.username?
@Members.template
else
@Loading.template
insertTpl: '${atwho-at}${username}'
searchKey: 'search'
data: ['loading']
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (members) ->
$.map members, (m) ->
return m if not m.username?
title = m.name
title += " (#{m.count})" if m.count
......@@ -72,24 +114,64 @@ GitLab.GfmAutoComplete =
at: '#'
alias: 'issues'
searchKey: 'search'
displayTpl: @Issues.template
displayTpl: (value) =>
if value.title?
@Issues.template
else
@Loading.template
data: ['loading']
insertTpl: '${atwho-at}${id}'
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (issues) ->
$.map issues, (i) ->
return i if not i.title?
id: i.iid
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
@input.atwho
at: '%'
alias: 'milestones'
searchKey: 'search'
displayTpl: (value) =>
if value.title?
@Milestones.template
else
@Loading.template
insertTpl: '${atwho-at}"${title}"'
data: ['loading']
callbacks:
beforeSave: (milestones) ->
$.map milestones, (m) ->
return m if not m.title?
id: m.iid
title: sanitize(m.title)
search: "#{m.title}"
@input.atwho
at: '!'
alias: 'mergerequests'
searchKey: 'search'
displayTpl: @Issues.template
displayTpl: (value) =>
if value.title?
@Issues.template
else
@Loading.template
data: ['loading']
insertTpl: '${atwho-at}${id}'
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (merges) ->
$.map merges, (m) ->
return m if not m.title?
id: m.iid
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
......@@ -101,11 +183,19 @@ GitLab.GfmAutoComplete =
$.getJSON(dataSource)
loadData: (data) ->
@dataLoaded = true
# load members
@input.atwho 'load', '@', data.members
# load issues
@input.atwho 'load', 'issues', data.issues
# load milestones
@input.atwho 'load', 'milestones', data.milestones
# load merge requests
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
@input.atwho 'load', ':', data.emojis
# This trigger at.js again
# otherwise we would be stuck with loading until the user types
$(':focus').trigger('keyup')
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require Chart
#= require_tree .
#= require d3
#= require stat_graph_contributors_util
class @ContributorsStatGraph
init: (log) ->
......
issuable_created = false
@Issuable =
init: ->
Issuable.initTemplates()
Issuable.initSearch()
unless issuable_created
issuable_created = true
Issuable.initTemplates()
Issuable.initSearch()
Issuable.initChecks()
Issuable.initLabelFilterRemove()
initTemplates: ->
Issuable.labelRow = _.template(
'<% _.each(labels, function(label){ %>
<span class="label-row">
<a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a>
<span class="label-row btn-group" role="group" aria-label="<%= _.escape(label.title) %>" style="color: <%= label.text_color %>;">
<a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%= label.color %>;" title="<%= _.escape(label.description) %>" data-container="body">
<%= _.escape(label.title) %>
</a>
<button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%= label.color %>;" data-label="<%= _.escape(label.title) %>">
<i class="fa fa-times"></i>
</button>
</span>
<% }); %>'
)
......@@ -19,9 +29,33 @@
.on 'keyup', ->
clearTimeout(@timer)
@timer = setTimeout( ->
Issuable.filterResults $('#issue_search_form')
$search = $('#issue_search')
$form = $('.js-filter-form')
$input = $("input[name='#{$search.attr('name')}']", $form)
if $input.length is 0
$form.append "<input type='hidden' name='#{$search.attr('name')}' value='#{_.escape($search.val())}'/>"
else
$input.val $search.val()
Issuable.filterResults $form
, 500)
initLabelFilterRemove: ->
$(document)
.off 'click', '.js-label-filter-remove'
.on 'click', '.js-label-filter-remove', (e) ->
$button = $(@)
# Remove the label input box
$('input[name="label_name[]"]')
.filter -> @value is $button.data('label')
.remove()
# Submit the form to get new data
Issuable.filterResults $('.filter-form')
$('.js-label-select').trigger('update.label')
toggleLabelFilters: ->
$filteredLabels = $('.filtered-labels')
if $filteredLabels.find('.label-row').length > 0
......@@ -59,15 +93,22 @@
dataType: "json"
reload: ->
if Issues.created
Issues.initChecks()
if Issuable.created
Issuable.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
initChecks: ->
$('.check_all_issues').on 'click', ->
$('.selected_issue').prop('checked', @checked)
Issuable.checkChanged()
$('.selected_issue').on 'change', Issuable.checkChanged
updateStateFilters: ->
stateFilters = $('.issues-state-filters')
stateFilters = $('.issues-state-filters, .dropdown-menu-sort')
newParams = {}
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
......@@ -82,3 +123,17 @@
else
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
$(this).attr 'href', newUrl
checkChanged: ->
checked_issues = $('.selected_issue:checked')
if checked_issues.length > 0
ids = $.map checked_issues, (value) ->
$(value).data('id')
$('#update_issues_ids').val ids
$('.issues-other-filters').hide()
$('.issues_bulk_update').show()
else
$('#update_issues_ids').val []
$('.issues_bulk_update').hide()
$('.issues-other-filters').show()
......@@ -19,6 +19,16 @@ class @IssuableForm
@form.on "click", ".btn-cancel", @resetAutosave
@initWip()
@initMoveDropdown()
$issuableDueDate = $('#issuable-due-date')
if $issuableDueDate.length
$('.datepicker').datepicker(
dateFormat: 'yy-mm-dd',
onSelect: (dateText, inst) ->
$issuableDueDate.val dateText
).datepicker 'setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val())
initAutosave: ->
new Autosave @titleField, [
......@@ -80,3 +90,19 @@ class @IssuableForm
addWip: ->
@titleField.val "WIP: #{@titleField.val()}"
initMoveDropdown: ->
$moveDropdown = $('.js-move-dropdown')
if $moveDropdown.length
$('.js-move-dropdown').select2
ajax:
url: $moveDropdown.data('projects-url')
results: (data) ->
return {
results: data
}
formatResult: (project) ->
project.name_with_namespace
formatSelection: (project) ->
project.name_with_namespace
class @IssuableBulkActions
constructor: (opts = {}) ->
# Set defaults
{
@container = $('.content')
@form = @getElement('.bulk-update')
@issues = @getElement('.issues-list .issue')
} = opts
@bindEvents()
getElement: (selector) ->
@container.find selector
bindEvents: ->
@form.off('submit').on('submit', @onFormSubmit.bind(@))
onFormSubmit: (e) ->
e.preventDefault()
@submit()
submit: ->
_this = @
xhr = $.ajax
url: @form.attr 'action'
method: @form.attr 'method'
dataType: 'JSON',
data: @getFormDataAsObject()
xhr.done (response, status, xhr) ->
location.reload()
xhr.fail ->
new Flash("Issue update failed")
xhr.always @onFormSubmitAlways.bind(@)
onFormSubmitAlways: ->
@form.find('[type="submit"]').enable()
getSelectedIssues: ->
@issues.has('.selected_issue:checked')
getLabelsFromSelection: ->
labels = []
@getSelectedIssues().map ->
_labels = $(@).data('labels')
if _labels
_labels.map (labelId) ->
labels.push(labelId) if labels.indexOf(labelId) is -1
labels
###*
* Will return only labels that were marked previously and the user has unmarked
* @return {Array} Label IDs
###
getUnmarkedIndeterminedLabels: ->
result = []
labelsToKeep = []
for el in @getElement('.labels-filter .is-indeterminate')
labelsToKeep.push $(el).data('labelId')
for id in @getLabelsFromSelection()
# Only the ones that we are not going to keep
result.push(id) if labelsToKeep.indexOf(id) is -1
result
###*
* Simple form serialization, it will return just what we need
* Returns key/value pairs from form data
###
getFormDataAsObject: ->
formData =
update:
state_event : @form.find('input[name="update[state_event]"]').val()
assignee_id : @form.find('input[name="update[assignee_id]"]').val()
milestone_id : @form.find('input[name="update[milestone_id]"]').val()
issues_ids : @form.find('input[name="update[issues_ids]"]').val()
add_label_ids : []
remove_label_ids : []
@getLabelsToApply().map (id) ->
formData.update.add_label_ids.push id
@getLabelsToRemove().map (id) ->
formData.update.remove_label_ids.push id
formData
getLabelsToApply: ->
labelIds = []
$labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
$labels.each (k, label) ->
labelIds.push $(label).val() if label
labelIds
###*
* Just an alias of @getUnmarkedIndeterminedLabels
* @return {Array} Array of labels
###
getLabelsToRemove: ->
@getUnmarkedIndeterminedLabels()
@Issues =
init: ->
Issues.created = true
Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
t = $(this)
totalIssues = undefined
reopen = t.hasClass("reopen_issue")
$(".issue_counter").each ->
issue = $(this)
totalIssues = parseInt($(this).html(), 10)
if reopen and issue.closest(".main_menu").length
$(this).html totalIssues + 1
else
$(this).html totalIssues - 1
initChecks: ->
$(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked)
Issues.checkChanged()
$(".selected_issue").bind "change", Issues.checkChanged
checkChanged: ->
checked_issues = $(".selected_issue:checked")
if checked_issues.length > 0
ids = []
$.each checked_issues, (index, value) ->
ids.push $(value).attr("data-id")
$("#update_issues_ids").val ids
$(".issues-other-filters").hide()
$(".issues_bulk_update").show()
else
$("#update_issues_ids").val []
$(".issues_bulk_update").hide()
$(".issues-other-filters").show()
class @LabelsSelect
constructor: ->
_this = @
$('.js-label-select').each (i, dropdown) ->
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
......@@ -93,8 +95,11 @@ class @LabelsSelect
$newLabelCreateButton.enable()
if label.message?
errors = _.map label.message, (value, key) ->
"#{key} #{value[0]}"
$newLabelError
.text label.message
.html errors.join("<br/>")
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
......@@ -196,10 +201,18 @@ class @LabelsSelect
callback data
renderRow: (label) ->
removesAll = label.id is 0 or not label.id?
renderRow: (label, instance) ->
$li = $('<li>')
$a = $('<a href="#">')
selectedClass = []
removesAll = label.id is 0 or not label.id?
if $dropdown.hasClass('js-filter-bulk-update')
indeterminate = instance.indeterminateIds
if indeterminate.indexOf(label.id) isnt -1
selectedClass.push 'is-indeterminate'
if $form.find("input[type='hidden']\
[name='#{$dropdown.data('fieldName')}']\
[value='#{this.id(label)}']").length
......@@ -230,17 +243,21 @@ class @LabelsSelect
else
colorEl = ''
"<li>
<a href='#' class='#{selectedClass.join(' ')}'>
#{colorEl}
#{_.escape(label.title)}
</a>
</li>"
filterable: true
# We need to identify which items are actually labels
if label.id
selectedClass.push('label-item')
$a.attr('data-label-id', label.id)
$a.addClass(selectedClass.join(' '))
.html("#{colorEl} #{_.escape(label.title)}")
# Return generated html
$li.html($a).prop('outerHTML')
persistWhenHide: $dropdown.data('persistWhenHide')
search:
fields: ['title']
selectable: true
filterable: true
toggleLabel: (selected, el) ->
selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
......@@ -280,10 +297,19 @@ class @LabelsSelect
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
saveLabelData()
if not $dropdown.hasClass 'js-filter-bulk-update'
saveLabelData()
if $dropdown.hasClass('js-filter-bulk-update')
# If we are persisting state we need the classes
if not @options.persistWhenHide
$dropdown.parent().find('.is-active, .is-indeterminate').removeClass()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
if $dropdown.hasClass('js-filter-bulk-update')
return
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is 'projects:merge_requests:index'
......@@ -298,4 +324,31 @@ class @LabelsSelect
return
else
saveLabelData()
setIndeterminateIds: ->
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@indeterminateIds = _this.getIndeterminateIds()
)
@bindEvents()
bindEvents: ->
$('body').on 'change', '.selected_issue', @onSelectCheckboxIssue
onSelectCheckboxIssue: ->
return if $('.selected_issue:checked').length
# Remove inputs
$('.issues_bulk_update .labels-filter input[type="hidden"]').remove()
# Also restore button text
$('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')
getIndeterminateIds: ->
label_ids = []
$('.selected_issue:checked').each (i, el) ->
issue_id = $(el).data('id')
label_ids.push $("#issue_#{issue_id}").data('labels')
_.flatten(label_ids)
class @LayoutNav
$ ->
$('.fade-left').addClass('end-scroll')
$('.scrolling-tabs').on 'scroll', (event) ->
$this = $(this)
$el = $(event.target)
currentPosition = $this.scrollLeft()
size = bp.getBreakpointSize()
controlBtnWidth = $('.controls').width()
maxPosition = $this.get(0).scrollWidth - $this.parent().width()
maxPosition += controlBtnWidth if size isnt 'xs' and $('.nav-control').length
$el.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
$el.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)
((w) ->
jQuery.timefor = (time, suffix, expiredLabel) ->
return '' unless time
suffix or= 'remaining'
expiredLabel or= 'Past due'
jQuery.timeago.settings.allowFuture = yes
{ suffixFromNow } = jQuery.timeago.settings.strings
jQuery.timeago.settings.strings.suffixFromNow = suffix
timefor = $.timeago time
if timefor.indexOf('ago') > -1
timefor = expiredLabel
jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow
return timefor
) window
......@@ -12,6 +12,13 @@
$el.attr('title', gl.utils.formatDate($el.attr('datetime')))
)
$timeagoEls.timeago() if setTimeago
if setTimeago
$timeagoEls.timeago()
$timeagoEls.tooltip('destroy')
# Recreate with custom template
$timeagoEls.tooltip(
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
)
) window
gl.emojiAliases = ->
JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.isObject = (obj) ->
obj? and (obj.constructor is Object)
) window
......@@ -26,10 +26,19 @@
newUrl = decodeURIComponent(url)
for paramName, paramValue of params
pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
if url.search(pattern) >= 0
if not paramValue?
newUrl = newUrl.replace pattern, ''
else if url.search(pattern) isnt -1
newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
else
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
# Remove a trailing ampersand
lastChar = newUrl[newUrl.length - 1]
if lastChar is '&'
newUrl = newUrl.slice 0, -1
newUrl
# removes parameter query string from url. returns the modified url
......
......@@ -47,4 +47,4 @@ $ ->
# Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690).
$('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click()
Turbolinks.visit('/')
......@@ -75,6 +75,9 @@ class @MergeRequestTabs
@loadDiff($target.attr('href'))
if bp? and bp.getBreakpointSize() isnt 'lg'
@shrinkView()
navBarHeight = $('.navbar-gitlab').outerHeight()
$.scrollTo(".merge-request-details .merge-request-tabs", offset: -navBarHeight)
else if action == 'builds'
@loadBuilds($target.attr('href'))
@expandView()
......
......@@ -10,6 +10,7 @@ class @MergeRequestWidget
$('#modal_merge_info').modal(show: false)
@firstCICheck = true
@readyForCICheck = false
@cancel = false
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
......@@ -21,10 +22,16 @@ class @MergeRequestWidget
clearEventListeners: ->
$(document).off 'page:change.merge_request'
cancelPolling: ->
@cancel = true
addEventListeners: ->
allowedPages = ['show', 'commits', 'builds', 'changes']
$(document).on 'page:change.merge_request', =>
if $('body').data('page') isnt 'projects:merge_requests:show'
page = $('body').data('page').split(':').last()
if allowedPages.indexOf(page) < 0
clearInterval @fetchBuildStatusInterval
@cancelPolling()
@clearEventListeners()
mergeInProgress: (deleteSourceBranch = false)->
......@@ -67,6 +74,7 @@ class @MergeRequestWidget
$('.ci-widget-fetching').show()
$.getJSON @opts.ci_status_url, (data) =>
return if @cancel
@readyForCICheck = true
if data.status is ''
......@@ -106,6 +114,7 @@ class @MergeRequestWidget
@firstCICheck = false
showCIStatus: (state) ->
return if not state?
$('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states
......@@ -113,7 +122,7 @@ class @MergeRequestWidget
switch state
when "failed", "canceled", "not_found"
@setMergeButtonClass('btn-danger')
when "running", "pending"
when "running"
@setMergeButtonClass('btn-warning')
when "success"
@setMergeButtonClass('btn-create')
......@@ -126,6 +135,6 @@ class @MergeRequestWidget
$('.ci_widget:visible .ci-coverage').text(text)
setMergeButtonClass: (css_class) ->
$('.accept_merge_request')
$('.js-merge-button,.accept-action .dropdown-toggle')
.removeClass('btn-danger btn-warning btn-create')
.addClass(css_class)
......@@ -24,11 +24,21 @@ class @MilestoneSelect
if issueUpdateURL
milestoneLinkTemplate = _.template(
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>'
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>">
<span class="has-tooltip" data-container="body" title="<%= remaining %>">
<%= _.escape(title) %>
</span>
</a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
collapsedSidebarLabelTemplate = _.template(
'<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
<%= _.escape(title) %>
</span>'
)
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
......@@ -83,7 +93,7 @@ class @MilestoneSelect
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
$value.css('display', '')
clicked: (selected) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
......@@ -118,12 +128,13 @@ class @MilestoneSelect
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
$value.removeAttr('style')
$value.css('display', '')
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
data.milestone.remaining = $.timefor data.milestone.due_date
$value.html(milestoneLinkTemplate(data.milestone))
$sidebarCollapsedValue.find('span').text(data.milestone.title)
$sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone))
else
$value.html(milestoneLinkNoneTemplate)
$sidebarCollapsedValue.find('span').text('No')
......
......@@ -114,9 +114,9 @@ class @Notes
@refresh()
, @pollingInterval
refresh: ->
refresh: =>
return if @refreshing is true
refreshing = true
@refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent()
......@@ -134,8 +134,8 @@ class @Notes
@renderDiscussionNote(note)
else
@renderNote(note)
always: =>
@refreshing = false
.always () =>
@refreshing = false
###
Increase @pollingInterval up to 120 seconds on every function call,
......@@ -162,13 +162,14 @@ class @Notes
renderNote: (note) ->
unless note.valid
if note.award
flash = new Flash('You have already used this award emoji!', 'alert')
flash = new Flash('You have already awarded this emoji!', 'alert')
flash.pinTo('.header-content')
return
if note.award
awardsHandler.addAwardToEmojiBar(note.note)
awardsHandler.scrollToAwards()
votesBlock = $('.js-awards-block').eq 0
gl.awardsHandler.addAwardToEmojiBar votesBlock, note.name
gl.awardsHandler.scrollToAwards()
# render note if it not present in loaded list
# or skip if rendered
......@@ -285,6 +286,7 @@ class @Notes
form.addClass "js-main-target-form"
form.find("#note_line_code").remove()
form.find("#note_type").remove()
###
General note form setup.
......@@ -328,7 +330,7 @@ class @Notes
@renderDiscussionNote(note)
# cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm($("#new-discussion-note-form-#{note.discussion_id}"))
@removeDiscussionNoteForm($(xhr.target))
###
Called in response to the edit note form being submitted
......@@ -352,8 +354,7 @@ class @Notes
Called in response to clicking the edit note link
Replaces the note text with the note edit form
Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels
Adds a data attribute to the form with the original content of the note for cancellations
###
showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault()
......@@ -369,6 +370,8 @@ class @Notes
done = ($noteText) ->
# Neat little trick to put the cursor at the end
noteTextVal = $noteText.val()
# Store the original note text in a data attribute to retrieve if a user cancels edit.
form.find('form.edit-note').data 'original-note', noteTextVal
$noteText.val('').val(noteTextVal);
new GLForm form
......@@ -391,14 +394,16 @@ class @Notes
###
Called in response to clicking the edit note link
Hides edit form
Hides edit form and restores the original note text to the editor textarea.
###
cancelEdit: (e) ->
e.preventDefault()
note = $(this).closest(".note")
form = note.find(".current-note-edit-form")
note.removeClass "is-editting"
note.find(".current-note-edit-form")
.removeClass("current-note-edit-form")
form.removeClass("current-note-edit-form")
# Replace markdown textarea text with original note text.
form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'))
###
Called in response to deleting a note of any kind.
......@@ -472,6 +477,7 @@ class @Notes
setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target
form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
form.find("#note_type").val dataHolder.data("noteType")
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")
......
@Pager =
init: (@limit = 0, preload, @disable = false) ->
init: (@limit = 0, preload, @disable = false, @callback = $.noop) ->
@loading = $('.loading').first()
if preload
......@@ -19,6 +19,7 @@
@loading.hide()
success: (data) ->
Pager.append(data.count, data.html)
Pager.callback()
dataType: "json"
append: (count, html) ->
......
......@@ -7,12 +7,17 @@ class @ProjectNew
@toggleSettingsOnclick()
toggleSettings: ->
checked = $("#project_builds_enabled").prop("checked")
if checked
$('.builds-feature').show()
else
$('.builds-feature').hide()
toggleSettings: =>
@_showOrHide('#project_builds_enabled', '.builds-feature')
@_showOrHide('#project_merge_requests_enabled', '.merge-requests-feature')
toggleSettingsOnclick: ->
$("#project_builds_enabled").on 'click', @toggleSettings
$('#project_builds_enabled, #project_merge_requests_enabled').on 'click', @toggleSettings
_showOrHide: (checkElement, container) ->
$container = $(container)
if $(checkElement).prop('checked')
$container.show()
else
$container.hide()
......@@ -10,6 +10,40 @@ class @Sidebar
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
$(document)
.off 'click', '.js-sidebar-toggle'
.on 'click', '.js-sidebar-toggle', (e, triggered) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
$allGutterToggleIcons = $('.js-sidebar-toggle i')
if $thisIcon.hasClass('fa-angle-double-right')
$allGutterToggleIcons
.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
$('aside.right-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
$allGutterToggleIcons
.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
$('aside.right-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
if not triggered
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
sidebarDropdownLoading: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
img = $sidebarCollapsedIcon.find('img')
......@@ -76,7 +110,7 @@ class @Sidebar
@triggerOpenSidebar() if not @isOpen()
if action is 'hide'
@triggerOpenSidebar() is @isOpen()
@triggerOpenSidebar() if @isOpen()
isOpen: ->
@sidebar.is('.right-sidebar-expanded')
......
......@@ -20,8 +20,7 @@ class @SearchAutocomplete
@dropdown = @wrap.find('.dropdown')
@dropdownContent = @dropdown.find('.dropdown-content')
@locationBadgeEl = @getElement('.search-location-badge')
@locationText = @getElement('.location-text')
@locationBadgeEl = @getElement('.location-badge')
@scopeInputEl = @getElement('#scope')
@searchInput = @getElement('.search-input')
@projectInputEl = @getElement('#search_project_id')
......@@ -133,7 +132,7 @@ class @SearchAutocomplete
scope: @scopeInputEl.val()
# Location badge
_location: @locationText.text()
_location: @locationBadgeEl.text()
}
bindEvents: ->
......@@ -143,23 +142,28 @@ class @SearchAutocomplete
@searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus
@clearInput.on 'click', @onClearInputClick
@locationBadgeEl.on 'click', =>
@searchInput.focus()
onDocumentClick: (e) =>
# If clicking outside the search box
# And search input is not focused
# And we are not clicking inside a suggestion
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).closest('.search-form').length
@onSearchInputBlur()
enableAutocomplete: ->
# No need to enable anything if user is not logged in
return if !gon.current_user_id
_this = @
@loadingSuggestions = false
unless @dropdown.hasClass('open')
_this = @
@loadingSuggestions = false
@dropdown.addClass('open')
@searchInput.removeClass('disabled')
@dropdown
.addClass('open')
.trigger('shown.bs.dropdown')
@searchInput.removeClass('disabled')
onSearchInputKeyDown: =>
# Saves last length of the entered text
......@@ -190,7 +194,7 @@ class @SearchAutocomplete
@disableAutocomplete()
else
# We should display the menu only when input is not empty
@enableAutocomplete()
@enableAutocomplete() if e.keyCode isnt KEYCODE.ENTER
@wrap.toggleClass 'has-value', !!e.target.value
......@@ -221,10 +225,8 @@ class @SearchAutocomplete
category = if item.category? then "#{item.category}: " else ''
value = if item.value? then item.value else ''
html = "<span class='location-badge'>
<i class='location-text'>#{category}#{value}</i>
</span>"
@locationBadgeEl.html(html)
badgeText = "#{category}#{value}"
@locationBadgeEl.text(badgeText).show()
@wrap.addClass('has-location-badge')
restoreOriginalState: ->
......@@ -233,9 +235,8 @@ class @SearchAutocomplete
for input in inputs
@getElement("##{input}").val(@originalState[input])
if @originalState._location is ''
@locationBadgeEl.empty()
@locationBadgeEl.hide()
else
@addLocationBadge(
value: @originalState._location
......@@ -244,7 +245,7 @@ class @SearchAutocomplete
@dropdown.removeClass 'open'
badgePresent: ->
@locationBadgeEl.children().length
@locationBadgeEl.length
resetSearchState: ->
inputs = Object.keys @originalState
......@@ -257,7 +258,7 @@ class @SearchAutocomplete
@getElement("##{input}").val('')
removeLocationBadge: ->
@locationBadgeEl.empty()
@locationBadgeEl.hide()
# Reset state
@resetSearchState()
......
......@@ -3,10 +3,10 @@
class @ShortcutsDashboardNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-activity'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-projects'))
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'))
@findAndFollowLink: (selector) ->
link = $(selector).attr('href')
......
......@@ -6,12 +6,8 @@ class @ShortcutsIssuable extends ShortcutsNavigation
super()
Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
Mousetrap.bind('j', =>
@prevIssue()
return false
)
Mousetrap.bind('k', =>
@nextIssue()
Mousetrap.bind('r', =>
@replyWithSelectedText()
return false
)
Mousetrap.bind('e', =>
......@@ -25,16 +21,6 @@ class @ShortcutsIssuable extends ShortcutsNavigation
else
@enabledHelp.push('.hidden-shortcut.issues')
prevIssue: ->
$prevBtn = $('.prev-btn')
if not $prevBtn.hasClass('disabled')
Turbolinks.visit($prevBtn.attr('href'))
nextIssue: ->
$nextBtn = $('.next-btn')
if not $nextBtn.hasClass('disabled')
Turbolinks.visit($nextBtn.attr('href'))
replyWithSelectedText: ->
if window.getSelection
selected = window.getSelection().toString()
......
......@@ -4,23 +4,14 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( ->
niceScrollBars = $('.nicescroll').niceScroll();
niceScrollBars.updateScrollBar();
), 300
$(document).on("click", '.toggle-nav-collapse', (e) ->
$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
e.preventDefault()
toggleSidebar()
)
$ ->
size = bp.getBreakpointSize()
if size is "xs" or size is "sm"
if $('.page-with-sidebar').hasClass(expanded)
toggleSidebar()
......@@ -19,3 +19,8 @@ class @Subscription
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
btn.find('span').text(action)
@subscription_status.find('>div').toggleClass('hidden')
if btn.attr('data-original-title')
btn.tooltip('hide')
.attr('data-original-title', action)
.tooltip('fixTitle')
# Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
#
# State Flow #1: setup -> in_progress -> authenticated -> POST to server
# State Flow #2: setup -> in_progress -> error -> setup
class @U2FAuthenticate
constructor: (@container, u2fParams) ->
@appId = u2fParams.app_id
@challenges = u2fParams.challenges
@signRequests = u2fParams.sign_requests
start: () =>
if U2FUtil.isU2FSupported()
@renderSetup()
else
@renderNotSupported()
authenticate: () =>
u2f.sign(@appId, @challenges, @signRequests, (response) =>
if response.errorCode
error = new U2FError(response.errorCode)
@renderError(error);
else
@renderAuthenticated(JSON.stringify(response))
, 10)
#############
# Rendering #
#############
templates: {
"notSupported": "#js-authenticate-u2f-not-supported",
"setup": '#js-authenticate-u2f-setup',
"inProgress": '#js-authenticate-u2f-in-progress',
"error": '#js-authenticate-u2f-error',
"authenticated": '#js-authenticate-u2f-authenticated'
}
renderTemplate: (name, params) =>
templateString = $(@templates[name]).html()
template = _.template(templateString)
@container.html(template(params))
renderSetup: () =>
@renderTemplate('setup')
@container.find('#js-login-u2f-device').on('click', @renderInProgress)
renderInProgress: () =>
@renderTemplate('inProgress')
@authenticate()
renderError: (error) =>
@renderTemplate('error', {error_message: error.message()})
@container.find('#js-u2f-try-again').on('click', @renderSetup)
renderAuthenticated: (deviceResponse) =>
@renderTemplate('authenticated')
# Prefer to do this instead of interpolating using Underscore templates
# because of JSON escaping issues.
@container.find("#js-device-response").val(deviceResponse)
renderNotSupported: () =>
@renderTemplate('notSupported')
class @U2FError
constructor: (@errorCode) ->
@httpsDisabled = (window.location.protocol isnt 'https:')
console.error("U2F Error Code: #{@errorCode}")
message: () =>
switch
when (@errorCode is u2f.ErrorCodes.BAD_REQUEST and @httpsDisabled)
"U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
when @errorCode is u2f.ErrorCodes.DEVICE_INELIGIBLE
"This device has already been registered with us."
else
"There was a problem communicating with your device."
This diff is collapsed.
# Helper class for U2F (universal 2nd factor) device registration and authentication.
class @U2FUtil
@isU2FSupported: ->
if @testMode
true
else
gon.u2f.browser_supports_u2f
@enableTestMode: ->
@testMode = true
<% if Rails.env.test? %>
U2FUtil.enableTestMode();
<% end %>
This diff is collapsed.
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require d3
#= require_tree .
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment