summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlfredo Sumaran <alfredo@gitlab.com>2016-03-29 10:48:39 -0500
committerAlfredo Sumaran <alfredo@gitlab.com>2016-03-29 10:48:39 -0500
commita41f5f59cbd1d29d0acc4b9d9782edabf81603b5 (patch)
treefcd45bd5f24902480c12179b9b2fbc04778ff0db
parent2e5cd0f1669eea56c41bb690f28a85c3f98f68ed (diff)
parent54957d6932c2b159e01b60ee1d4e191cfdf5b713 (diff)
downloadgitlab-ce-issue_3400_port.tar.gz
Merge branch 'master' into issue_3400_portissue_3400_port
# Conflicts: # app/assets/javascripts/gl_dropdown.js.coffee
-rw-r--r--CHANGELOG3
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock36
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee50
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee23
-rw-r--r--app/assets/javascripts/labels_select.js.coffee147
-rw-r--r--app/assets/javascripts/lib/animate.js.coffee13
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee90
-rw-r--r--app/assets/javascripts/users_select.js.coffee111
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss8
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/selects.scss1
-rw-r--r--app/assets/stylesheets/pages/commits.scss1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss21
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss6
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/controllers/ci/projects_controller.rb10
-rw-r--r--app/controllers/projects/issues_controller.rb13
-rw-r--r--app/controllers/projects/merge_requests_controller.rb5
-rw-r--r--app/controllers/projects/milestones_controller.rb1
-rw-r--r--app/controllers/projects/snippets_controller.rb6
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb10
-rw-r--r--app/models/ability.rb56
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/views/ci/projects/index.html.haml20
-rw-r--r--app/views/projects/buttons/_fork.html.haml2
-rw-r--r--app/views/projects/find_file/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml4
-rw-r--r--app/views/shared/issuable/_filter.html.haml10
-rw-r--r--app/views/shared/issuable/_form.html.haml6
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml50
-rw-r--r--db/migrate/20130218141258_convert_closed_to_state_in_issue.rb18
-rw-r--r--db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb22
-rw-r--r--db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb18
-rw-r--r--db/migrate/20130220125544_convert_merge_status_in_merge_request.rb22
-rw-r--r--db/migrate/20130419190306_allow_merges_for_forks.rb8
-rw-r--r--doc/README.md4
-rw-r--r--doc/administration/auth/README.md11
-rw-r--r--doc/administration/auth/ldap.md277
-rw-r--r--doc/api/issues.md1
-rw-r--r--doc/integration/github.md2
-rw-r--r--doc/integration/ldap.md227
-rw-r--r--doc/update/8.5-to-8.6.md42
-rw-r--r--doc/web_hooks/web_hooks.md9
-rw-r--r--lib/api/issues.rb16
-rw-r--r--spec/controllers/ci/projects_controller_spec.rb21
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb107
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb2
-rw-r--r--spec/features/issues_spec.rb77
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb78
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb63
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb93
-rw-r--r--spec/models/project_spec.rb6
-rw-r--r--spec/models/repository_spec.rb6
-rw-r--r--spec/requests/api/issues_spec.rb11
-rw-r--r--vendor/assets/stylesheets/animate.css11
61 files changed, 1347 insertions, 533 deletions
diff --git a/CHANGELOG b/CHANGELOG
index c9527e6627e..3289dc99ef9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,8 +1,10 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
+ - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu)
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
+ - Allow back dating on issues when created through the API
- Fix avatar stretching by providing a cropping feature
- Add links to CI setup documentation from project settings and builds pages
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
@@ -10,6 +12,7 @@ v 8.7.0 (unreleased)
v 8.6.2 (unreleased)
- Comments on confidential issues don't show up in activity feed to non-members
+ - Fix NoMethodError when visiting CI root path at `/ci`
v 8.6.1
- Add option to reload the schema before restoring a database backup. !2807
diff --git a/Gemfile b/Gemfile
index cebd76e7370..006e53e0c10 100644
--- a/Gemfile
+++ b/Gemfile
@@ -234,7 +234,7 @@ end
group :development do
gem "foreman"
- gem 'brakeman', '~> 3.1.0', require: false
+ gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.6.0"
gem "letter_opener", '~> 1.1.2'
@@ -279,7 +279,7 @@ group :development, :test do
gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0'
- gem 'teaspoon', '~> 1.0.0'
+ gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.6.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 16c09ab6e6d..da27c62acbf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -84,21 +84,19 @@ GEM
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
- brakeman (3.1.4)
+ brakeman (3.2.1)
erubis (~> 2.6)
- fastercsv (~> 1.5)
haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0)
- multi_json (~> 1.2)
- ruby2ruby (>= 2.1.1, < 2.3.0)
- ruby_parser (~> 3.7.0)
+ ruby2ruby (~> 2.3.0)
+ ruby_parser (~> 3.8.1)
safe_yaml (>= 1.0)
sass (~> 3.0)
slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4)
browser (1.0.1)
builder (3.2.2)
- bullet (4.14.10)
+ bullet (5.0.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0)
bundler-audit (0.4.0)
@@ -208,7 +206,6 @@ GEM
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
- fastercsv (1.5.5)
ffaker (2.0.0)
ffi (1.9.10)
fission (0.5.0)
@@ -328,8 +325,8 @@ GEM
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
- font-awesome-rails (4.5.0.0)
- railties (>= 3.2, < 5.0)
+ font-awesome-rails (4.5.0.1)
+ railties (>= 3.2, < 5.1)
foreman (0.78.0)
thor (~> 0.19.1)
formatador (0.2.5)
@@ -706,10 +703,10 @@ GEM
ruby-saml (1.1.2)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
- ruby2ruby (2.2.0)
+ ruby2ruby (2.3.0)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
- ruby_parser (3.7.2)
+ ruby_parser (3.8.1)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
@@ -718,7 +715,7 @@ GEM
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
- sass (3.4.20)
+ sass (3.4.21)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
@@ -742,7 +739,7 @@ GEM
sentry-raven (0.15.6)
faraday (>= 0.7.6)
settingslogic (2.0.9)
- sexp_processor (4.6.0)
+ sexp_processor (4.7.0)
sham_rack (1.3.6)
rack
shoulda-matchers (2.8.0)
@@ -806,8 +803,8 @@ GEM
systemu (2.6.5)
task_list (1.0.2)
html-pipeline
- teaspoon (1.0.2)
- railties (>= 3.2.5, < 5)
+ teaspoon (1.1.5)
+ railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.6)
@@ -868,7 +865,7 @@ GEM
equalizer (~> 0.0, >= 0.0.9)
warden (1.2.4)
rack (>= 1.0)
- web-console (2.2.1)
+ web-console (2.3.0)
activemodel (>= 4.0)
binding_of_caller (>= 0.7.2)
railties (>= 4.0)
@@ -910,7 +907,7 @@ DEPENDENCIES
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
- brakeman (~> 3.1.0)
+ brakeman (~> 3.2.0)
browser (~> 1.0.0)
bullet
bundler-audit
@@ -1048,7 +1045,7 @@ DEPENDENCIES
sprockets (~> 3.3.5)
state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2)
- teaspoon (~> 1.0.0)
+ teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2)
thin (~> 1.6.1)
@@ -1064,3 +1061,6 @@ DEPENDENCIES
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
+
+BUNDLED WITH
+ 1.11.2
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 1810779c40a..85dcd493e53 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -152,8 +152,10 @@ class GitLabDropdown
@selectFirstRow()
# Event listeners
+
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
+ @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
if @dropdown.find(".dropdown-toggle-page").length
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
@@ -169,10 +171,11 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) ->
+ e.preventDefault()
self.rowClicked $(@)
if self.options.clicked
- self.options.clicked()
+ self.options.clicked.call(@,e)
# Finds an element inside wrapper element
getElement: (selector) ->
@@ -206,6 +209,15 @@ class GitLabDropdown
@appendMenu(full_html)
+ shouldPropagate: (e) =>
+ if @options.multiSelect
+ $target = $(e.target)
+ if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
+ e.stopPropagation()
+ return false
+ else
+ return true
+
opened: =>
contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is ""
@@ -214,7 +226,7 @@ class GitLabDropdown
if @options.filterable
@filterInput.focus()
- hidden: =>
+ hidden: (e) =>
if @options.filterable
@dropdown
.find(".dropdown-input-field")
@@ -225,6 +237,9 @@ class GitLabDropdown
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
+ if @options.hidden
+ @options.hidden.call(@,e)
+
# Render the full menu
renderMenu: (html) ->
@@ -262,7 +277,12 @@ class GitLabDropdown
# Call the render function
html = @options.renderRow(data)
else
- selected = if @options.isSelected then @options.isSelected(data) else false
+ if not selected
+ value = if @options.id then @options.id(data) else data.id
+ fieldName = @options.fieldName
+ field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
+ if field.length
+ selected = true
# Set URL
if @options.url?
@@ -315,26 +335,28 @@ class GitLabDropdown
rowClicked: (el) ->
fieldName = @options.fieldName
- field = @dropdown.parent().find("input[name='#{fieldName}']")
-
+ selectedIndex = el.parent().index()
+ if @renderedData
+ selectedObject = @renderedData[selectedIndex]
+ value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
+ field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS)
+ el.removeClass(ACTIVE_CLASS)
field.remove()
else
fieldName = @options.fieldName
selectedIndex = el.parent().index()
if @renderedData
selectedObject = @renderedData[selectedIndex]
+ selectedObject.selected = true
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
if !value?
field.remove()
- if @options.multiSelect
- oldValue = field.val()
- if oldValue
- value = "#{oldValue},#{value}"
- else
+ if not @options.multiSelect
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
+ @dropdown.parent().find("input[name='#{fieldName}']").remove()
# Toggle active class for the tick mark
el.toggleClass "is-active"
@@ -342,15 +364,15 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
-
if value?
if !field.length
# Create hidden input for form
- input = "<input type='hidden' name='#{fieldName}' />"
+ input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
+ if @options.inputId?
+ input = $(input)
+ .attr('id', @options.inputId)
@dropdown.before input
- @dropdown.parent().find("input[name='#{fieldName}']").val value
-
selectFirstRow: ->
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index d6d09b36d8d..6fc924d3d66 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -1,8 +1,7 @@
class @IssuableContext
- constructor: ->
+ constructor: (currentUser) ->
@initParticipants()
-
- new UsersSelect()
+ new UsersSelect(currentUser)
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$(".issuable-sidebar .inline-update").on "change", "select", ->
@@ -11,10 +10,20 @@ class @IssuableContext
$(this).submit()
$(document).on "click",".edit-link", (e) ->
- block = $(@).parents('.block')
- block.find('.selectbox').show()
- block.find('.value').hide()
- block.find('.js-select2').select2("open")
+ $block = $(@).parents('.block')
+ $selectbox = $block.find('.selectbox')
+ if $selectbox.is(':visible')
+ $selectbox.hide()
+ $block.find('.value').show()
+ else
+ $selectbox.show()
+ $block.find('.value').hide()
+
+ if $selectbox.is(':visible')
+ setTimeout (->
+ $block.find('.dropdown-menu-toggle').trigger 'click'
+ ), 0
+
$(".right-sidebar").niceScroll()
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index e08648d583b..b5c7af9a8ad 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -4,14 +4,20 @@ class @LabelsSelect
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
labelUrl = $dropdown.data('labels')
+ issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected')
- if selectedLabel
- selectedLabel = selectedLabel.toString().split(',')
+ if selectedLabel?
+ selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
showNo = $dropdown.data('show-no')
showAny = $dropdown.data('show-any')
defaultLabel = $dropdown.data('default-label')
+ abilityName = $dropdown.data('ability-name')
+ $selectbox = $dropdown.closest('.selectbox')
+ $block = $selectbox.closest('.block')
+ $value = $block.find('.value')
+ $loading = $block.find('.block-loading').fadeOut()
if newLabelField.length
$newLabelCreateButton = $('.js-new-label-btn')
@@ -21,6 +27,22 @@ class @LabelsSelect
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on 'click', (e) ->
+
+ issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
+ if issueUpdateURL
+ labelHTMLTemplate = _.template(
+ '<% _.each(labels, function(label){ %>
+ <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
+ <span class="label color-label" style="background-color: <%= label.color %>;">
+ <%= label.title %>
+ </span>
+ </a>
+ <% }); %>'
+ );
+ labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
+
+ if newLabelField.length and $dropdown.hasClass 'js-extra-options'
+ $('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
newColorField
@@ -57,6 +79,23 @@ class @LabelsSelect
# This allows us to enable the button when ready
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
+ $newLabelError.hide()
+ $('.js-new-label-btn').disable()
+
+ # Create new label with API
+ Api.newLabel projectId, {
+ name: newLabelField.val()
+ color: newColorField.val()
+ }, (label) ->
+ $('.js-new-label-btn').enable()
+
+ if label.message?
+ $newLabelError
+ .text label.message
+ .show()
+ else
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
@@ -90,41 +129,78 @@ class @LabelsSelect
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+ saveLabelData = ->
+ selected = $dropdown
+ .closest('.selectbox')
+ .find("input[name='#{$dropdown.data('field-name')}']")
+ .map(->
+ @value
+ ).get()
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].label_ids = selected
+ if not selected.length
+ data[abilityName].label_ids = ['']
+ $loading.fadeIn()
+ $.ajax(
+ type: 'PUT'
+ url: issueUpdateURL
+ dataType: 'JSON'
+ data: data
+ ).done (data) ->
+ $loading.fadeOut()
+ $selectbox.hide()
+ data.issueURLSplit = issueURLSplit
+ if not data.labels.length
+ template = labelNoneHTMLTemplate()
+ else
+ template = labelHTMLTemplate(data)
+ href = $value
+ .show()
+ .html(template)
+ $value
+ .find('a')
+ .each((i) ->
+ setTimeout(=>
+ glAnimate($(@), 'pulse')
+ ,200 * i
+ )
+ )
+
+
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: labelUrl
).done (data) ->
- if showNo
- data.unshift(
- id: 0
- title: 'No Label'
- )
-
- if showAny
- data.unshift(
- isAny: true
- title: 'Any Label'
- )
-
- if data.length > 2
- data.splice 2, 0, 'divider'
-
+ if $dropdown.hasClass 'js-extra-options'
+ if showNo
+ data.unshift(
+ id: 0
+ title: 'No Label'
+ )
+
+ if showAny
+ data.unshift(
+ isAny: true
+ title: 'Any Label'
+ )
+
+ if data.length > 2
+ data.splice 2, 0, 'divider'
callback data
+
renderRow: (label) ->
- if $.isArray(selectedLabel)
- selected = ''
- $.each selectedLabel, (i, selectedLbl) ->
- selectedLbl = selectedLbl.trim()
- if selected is '' and label.title is selectedLbl
- selected = 'is-active'
- else
- selected = if label.title is selectedLabel then 'is-active' else ''
+ selectedClass = ''
+ if $selectbox.find("input[type='hidden']\
+ [name='#{$dropdown.data('field-name')}']\
+ [value='#{label.id}']").length
+ selectedClass = 'is-active'
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
"<li>
- <a href='#' class='#{selected}'>
+ <a href='#' class='#{selectedClass}'>
#{color}
#{label.title}
</a>
@@ -133,6 +209,7 @@ class @LabelsSelect
search:
fields: ['title']
selectable: true
+
toggleLabel: (selected) ->
if selected and selected.title isnt 'Any Label'
selected.title
@@ -142,8 +219,19 @@ class @LabelsSelect
id: (label) ->
if label.isAny?
''
- else
+ else if $dropdown.hasClass "js-filter-submit"
label.title
+ else
+ label.id
+
+ hidden: ->
+ $selectbox.hide()
+ $value.show()
+ if $dropdown.hasClass 'js-multiselect'
+ saveLabelData()
+
+ multiSelect: $dropdown.hasClass 'js-multiselect'
+
clicked: ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
@@ -153,4 +241,9 @@ class @LabelsSelect
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
+ else
+ if $dropdown.hasClass 'js-multiselect'
+ return
+ else
+ saveLabelData()
)
diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee
new file mode 100644
index 00000000000..8f892b5a2b9
--- /dev/null
+++ b/app/assets/javascripts/lib/animate.js.coffee
@@ -0,0 +1,13 @@
+((w) ->
+
+ w.glAnimate = ($el, animation, done) ->
+ $el
+ .removeClass()
+ .addClass(animation + ' animated')
+ .one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
+ $(this).removeClass()
+ return
+ return
+ return
+
+) window \ No newline at end of file
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index e17a1adb648..d1746c38e74 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -1,35 +1,52 @@
class @MilestoneSelect
- constructor: ->
+ constructor: (currentProject) ->
+ if currentProject?
+ _this = @
+ @currentProject = JSON.parse(currentProject)
$('.js-milestone-select').each (i, dropdown) ->
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
milestonesUrl = $dropdown.data('milestones')
+ issueUpdateURL = $dropdown.data('issueUpdate')
selectedMilestone = $dropdown.data('selected')
showNo = $dropdown.data('show-no')
showAny = $dropdown.data('show-any')
useId = $dropdown.data('use-id')
defaultLabel = $dropdown.data('default-label')
+ issuableId = $dropdown.data('issuable-id')
+ abilityName = $dropdown.data('ability-name')
+ $selectbox = $dropdown.closest('.selectbox')
+ $block = $selectbox.closest('.block')
+ $value = $block.find('.value')
+ $loading = $block.find('.block-loading').fadeOut()
+
+ if issueUpdateURL
+ milestoneLinkTemplate = _.template(
+ '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
+ )
+
+ milestoneLinkNoneTemplate = '<div class="light">None</div>'
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: milestonesUrl
).done (data) ->
- if showNo
- data.unshift(
- id: '0'
- title: 'No Milestone'
- )
-
- if showAny
- data.unshift(
- isAny: true
- title: 'Any Milestone'
- )
+ if $dropdown.hasClass "js-extra-options"
+ if showNo
+ data.unshift(
+ id: '0'
+ title: 'No Milestone'
+ )
- if data.length > 2
- data.splice 2, 0, 'divider'
+ if showAny
+ data.unshift(
+ isAny: true
+ title: 'Any Milestone'
+ )
+ if data.length > 2
+ data.splice 2, 0, 'divider'
callback(data)
filterable: true
search:
@@ -53,13 +70,38 @@ class @MilestoneSelect
milestone.id
isSelected: (milestone) ->
milestone.title is selectedMilestone
- clicked: ->
- page = $('body').data 'page'
- isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is page is 'projects:merge_requests:index'
-
- if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- Issues.filterResults $dropdown.closest('form')
- else if $dropdown.hasClass 'js-filter-submit'
- $dropdown.closest('form').submit()
- )
+ hidden: ->
+ $selectbox.hide()
+ $value.show()
+ clicked: (e) ->
+ if $dropdown.hasClass 'js-filter-bulk-update'
+ return
+
+ if $dropdown.hasClass 'js-filter-submit'
+ $dropdown.parents('form').submit()
+ else
+ selected = $selectbox
+ .find('input[type="hidden"]')
+ .val()
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].milestone_id = selected
+ $loading
+ .fadeIn()
+ $.ajax(
+ type: 'PUT'
+ url: issueUpdateURL
+ data: data
+ ).done (data) ->
+ $loading.fadeOut()
+ $selectbox.hide()
+ $milestoneLink = $value
+ .show()
+ .find('a')
+ if data.milestone?
+ data.milestone.namespace = _this.currentProject.namespace
+ data.milestone.path = _this.currentProject.path
+ $value.html(milestoneLinkTemplate(data.milestone))
+ else
+ $value.html(milestoneLinkNoneTemplate)
+ ) \ No newline at end of file
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 84193400890..3262d8b8c90 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,7 +1,9 @@
class @UsersSelect
- constructor: ->
+ constructor: (currentUser) ->
@usersPath = "/autocomplete/users.json"
@userPath = "/autocomplete/users/:id.json"
+ if currentUser?
+ @currentUser = JSON.parse(currentUser)
$('.js-user-search').each (i, dropdown) =>
$dropdown = $(dropdown)
@@ -12,6 +14,67 @@ class @UsersSelect
firstUser = $dropdown.data('first-user')
selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label')
+ issueURL = $dropdown.data('issueUpdate')
+ $selectbox = $dropdown.closest('.selectbox')
+ $block = $selectbox.closest('.block')
+ abilityName = $dropdown.data('ability-name')
+ $value = $block.find('.value')
+ $loading = $block.find('.block-loading').fadeOut()
+
+ $block.on('click', '.js-assign-yourself', (e) =>
+ e.preventDefault()
+ assignTo(@currentUser.id)
+ )
+
+ assignTo = (selected) ->
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].assignee_id = selected
+ $loading
+ .fadeIn()
+ $.ajax(
+ type: 'PUT'
+ dataType: 'json'
+ url: issueURL
+ data: data
+ ).done (data) ->
+ $loading.fadeOut()
+ $selectbox.hide()
+
+ if data.assignee
+ user =
+ name: data.assignee.name
+ username: data.assignee.username
+ avatar: data.assignee.avatar_url
+ else
+ user =
+ name: 'Unassigned'
+ username: ''
+ avatar: ''
+
+ $value.html(noAssigneeTemplate(user))
+ $value.find('a').attr('href')
+
+ noAssigneeTemplate = _.template(
+ '<% if (username) { %>
+ <a class="author_link " href="/u/<%= username %>">
+ <% if( avatar ) { %>
+ <img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
+ <% } %>
+ <span class="author"><%= name %></span>
+ <span class="username">
+ @<%= username %>
+ </span>
+ </a>
+ <% } else { %>
+ <span class="assign-yourself">
+ No assignee -
+ <a href="#" class="js-assign-yourself">
+ assign yourself
+ </a>
+ </span>
+ <% } %>'
+ )
$dropdown.glDropdown(
data: (term, callback) =>
@@ -57,20 +120,36 @@ class @UsersSelect
fields: ['name', 'username']
selectable: true
fieldName: $dropdown.data('field-name')
+
toggleLabel: (selected) ->
if selected && 'id' of selected
selected.name
else
defaultLabel
+
+ inputId: 'issue_assignee_id'
+
+ hidden: (e) ->
+ $selectbox.hide()
+ $value.show()
+
clicked: ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
+ if $dropdown.hasClass('js-filter-bulk-update')
+ return
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
+ else
+ selected = $dropdown
+ .closest('.selectbox')
+ .find("input[name='#{$dropdown.data('field-name')}']").val()
+ assignTo(selected)
+
renderRow: (user) ->
username = if user.username then "@#{user.username}" else ""
avatar = if user.avatar_url then user.avatar_url else false
@@ -87,17 +166,25 @@ class @UsersSelect
if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
- "<li>
- <a href='#' class='dropdown-menu-user-link #{selected}'>
- #{img}
- <strong class='dropdown-menu-user-full-name'>
- #{user.name}
- </strong>
- <span class='dropdown-menu-user-username'>
- #{username}
- </span>
- </a>
- </li>"
+ # split into three parts so we can remove the username section if nessesary
+ listWithName = "<li>
+ <a href='#' class='dropdown-menu-user-link #{selected}'>
+ #{img}
+ <strong class='dropdown-menu-user-full-name'>
+ #{user.name}
+ </strong>"
+
+ listWithUserName = "<span class='dropdown-menu-user-username'>
+ #{username}
+ </span>"
+ listClosingTags = "</a>
+ </li>"
+
+
+ if username is ''
+ listWithUserName = ''
+
+ listWithName + listWithUserName + listClosingTags
)
$('.ajax-users-select').each (i, select) =>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index e2d590f4df4..69b3b6586de 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -10,6 +10,7 @@
*= require dropzone/basic
*= require cal-heatmap
*= require cropper.css
+ *= require animate
*/
/*
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 182c06eba66..82dc1acbd01 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -144,6 +144,10 @@
background-color: $dropdown-empty-row-bg;
}
}
+
+ &.dropdown-menu-user-link {
+ line-height: 16px;
+ }
}
.dropdown-header {
@@ -190,13 +194,13 @@
}
.dropdown-menu-user-link {
- padding-top: 7px;
+ padding-top: 10px;
padding-bottom: 7px;
}
.dropdown-menu-user-full-name {
display: block;
- font-weight: 600;
+ font-weight: 500;
line-height: 16px;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index ad0e88cda86..a26ace5cc19 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -3,12 +3,10 @@
*
*/
.file-holder {
- border: none;
border: 1px solid $border-color;
&.readme-holder {
- margin-top: 10px;
- border-bottom: 0;
+ margin: $gl-padding-top 0;
}
table {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index fa7944cdabe..e82d052f45a 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -44,6 +44,7 @@
@include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
@include border-radius ($border-radius-default);
border: none;
+ min-width: 175px;
}
.select2-results .select2-result-label {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 5e91496679a..b6011fe7679 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -93,7 +93,6 @@ li.commit {
.commit-row-info {
color: $gl-gray;
line-height: 24px;
- font-size: 13px;
a {
color: $gl-gray;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 5300bb52a1b..88c1b614c74 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -30,6 +30,10 @@
}
.issuable-sidebar {
+ a {
+ color: inherit;
+ }
+
.block {
@include clearfix;
padding: $gl-padding 0;
@@ -89,7 +93,7 @@
}
.cross-project-reference {
- color: $gl-link-color;
+ color: inherit;
span {
white-space: nowrap;
@@ -133,6 +137,12 @@
.value {
line-height: 1;
+
+ .assign-yourself {
+ margin-top: 10px;
+ font-weight: normal;
+ display: block;
+ }
}
.bold {
@@ -252,6 +262,15 @@
text-decoration: none;
}
}
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ padding-top: 6px;
+ }
+
+ .open .dropdown-menu {
+ width: 100%;
+ }
}
.btn-default.gutter-toggle {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index cee5c47cfb2..7ff63ca20b6 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -230,3 +230,9 @@
}
}
}
+
+.builds {
+ .table-holder {
+ overflow-x: scroll;
+ }
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index daf2651425f..655f88b0c2c 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -71,8 +71,6 @@
}
.note-form-actions {
- background: #fff;
-
.note-form-option {
margin-top: 8px;
margin-left: 30px;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 71bde1174ee..4e6aa8cd1a6 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -162,7 +162,7 @@
margin-right: 12px;
a {
- margin: -1px !important;
+ margin: -1px;
}
}
@@ -222,7 +222,7 @@
padding: 0;
background: transparent;
border: none;
- line-height: 42px;
+ line-height: 36px;
margin: 0;
> li + li:before {
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 081e01a75e0..8bf71a1adbb 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,11 +1,15 @@
module Ci
class ProjectsController < Ci::ApplicationController
before_action :project
- before_action :authorize_read_project!, except: [:badge]
before_action :no_cache, only: [:badge]
+ before_action :authorize_read_project!, except: [:badge, :index]
skip_before_action :authenticate_user!, only: [:badge]
protect_from_forgery
+ def index
+ redirect_to root_path
+ end
+
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.namespace, project)
@@ -35,5 +39,9 @@ module Ci
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
+
+ def authorize_read_project!
+ return access_denied! unless can?(current_user, :read_project, project)
+ end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 877b39c9b1b..6d649e72f84 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -68,7 +68,13 @@ class Projects::IssuesController < Projects::ApplicationController
@merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
- respond_with(@issue)
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: @issue.to_json(include: [:milestone, :labels])
+ end
+ end
+
end
def create
@@ -107,10 +113,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
- render json: {
- saved: @issue.valid?,
- assignee_avatar_url: @issue.assignee.try(:avatar_url)
- }
+ render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
end
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index b830d777752..6189de09f27 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -154,10 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.target_project, @merge_request])
end
format.json do
- render json: {
- saved: @merge_request.valid?,
- assignee_avatar_url: @merge_request.assignee.try(:avatar_url)
- }
+ render json: @merge_request.to_json(include: [:milestone, :labels, :assignee])
end
end
else
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index b2e974eff17..5b0a63a933c 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -19,7 +19,6 @@ class Projects::MilestonesController < Projects::ApplicationController
end
@milestones = @milestones.includes(:project)
-
respond_to do |format|
format.html do
@milestones = @milestones.page(params[:page])
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index b578b419a46..6d2901a24a4 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -3,7 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read any snippet
- before_action :authorize_read_project_snippet!
+ before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
# Allow write(create) snippet
before_action :authorize_create_project_snippet!, only: [:new, :create]
@@ -81,6 +81,10 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet ||= @project.snippets.find(params[:id])
end
+ def authorize_read_project_snippet!
+ return render_404 unless can?(current_user, :read_project_snippet, @snippet)
+ end
+
def authorize_update_project_snippet!
return render_404 unless can?(current_user, :update_project_snippet, @snippet)
end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 316a10b7da3..14697f774cc 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -60,7 +60,7 @@ module DropdownsHelper
title_output << content_tag(:span, title)
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
- icon('times')
+ icon('times', class: 'dropdown-menu-close-icon')
end
title_output.html_safe
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 81df2094392..62050691a39 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -16,6 +16,16 @@ module IssuablesHelper
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
end
+ def issuable_json_path(issuable)
+ project = issuable.project
+
+ if issuable.kind_of?(MergeRequest)
+ namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
+ else
+ namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
+ end
+ end
+
def prev_issuable_for(issuable)
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index fa2345f6faa..c0bf6def7c5 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -27,6 +27,8 @@ class Ability
case true
when subject.is_a?(PersonalSnippet)
anonymous_personal_snippet_abilities(subject)
+ when subject.is_a?(ProjectSnippet)
+ anonymous_project_snippet_abilities(subject)
when subject.is_a?(CommitStatus)
anonymous_commit_status_abilities(subject)
when subject.is_a?(Project) || subject.respond_to?(:project)
@@ -100,6 +102,14 @@ class Ability
end
end
+ def anonymous_project_snippet_abilities(snippet)
+ if snippet.public?
+ [:read_project_snippet]
+ else
+ []
+ end
+ end
+
def global_abilities(user)
rules = []
rules << :create_group if user.can_create_group
@@ -338,24 +348,22 @@ class Ability
end
end
- [:note, :project_snippet].each do |name|
- define_method "#{name}_abilities" do |user, subject|
- rules = []
-
- if subject.author == user
- rules += [
- :"read_#{name}",
- :"update_#{name}",
- :"admin_#{name}"
- ]
- end
+ def note_abilities(user, note)
+ rules = []
- if subject.respond_to?(:project) && subject.project
- rules += project_abilities(user, subject.project)
- end
+ if note.author == user
+ rules += [
+ :read_note,
+ :update_note,
+ :admin_note
+ ]
+ end
- rules
+ if note.respond_to?(:project) && note.project
+ rules += project_abilities(user, note.project)
end
+
+ rules
end
def personal_snippet_abilities(user, snippet)
@@ -376,6 +384,24 @@ class Ability
rules
end
+ def project_snippet_abilities(user, snippet)
+ rules = []
+
+ if snippet.author == user || user.admin?
+ rules += [
+ :read_project_snippet,
+ :update_project_snippet,
+ :admin_project_snippet
+ ]
+ end
+
+ if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user))
+ rules << :read_project_snippet
+ end
+
+ rules
+ end
+
def group_member_abilities(user, subject)
rules = []
target_user = subject.user
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 908d765fb47..c07e8072043 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -889,6 +889,8 @@ class Repository
end
def avatar
+ return nil unless exists?
+
@avatar ||= cache.fetch(:avatar) do
AVATAR_FILES.find do |file|
blob_at_branch('master', file)
diff --git a/app/views/ci/projects/index.html.haml b/app/views/ci/projects/index.html.haml
deleted file mode 100644
index 9c2290bc4a5..00000000000
--- a/app/views/ci/projects/index.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.wiki
- %h1
- GitLab CI is now integrated in GitLab UI
- %h2 For existing projects
-
- %p
- Check the following pages to find the CI status you're looking for:
-
- %ul
- %li Projects page - shows CI status for each project.
- %li Project commits page - show CI status for each commit.
-
-
-
- %h2 For new projects
-
- %p
- If you want to enable CI for a new project it is easy as adding
- = link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
- file to your repository
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 88cbb7c03c5..5fb5fe5af2f 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -12,7 +12,7 @@
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
- %div.count-with-arrow
+ = link_to namespace_project_forks_path(@project.namespace, @project), class: 'count-with-arrow' do
%span.arrow
%span.count
= @project.forks_count
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 905f6bbbd48..1fe1d98bf13 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -2,7 +2,7 @@
- header_title project_title(@project, "Files", project_files_path(@project))
.file-finder-holder.tree-holder.clearfix
- .gray-content-block.top-block
+ .nav-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'find_file', path: @path
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index ee5b9fd95a8..1dd8f721f7e 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -10,7 +10,7 @@
.merge-request{'data-url' => merge_request_path(@merge_request)}
= render "projects/merge_requests/show/mr_title"
- .merge-request-details.issuable-details
+ .merge-request-details.issuable-details{data: {id: @merge_request.project.id}}
= render "projects/merge_requests/show/mr_box"
.append-bottom-default.mr-source-target.prepend-top-default
- if @merge_request.open?
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index ba69569b1e7..1c5f8b3928b 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -15,11 +15,11 @@
- if current_user
%li
- if !on_top_of_branch?
- %span.btn.btn-sm.add-to-tree.disabled.has-tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
+ %span.btn.add-to-tree.disabled.has-tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
= icon('plus')
- else
%span.dropdown
- %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
- if can_edit_tree?
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index f91ff0e3694..c99da92be9f 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -9,13 +9,13 @@
.filter-item.inline
- if params[:author_id]
= hidden_field_tag(:author_id, params[:author_id])
- = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
+ = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- if params[:assignee_id]
= hidden_field_tag(:assignee_id, params[:assignee_id])
- = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee",
+ = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
@@ -23,7 +23,6 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown"
-
.pull-right
= render 'shared/sort_dropdown'
@@ -38,11 +37,10 @@
%li
%a{href: "#", data: {id: "close"}} Closed
.filter-item.inline
- = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
+ = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
.filter-item.inline
- = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
- placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
+ = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 6a42a6e3f58..e2a9e5bfb92 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -14,7 +14,7 @@
- if issuable.is_a?(MergeRequest)
%p.help-block
.js-wip-explanation
- %a.js-toggle-wip{href: ""}
+ %a.js-toggle-wip{href: "", tabindex: -1}
Remove the
%code WIP:
prefix from the title
@@ -22,7 +22,7 @@
%strong Work In Progress
merge request to be merged when it's ready.
.js-no-wip-explanation
- %a.js-toggle-wip{href: ""}
+ %a.js-toggle-wip{href: "", tabindex: -1}
Start the title with
%code WIP:
to prevent a
@@ -127,7 +127,7 @@
for this project.
- if issuable.new_record?
- = link_to 'Cancel', polymorphic_path([@project.namespace, @project, issuable.class]), class: 'btn btn-cancel'
+ = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
.pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 0434506c8d7..1c79494f816 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -1,6 +1,6 @@
- if params[:milestone_title]
= hidden_field_tag(:milestone_title, params[:milestone_title])
-= dropdown_tag(h(params[:milestone_title].presence || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
+= dropdown_tag(h(params[:milestone_title].presence || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit js-extra-options', filter: true, dropdown_class: "dropdown-menu-selectable",
placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if @project
%ul.dropdown-footer-list
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 0e20e86356d..451c64da2c4 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -28,6 +28,7 @@
= icon('user')
.title.hide-collapsed
Assignee
+ = icon('spinner spin', class: 'block-loading')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed
@@ -39,10 +40,14 @@
%span.username
= issuable.assignee.to_reference
- else
- .light None
+ %span.assign-yourself
+ No assignee -
+ %a.js-assign-yourself{ href: '#' }
+ assign yourself
.selectbox.hide-collapsed
- = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
+ = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
+ = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone
.sidebar-collapsed-icon
@@ -54,6 +59,7 @@
No
.title.hide-collapsed
Milestone
+ = icon('spinner spin', class: 'block-loading')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed
@@ -62,10 +68,10 @@
= issuable.milestone.title
- else
.light None
+
.selectbox.hide-collapsed
- = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
- = hidden_field_tag :issuable_context
- = f.submit class: 'btn hide'
+ = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
+ = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }})
- if issuable.project.labels.any?
.block.labels
@@ -75,6 +81,7 @@
= issuable.labels.count
.title.hide-collapsed
Labels
+ = icon('spinner spin', class: 'block-loading')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) }
@@ -84,8 +91,31 @@
- else
.light None
.selectbox.hide-collapsed
- = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
+ - issuable.labels.each do |label|
+ = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
+ .dropdown
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}}
+ %span.dropdown-toggle-text
+ Label
+ = icon('chevron-down')
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-page-one
+ = dropdown_title("Assign labels")
+ = dropdown_filter("Search labels")
+ = dropdown_content
+ - if @project
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_label, @project
+ %li
+ %a.dropdown-toggle-page{href: "#"}
+ Create new
+ %li
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
@@ -116,5 +146,7 @@
= clipboard_button(clipboard_text: project_ref)
:javascript
- new Subscription('.subscription');
- new IssuableContext();
+ new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
+ new LabelsSelect();
+ new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
+ new Subscription('.subscription')
diff --git a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
index 9fa96203ffd..99289166e81 100644
--- a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
+++ b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
@@ -1,14 +1,18 @@
class ConvertClosedToStateInIssue < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
- Issue.transaction do
- Issue.where(closed: true).update_all(state: :closed)
- Issue.where(closed: false).update_all(state: :opened)
- end
+ execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value}"
+ execute "UPDATE #{table_name} SET state = 'opened' WHERE closed = #{false_value}"
end
def down
- Issue.transaction do
- Issue.where(state: :closed).update_all(closed: true)
- end
+ execute "UPDATE #{table_name} SET closed = #{true_value} WHERE state = 'closed'"
+ end
+
+ private
+
+ def table_name
+ Issue.table_name
end
end
diff --git a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
index ebb7ae585e6..bd1e016d679 100644
--- a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
+++ b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
@@ -1,16 +1,20 @@
class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
- MergeRequest.transaction do
- MergeRequest.where(closed: true, merged: true).update_all(state: :merged)
- MergeRequest.where(closed: true, merged: false).update_all(state: :closed)
- MergeRequest.where(closed: false).update_all(state: :opened)
- end
+ execute "UPDATE #{table_name} SET state = 'merged' WHERE closed = #{true_value} AND merged = #{true_value}"
+ execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value} AND merged = #{false_value}"
+ execute "UPDATE #{table_name} SET state = 'opened' WHERE closed = #{false_value}"
end
def down
- MergeRequest.transaction do
- MergeRequest.where(state: :closed).update_all(closed: true)
- MergeRequest.where(state: :merged).update_all(closed: true, merged: true)
- end
+ execute "UPDATE #{table_name} SET closed = #{true_value} WHERE state = 'closed'"
+ execute "UPDATE #{table_name} SET closed = #{true_value}, merged = #{true_value} WHERE state = 'merged'"
+ end
+
+ private
+
+ def table_name
+ MergeRequest.table_name
end
end
diff --git a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
index 1978ea89153..d1174bc3d98 100644
--- a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
+++ b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
@@ -1,14 +1,18 @@
class ConvertClosedToStateInMilestone < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
- Milestone.transaction do
- Milestone.where(closed: true).update_all(state: :closed)
- Milestone.where(closed: false).update_all(state: :active)
- end
+ execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value}"
+ execute "UPDATE #{table_name} SET state = 'active' WHERE closed = #{false_value}"
end
def down
- Milestone.transaction do
- Milestone.where(state: :closed).update_all(closed: true)
- end
+ execute "UPDATE #{table_name} SET closed = #{true_value} WHERE state = 'cloesd'"
+ end
+
+ private
+
+ def table_name
+ Milestone.table_name
end
end
diff --git a/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
index b310b35e373..1c758c56ffe 100644
--- a/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
+++ b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
@@ -1,17 +1,19 @@
class ConvertMergeStatusInMergeRequest < ActiveRecord::Migration
def up
- MergeRequest.transaction do
- MergeRequest.where(merge_status: 1).update_all("new_merge_status = 'unchecked'")
- MergeRequest.where(merge_status: 2).update_all("new_merge_status = 'can_be_merged'")
- MergeRequest.where(merge_status: 3).update_all("new_merge_status = 'cannot_be_merged'")
- end
+ execute "UPDATE #{table_name} SET new_merge_status = 'unchecked' WHERE merge_status = 1"
+ execute "UPDATE #{table_name} SET new_merge_status = 'can_be_merged' WHERE merge_status = 2"
+ execute "UPDATE #{table_name} SET new_merge_status = 'cannot_be_merged' WHERE merge_status = 3"
end
def down
- MergeRequest.transaction do
- MergeRequest.where(new_merge_status: :unchecked).update_all("merge_status = 1")
- MergeRequest.where(new_merge_status: :can_be_merged).update_all("merge_status = 2")
- MergeRequest.where(new_merge_status: :cannot_be_merged).update_all("merge_status = 3")
- end
+ execute "UPDATE #{table_name} SET merge_status = 1 WHERE new_merge_status = 'unchecked'"
+ execute "UPDATE #{table_name} SET merge_status = 2 WHERE new_merge_status = 'can_be_merged'"
+ execute "UPDATE #{table_name} SET merge_status = 3 WHERE new_merge_status = 'cannot_be_merged'"
+ end
+
+ private
+
+ def table_name
+ MergeRequest.table_name
end
end
diff --git a/db/migrate/20130419190306_allow_merges_for_forks.rb b/db/migrate/20130419190306_allow_merges_for_forks.rb
index 56ce58a846d..56ea97e8561 100644
--- a/db/migrate/20130419190306_allow_merges_for_forks.rb
+++ b/db/migrate/20130419190306_allow_merges_for_forks.rb
@@ -1,7 +1,7 @@
class AllowMergesForForks < ActiveRecord::Migration
def self.up
add_column :merge_requests, :target_project_id, :integer, :null => true
- MergeRequest.update_all("target_project_id = project_id")
+ execute "UPDATE #{table_name} SET target_project_id = project_id"
change_column :merge_requests, :target_project_id, :integer, :null => false
rename_column :merge_requests, :project_id, :source_project_id
end
@@ -10,4 +10,10 @@ class AllowMergesForForks < ActiveRecord::Migration
remove_column :merge_requests, :target_project_id
rename_column :merge_requests, :source_project_id,:project_id
end
+
+ private
+
+ def table_name
+ MergeRequest.table_name
+ end
end
diff --git a/doc/README.md b/doc/README.md
index e6fa4fc049b..724c7cca0f1 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -19,10 +19,12 @@
## Administrator documentation
+- [Authentication/Authorization](administration/auth/README.md) Configure
+ external authentication with LDAP, SAML, CAS and additional Omniauth providers.
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough.
- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components
-- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
+- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](logs/logs.md) Log system.
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
new file mode 100644
index 00000000000..07e548aaabe
--- /dev/null
+++ b/doc/administration/auth/README.md
@@ -0,0 +1,11 @@
+# Authentication and Authorization
+
+GitLab integrates with the following external authentication and authorization
+providers.
+
+- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP,
+ and 389 Server
+- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google,
+ Bitbucket, Facebook, Shibboleth, Crowd and Azure
+- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
+- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
new file mode 100644
index 00000000000..237700bbcd9
--- /dev/null
+++ b/doc/administration/auth/ldap.md
@@ -0,0 +1,277 @@
+# LDAP
+
+GitLab integrates with LDAP to support user authentication.
+This integration works with most LDAP-compliant directory
+servers, including Microsoft Active Directory, Apple Open Directory, Open LDAP,
+and 389 Server. GitLab EE includes enhanced integration, including group
+membership syncing.
+
+## Security
+
+GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email'
+or 'userPrincipalName' attribute. An LDAP user who is allowed to change their
+email on the LDAP server can potentially
+[take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users)
+on your GitLab server.
+
+We recommend against using LDAP integration if your LDAP users are
+allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on
+the LDAP server.
+
+### User deletion
+
+If a user is deleted from the LDAP server, they will be blocked in GitLab, as
+well. Users will be immediately blocked from logging in. However, there is an
+LDAP check cache time (sync time) of one hour (see note). This means users that
+are already logged in or are using Git over SSH will still be able to access
+GitLab for up to one hour. Manually block the user in the GitLab Admin area to
+immediately block all access.
+
+>**Note**: GitLab EE supports a configurable sync time, with a default
+of one hour.
+
+## Configuration
+
+To enable LDAP integration you need to add your LDAP server settings in
+`/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
+
+>**Note**: In GitLab EE, you can configure multiple LDAP servers to connect to
+one GitLab server.
+
+Prior to version 7.4, GitLab used a different syntax for configuring
+LDAP integration. The old LDAP integration syntax still works but may be
+removed in a future version. If your `gitlab.rb` or `gitlab.yml` file contains
+LDAP settings in both the old syntax and the new syntax, only the __old__
+syntax will be used by GitLab.
+
+The configuration inside `gitlab_rails['ldap_servers']` below is sensitive to
+incorrect indentation. Be sure to retain the indentation given in the example.
+Copy/paste can sometimes cause problems.
+
+**Omnibus configuration**
+
+```ruby
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
+main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ host: '_your_ldap_server'
+ port: 389
+ uid: 'sAMAccountName'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
+ # a request if the LDAP server becomes unresponsive.
+ # A value of 0 means there is no timeout.
+ timeout: 10
+
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # To maintain tight control over the number of active users on your GitLab installation,
+ # enable this setting to keep new users blocked until they have been cleared by the admin
+ # (default: false).
+ block_auto_created_users: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+ # LDAP attributes that GitLab will use to create an account for the LDAP user.
+ # The specified attribute can either be the attribute name as a string (e.g. 'mail'),
+ # or an array of attribute names to try in order (e.g. ['mail', 'email']).
+ # Note that the user's LDAP login will always be the attribute specified as `uid` above.
+ attributes:
+ # The username will be used in paths for the user's own projects
+ # (like `gitlab.example.com/username/project`) and when mentioning
+ # them in issues, merge request and comments (like `@username`).
+ # If the attribute specified for `username` contains an email address,
+ # the GitLab username will be the part of the email address before the '@'.
+ username: ['uid', 'userid', 'sAMAccountName']
+ email: ['mail', 'email', 'userPrincipalName']
+
+ # If no full name could be found at the attribute specified for `name`,
+ # the full name is determined using the attributes specified for
+ # `first_name` and `last_name`.
+ name: 'cn'
+ first_name: 'givenName'
+ last_name: 'sn'
+
+ ## EE only
+
+ # Base where we can search for groups
+ #
+ # Ex. ou=groups,dc=gitlab,dc=example
+ #
+ group_base: ''
+
+ # The CN of a group containing GitLab administrators
+ #
+ # Ex. administrators
+ #
+ # Note: Not `cn=administrators` or the full DN
+ #
+ admin_group: ''
+
+ # The LDAP attribute containing a user's public SSH key
+ #
+ # Ex. ssh_public_key
+ #
+ sync_ssh_keys: false
+
+# GitLab EE only: add more LDAP servers
+# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+# so that GitLab can remember which LDAP server a user belongs to.
+# uswest2:
+# label:
+# host:
+# ....
+EOS
+```
+
+**Source configuration**
+
+Use the same format as `gitlab_rails['ldap_servers']` for the contents under
+`servers:` in the example below:
+
+```
+production:
+ # snip...
+ ldap:
+ enabled: false
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+ # snip...
+```
+
+## Using an LDAP filter to limit access to your GitLab server
+
+If you want to limit all GitLab access to a subset of the LDAP users on your
+LDAP server, the first step should be to narrow the configured `base`. However,
+it is sometimes necessary to filter users further. In this case, you can set up
+an LDAP user filter. The filter must comply with
+[RFC 4515](https://tools.ietf.org/search/rfc4515).
+
+**Omnibus configuration**
+
+```ruby
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+EOS
+```
+
+**Source configuration**
+
+```yaml
+production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+```
+
+Tip: If you want to limit access to the nested members of an Active Directory
+group you can use the following syntax:
+
+```
+(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
+```
+
+Please note that GitLab does not support the custom filter syntax used by
+omniauth-ldap.
+
+## Enabling LDAP sign-in for existing GitLab users
+
+When a user signs in to GitLab with LDAP for the first time, and their LDAP
+email address is the primary email address of an existing GitLab user, then
+the LDAP DN will be associated with the existing user. If the LDAP email
+attribute is not found in GitLab's database, a new user is created.
+
+In other words, if an existing GitLab user wants to enable LDAP sign-in for
+themselves, they should check that their GitLab email address matches their
+LDAP email address, and then sign into GitLab via their LDAP credentials.
+
+## Limitations
+
+### TLS Client Authentication
+
+Not implemented by `Net::LDAP`.
+You should disable anonymous LDAP authentication and enable simple or SASL
+authentication. The TLS client authentication setting in your LDAP server cannot
+be mandatory and clients cannot be authenticated with the TLS protocol.
+
+### TLS Server Authentication
+
+Not supported by GitLab's configuration options.
+When setting `method: ssl`, the underlying authentication method used by
+`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
+the LDAP server before any LDAP-protocol data is exchanged but no validation of
+the LDAP server's SSL certificate is performed.
+
+## Troubleshooting
+
+### Invalid credentials when logging in
+
+- Make sure the user you are binding with has enough permissions to read the user's
+tree and traverse it.
+- Check that the `user_filter` is not blocking otherwise valid users.
+- Run the following check command to make sure that the LDAP settings are
+ correct and GitLab can see your users:
+
+ ```bash
+ # For Omnibus installations
+ sudo gitlab-rake gitlab:ldap:check
+
+ # For installations from source
+ sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
+ ```
+
+### Connection Refused
+
+If you are getting 'Connection Refused' errors when trying to connect to the
+LDAP server please double-check the LDAP `port` and `method` settings used by
+GitLab. Common combinations are `method: 'plain'` and `port: 389`, OR
+`method: 'ssl'` and `port: 636`.
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 18d64c41986..cc6355d34ef 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -237,6 +237,7 @@ POST /projects/:id/issues
| `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 |
+| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 886784a27c9..1890edd7a4c 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -17,7 +17,7 @@ GitHub will generate an application ID and secret key for you to use.
- Application name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive.
- Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
- Application description: Fill this in if you wish.
- - Authorization callback URL: 'https://gitlab.company.com/'
+ - Default authorization callback URL is '${YOUR_DOMAIN}/import/github/callback'
1. Select "Register application".
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index cf1f98492ea..fb20308c49c 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -1,228 +1,3 @@
# GitLab LDAP integration
-GitLab can be configured to allow your users to sign with their LDAP credentials to integrate with e.g. Active Directory.
-
-The first time a user signs in with LDAP credentials, GitLab will create a new GitLab user associated with the LDAP Distinguished Name (DN) of the LDAP user.
-
-GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
-
-## Security
-
-GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email' or 'userPrincipalName' attribute.
-An LDAP user who is allowed to change their email on the LDAP server can [take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users) on your GitLab server.
-
-We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on the LDAP server.
-
-If a user is deleted from the LDAP server, they will be blocked in GitLab as well.
-Users will be immediately blocked from logging in. However, there is an LDAP check
-cache time of one hour. The means users that are already logged in or are using Git
-over SSH will still be able to access GitLab for up to one hour. Manually block
-the user in the GitLab Admin area to immediately block all access.
-
-## Configuring GitLab for LDAP integration
-
-To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
-In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
-
-Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
-The old LDAP integration syntax still works in GitLab 7.4.
-If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
-
-```ruby
-# For omnibus packages
-gitlab_rails['ldap_enabled'] = true
-gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
-main: # 'main' is the GitLab 'provider ID' of this LDAP server
- ## label
- #
- # A human-friendly name for your LDAP server. It is OK to change the label later,
- # for instance if you find out it is too large to fit on the web page.
- #
- # Example: 'Paris' or 'Acme, Ltd.'
- label: 'LDAP'
-
- host: '_your_ldap_server'
- port: 389
- uid: 'sAMAccountName'
- method: 'plain' # "tls" or "ssl" or "plain"
- bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
- password: '_the_password_of_the_bind_user'
-
- # Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
- # a request if the LDAP server becomes unresponsive.
- # A value of 0 means there is no timeout.
- timeout: 10
-
- # This setting specifies if LDAP server is Active Directory LDAP server.
- # For non AD servers it skips the AD specific queries.
- # If your LDAP server is not AD, set this to false.
- active_directory: true
-
- # If allow_username_or_email_login is enabled, GitLab will ignore everything
- # after the first '@' in the LDAP username submitted by the user on login.
- #
- # Example:
- # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
- # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
- #
- # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
- # disable this setting, because the userPrincipalName contains an '@'.
- allow_username_or_email_login: false
-
- # To maintain tight control over the number of active users on your GitLab installation,
- # enable this setting to keep new users blocked until they have been cleared by the admin
- # (default: false).
- block_auto_created_users: false
-
- # Base where we can search for users
- #
- # Ex. ou=People,dc=gitlab,dc=example
- #
- base: ''
-
- # Filter LDAP users
- #
- # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
- # Ex. (employeeType=developer)
- #
- # Note: GitLab does not support omniauth-ldap's custom filter syntax.
- #
- user_filter: ''
-
- # LDAP attributes that GitLab will use to create an account for the LDAP user.
- # The specified attribute can either be the attribute name as a string (e.g. 'mail'),
- # or an array of attribute names to try in order (e.g. ['mail', 'email']).
- # Note that the user's LDAP login will always be the attribute specified as `uid` above.
- attributes:
- # The username will be used in paths for the user's own projects
- # (like `gitlab.example.com/username/project`) and when mentioning
- # them in issues, merge request and comments (like `@username`).
- # If the attribute specified for `username` contains an email address,
- # the GitLab username will be the part of the email address before the '@'.
- username: ['uid', 'userid', 'sAMAccountName']
- email: ['mail', 'email', 'userPrincipalName']
-
- # If no full name could be found at the attribute specified for `name`,
- # the full name is determined using the attributes specified for
- # `first_name` and `last_name`.
- name: 'cn'
- first_name: 'givenName'
- last_name: 'sn'
-
-# GitLab EE only: add more LDAP servers
-# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
-# so that GitLab can remember which LDAP server a user belongs to.
-# uswest2:
-# label:
-# host:
-# ....
-EOS
-```
-
-If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `method` settings used by GitLab.
-Common combinations are `method: 'plain'` and `port: 389`, OR `method: 'ssl'` and `port: 636`.
-
-If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
-
-```
-production:
- # snip...
- ldap:
- enabled: false
- servers:
- main: # 'main' is the GitLab 'provider ID' of this LDAP server
- ## label
- #
- # A human-friendly name for your LDAP server. It is OK to change the label later,
- # for instance if you find out it is too large to fit on the web page.
- #
- # Example: 'Paris' or 'Acme, Ltd.'
- label: 'LDAP'
- # snip...
-```
-
-## Enabling LDAP sign-in for existing GitLab users
-
-When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
-
-If the LDAP email attribute is not found in GitLab's database, a new user is created.
-
-In other words, if an existing GitLab user wants to enable LDAP sign-in for themselves, they should check that their GitLab email address matches their LDAP email address, and then sign into GitLab via their LDAP credentials.
-
-GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`.
-
-If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`.
-
-## Using an LDAP filter to limit access to your GitLab server
-
-If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter.
-The filter must comply with [RFC 4515](https://tools.ietf.org/search/rfc4515).
-
-```ruby
-# For omnibus packages; new LDAP server syntax
-gitlab_rails['ldap_servers'] = YAML.load <<-EOS
-main:
- # snip...
- user_filter: '(employeeType=developer)'
-EOS
-```
-
-```yaml
-# For installations from source; new LDAP server syntax
-production:
- ldap:
- servers:
- main:
- # snip...
- user_filter: '(employeeType=developer)'
-```
-
-Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax:
-
-```
-(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
-```
-
-Please note that GitLab does not support the custom filter syntax used by omniauth-ldap.
-
-## Limitations
-
-GitLab's LDAP client is based on [omniauth-ldap](https://gitlab.com/gitlab-org/omniauth-ldap)
-which encapsulates Ruby's `Net::LDAP` class. It provides a pure-Ruby implementation
-of the LDAP client protocol. As a result, GitLab is limited by `omniauth-ldap` and may impact your LDAP
-server settings.
-
-### TLS Client Authentication
-Not implemented by `Net::LDAP`.
-So you should disable anonymous LDAP authentication and enable simple or SASL
-authentication. TLS client authentication setting in your LDAP server cannot be
-mandatory and clients cannot be authenticated with the TLS protocol.
-
-### TLS Server Authentication
-Not supported by GitLab's configuration options.
-When setting `method: ssl`, the underlying authentication method used by
-`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
-the LDAP server before any LDAP-protocol data is exchanged but no validation of
-the LDAP server's SSL certificate is performed.
-
-## Troubleshooting
-
-### Invalid credentials when logging in
-
-Make sure the user you are binding with has enough permissions to read the user's
-tree and traverse it.
-
-Also make sure that the `user_filter` is not blocking otherwise valid users.
-
-To make sure that the LDAP settings are correct and GitLab can see your users,
-execute the following command:
-
-
-```bash
-# For Omnibus installations
-sudo gitlab-rake gitlab:ldap:check
-
-# For installations from source
-sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
-```
-
+This document was moved under [`administration/auth/ldap`](administration/auth/ldap.md).
diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md
index 712e9fdf93a..b9abcbd2c12 100644
--- a/doc/update/8.5-to-8.6.md
+++ b/doc/update/8.5-to-8.6.md
@@ -62,7 +62,26 @@ sudo -u git -H git checkout v0.7.1
sudo -u git -H make
```
-### 6. Install libs, migrations, etc.
+### 6. Updates for PostgreSQL Users
+
+Starting with 8.6 users using GitLab in combination with PostgreSQL are required
+to have the `pg_trgm` extension enabled for all GitLab databases. If you're
+using GitLab's Omnibus packages there's nothing you'll need to do manually as
+this extension is enabled automatically. Users who install GitLab without using
+Omnibus (e.g. by building from source) have to enable this extension manually.
+To enable this extension run the following SQL command as a PostgreSQL super
+user for _every_ GitLab database:
+
+```sql
+CREATE EXTENSION IF NOT EXISTS pg_trgm;
+```
+
+Certain operating systems might require the installation of extra packages for
+this extension to be available. For example, users using Ubuntu will have to
+install the `postgresql-contrib` package in order for this extension to be
+available.
+
+### 7. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -84,7 +103,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
```
-### 7. Update configuration files
+### 8. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -120,25 +139,6 @@ Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-### 8. Updates for PostgreSQL Users
-
-Starting with 8.6 users using GitLab in combination with PostgreSQL are required
-to have the `pg_trgm` extension enabled for all GitLab databases. If you're
-using GitLab's Omnibus packages there's nothing you'll need to do manually as
-this extension is enabled automatically. Users who install GitLab without using
-Omnibus (e.g. by building from source) have to enable this extension manually.
-To enable this extension run the following SQL command as a PostgreSQL super
-user for _every_ GitLab database:
-
-```sql
-CREATE EXTENSION IF NOT EXISTS pg_trgm;
-```
-
-Certain operating systems might require the installation of extra packages for
-this extension to be available. For example, users using Ubuntu will have to
-install the `postgresql-contrib` package in order for this extension to be
-available.
-
### 9. Start application
sudo service gitlab start
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index afdf1a682e2..22e207b6d32 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -58,13 +58,13 @@ X-Gitlab-Event: Push Hook
"path_with_namespace":"mike/diaspora",
"default_branch":"master",
"homepage":"http://example.com/mike/diaspora",
- "url":"git@example.com:mike/diasporadiaspora.git",
+ "url":"git@example.com:mike/diaspora.git",
"ssh_url":"git@example.com:mike/diaspora.git",
"http_url":"http://example.com/mike/diaspora.git"
},
"repository":{
"name": "Diaspora",
- "url": "git@example.com:mike/diasporadiaspora.git",
+ "url": "git@example.com:mike/diaspora.git",
"description": "",
"homepage": "http://example.com/mike/diaspora",
"git_http_url":"http://example.com/mike/diaspora.git",
@@ -113,7 +113,6 @@ Triggered when you create (or delete) tags to the repository.
X-Gitlab-Event: Tag Push Hook
```
-
**Request body:**
```json
@@ -143,7 +142,7 @@ X-Gitlab-Event: Tag Push Hook
"http_url":"http://example.com/jsmith/example.git"
},
"repository":{
- "name": "jsmith",
+ "name": "Example",
"url": "ssh://git@example.com/jsmith/example.git",
"description": "",
"homepage": "http://example.com/jsmith/example",
@@ -478,7 +477,7 @@ X-Gitlab-Event: Note Hook
},
"repository":{
"name":"diaspora",
- "url":"git@example.com:mike/diasporadiaspora.git",
+ "url":"git@example.com:mike/diaspora.git",
"description":"",
"homepage":"http://example.com/mike/diaspora"
},
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index e5ae88eb96f..1fee1dee1a6 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -111,17 +111,21 @@ module API
# Create a new project issue
#
# Parameters:
- # 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
+ # 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) - The labels of an issue
+ # labels (optional) - The labels of an issue
+ # created_at (optional) - The date
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
required_attributes! [:title]
- attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
+
+ keys = [:title, :description, :assignee_id, :milestone_id]
+ keys << :created_at if current_user.admin? || user_project.owner == current_user
+ attrs = attributes_for_keys(keys)
# Validate label names in advance
if (errors = validate_label_params(params)).any?
diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb
index db0748f323f..5022a3e2c80 100644
--- a/spec/controllers/ci/projects_controller_spec.rb
+++ b/spec/controllers/ci/projects_controller_spec.rb
@@ -5,6 +5,27 @@ describe Ci::ProjectsController do
let!(:project) { create(:project, visibility, ci_id: 1) }
let(:ci_id) { project.ci_id }
+ describe '#index' do
+ context 'user signed in' do
+ before do
+ sign_in(create(:user))
+ get(:index)
+ end
+
+ it 'redirects to /' do
+ expect(response).to redirect_to(root_path)
+ end
+ end
+
+ context 'user not signed in' do
+ before { get(:index) }
+
+ it 'redirects to sign in page' do
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
##
# Specs for *deprecated* CI badge
#
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
new file mode 100644
index 00000000000..0f32a30f18b
--- /dev/null
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe Projects::SnippetsController do
+ let(:project) { create(:project_empty_repo, :public, snippets_enabled: true) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ project.team << [user2, :master]
+ end
+
+ describe 'GET #index' do
+ context 'when the project snippet is private' do
+ let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+ context 'when anonymous' do
+ it 'does not include the private snippet' do
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+
+ expect(assigns(:snippets)).not_to include(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when signed in as the author' do
+ before { sign_in(user) }
+
+ it 'renders the snippet' do
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+
+ expect(assigns(:snippets)).to include(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when signed in as a project member' do
+ before { sign_in(user2) }
+
+ it 'renders the snippet' do
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+
+ expect(assigns(:snippets)).to include(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+ end
+
+ %w[show raw].each do |action|
+ describe "GET ##{action}" do
+ context 'when the project snippet is private' do
+ let(:project_snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+ context 'when anonymous' do
+ it 'responds with status 404' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in as the author' do
+ before { sign_in(user) }
+
+ it 'renders the snippet' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when signed in as a project member' do
+ before { sign_in(user2) }
+
+ it 'renders the snippet' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(project_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when the project snippet does not exist' do
+ context 'when anonymous' do
+ it 'responds with status 404' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in' do
+ before { sign_in(user) }
+
+ it 'responds with status 404' do
+ get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index f6e33f651c4..d8e2ecb9feb 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -31,7 +31,7 @@ feature 'Issue filtering by Milestone', feature: true do
def filter_by_milestone(title)
find(".js-milestone-select").click
sleep 0.5
- find(".milestone-filter a", text: title).click
+ find(".milestone-filter .dropdown-content a", text: title).click
sleep 1
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index e844e681ebf..db46657c36a 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -34,20 +34,7 @@ describe 'Issues', feature: true do
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
end
-
- it 'does not change issue count' do
- expect { click_button 'Save changes' }.to_not change { Issue.count }
- end
-
- it 'should update issue fields' do
- click_button 'Save changes'
-
- expect(page).to have_content @user.name
- expect(page).to have_content 'bug 345'
- expect(page).to have_content project.name
- end
end
-
end
describe 'Editing issue assignee' do
@@ -70,7 +57,7 @@ describe 'Issues', feature: true do
click_button 'Save changes'
page.within('.assignee') do
- expect(page).to have_content 'None'
+ expect(page).to have_content 'No assignee - assign yourself'
end
expect(issue.reload.assignee).to be_nil
@@ -198,20 +185,26 @@ describe 'Issues', feature: true do
end
describe 'update assignee from issue#show' do
- let(:issue) { create(:issue, project: project, author: @user) }
+ let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
context 'by autorized user' do
- it 'with dropdown menu' do
+ it 'allows user to select unassigned', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
- find('.issuable-sidebar #issue_assignee_id').
- set project.team.members.first.id
- click_button 'Update Issue'
+ page.within('.assignee') do
+ expect(page).to have_content "#{@user.name}"
+ end
+
+ find('.block.assignee .edit-link').click
+ sleep 2 # wait for ajax stuff to complete
+ first('.dropdown-menu-user-link').click
+ sleep 2
+ page.within('.assignee') do
+ expect(page).to have_content 'No assignee'
+ end
- expect(page).to have_content 'Assignee'
- has_select?('issue_assignee_id',
- selected: project.team.members.first.name)
+ expect(issue.reload.assignee).to be_nil
end
end
@@ -221,8 +214,6 @@ describe 'Issues', feature: true do
before :each do
project.team << [[guest], :guest]
- issue.assignee = @user
- issue.save
end
it 'shows assignee text', js: true do
@@ -241,20 +232,23 @@ describe 'Issues', feature: true do
context 'by authorized user' do
- it 'with dropdown menu' do
- visit namespace_project_issue_path(project.namespace, project, issue)
- find('.issuable-sidebar').
- select(milestone.title, from: 'issue_milestone_id')
- click_button 'Update Issue'
+ it 'allows user to select unassigned', js: true do
+ visit namespace_project_issue_path(project.namespace, project, issue)
- expect(page).to have_content "Milestone changed to #{milestone.title}"
+ page.within('.milestone') do
+ expect(page).to have_content "None"
+ end
+ find('.block.milestone .edit-link').click
+ sleep 2 # wait for ajax stuff to complete
+ first('.dropdown-content li').click
+ sleep 2
page.within('.milestone') do
- expect(page).to have_content milestone.title
+ expect(page).to have_content 'None'
end
- has_select?('issue_assignee_id', selected: milestone.title)
+ expect(issue.reload.milestone).to be_nil
end
end
@@ -283,25 +277,6 @@ describe 'Issues', feature: true do
issue.assignee = user2
issue.save
end
-
- it 'allows user to remove assignee', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
-
- page.within('.assignee') do
- expect(page).to have_content user2.name
- end
-
- find('.assignee .edit-link').click
- sleep 2 # wait for ajax stuff to complete
- first('.user-result').click
-
- page.within('.assignee') do
- expect(page).to have_content 'None'
- end
-
- sleep 2 # wait for ajax stuff to complete
- expect(issue.reload.assignee).to be_nil
- end
end
end
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
new file mode 100644
index 00000000000..db53a9cec97
--- /dev/null
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+describe "Internal Project Snippets Access", feature: true do
+ include AccessMatchers
+
+ let(:project) { create(:project, :internal) }
+
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) }
+ let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) }
+
+ before do
+ project.team << [master, :master]
+ project.team << [developer, :developer]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { namespace_project_snippets_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/new" do
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for an internal snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for a private snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+end
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
new file mode 100644
index 00000000000..d23d645c8e5
--- /dev/null
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe "Private Project Snippets Access", feature: true do
+ include AccessMatchers
+
+ let(:project) { create(:project, :private) }
+
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) }
+
+ before do
+ project.team << [master, :master]
+ project.team << [developer, :developer]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { namespace_project_snippets_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/new" do
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for a private snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+end
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
new file mode 100644
index 00000000000..e3665b6116a
--- /dev/null
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe "Public Project Snippets Access", feature: true do
+ include AccessMatchers
+
+ let(:project) { create(:project, :public) }
+
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:public_snippet) { create(:project_snippet, :public, project: project, author: owner) }
+ let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) }
+ let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) }
+
+ before do
+ project.team << [master, :master]
+ project.team << [developer, :developer]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { namespace_project_snippets_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/new" do
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for a public snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for an internal snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/:id for a private snippet" do
+ subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 20f06f4b7e1..55f1c665b86 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -422,6 +422,12 @@ describe Project, models: true do
it { should eq "http://localhost#{avatar_path}" }
end
+
+ context 'when git repo is empty' do
+ let(:project) { create(:empty_project) }
+
+ it { should eq nil }
+ end
end
describe :ci_commit do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 417f11acca4..f10d671104c 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -744,6 +744,12 @@ describe Repository, models: true do
end
describe '#avatar' do
+ it 'returns nil if repo does not exist' do
+ expect(repository).to receive(:exists?).and_return(false)
+
+ expect(repository.avatar).to eq(nil)
+ end
+
it 'returns the first avatar file found in the repository' do
expect(repository).to receive(:blob_at_branch).
with('master', 'logo.png').
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index ce55cb7b0ae..822d3ad3017 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -318,6 +318,17 @@ describe API::API, api: true do
'is too long (maximum is 255 characters)'
])
end
+
+ context 'when an admin or owner makes the request' do
+ it "accepts the creation date to be set" do
+ post api("/projects/#{project.id}/issues", user),
+ title: 'new issue', labels: 'label, label2', created_at: 2.weeks.ago
+
+ expect(response.status).to eq(201)
+ # this take about a second, so probably not equal
+ expect(Time.parse(json_response['created_at'])).to be <= 2.weeks.ago
+ end
+ end
end
describe 'POST /projects/:id/issues with spam filtering' do
diff --git a/vendor/assets/stylesheets/animate.css b/vendor/assets/stylesheets/animate.css
new file mode 100644
index 00000000000..b6f61295392
--- /dev/null
+++ b/vendor/assets/stylesheets/animate.css
@@ -0,0 +1,11 @@
+@charset "UTF-8";
+
+/*!
+ * animate.css -http://daneden.me/animate
+ * Version - 3.5.1
+ * Licensed under the MIT license - http://opensource.org/licenses/MIT
+ *
+ * Copyright (c) 2016 Daniel Eden
+ */
+
+.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}.headShake{-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-name:headShake;animation-name:headShake}@-webkit-keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}@keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}.jello{-webkit-animation-name:jello;animation-name:jello;-webkit-transform-origin:center;transform-origin:center}@-webkit-keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}@keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}@keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp} \ No newline at end of file