Commit 23bb3787 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream1

parents 828c218f 0115ab7f
......@@ -131,56 +131,56 @@ spinach 7 10: *spinach-knapsack
spinach 8 10: *spinach-knapsack
spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.2
.ruby-22: &ruby-22
image: "ruby:2.2"
# Execute all testing suites against Ruby 2.3
.ruby-23: &ruby-23
image: "ruby:2.3"
only:
- master
cache:
key: "ruby22"
key: "ruby-23"
paths:
- vendor
- vendor/apt
- vendor/ruby
.rspec-knapsack-ruby22: &rspec-knapsack-ruby22
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
<<: *rspec-knapsack
<<: *ruby-22
<<: *ruby-23
.spinach-knapsack-ruby22: &spinach-knapsack-ruby22
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23
<<: *spinach-knapsack
<<: *ruby-22
<<: *ruby-23
rspec 0 20 ruby22: *rspec-knapsack-ruby22
rspec 1 20 ruby22: *rspec-knapsack-ruby22
rspec 2 20 ruby22: *rspec-knapsack-ruby22
rspec 3 20 ruby22: *rspec-knapsack-ruby22
rspec 4 20 ruby22: *rspec-knapsack-ruby22
rspec 5 20 ruby22: *rspec-knapsack-ruby22
rspec 6 20 ruby22: *rspec-knapsack-ruby22
rspec 7 20 ruby22: *rspec-knapsack-ruby22
rspec 8 20 ruby22: *rspec-knapsack-ruby22
rspec 9 20 ruby22: *rspec-knapsack-ruby22
rspec 10 20 ruby22: *rspec-knapsack-ruby22
rspec 11 20 ruby22: *rspec-knapsack-ruby22
rspec 12 20 ruby22: *rspec-knapsack-ruby22
rspec 13 20 ruby22: *rspec-knapsack-ruby22
rspec 14 20 ruby22: *rspec-knapsack-ruby22
rspec 15 20 ruby22: *rspec-knapsack-ruby22
rspec 16 20 ruby22: *rspec-knapsack-ruby22
rspec 17 20 ruby22: *rspec-knapsack-ruby22
rspec 18 20 ruby22: *rspec-knapsack-ruby22
rspec 19 20 ruby22: *rspec-knapsack-ruby22
spinach 0 10 ruby22: *spinach-knapsack-ruby22
spinach 1 10 ruby22: *spinach-knapsack-ruby22
spinach 2 10 ruby22: *spinach-knapsack-ruby22
spinach 3 10 ruby22: *spinach-knapsack-ruby22
spinach 4 10 ruby22: *spinach-knapsack-ruby22
spinach 5 10 ruby22: *spinach-knapsack-ruby22
spinach 6 10 ruby22: *spinach-knapsack-ruby22
spinach 7 10 ruby22: *spinach-knapsack-ruby22
spinach 8 10 ruby22: *spinach-knapsack-ruby22
spinach 9 10 ruby22: *spinach-knapsack-ruby22
rspec 0 20 ruby23: *rspec-knapsack-ruby23
rspec 1 20 ruby23: *rspec-knapsack-ruby23
rspec 2 20 ruby23: *rspec-knapsack-ruby23
rspec 3 20 ruby23: *rspec-knapsack-ruby23
rspec 4 20 ruby23: *rspec-knapsack-ruby23
rspec 5 20 ruby23: *rspec-knapsack-ruby23
rspec 6 20 ruby23: *rspec-knapsack-ruby23
rspec 7 20 ruby23: *rspec-knapsack-ruby23
rspec 8 20 ruby23: *rspec-knapsack-ruby23
rspec 9 20 ruby23: *rspec-knapsack-ruby23
rspec 10 20 ruby23: *rspec-knapsack-ruby23
rspec 11 20 ruby23: *rspec-knapsack-ruby23
rspec 12 20 ruby23: *rspec-knapsack-ruby23
rspec 13 20 ruby23: *rspec-knapsack-ruby23
rspec 14 20 ruby23: *rspec-knapsack-ruby23
rspec 15 20 ruby23: *rspec-knapsack-ruby23
rspec 16 20 ruby23: *rspec-knapsack-ruby23
rspec 17 20 ruby23: *rspec-knapsack-ruby23
rspec 18 20 ruby23: *rspec-knapsack-ruby23
rspec 19 20 ruby23: *rspec-knapsack-ruby23
spinach 0 10 ruby23: *spinach-knapsack-ruby23
spinach 1 10 ruby23: *spinach-knapsack-ruby23
spinach 2 10 ruby23: *spinach-knapsack-ruby23
spinach 3 10 ruby23: *spinach-knapsack-ruby23
spinach 4 10 ruby23: *spinach-knapsack-ruby23
spinach 5 10 ruby23: *spinach-knapsack-ruby23
spinach 6 10 ruby23: *spinach-knapsack-ruby23
spinach 7 10 ruby23: *spinach-knapsack-ruby23
spinach 8 10 ruby23: *spinach-knapsack-ruby23
spinach 9 10 ruby23: *spinach-knapsack-ruby23
# Other generic tests
......
This diff is collapsed.
......@@ -52,7 +52,7 @@ gem 'u2f', '~> 0.2.1'
gem 'validates_hostname', '~> 1.0.0'
# Browser detection
gem "browser", '~> 2.0.3'
gem "browser", '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
......@@ -81,7 +81,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
gem "kaminari", "~> 0.17.0"
# HAML
gem "haml-rails", '~> 0.9.0'
gem 'hamlit', '~> 2.5'
# Files attachments
gem "carrierwave", '~> 0.10.0'
......@@ -231,7 +231,7 @@ gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.2'
gem 'font-awesome-rails', '~> 4.6.1'
gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
......@@ -245,7 +245,7 @@ gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 0.0.4"
# Sentry integration
gem 'sentry-raven', '~> 0.15'
gem 'sentry-raven', '~> 1.1.0'
gem 'premailer-rails', '~> 1.9.0'
......@@ -341,7 +341,7 @@ gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.7"
gem "mail_room", "~> 0.8"
gem 'email_reply_parser', '~> 0.5.8'
......
......@@ -98,7 +98,7 @@ GEM
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
brakeman (3.3.2)
browser (2.0.3)
browser (2.2.0)
builder (3.2.2)
bullet (5.0.0)
activesupport (>= 3.0.0)
......@@ -259,7 +259,7 @@ GEM
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.5.0.1)
font-awesome-rails (4.6.1.0)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
thor (~> 0.19.1)
......@@ -299,7 +299,7 @@ GEM
gitlab-license (0.0.4)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.2.0)
gitlab_git (10.2.3)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
......@@ -342,16 +342,10 @@ GEM
grape-entity (0.4.8)
activesupport
multi_json (>= 1.3.2)
gssapi (1.2.0)
ffi (>= 1.0.1)
haml (4.0.7)
hamlit (2.5.0)
temple (~> 0.7.6)
thor
tilt
haml-rails (0.9.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
haml (>= 4.0.6, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hashie (3.4.3)
health_check (1.5.1)
rails (>= 2.3.0)
......@@ -361,11 +355,6 @@ GEM
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
html2haml (2.0.0)
erubis (~> 2.7.0)
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
htmlentities (4.3.4)
http_parser.rb (0.5.3)
httparty (0.13.7)
......@@ -422,7 +411,7 @@ GEM
systemu (~> 2.6.2)
mail (2.6.4)
mime-types (>= 1.16, < 4)
mail_room (0.7.0)
mail_room (0.8.0)
method_source (0.8.2)
mime-types (2.99.2)
mimemagic (0.3.0)
......@@ -689,7 +678,7 @@ GEM
activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3)
thor (~> 0.14)
sentry-raven (0.15.6)
sentry-raven (1.1.0)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.7.0)
......@@ -757,6 +746,7 @@ GEM
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.7)
term-ansicolor (1.3.2)
tins (~> 1.0)
test_after_commit (0.4.2)
......@@ -860,7 +850,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0)
browser (~> 2.0.3)
browser (~> 2.2)
bullet
bundler-audit
byebug
......@@ -895,7 +885,7 @@ DEPENDENCIES
fog-google (~> 0.3)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
font-awesome-rails (~> 4.2)
font-awesome-rails (~> 4.6.1)
foreman
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
......@@ -913,8 +903,7 @@ DEPENDENCIES
gon (~> 6.0.1)
grape (~> 0.13.0)
grape-entity (~> 0.4.2)
gssapi
haml-rails (~> 0.9.0)
hamlit (~> 2.5)
health_check (~> 1.5.1)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
......@@ -931,7 +920,7 @@ DEPENDENCIES
license_finder
licensee (~> 8.0.0)
loofah (~> 2.0.3)
mail_room (~> 0.7)
mail_room (~> 0.8)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
......@@ -993,7 +982,7 @@ DEPENDENCIES
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 0.15)
sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9)
sham_rack
shoulda-matchers (~> 2.8.0)
......
8.9.0-ee-pre
8.10.0-ee-pre
......@@ -27,6 +27,11 @@ class @LabelManager
$btn = $(e.currentTarget)
$label = $("##{$btn.data('domId')}")
action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
# Make sure tooltip will hide
$tooltip = $ "##{$btn.find('.has-tooltip:visible').attr('aria-describedby')}"
$tooltip.tooltip 'destroy'
_this.toggleLabelPriority($label, action)
toggleLabelPriority: ($label, action, persistState = true) ->
......
......@@ -8,6 +8,7 @@
licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key"
ldapGroupsPath: "/api/:version/ldap/:provider/groups.json"
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.groupPath)
......@@ -111,6 +112,12 @@
$.get url, (gitignore) ->
callback(gitignore)
gitlabCiYml: (key, callback) ->
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
$.get url, (file) ->
callback(file)
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
......
......@@ -51,7 +51,7 @@
#= require_directory ./ci
#= require_directory ./commit
#= require_directory ./extensions
#= require_directory ./lib
#= require_directory ./lib/utils
#= require_directory ./u2f
#= require_directory .
#= require fuzzaldrin-plus
......@@ -124,6 +124,11 @@ window.onload = ->
setTimeout shiftWindow, 100
$ ->
$document = $(document)
$window = $(window)
$body = $('body')
gl.utils.preventDisabledButtons()
bootstrapBreakpoint = bp.getBreakpointSize()
......@@ -155,7 +160,7 @@ $ ->
), 1
# Initialize tooltips
$('body').tooltip(
$body.tooltip(
selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
......@@ -174,7 +179,7 @@ $ ->
flash.show()
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
$body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
buttons = $('[type="submit"]', @)
switch e.type
......@@ -187,7 +192,7 @@ $ ->
$('.account-box').hover -> $(@).toggleClass('hover')
# Commit show suppressed diff
$(document).on 'click', '.diff-content .js-show-suppressed-diff', ->
$document.on 'click', '.diff-content .js-show-suppressed-diff', ->
$container = $(@).parent()
$container.next('table').show()
$container.remove()
......@@ -200,13 +205,13 @@ $ ->
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
$body.on "click", ".js-toggle-diff-comments", (e) ->
$(@).toggleClass('active')
$(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault()
$(document).off "click", '.js-confirm-danger'
$(document).on "click", '.js-confirm-danger', (e) ->
$document.off "click", '.js-confirm-danger'
$document.on "click", '.js-confirm-danger', (e) ->
e.preventDefault()
btn = $(e.target)
text = btn.data("confirm-danger-message")
......@@ -215,7 +220,7 @@ $ ->
new ConfirmDangerModal(form, text, warningMessage: warningMessage)
$(document).on 'click', 'button', ->
$document.on 'click', 'button', ->
$(this).blur()
$('input[type="search"]').each ->
......@@ -223,7 +228,7 @@ $ ->
$this.attr 'value', $this.val()
return
$(document)
$document
.off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
......@@ -231,7 +236,7 @@ $ ->
$sidebarGutterToggle = $('.js-sidebar-toggle')
$(document)
$document
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
......@@ -243,14 +248,14 @@ $ ->
oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$document.trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$document.trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
$window
.off "resize.app"
.on "resize.app", (e) ->
fitSidebarForSize()
......@@ -260,29 +265,45 @@ $ ->
new Aside()
# Sidenav pinning
if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false')
if $window.width() < 1440 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false', { path: '/' })
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').removeClass('header-pinned-nav')
$(document)
$document
.off 'click', '.js-nav-pin'
.on 'click', '.js-nav-pin', (e) ->
e.preventDefault()
$pinBtn = $(e.currentTarget)
$page = $ '.page-with-sidebar'
$topNav = $ '.navbar-fixed-top'
$tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
doPinNav = not $page.is('.page-sidebar-pinned')
tooltipText = 'Pin navigation'
$(this).toggleClass 'is-active'
if $.cookie('pin_nav') is 'true'
$.cookie 'pin_nav', 'false'
$('.page-with-sidebar')
.removeClass('page-sidebar-pinned')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$('.navbar-fixed-top')
.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
if doPinNav
$page.addClass('page-sidebar-pinned')
$topNav.addClass('header-pinned-nav')
else
$.cookie 'pin_nav', 'true'
$('.page-with-sidebar').addClass('page-sidebar-pinned')
$('.navbar-fixed-top').addClass('header-pinned-nav')
$tooltip.remove() # Remove it immediately when collapsing the sidebar
$page.removeClass('page-sidebar-pinned')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$topNav.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
# Save settings
$.cookie 'pin_nav', doPinNav, { path: '/' }
if $.cookie('pin_nav') is 'true' or doPinNav
tooltipText = 'Unpin navigation'
# Update tooltip text immediately
$tooltip.find('.tooltip-inner').text(tooltipText)
# Persist tooltip title
$pinBtn.attr('title', tooltipText).tooltip('fixTitle')
#= require blob/template_selector
class @BlobCiYamlSelector extends TemplateSelector
requestFile: (query) ->
Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
class @BlobCiYamlSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitlab-ci-yml-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobCiYamlSelector(
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)
......@@ -15,6 +15,7 @@ class @EditBlob
new BlobLicenseSelectors { @editor }
new BlobGitignoreSelectors { @editor }
new BlobCiYamlSelectors { @editor }
initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane")
......
......@@ -19,6 +19,7 @@ class @TemplateSelector
data: @data,
filterable: true,
selectable: true,
toggleLabel: @toggleLabel,
search:
fields: ['name']
clicked: @onClick
......@@ -31,6 +32,9 @@ class @TemplateSelector
@onFilenameUpdate()
)
toggleLabel: (item) ->
item.name
onFilenameUpdate: ->
return unless @$input.length
......
......@@ -78,6 +78,7 @@ class Dispatcher
when 'projects:show'
shortcut_handler = new ShortcutsNavigation()
new NotificationsForm()
new TreeView() if $('#tree-slider').length
when 'groups:activity'
new Activities()
......@@ -133,6 +134,8 @@ class Dispatcher
shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles'
new Profile()
new NotificationsForm()
new NotificationsDropdown()
when 'projects'
new Project()
new ProjectAvatar()
......@@ -140,8 +143,12 @@ class Dispatcher
when 'edit'
shortcut_handler = new ShortcutsNavigation()
new ProjectNew()
when 'new', 'show'
when 'new'
new ProjectNew()
when 'show'
new ProjectNew()
new ProjectShow()
new NotificationsDropdown()
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
......
......@@ -58,7 +58,7 @@ class GitLabDropdownFilter
filter: (search_text) ->
data = @options.data()
if data?
if data? and not @options.filterByText
results = data
if search_text isnt ''
......@@ -102,10 +102,11 @@ class GitLabDropdownFilter
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length
$el.show()
else
$el.hide()
unless $el.is('.dropdown-header')
if matches.length
$el.show()
else
$el.hide()
else
elements.show()
......@@ -191,6 +192,7 @@ class GitLabDropdown
if @options.filterable
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
filterByText: @options.filterByText
remote: @options.filterRemote
query: @options.data
keys: searchFields
......@@ -278,7 +280,7 @@ class GitLabDropdown
html = @renderData(data)
# Render the full menu
full_html = @renderMenu(html.join(""))
full_html = @renderMenu(html)
@appendMenu(full_html)
......@@ -302,6 +304,9 @@ class GitLabDropdown
if @options.setIndeterminateIds
@options.setIndeterminateIds.call(@)
if @options.setActiveIds
@options.setActiveIds.call(@)
# Makes indeterminate items effective
if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@parseData @fullData
......@@ -346,7 +351,8 @@ class GitLabDropdown
if @options.renderMenu
menu_html = @options.renderMenu(html)
else
menu_html = "<ul>#{html}</ul>"
menu_html = $('<ul />')
.append(html)
return menu_html
......@@ -355,7 +361,9 @@ class GitLabDropdown
selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content"
$(selector, @dropdown).html html
$(selector, @dropdown)
.empty()
.append(html)
# Render the row
renderItem: (data, group = false, index = false) ->
......@@ -454,7 +462,7 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
@updateLabel()
@updateLabel(selectedObject, el, @)
else
selectedObject
else if el.hasClass(INDETERMINATE_CLASS)
......@@ -481,7 +489,7 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
@updateLabel(selectedObject, el)
@updateLabel(selectedObject, el, @)
if value?
if !field.length and fieldName
@addInput(fieldName, value)
......@@ -580,8 +588,8 @@ class GitLabDropdown
# Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop)
updateLabel: (selected = null, el = null) =>
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el)
updateLabel: (selected = null, el = null, instance = null) =>
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
$.fn.glDropdown = (opts) ->
return @.each ->
......
......@@ -34,6 +34,8 @@ class @GLForm
# form and textarea event listeners
@addEventListeners()
gl.text.init(@form)
# hide discard button
@form.find('.js-note-discard').hide()
......@@ -42,6 +44,7 @@ class @GLForm
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
gl.text.removeListeners(@form)
addEventListeners: ->
@textarea.on 'focus', ->
......
......@@ -4,5 +4,4 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require Chart
#= require_tree .
......@@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.content').width()/2 - 100
# Don't split graph size in half for mobile devices.
if $(window).width() < 768
@width = $('.content').width() - 80
else
@width = ($('.content').width() / 2) - 100
@height = 200
@x = null
@y = null
......
......@@ -68,12 +68,15 @@ issuable_created = false
Turbolinks.visit(issuesUrl);
initChecks: ->
@issuableBulkActions = $('.bulk-update').data('bulkActions')
$('.check_all_issues').off('click').on('click', ->
$('.selected_issue').prop('checked', @checked)
Issuable.checkChanged()
)
$('.selected_issue').off('change').on('change', Issuable.checkChanged)
$('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@))
checkChanged: ->
checked_issues = $('.selected_issue:checked')
......@@ -88,3 +91,6 @@ issuable_created = false
$('#update_issues_ids').val []
$('.issues_bulk_update').hide()
$('.issues-other-filters').show()
@issuableBulkActions.willUpdateLabels = false
return true
......@@ -6,6 +6,13 @@ class @IssueStatusSelect
$(el).glDropdown(
selectable: true
fieldName: fieldName
toggleLabel: (selected, el, instance) =>
label = 'Author'
$item = instance.dropdown.find('.is-active')
label = $item.text() if $item.length
label
clicked: (item, $el, e)->
e.preventDefault()
id: (obj, el) ->
$(el).data("id")
)
......@@ -7,6 +7,11 @@ class @IssuableBulkActions
@issues = @getElement('.issues-list .issue')
} = opts
# Save instance
@form.data 'bulkActions', @
@willUpdateLabels = false
@bindEvents()
# Fixes bulk-assign not working when navigating through pages
......@@ -87,11 +92,12 @@ class @IssuableBulkActions
add_label_ids : []
remove_label_ids : []
@getLabelsToApply().map (id) ->
formData.update.add_label_ids.push id
if @willUpdateLabels
@getLabelsToApply().map (id) ->
formData.update.add_label_ids.push id
@getLabelsToRemove().map (id) ->
formData.update.remove_label_ids.push id
@getLabelsToRemove().map (id) ->
formData.update.remove_label_ids.push id
formData
......
......@@ -210,9 +210,21 @@ class @LabelsSelect
if $dropdown.hasClass('js-filter-bulk-update')
indeterminate = instance.indeterminateIds
active = instance.activeIds
if indeterminate.indexOf(label.id) isnt -1
selectedClass.push 'is-indeterminate'
if active.indexOf(label.id) isnt -1
# Remove is-indeterminate class if the item will be marked as active
i = selectedClass.indexOf 'is-indeterminate'
selectedClass.splice i, 1 unless i is -1
selectedClass.push 'is-active'
# Add input manually
instance.addInput @fieldName, label.id
if $form.find("input[type='hidden']\
[name='#{$dropdown.data('fieldName')}']\
[value='#{this.id(label)}']").length
......@@ -307,6 +319,8 @@ class @LabelsSelect
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
_this.enableBulkLabelDropdown()
if $dropdown.hasClass('js-filter-bulk-update')
return
......@@ -328,6 +342,10 @@ class @LabelsSelect
setIndeterminateIds: ->
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@indeterminateIds = _this.getIndeterminateIds()
setActiveIds: ->
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@activeIds = _this.getActiveIds()
)
@bindEvents()
......@@ -352,3 +370,17 @@ class @LabelsSelect
label_ids.push $("#issue_#{issue_id}").data('labels')
_.flatten(label_ids)
getActiveIds: ->
label_ids = []
$('.selected_issue:checked').each (i, el) ->
issue_id = $(el).data('id')
label_ids.push $("#issue_#{issue_id}").data('labels')
_.intersection.apply _, label_ids
enableBulkLabelDropdown: ->
if $('.selected_issue:checked').length
issuableBulkActions = $('.bulk-update').data('bulkActions')
issuableBulkActions.willUpdateLabels = true
......@@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) ->
$this = $(@)
$this
.find('.fade-right')
.toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth'))
.siblings('.fade-right')
.toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'))
$ ->
$('.fade-left').addClass('end-scroll')
hideEndFade($('.scrolling-tabs'))
......@@ -21,5 +20,5 @@ $ ->
currentPosition = $this.scrollLeft()
maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
$this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
$this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)
$this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0)
$this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1)
#= require raphael
#= require g.raphael
#= require g.bar
((w) ->
w.gl ?= {}
w.gl.text ?= {}
gl.text.randomString = -> Math.random().toString(36).substring(7)
gl.text.replaceRange = (s, start, end, substitute) ->
s.substring(0, start) + substitute + s.substring(end);
gl.text.selectedText = (text, textarea) ->
text.substring(textarea.selectionStart, textarea.selectionEnd)
gl.text.insertText = (textArea, text, tag, selected, wrap) ->
selectedSplit = selected.split('\n')
startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
if selectedSplit.length > 1 and not wrap
insertText = selectedSplit.map((val) ->
if val.indexOf(tag) is 0
"#{val.replace(tag, '')}"
else
"#{tag}#{val}"
).join('\n')
else
insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
if document.queryCommandSupported('insertText')
document.execCommand 'insertText', false, insertText
else
try
document.execCommand("ms-beginUndoUnit")
textArea.value = @replaceRange(
text,
textArea.selectionStart,
textArea.selectionEnd,
insertText)
try
document.execCommand("ms-endUndoUnit")
@moveCursor(textArea, tag, wrap)
gl.text.moveCursor = (textArea, tag, wrapped) ->
return unless textArea.setSelectionRange
if textArea.selectionStart is textArea.selectionEnd
if wrapped
pos = textArea.selectionStart - tag.length
else
pos = textArea.selectionStart
textArea.setSelectionRange pos, pos
gl.text.updateText = (textArea, tag, wrap) ->
$textArea = $(textArea)
oldVal = $textArea.val()
textArea = $textArea.get(0)
text = $textArea.val()
selected = @selectedText(text, textArea)
$textArea.focus()
@insertText(textArea, text, tag, selected, wrap)
gl.text.init = (form) ->
self = @
$('.js-md', form)
.off 'click'
.on 'click', ->
$this = $(@)
self.updateText(
$this.closest('.md-area').find('textarea'),
$this.data('md-tag'),
not $this.data('md-prepend')
)
gl.text.removeListeners = (form) ->
$('.js-md', form).off()
) window
......@@ -4,18 +4,10 @@ class @Milestone
type: "PUT"
url: issue_url
data: data
success: (data) ->
if data.saved == true
if data.assignee_avatar_url
img_tag = $('<img/>')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
new Flash("Issue update failed", 'alert')
success: (_data) =>
@successCallback(_data, li)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
@sortIssues: (data) ->
......@@ -25,9 +17,10 @@ class @Milestone
type: "PUT"
url: sort_issues_url
data: data
success: (data) ->
if data.saved != true
new Flash("Issues update failed", 'alert')
success: (_data) =>
@successCallback(_data)
error: ->
new Flash("Issues update failed", 'alert')
dataType: "json"
@sortMergeRequests: (data) ->
......@@ -37,9 +30,10 @@ class @Milestone
type: "PUT"
url: sort_mr_url
data: data
success: (data) ->
if data.saved != true
new Flash("MR update failed", 'alert')
success: (_data) =>
@successCallback(_data)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
@updateMergeRequest: (li, merge_request_url, data) ->
......@@ -47,20 +41,23 @@ class @Milestone
type: "PUT"
url: merge_request_url
data: data
success: (data) ->
if data.saved == true
if data.assignee_avatar_url
img_tag = $('<img/>')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
new Flash("Issue update failed", 'alert')
success: (_data) =>
@successCallback(_data, li)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
@successCallback: (data, element) =>
if data.assignee
img_tag = $('<img/>')
img_tag.attr('src', data.assignee.avatar_url)
img_tag.addClass('avatar s16')
$(element).find('.assignee-icon').html(img_tag)
else
$(element).find('.assignee-icon').html('')
$(element).effect 'highlight'
constructor: ->
oldMouseStart = $.ui.sortable.prototype._mouseStart
$.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
......@@ -81,8 +78,10 @@ class @Milestone
stop: (event, ui) ->
$(".issues-sortable-list").css "min-height", "0px"
update: (event, ui) ->
data = $(this).sortable("serialize")
Milestone.sortIssues(data)
# Prevents sorting from container which element has been removed.
if $(this).find(ui.item).length > 0
data = $(this).sortable("serialize")
Milestone.sortIssues(data)
receive: (event, ui) ->
new_state = $(this).data('state')
......
......@@ -4,9 +4,6 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require raphael
#= require g.raphael
#= require g.bar
#= require_tree .
$ ->
......
......@@ -102,12 +102,15 @@ class @Notes
keydownNoteText: (e) ->
$this = $(this)
if $this.val() is '' and e.which is 38 #aka the up key
if $this.val() is '' and e.which is 38 and not isMetaKey e
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote])
isMetaKey = (e) ->
(e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
initRefresh: ->
clearInterval(Notes.interval)
Notes.interval = setInterval =>
......
class @NotificationsDropdown
constructor: ->
$(document)
.off 'click', '.update-notification'
.on 'click', '.update-notification', (e) ->
e.preventDefault()
return if $(this).is('.is-active') and $(this).data('notification-level') is 'custom'
notificationLevel = $(@).data 'notification-level'
label = $(@).data 'notification-title'
form = $(this).parents('.notification-form:first')
form.find('.js-notification-loading').toggleClass 'fa-bell fa-spin fa-spinner'
form.find('#notification_setting_level').val(notificationLevel)
form.submit()
$(document)
.off 'ajax:success', '.notification-form'
.on 'ajax:success', '.notification-form', (e, data) ->
if data.saved
$(e.currentTarget)
.closest('.notification-dropdown')
.replaceWith(data.html)
else
new Flash('Failed to save new settings', 'alert')
class @NotificationsForm
constructor: ->
@removeEventListeners()
@initEventListeners()
removeEventListeners: ->
$(document).off 'change', '.js-custom-notification-event'
initEventListeners: ->
$(document).on 'change', '.js-custom-notification-event', @toggleCheckbox
toggleCheckbox: (e) =>
$checkbox = $(e.currentTarget)
$parent = $checkbox.closest('.checkbox')
@saveEvent($checkbox, $parent)
showCheckboxLoadingSpinner: ($parent) ->
$parent
.addClass 'is-loading'
.find '.custom-notification-event-loading'
.removeClass 'fa-check'
.addClass 'fa-spin fa-spinner'
.removeClass 'is-done'
saveEvent: ($checkbox, $parent) ->
form = $parent.parents('form:first')
$.ajax(
url: form.attr('action')
method: form.attr('method')
dataType: 'json'
data: form.serialize()
beforeSend: =>
@showCheckboxLoadingSpinner($parent)
).done (data) ->
$checkbox.enable()
if data.saved
$parent
.find '.custom-notification-event-loading'
.toggleClass 'fa-spin fa-spinner fa-check is-done'
setTimeout(->
$parent
.removeClass 'is-loading'
.find '.custom-notification-event-loading'
.toggleClass 'fa-spin fa-spinner fa-check is-done'
, 2000)
......@@ -8,6 +8,10 @@ class @Profile
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$(this).parents('form').submit()
# Automatically submit email form when it changes
$('#user_notification_email').on 'change', ->
$(this).parents('form').submit()
$('.update-username').on 'ajax:before', ->
$('.loading-username').show()
$(this).find('.update-success').hide()
......
......@@ -19,6 +19,7 @@ class @Project
$('.clone').text(url)
# Ref switcher
@initRefSwitcher()
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
......@@ -34,23 +35,6 @@ class @Project
$(@).parents('.no-password-message').remove()
e.preventDefault()
$('.update-notification').on 'click', (e) ->
e.preventDefault()
notification_level = $(@).data 'notification-level'
label = $(@).data 'notification-title'
$('#notification_setting_level').val(notification_level)
$('#notification-form').submit()
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
$('#notification-form').on 'ajax:success', (e, data) ->
if data.saved
new Flash("Notification settings saved", "notice")
else
new Flash("Failed to save new settings", "alert")
@projectSelectDropdown()
projectSelectDropdown: ->
......@@ -66,3 +50,42 @@ class @Project
changeProject: (url) ->
window.location = url
initRefSwitcher: ->
$('.js-project-refs-dropdown').each ->
$dropdown = $(@)
selected = $dropdown.data('selected')
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: $dropdown.data('refs-url')
data:
ref: $dropdown.data('ref')
).done (refs) ->
callback(refs)
selectable: true
filterable: true
filterByText: true
fieldName: 'ref'
renderRow: (ref) ->
if ref.header?
$('<li />')
.addClass('dropdown-header')
.text(ref.header)
else
link = $('<a />')
.attr('href', '#')
.addClass(if ref is selected then 'is-active' else '')
.text(ref)
.attr('data-ref', escape(ref))
$('<li />')
.append(link)
id: (obj, $el) ->
$el.attr('data-ref')
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e) ->
$dropdown.closest('form').submit()
)
......@@ -51,15 +51,19 @@ class @Sidebar
$this = $(e.currentTarget)
$todoLoading = $('.js-issuable-todo-loading')
$btnText = $('.js-issuable-todo-text', $this)
ajaxType = if $this.attr('data-id') then 'PATCH' else 'POST'
ajaxUrlExtra = if $this.attr('data-id') then "/#{$this.attr('data-id')}" else ''
ajaxType = if $this.attr('data-delete-path') then 'DELETE' else 'POST'
if $this.attr('data-delete-path')
url = "#{$this.attr('data-delete-path')}"
else
url = "#{$this.data('url')}"
$.ajax(
url: "#{$this.data('url')}#{ajaxUrlExtra}"
url: url
type: ajaxType
dataType: 'json'
data:
issuable_id: $this.data('issuable')
issuable_id: $this.data('issuable-id')
issuable_type: $this.data('issuable-type')
beforeSend: =>
@beforeTodoSend($this, $todoLoading)
......@@ -82,15 +86,15 @@ class @Sidebar
else
$todoPendingCount.removeClass 'hidden'
if data.todo?
if data.delete_path?
$btn
.attr 'aria-label', $btn.data('mark-text')
.attr 'data-id', data.todo.id
.attr 'data-delete-path', data.delete_path
$btnText.text $btn.data('mark-text')
else
$btn
.attr 'aria-label', $btn.data('todo-text')
.removeAttr 'data-id'
.removeAttr 'data-delete-path'
$btnText.text $btn.data('todo-text')
sidebarDropdownLoading: (e) ->
......
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require d3
#= require_tree .
......@@ -6,12 +6,6 @@ class @Calendar
@daySizeWithSpace = @daySize + (@daySpace * 2)
@monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
@months = []
@highestValue = 0
# Get the highest value from the timestampes
_.each timestamps, (count) =>
if count > @highestValue
@highestValue = count
# Loop through the timestamps to create a group of objects
# The group of objects will be grouped based on the day of the week they are
......@@ -39,8 +33,8 @@ class @Calendar
i++
# Init color functions
@color = @initColor()
@colorKey = @initColorKey()
@color = @initColor()
# Init the svg element
@renderSvg(group)
......@@ -104,7 +98,7 @@ class @Calendar
.attr 'class', 'user-contrib-cell js-tooltip'
.attr 'fill', (stamp) =>
if stamp.count isnt 0
@color(stamp.count)
@color(Math.min(stamp.count, 40))
else
'#ededed'
.attr 'data-container', 'body'
......@@ -164,10 +158,11 @@ class @Calendar
color
initColor: ->
colorRange = ['#ededed', @colorKey(0), @colorKey(1), @colorKey(2), @colorKey(3)]
d3.scale
.linear()
.range(['#acd5f2', '#254e77'])
.domain([0, @highestValue])
.threshold()
.domain([0, 10, 20, 30])
.range(colorRange)
initColorKey: ->
d3.scale
......
......@@ -37,3 +37,4 @@
@import "framework/timeline.scss";
@import "framework/typography.scss";
@import "framework/zen.scss";
@import "framework/blank";
.blank-state {
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
}
.blank-state-no-icon {
padding-top: 40px;
padding-bottom: 40px;
}
.blank-state-title {
margin-top: 0;
margin-bottom: 5px;
font-size: 19px;
font-weight: normal;
}
.blank-state-text {
margin-top: 0;
margin-bottom: $gl-padding;
font-size: 15px;
}
......@@ -97,6 +97,22 @@
}
}
.sub-header-block {
background-color: $white-light;
border-bottom: 1px solid $white-dark;
padding: 11px 0;
margin-bottom: 11px;
.oneline {
line-height: 35px;
}
&.no-bottom-space {
border-bottom: 0;
margin-bottom: 0;
}
}
.cover-block {
text-align: center;
background: $background-color;
......
......@@ -461,10 +461,12 @@
}
}
.ui-state-active,
.ui-state-hover {
color: $md-link-color;
background-color: $calendar-hover-bg;
.ui-datepicker-calendar {
.ui-state-hover,
.ui-state-active {
color: #fff;
border: 0;
}
}
.ui-datepicker-prev,
......
......@@ -26,7 +26,6 @@ header {
text-align: center;
#tanuki-logo, img {
width: 36px;
height: 36px;
}
}
......@@ -132,6 +131,10 @@ header {
transition-duration: .3s;
z-index: 999;
svg, img {
height: 36px;
}
&:hover {
cursor: pointer;
}
......
......@@ -65,6 +65,11 @@
a {
padding-top: 0;
line-height: 1;
border-bottom: 1px solid $border-color;
&.btn.btn-xs {
padding: 2px 5px;
}
}
}
}
......@@ -97,5 +102,30 @@
white-space: pre-wrap;
word-break: keep-all;
}
@include bulleted-list;
}
}
.toolbar-group {
float: left;
margin-right: -5px;
margin-left: $gl-padding;
&:first-child {
margin-left: 0;
}
}
.toolbar-btn {
float: left;
padding: 0 5px;
color: #959494;
background: transparent;
border: 0;
outline: 0;
&:hover {
color: $gl-link-color;
}
}
......@@ -110,3 +110,17 @@
font-size: 16px;
line-height: 24px;
}
@mixin bulleted-list {
> ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
}
\ No newline at end of file
......@@ -52,6 +52,19 @@
.git-clone-holder {
display: none;
}
// Display Star and Fork buttons without counters on mobile.
.project-action-buttons {
display: block;
.count-buttons .btn {
margin: 0 10px;
}
.count-buttons .count-with-arrow {
display: none;
}
}
}
.project-stats {
......
@mixin fade($gradient-direction, $rgba, $gradient-color) {
visibility: visible;
opacity: 1;
visibility: hidden;
opacity: 0;
z-index: 2;
position: absolute;
bottom: 12px;
......@@ -13,11 +13,18 @@
background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
&.end-scroll {
visibility: hidden;
opacity: 0;
&.scrolling {
visibility: visible;
opacity: 1;
transition-duration: .3s;
}
.fa {
position: relative;
top: 3px;
font-size: 13px;
color: $btn-placeholder-gray;
}
}
@mixin scrolling-links() {
......@@ -25,6 +32,7 @@
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
}
......@@ -104,10 +112,6 @@
width: 50%;
line-height: 28px;
&.wiki-page {
padding: 16px 10px 11px;
}
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
......@@ -136,7 +140,7 @@
}
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
@media (max-width: $screen-xs-max) {
width: 100%;
}
}
......@@ -220,6 +224,7 @@
form {
display: block;
height: auto;
margin-bottom: 14px;
input {
width: 100%;
......@@ -268,7 +273,7 @@
float: right;
padding: 7px 0 0;
@media (max-width: $screen-xs-max) {
@media (max-width: $screen-sm-max) {
display: none;
}
......@@ -299,33 +304,9 @@
}
.nav-links {
@include scrolling-links();
border-bottom: none;
height: 51px;
svg {
position: relative;
top: 2px;
margin-right: 2px;
height: 15px;
width: auto;
path,
polygon {
fill: $layout-link-gray;
}
}
.fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0;
}
.fade-left {
@include fade(right, rgba(250, 250, 250, 0.4), $background-color);
left: 0;
}
li {
a {
......@@ -361,18 +342,6 @@
}
}
}
.nav-control {
.fade-right {
@media (min-width: $screen-xs-max) {
right: 68px;
}
@media (max-width: $screen-xs-min) {
right: 0;
}
}
}
}
.scrolling-tabs-container {
......@@ -380,15 +349,42 @@
.nav-links {
@include scrolling-links();
}
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: -5px;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: -5px;
.fa {
left: -7px;
}
}
&.sub-nav-scroll {
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: 0;
.fa {
right: -23px;
}
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: 0;
.fa {
left: 10px;
}
}
}
}
......@@ -401,21 +397,19 @@
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $white-light);
right: 0;
right: -5px;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $white-light);
left: 0;
}
&.event-filter {
.fade-right {
visibility: hidden;
left: -5px;
@media (max-width: $screen-xs-max) {
visibility: visible;
}
.fa {
left: -7px;
}
}
}
......
......@@ -9,6 +9,10 @@
margin-top: -2px;
float: right;
}
.dropdown-menu-toggle {
line-height: 20px;
}
}
.panel-body {
......
......@@ -165,11 +165,6 @@
background-size: 16px 16px !important;
}
/** Branch/tag selector **/
.project-refs-form .select2-container {
width: 160px !important;
}
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-ajax-error,
......
......@@ -91,7 +91,6 @@
text-decoration: none;
font-weight: normal;
outline: none;
white-space: nowrap;
&:hover,
&:active,
......
......@@ -268,5 +268,10 @@ $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: #faf9f9;
/*
* Personal Access Tokens
*/
$personal-access-tokens-disabled-label-color: #bbb;
$ci-output-bg: #1d1f21;
$ci-text-color: #c5c8c6;
......@@ -26,6 +26,8 @@
.commit-info-row {
margin-bottom: 10px;
line-height: 24px;
padding-top: 6px;
&.commit-info-row-header {
line-height: 34px;
......
......@@ -80,9 +80,14 @@
.commit {
padding: 10px 0;
position: relative;
@media (min-width: $screen-sm-min) {
padding-left: 46px;
padding-left: 20px;
.commit-info-block {
padding-left: 44px;
}
}
&:not(:last-child) {
......@@ -95,8 +100,11 @@
vertical-align: baseline;
}
.avatar {
margin-left: -46px;
position: absolute;
top: 10px;
left: 16px;
}
.item-title {
......
......@@ -4,6 +4,11 @@
margin-bottom: $gl-padding;
border-radius: 3px;
.commit-short-id {
font-family: $regular_font;
font-weight: 400;
}
.diff-header {
position: relative;
background: $background-color;
......
......@@ -60,13 +60,14 @@
.encoding-selector,
.license-selector,
.gitignore-selector {
.gitignore-selector,
.gitlab-ci-yml-selector {
display: inline-block;
vertical-align: top;
font-family: $regular_font;
}
.gitignore-selector, .license-selector {
.gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown {
line-height: 21px;
}
......@@ -76,4 +77,10 @@
width: 220px;
}
}
.gitlab-ci-yml-selector {
.dropdown-menu-toggle {
width: 250px;
}
}
}
......@@ -54,6 +54,10 @@
}
}
code {
white-space: pre-wrap;
}
pre {
border: none;
background: #f9f9f9;
......@@ -136,9 +140,10 @@
.event-last-push {
overflow: auto;
width: 100%;
.event-last-push-text {
@include str-truncated(100%);
padding: 5px 0;
padding: 4px 0;
font-size: 13px;
float: left;
margin-right: -150px;
......
......@@ -57,4 +57,11 @@
.documentation {
padding: 7px;
// Border around images in the help pages.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
}
}
......@@ -4,6 +4,13 @@
margin-right: 1px;
}
}
// Border around images in issue and MR descriptions.
.description img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
}
}
.issuable-filter-count {
......
......@@ -6,6 +6,7 @@
height: 30px;
display: inline-block;
margin-right: 10px;
margin-bottom: 10px;
}
&.suggest-colors-dropdown {
......@@ -50,11 +51,10 @@
.label-row {
.label-name {
display: block;
display: inline-block;
margin-bottom: 10px;
@media (min-width: $screen-sm-min) {
display: inline-block;
width: 200px;
margin-bottom: 0;
}
......@@ -63,6 +63,7 @@
.label-description {
display: block;
margin-bottom: 10px;
margin-left: 50px;
@media (min-width: $screen-sm-min) {
display: inline-block;
......
......@@ -119,7 +119,12 @@
margin-bottom: 0;
}
@media (max-width: $screen-sm-max) {
.btn-grouped {
margin-left: 0;
margin-right: 7px;
}
@media (max-width: $screen-xs-max) {
h4 {
font-size: 15px;
}
......@@ -131,10 +136,14 @@
.btn,
.btn-group,
.accept-action {
width: 100%;
margin-bottom: 4px;
}
.accept-action {
width: 100%;
text-align: center;
}
.accept-control {
width: 100%;
text-align: center;
......@@ -284,7 +293,7 @@
margin-bottom: 0;
}
@media (min-width: $screen-sm-min) {
@media (min-width: $screen-xs-min) {
float: left;
width: 50%;
margin-bottom: 0;
......
......@@ -179,6 +179,10 @@
border-top: 1px solid $border-color;
}
.md-helper {
padding-top: 10px;
}
.toolbar-button {
padding: 0;
background: none;
......@@ -219,3 +223,16 @@
float: left;
}
}
.note-form-actions {
@media (max-width: $screen-xs-max) {
.btn {
float: none;
width: 100%;
&:not(:last-child) {
margin-bottom: 10px;
}
}
}
}
......@@ -84,24 +84,14 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
// On diffs code should wrap nicely and not overflow
code {
white-space: pre-wrap;
}
// Reset ul style types since we're nested inside a ul already
& > ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
......@@ -117,6 +107,13 @@ ul.notes {
code {
word-break: keep-all;
}
// Border around images in issue and MR comments.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px 0;
}
}
}
......
......@@ -192,6 +192,25 @@
}
}
.personal-access-tokens-never-expires-label {
color: $personal-access-tokens-disabled-label-color;
}
.datepicker.personal-access-tokens-expires-at .ui-state-disabled span {
text-align: center;
}
.created-personal-access-token-container {
#created-personal-access-token {
width: 90%;
display: inline;
}
.btn-clipboard {
margin-left: 5px;
}
}
.user-profile {
@media (max-width: $screen-xs-max) {
.cover-block {
......
......@@ -101,7 +101,8 @@
.notifications-btn {
.fa-bell {
.fa-bell,
.fa-spinner {
margin-right: 6px;
}
......@@ -128,11 +129,6 @@
}
}
.btn-group:not(:first-child):not(:last-child) > .btn {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
form {
margin-left: 10px;
}
......@@ -378,7 +374,7 @@ a.deploy-project-label {
.project-stats {
margin-top: $gl-padding;
margin-bottom: 0;
padding: 16px 0;
padding: 0;
background-color: $white-light;
font-size: 0;
......@@ -387,13 +383,14 @@ a.deploy-project-label {
}
.nav li {
display: inline;
display: inline-block;
margin: 16px 0;
margin-right: 16px;
}
.nav > li > a {
background-color: transparent;
margin-right: 12px;
padding: 0 10px;
padding: 5px 10px;
font-size: 15px;
color: $notes-light-color;
}
......@@ -407,12 +404,17 @@ a.deploy-project-label {
font-size: 17px;
}
li.missing a {
color: #5a6069;
border: 1px dashed #dce0e5;
li.missing {
border: 1px dashed $border-gray-light;
border-radius: $border-radius-default;
a {
color: $notes-light-color;
display: block;
}
&:hover {
background-color: #f0f2f5;
background-color: $gray-normal;
}
}
......@@ -499,7 +501,8 @@ pre.light-well {
.activity-filter-block {
.controls {
padding-bottom: 10px;
padding-bottom: 7px;
margin-top: 8px;
border-bottom: 1px solid $border-color;
}
}
......@@ -607,3 +610,26 @@ pre.light-well {
.disabled-item {
@extend .btn.disabled;
}
.custom-notifications-form {
.is-loading {
.custom-notification-event-loading {
display: inline-block;
}
}
}
.custom-notification-event-loading {
display: none;
margin-left: 5px;
&.is-done {
color: $gl-text-green;
}
}
.project-refs-form {
.dropdown-menu {
width: 300px;
}
}
......@@ -14,24 +14,38 @@
font-size: 10px;
}
#contributors-master {
@include make-md-column(12);
svg {
width: 100%;
}
}
#contributors {
.contributors-list {
margin: 0 0 10px;
list-style: none;
padding: 0;
svg {
width: 100%;
}
}
.person {
&:nth-child(even) {
float: right;
}
float: left;
@include make-md-column(6);
margin-top: 10px;
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.person .spark {
display: block;
background: #f3f3f3;
width: 100%;
}
.person .area-contributor {
......
......@@ -62,6 +62,10 @@
}
}
code {
white-space: pre-wrap;
}
pre {
border: none;
background: #f9f9f9;
......
......@@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController
end
def preview
render 'preview', layout: 'devise'
end
def create
......
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user)
return head(403) if @runner.is_shared? || @runner.locked?
runner_project = @runner.assign_to(@project, current_user)
if runner_project.persisted?
redirect_to admin_runner_path(@runner)
else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
......
......@@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
include PageLayoutHelper
include WorkhorseHelper
before_action :authenticate_user_from_token!
before_action :authenticate_user_from_private_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :reject_blocked!
......@@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
......@@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base
render_404
end
rescue_from Gitlab::Access::AccessDeniedError do |exception|
render_403
end
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
......@@ -64,17 +68,10 @@ class ApplicationController < ActionController::Base
end
end
# From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
# https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
def authenticate_user_from_token!
user_token = if params[:authenticity_token].presence
params[:authenticity_token].presence
elsif params[:private_token].presence
params[:private_token].presence
elsif request.headers['PRIVATE-TOKEN'].present?
request.headers['PRIVATE-TOKEN']
end
user = user_token && User.find_by_authentication_token(user_token.to_s)
# This filter handles both private tokens and personal access tokens
def authenticate_user_from_private_token!
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
user = User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
if user
# Notice we are passing store false, so the user is not
......@@ -336,6 +333,10 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('git')
end
def gitlab_project_import_enabled?
current_application_settings.import_sources.include?('gitlab_project')
end
def two_factor_authentication_required?
current_application_settings.require_two_factor_authentication
end
......
......@@ -23,31 +23,21 @@ module MembershipActions
def leave
@member = membershipable.members.find_by(user_id: current_user)
return render_403 unless @member
Members::DestroyService.new(@member, current_user).execute
source_type = @member.real_source_type.humanize(capitalize: false)
if can?(current_user, action_member_permission(:destroy, @member), @member)
notice =
if @member.request?
"Your access request to the #{source_type} has been withdrawn."
else
"You left the \"#{@member.source.human_name}\" #{source_type}."
end
@member.destroy
log_audit_event(@member, action: :destroy) unless @member.request?
redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice
else
if cannot_leave?
alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}."
alert << " Transfer or delete the #{source_type}."
redirect_to polymorphic_url(membershipable), alert: alert
notice =
if @member.request?
"Your access request to the #{source_type} has been withdrawn."
else
render_403
"You left the \"#{@member.source.human_name}\" #{source_type}."
end
end
log_audit_event(@member, action: :destroy) unless @member.request?
redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize]
redirect_to redirect_path, notice: notice
end
protected
......@@ -56,10 +46,6 @@ module MembershipActions
raise NotImplementedError
end
def cannot_leave?
raise NotImplementedError
end
def log_audit_event(member, options = {})
AuditEventService.new(current_user, membershipable, options).
for_member(member).security_event
......
class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy, :destroy_all]
include TodosHelper
before_action :find_todos, only: [:index, :destroy_all]
def index
@todos = @todos.page(params[:page])
end
def destroy
todo.done
todo_notice = 'Todo was successfully marked as done.'
TodoService.new.mark_todos_as_done([todo], current_user)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: todo_notice }
format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
format.js { head :ok }
format.json do
render json: { count: @todos.size, done_count: current_user.todos.done.count }
end
format.json { render json: { count: todos_pending_count, done_count: todos_done_count } }
end
end
def destroy_all
@todos.each(&:done)
TodoService.new.mark_todos_as_done(@todos, current_user)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { head :ok }
format.json do
find_todos
render json: { count: @todos.size, done_count: current_user.todos.done.count }
end
format.json { render json: { count: todos_pending_count, done_count: todos_done_count } }
end
end
private
def todo
@todo ||= current_user.todos.find(params[:id])
@todo ||= find_todos.find(params[:id])
end
def find_todos
@todos = TodosFinder.new(current_user, params).execute
@todos ||= TodosFinder.new(current_user, params).execute
end
end
......@@ -48,9 +48,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
def destroy
@group_member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
Members::DestroyService.new(@group_member, current_user).execute
log_audit_event(@group_member, action: :destroy)
respond_to do |format|
......@@ -81,8 +79,4 @@ class Groups::GroupMembersController < Groups::ApplicationController
# MembershipActions concern
alias_method :membershipable, :group
def cannot_leave?
@group.last_owner?(current_user)
end
end
class Groups::NotificationSettingsController < Groups::ApplicationController
before_action :authenticate_user!
def update
notification_setting = current_user.notification_settings_for(group)
saved = notification_setting.update_attributes(notification_setting_params)
render json: { saved: saved }
end
private
def notification_setting_params
params.require(:notification_setting).permit(:level)
end
end
class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled
def new
@namespace_id = project_params[:namespace_id]
@namespace_name = Namespace.find(project_params[:namespace_id]).name
@path = project_params[:path]
end
def create
unless file_is_valid?
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
imported_file = project_params[:file].path + "-import"
FileUtils.copy_entry(project_params[:file].path, imported_file)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
File.expand_path(imported_file),
project_params[:path]).execute
if @project.saved?
redirect_to(
project_path(@project),
notice: "Project '#{@project.name}' is being imported."
)
else
redirect_to(
new_import_gitlab_project_path,
alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}"
)
end
end
private
def file_is_valid?
project_params[:file] && project_params[:file].respond_to?(:read)
end
def verify_gitlab_project_import_enabled
render_404 unless gitlab_project_import_enabled?
end
def project_params
params.permit(
:path, :namespace_id, :file
)
end
end
class NotificationSettingsController < ApplicationController
before_action :authenticate_user!
def create
project = Project.find(params[:project][:id])
return render_404 unless can?(current_user, :read_project, project)
@notification_setting = current_user.notification_settings_for(project)
@saved = @notification_setting.update_attributes(notification_setting_params)
render_response
end
def update
@notification_setting = current_user.notification_settings.find(params[:id])
@saved = @notification_setting.update_attributes(notification_setting_params)
render_response
end
private
def render_response
render json: {
html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting),
saved: @saved
}
end
def notification_setting_params
allowed_fields = NotificationSetting::EMAIL_EVENTS.dup
allowed_fields << :level
params.require(:notification_setting).permit(allowed_fields)
end
end
......@@ -5,7 +5,7 @@ class Profiles::AccountsController < Profiles::ApplicationController
def unlink
provider = params[:provider]
current_user.identities.find_by(provider: provider).destroy
current_user.identities.find_by(provider: provider).destroy unless provider.to_s == 'saml'
redirect_to profile_account_path
end
end
class Profiles::NotificationsController < Profiles::ApplicationController
def show
@user = current_user
@group_notifications = current_user.notification_settings.for_groups
@project_notifications = current_user.notification_settings.for_projects
@group_notifications = current_user.notification_settings.for_groups.order(:id)
@project_notifications = current_user.notification_settings.for_projects.order(:id)
@global_notification_setting = current_user.global_notification_setting
end
def update
if current_user.update_attributes(user_params) && update_notification_settings
if current_user.update_attributes(user_params)
flash[:notice] = "Notification settings saved"
else
flash[:alert] = "Failed to save new settings"
......@@ -19,16 +19,4 @@ class Profiles::NotificationsController < Profiles::ApplicationController
def user_params
params.require(:user).permit(:notification_email)
end
def global_notification_setting_params
params.require(:global_notification_setting).permit(:level)
end
private
def update_notification_settings
return true unless global_notification_setting_params
current_user.global_notification_setting.update_attributes(global_notification_setting_params)
end
end
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
before_action :load_personal_access_tokens, only: :index
def index
@personal_access_token = current_user.personal_access_tokens.build
end
def create
@personal_access_token = current_user.personal_access_tokens.generate(personal_access_token_params)
if @personal_access_token.save
flash[:personal_access_token] = @personal_access_token.token
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
else
load_personal_access_tokens
render :index
end
end
def revoke
@personal_access_token = current_user.personal_access_tokens.find(params[:id])
if @personal_access_token.revoke!
flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!"
else
flash[:alert] = "Could not revoke personal access token #{@personal_access_token.name}."
end
redirect_to profile_personal_access_tokens_path
end
private
def personal_access_token_params
params.require(:personal_access_token).permit(:name, :expires_at)
end
def load_personal_access_tokens
@active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at)
@inactive_personal_access_tokens = current_user.personal_access_tokens.inactive
end
end
......@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController
end
def require_branch_head
unless @repository.branch_names.include?(@ref)
unless @repository.branch_exists?(@ref)
redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref),
notice: "This action is not allowed unless you are on a branch"
......
......@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
def new
commit unless @repository.empty?
......@@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController
file_content_encoding: params[:encoding]
}
end
def validate_diff_params
if [:since, :to, :offset].any? { |key| params[key].blank? }
render nothing: true
end
end
end
......@@ -68,8 +68,12 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
raw_notes = @issue.notes_with_associations.fresh
@notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_to do |format|
......@@ -117,6 +121,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :edit
end
end
format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
......
......@@ -86,6 +86,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
respond_to do |format|
format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
......@@ -204,7 +213,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
return render_404 unless @merge_request.approved?
unless @merge_request.mergeable?
# Disable the CI check if merge_when_build_succeeds is enabled since we have
# to wait until CI completes to know
unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?)
@status = :failed
return
end
......@@ -225,8 +236,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present?
if @merge_request.pipeline && @merge_request.pipeline.active?
if params[:merge_when_build_succeeds].present?
unless @merge_request.pipeline
@status = :failed
return
end
if @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
......@@ -360,8 +376,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
@discussions = @notes.discussions
@discussions = @merge_request.mr_and_commit_notes.
inc_author_project_award_emoji.
fresh.
discussions
@notes = Banzai::NoteRenderer.render(
@discussions.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
@noteable = @merge_request
# Get commits from repository
......@@ -426,4 +455,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ensure_ref_fetched
@merge_request.ensure_ref_fetched
end
def merge_when_build_succeeds_active?
params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active?
end
end
......@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController
def create
@note = Notes::CreateService.new(project, current_user, note_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
......@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
......@@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController
name: note.name
}
elsif note.valid?
Banzai::NoteRenderer.render([note], @project, current_user)
{
valid: true,
id: note.id,
......
class Projects::NotificationSettingsController < Projects::ApplicationController
before_action :authenticate_user!
def update
notification_setting = current_user.notification_settings_for(project)
saved = notification_setting.update_attributes(notification_setting_params)
render json: { saved: saved }
end
private
def notification_setting_params
params.require(:notification_setting).permit(:level)
end
end
......@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def commit
@commit ||= @pipeline.commit_data
@commit ||= @pipeline.commit
end
end
......@@ -59,9 +59,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def destroy
@project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_project_member, @project_member)
@project_member.destroy
Members::DestroyService.new(@project_member, current_user).execute
log_audit_event(@project_member, action: :destroy)
......@@ -109,8 +107,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# MembershipActions concern
alias_method :membershipable, :project
def cannot_leave?
current_user == @project.owner
end
end
......@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) if @runner.is_shared? || @runner.locked?
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project)
runner_project = @runner.assign_to(project, current_user)
if @runner.assign_to(project, current_user)
if runner_project.persisted?
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
......
......@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
@runners = project.runners.ordered
@specific_runners = current_user.ci_authorized_runners.
where.not(id: project.runners).
ordered.page(params[:page]).per(20)
@project_runners = project.runners.ordered
@assignable_runners = current_user.ci_authorized_runners.
assignable_for(project).ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
......
......@@ -6,8 +6,10 @@ class Projects::TagsController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:destroy]
def index
sorted = VersionSorter.rsort(@repository.tag_names)
@tags = Kaminari.paginate_array(sorted).page(params[:page])
@sort = params[:sort] || 'name'
@tags = @repository.tags_sorted_by(@sort)
@tags = Kaminari.paginate_array(@tags).page(params[:page])
@releases = project.releases.where(tag: @tags)
end
......
class Projects::TodosController < Projects::ApplicationController
def create
todos = TodoService.new.mark_todo(issuable, current_user)
before_action :authenticate_user!, only: [:create]
render json: {
todo: todos,
count: current_user.todos.pending.count,
}
end
def update
current_user.todos.find_by_id(params[:id]).update(state: :done)
def create
todo = TodoService.new.mark_todo(issuable, current_user)
render json: {
count: current_user.todos.pending.count,
count: current_user.todos_pending_count,
delete_path: dashboard_todo_path(todo)
}
end
......@@ -22,7 +16,13 @@ class Projects::TodosController < Projects::ApplicationController
@issuable ||= begin
case params[:issuable_type]
when "issue"
@project.issues.find(params[:issuable_id])
issue = @project.issues.find(params[:issuable_id])
if can?(current_user, :read_issue, issue)
issue
else
render_404
end
when "merge_request"
@project.merge_requests.find(params[:issuable_id])
end
......
class ProjectsController < Projects::ApplicationController
include ExtractsPath
before_action :authenticate_user!, except: [:show, :activity]
before_action :authenticate_user!, except: [:show, :activity, :refs]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping]
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
......@@ -187,6 +187,48 @@ class ProjectsController < Projects::ApplicationController
)
end
def export
@project.add_export_job(current_user: current_user)
redirect_to(
edit_project_path(@project),
notice: "Project export started. A download link will be sent by email."
)
end
def download_export
export_project_path = @project.export_project_path
if export_project_path
send_file export_project_path, disposition: 'attachment'
else
redirect_to(
edit_project_path(@project),
alert: "Project export link has expired. Please generate a new export from your project settings."
)
end
end
def remove_export
if @project.remove_exports
flash[:notice] = "Project export has been deleted."
else
flash[:alert] = "Project export could not be deleted."
end
redirect_to(edit_project_path(@project))
end
def generate_new_export
if @project.remove_exports
export
else
redirect_to(
edit_project_path(@project),
alert: "Project export could not be deleted."
)
end
end
def toggle_star
current_user.toggle_star(@project)
@project.reload
......@@ -210,6 +252,24 @@ class ProjectsController < Projects::ApplicationController
}
end
def refs
options = {
'Branches' => @repository.branch_names,
}
unless @repository.tag_count.zero?
options['Tags'] = VersionSorter.rsort(@repository.tag_names)
end
# If reference is commit id - we should add it to branch/tag selectbox
ref = Addressable::URI.unescape(params[:ref])
if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options['Commits'] = [ref]
end
render json: options.to_json
end
private
def determine_layout
......@@ -255,8 +315,14 @@ class ProjectsController < Projects::ApplicationController
project.repository_exists? && !project.empty_repo?
end
# Override get_id from ExtractsPath, which returns the branch and file path
# Override extract_ref from ExtractsPath, which returns the branch and file path
# for the blob/tree, which in this case is just the root of the default branch.
# This way we avoid to access the repository.ref_names.
def extract_ref(_id)
[get_id, '']
end
# Override get_id from ExtractsPath in this case is just the root of the default branch.
def get_id
project.repository.root_ref
end
......
......@@ -123,7 +123,7 @@ class TodosFinder
end
def by_state(items)
case params[:state]
case params[:state].to_s
when 'done'
items.done
else
......
......@@ -101,22 +101,6 @@ module ApplicationHelper
'Never'
end
def grouped_options_refs
repository = @project.repository
options = [
['Branches', repository.branch_names],
['Tags', VersionSorter.rsort(repository.tag_names)]
]
# If reference is commit id - we should add it to branch/tag selectbox
if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options << ['Commit', [@ref]]
end
grouped_options_for_select(options, @ref || @project.default_branch)
end
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
......@@ -132,7 +116,7 @@ module ApplicationHelper
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
# Skip if user removed branch right after that
return false unless project.repository.branch_names.include?(event.branch_name)
return false unless project.repository.branch_exists?(event.branch_name)
# Skip if this was a mirror update
return false if project.mirror? && project.repository.up_to_date_with_upstream?(event.branch_name)
......@@ -216,7 +200,7 @@ module ApplicationHelper
def render_markup(file_name, file_content)
if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content))
Hamlit::RailsHelpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
elsif plain?(file_name)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment