Commit 7614d230 authored by James Lopez's avatar James Lopez

fix conflicts

parents ae777ea0 7ded28ff
...@@ -728,7 +728,7 @@ Metrics/ParameterLists: ...@@ -728,7 +728,7 @@ Metrics/ParameterLists:
# A complexity metric geared towards measuring complexity for a human reader. # A complexity metric geared towards measuring complexity for a human reader.
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Enabled: true Enabled: true
Max: 17 Max: 18
#################### Lint ################################ #################### Lint ################################
......
This diff is collapsed.
...@@ -323,6 +323,7 @@ request is as follows: ...@@ -323,6 +323,7 @@ request is as follows:
[shell command guidelines](doc/development/shell_commands.md) [shell command guidelines](doc/development/shell_commands.md)
1. If your code creates new files on disk please read the 1. If your code creates new files on disk please read the
[shared files guidelines](doc/development/shared_files.md). [shared files guidelines](doc/development/shared_files.md).
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
The **official merge window** is in the beginning of the month from the 1st to The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get the 7th day of the month. This is the best time to submit an MR and get
......
...@@ -190,6 +190,9 @@ gem 'babosa', '~> 1.0.2' ...@@ -190,6 +190,9 @@ gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input # Sanitizes SVG input
gem "loofah", "~> 2.0.3" gem "loofah", "~> 2.0.3"
# Working with license
gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing # Protect against bruteforcing
gem "rack-attack", '~> 4.3.1' gem "rack-attack", '~> 4.3.1'
...@@ -285,9 +288,9 @@ group :development, :test do ...@@ -285,9 +288,9 @@ group :development, :test do
gem 'teaspoon', '~> 1.1.0' gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0' gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.6.4' gem 'spring', '~> 1.7.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.0.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.38.0', require: false gem 'rubocop', '~> 0.38.0', require: false
...@@ -315,7 +318,7 @@ end ...@@ -315,7 +318,7 @@ end
gem "newrelic_rpm", '~> 3.14' gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 3.8.0' gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.6.1" gem "mail_room", "~> 0.6.1"
......
...@@ -452,6 +452,8 @@ GEM ...@@ -452,6 +452,8 @@ GEM
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.1.2) letter_opener (1.1.2)
launchy (~> 2.2) launchy (~> 2.2)
licensee (8.0.0)
rugged (>= 0.24b)
listen (3.0.5) listen (3.0.5)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
...@@ -485,8 +487,8 @@ GEM ...@@ -485,8 +487,8 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (~> 1.2) rack (~> 1.2)
octokit (3.8.0) octokit (4.3.0)
sawyer (~> 0.6.0, >= 0.5.3) sawyer (~> 0.7.0, >= 0.5.3)
omniauth (1.3.1) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
...@@ -712,8 +714,8 @@ GEM ...@@ -712,8 +714,8 @@ GEM
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3) tilt (>= 1.1, < 3)
sawyer (0.6.0) sawyer (0.7.0)
addressable (~> 2.3.5) addressable (>= 2.3.5, < 2.5)
faraday (~> 0.8, < 0.10) faraday (~> 0.8, < 0.10)
scss_lint (0.47.1) scss_lint (0.47.1)
rake (>= 0.9, < 11) rake (>= 0.9, < 11)
...@@ -769,10 +771,10 @@ GEM ...@@ -769,10 +771,10 @@ GEM
spinach (>= 0.4) spinach (>= 0.4)
spinach-rerun-reporter (0.0.2) spinach-rerun-reporter (0.0.2)
spinach (~> 0.8) spinach (~> 0.8)
spring (1.6.4) spring (1.7.1)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.0.0) spring-commands-spinach (1.1.0)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2) spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1) spring (>= 0.9.1)
...@@ -957,6 +959,7 @@ DEPENDENCIES ...@@ -957,6 +959,7 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0) jquery-ui-rails (~> 5.0.0)
kaminari (~> 0.16.3) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener (~> 1.1.2)
licensee (~> 8.0.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.6.1) mail_room (~> 0.6.1)
method_source (~> 0.8) method_source (~> 0.8)
...@@ -968,7 +971,7 @@ DEPENDENCIES ...@@ -968,7 +971,7 @@ DEPENDENCIES
newrelic_rpm (~> 3.14) newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 4.3.0)
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
...@@ -1030,9 +1033,9 @@ DEPENDENCIES ...@@ -1030,9 +1033,9 @@ DEPENDENCIES
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1) spinach-rails (~> 0.2.1)
spinach-rerun-reporter (~> 0.0.2) spinach-rerun-reporter (~> 0.0.2)
spring (~> 1.6.4) spring (~> 1.7.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0) spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.6.0) sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.3.0) state_machines-activerecord (~> 0.3.0)
......
...@@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart, ...@@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide]. the [Thoughtbot code review guide].
## Feature Freeze
5 working days before the 22nd the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of
things.
What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the
decision will be made by those reviewing a merge request and the release
manager.
During the feature freeze all merge requests that are meant to go into the next
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches.
## Copy & paste responses ## Copy & paste responses
### Improperly formatted issue ### Improperly formatted issue
......
8.7.0-pre 8.8.0-pre
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
group_projects_path: "/api/:version/groups/:id/projects.json" group_projects_path: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json" projects_path: "/api/:version/projects.json"
labels_path: "/api/:version/projects/:id/labels" labels_path: "/api/:version/projects/:id/labels"
license_path: "/api/:version/licenses/:key"
group: (group_id, callback) -> group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path) url = Api.buildUrl(Api.group_path)
...@@ -92,6 +93,16 @@ ...@@ -92,6 +93,16 @@
).done (projects) -> ).done (projects) ->
callback(projects) callback(projects)
# Return text for a specific license
licenseText: (key, data, callback) ->
url = Api.buildUrl(Api.license_path).replace(':key', key)
$.ajax(
url: url
data: data
).done (license) ->
callback(license)
buildUrl: (url) -> buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root? url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version) return url.replace(':version', gon.api_version)
...@@ -22,7 +22,17 @@ ...@@ -22,7 +22,17 @@
#= require cal-heatmap #= require cal-heatmap
#= require turbolinks #= require turbolinks
#= require autosave #= require autosave
#= require bootstrap #= require bootstrap/affix
#= require bootstrap/alert
#= require bootstrap/button
#= require bootstrap/collapse
#= require bootstrap/dropdown
#= require bootstrap/modal
#= require bootstrap/scrollspy
#= require bootstrap/tab
#= require bootstrap/transition
#= require bootstrap/tooltip
#= require bootstrap/popover
#= require select2 #= require select2
#= require raphael #= require raphael
#= require g.raphael #= require g.raphael
...@@ -41,6 +51,7 @@ ...@@ -41,6 +51,7 @@
#= require shortcuts_issuable #= require shortcuts_issuable
#= require shortcuts_network #= require shortcuts_network
#= require jquery.nicescroll #= require jquery.nicescroll
#= require date.format
#= require_tree . #= require_tree .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
#= require cropper #= require cropper
...@@ -163,7 +174,7 @@ $ -> ...@@ -163,7 +174,7 @@ $ ->
$('.trigger-submit').on 'change', -> $('.trigger-submit').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
$('abbr.timeago, .js-timeago').timeago() gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
# Flash # Flash
if (flash = $(".flash-container")).length > 0 if (flash = $(".flash-container")).length > 0
......
...@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> ...@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
e.preventDefault() e.preventDefault()
$form = $(e.target).closest('form') $form = $(e.target).closest('form')
$form.find('input[type=submit], button[type=submit]').disable() $submit_button = $form.find('input[type=submit], button[type=submit]')
return if $submit_button.attr('disabled')
$submit_button.disable()
$form.submit() $form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a # If the user tabs to a submit button on a `js-quick-submit` form, display a
......
class @BlobLicenseSelector
licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
constructor: (editor) ->
@$licenseSelector = $('.js-license-selector')
$fileNameInput = $('#file_name')
initialFileNameValue = if $fileNameInput.length
$fileNameInput.val()
else if $('.editor-file-name').length
$('.editor-file-name').text().trim()
@toggleLicenseSelector(initialFileNameValue)
if $fileNameInput
$fileNameInput.on 'keyup blur', (e) =>
@toggleLicenseSelector($(e.target).val())
$('select.license-select').on 'change', (e) ->
data =
project: $(this).data('project')
fullname: $(this).data('fullname')
Api.licenseText $(this).val(), data, (license) ->
editor.setValue(license.content, -1)
toggleLicenseSelector: (fileName) =>
if @licenseRegex.test(fileName)
@$licenseSelector.show()
else
@$licenseSelector.hide()
class @EditBlob class @EditBlob
constructor: (assets_path, mode)-> constructor: (assets_path, ace_mode = null) ->
ace.config.set "modePath", assets_path + '/ace' ace.config.set "modePath", "#{assets_path}/ace"
ace.config.loadModule "ace/ext/searchbox" ace.config.loadModule "ace/ext/searchbox"
if mode @editor = ace.edit("editor")
ace_mode = mode @editor.focus()
editor = ace.edit("editor") @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
# Before a form submission, move the content from the Ace editor into the # Before a form submission, move the content from the Ace editor into the
# submitted textarea # submitted textarea
$('form').submit -> $('form').submit =>
$("#file-content").val(editor.getValue()) $("#file-content").val(@editor.getValue())
@initModePanesAndLinks()
new BlobLicenseSelector(@editor)
editModePanes = $(".js-edit-mode-pane") initModePanesAndLinks: ->
editModeLinks = $(".js-edit-mode a") @$editModePanes = $(".js-edit-mode-pane")
editModeLinks.click (event) -> @$editModeLinks = $(".js-edit-mode a")
event.preventDefault() @$editModeLinks.click @editModeLinkClickHandler
currentLink = $(this)
paneId = currentLink.attr("href")
currentPane = editModePanes.filter(paneId)
editModeLinks.parent().removeClass "active hover"
currentLink.parent().addClass "active hover"
editModePanes.hide()
if paneId is "#preview"
currentPane.fadeIn 200
$.post currentLink.data("preview-url"),
content: editor.getValue()
, (response) ->
currentPane.empty().append response
currentPane.syntaxHighlight()
return
else editModeLinkClickHandler: (event) =>
currentPane.fadeIn 200 event.preventDefault()
editor.focus() currentLink = $(event.target)
return paneId = currentLink.attr("href")
currentPane = @$editModePanes.filter(paneId)
@$editModeLinks.parent().removeClass "active hover"
currentLink.parent().addClass "active hover"
@$editModePanes.hide()
currentPane.fadeIn 200
if paneId is "#preview"
$.post currentLink.data("preview-url"),
content: @editor.getValue()
, (response) ->
currentPane.empty().append response
currentPane.syntaxHighlight()
editor: -> else
return @editor @editor.focus()
class @NewBlob
constructor: (assets_path, mode)->
ace.config.set "modePath", assets_path + '/ace'
ace.config.loadModule "ace/ext/searchbox"
if mode
ace_mode = mode
editor = ace.edit("editor")
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
# Before a form submission, move the content from the Ace editor into the
# submitted textarea
$('form').submit ->
$("#file-content").val(editor.getValue())
editor: ->
return @editor
...@@ -17,6 +17,7 @@ class Dispatcher ...@@ -17,6 +17,7 @@ class Dispatcher
switch page switch page
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
Issuable.init()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show' when 'projects:issues:show'
new Issue() new Issue()
...@@ -28,26 +29,26 @@ class Dispatcher ...@@ -28,26 +29,26 @@ class Dispatcher
new Todos() new Todos()
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.milestone-form')) new GLForm($('.milestone-form'))
when 'groups:milestones:new' when 'groups:milestones:new'
new ZenMode() new ZenMode()
when 'projects:compare:show' when 'projects:compare:show'
new Diff() new Diff()
when 'projects:issues:new','projects:issues:edit' when 'projects:issues:new','projects:issues:edit'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.issue-form')) new GLForm($('.issue-form'))
new IssuableForm($('.issue-form')) new IssuableForm($('.issue-form'))
when 'projects:merge_requests:new', 'projects:merge_requests:edit' when 'projects:merge_requests:new', 'projects:merge_requests:edit'
new Diff() new Diff()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.merge-request-form')) new GLForm($('.merge-request-form'))
new IssuableForm($('.merge-request-form')) new IssuableForm($('.merge-request-form'))
when 'projects:tags:new' when 'projects:tags:new'
new ZenMode() new ZenMode()
new DropzoneInput($('.tag-form')) new GLForm($('.tag-form'))
when 'projects:releases:edit' when 'projects:releases:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.release-form')) new GLForm($('.release-form'))
when 'projects:merge_requests:show' when 'projects:merge_requests:show'
new Diff() new Diff()
shortcut_handler = new ShortcutsIssuable(true) shortcut_handler = new ShortcutsIssuable(true)
...@@ -57,7 +58,7 @@ class Dispatcher ...@@ -57,7 +58,7 @@ class Dispatcher
new ZenMode() new ZenMode()
when 'projects:merge_requests:index' when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
MergeRequests.init() Issuable.init()
when 'dashboard:activity' when 'dashboard:activity'
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
...@@ -137,7 +138,7 @@ class Dispatcher ...@@ -137,7 +138,7 @@ class Dispatcher
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() new ZenMode()
new DropzoneInput($('.wiki-form')) new GLForm($('.wiki-form'))
when 'snippets' when 'snippets'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show' new ZenMode() if path[2] == 'show'
......
...@@ -15,11 +15,13 @@ class @DropzoneInput ...@@ -15,11 +15,13 @@ class @DropzoneInput
project_uploads_path = window.project_uploads_path or null project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10 max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area") form_textarea = $(form).find(".js-gfm-input")
form_textarea.wrap "<div class=\"div-dropzone\"></div>" form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) => form_textarea.on 'paste', (event) =>
handlePaste(event) handlePaste(event)
$mdArea = $(form_textarea).closest('.md-area')
$(form).setupMarkdownPreview() $(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone') form_dropzone = $(form).find('.div-dropzone')
...@@ -49,17 +51,17 @@ class @DropzoneInput ...@@ -49,17 +51,17 @@ class @DropzoneInput
$(".div-dropzone-alert").alert "close" $(".div-dropzone-alert").alert "close"
dragover: -> dragover: ->
form_textarea.addClass "div-dropzone-focus" $mdArea.addClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0.7 form.find(".div-dropzone-hover").css "opacity", 0.7
return return
dragleave: -> dragleave: ->
form_textarea.removeClass "div-dropzone-focus" $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0 form.find(".div-dropzone-hover").css "opacity", 0
return return
drop: -> drop: ->
form_textarea.removeClass "div-dropzone-focus" $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0 form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus() form_textarea.focus()
return return
......
class @DueDateSelect
constructor: ->
$loading = $('.js-issuable-update .due_date')
.find('.block-loading')
.hide()
$('.js-due-date-select').each (i, dropdown) ->
$dropdown = $(dropdown)
$dropdownParent = $dropdown.closest('.dropdown')
$datePicker = $dropdownParent.find('.js-due-date-calendar')
$block = $dropdown.closest('.block')
$selectbox = $dropdown.closest('.selectbox')
$value = $block.find('.value')
$sidebarValue = $('.js-due-date-sidebar-value', $block)
fieldName = $dropdown.data('field-name')
abilityName = $dropdown.data('ability-name')
issueUpdateURL = $dropdown.data('issue-update')
$dropdown.glDropdown(
hidden: ->
$selectbox.hide()
$value.removeAttr('style')
)
addDueDate = ->
# Create the post date
value = $("input[name='#{fieldName}']").val()
date = new Date value.replace(new RegExp('-', 'g'), ',')
mediumDate = $.datepicker.formatDate 'M d, yy', date
data = {}
data[abilityName] = {}
data[abilityName].due_date = value
$.ajax(
type: 'PUT'
url: issueUpdateURL
data: data
beforeSend: ->
$loading.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$selectbox.hide()
$value.removeAttr('style')
$value.html(mediumDate)
$sidebarValue.html(mediumDate)
).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$dropdown.dropdown('toggle')
$loading.fadeOut()
$datePicker.datepicker(
dateFormat: 'yy-mm-dd',
defaultDate: $("input[name='#{fieldName}']").val()
altField: "input[name='#{fieldName}']"
onSelect: ->
addDueDate()
)
$(document)
.off 'click', '.ui-datepicker-header a'
.on 'click', '.ui-datepicker-header a', (e) ->
e.stopImmediatePropagation()
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
window.GitLab ?= {} window.GitLab ?= {}
GitLab.GfmAutoComplete = GitLab.GfmAutoComplete =
dataLoading: false
dataSource: '' dataSource: ''
# Emoji # Emoji
...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete = ...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>' template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input. # Add GFM auto-completion to all input fields, that accept GFM input.
setup: -> setup: (wrap) ->
input = $('.js-gfm-input') @input = $('.js-gfm-input')
# destroy previous instances
@destroyAtWho()
# set up instances
@setupAtWho()
if @dataSource
if !@dataLoading
@dataLoading = true
# We should wait until initializations are done
# and only trigger the last .setup since
# The previous .dataSource belongs to the previous issuable
# and the last one will have the **proper** .dataSource property
# TODO: Make this a singleton and turn off events when moving to another page
setTimeout( =>
fetch = @fetchData(@dataSource)
fetch.done (data) =>
@dataLoading = false
@loadData(data)
, 1000)
setupAtWho: ->
# Emoji # Emoji
input.atwho @input.atwho
at: ':' at: ':'
displayTpl: @Emoji.template displayTpl: @Emoji.template
insertTpl: ':${name}:' insertTpl: ':${name}:'
# Team Members # Team Members
input.atwho @input.atwho
at: '@' at: '@'
displayTpl: @Members.template displayTpl: @Members.template
insertTpl: '${atwho-at}${username}' insertTpl: '${atwho-at}${username}'
...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete = ...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title) title: sanitize(title)
search: sanitize("#{m.username} #{m.name}") search: sanitize("#{m.username} #{m.name}")
input.atwho @input.atwho
at: '#' at: '#'
alias: 'issues' alias: 'issues'
searchKey: 'search' searchKey: 'search'
...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete = ...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title) title: sanitize(i.title)
search: "#{i.iid} #{i.title}" search: "#{i.iid} #{i.title}"
input.atwho @input.atwho
at: '!' at: '!'
alias: 'mergerequests' alias: 'mergerequests'
searchKey: 'search' searchKey: 'search'
...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete = ...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title) title: sanitize(m.title)
search: "#{m.iid} #{m.title}" search: "#{m.iid} #{m.title}"
if @dataSource destroyAtWho: ->
$.getJSON(@dataSource).done (data) -> @input.atwho('destroy')
# load members
input.atwho 'load', '@', data.members fetchData: (dataSource) ->
# load issues $.getJSON(dataSource)
input.atwho 'load', 'issues', data.issues
# load merge requests loadData: (data) ->
input.atwho 'load', 'mergerequests', data.mergerequests # load members
# load emojis @input.atwho 'load', '@', data.members
input.atwho 'load', ':', data.emojis # load issues
@input.atwho 'load', 'issues', data.issues
# load merge requests
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
@input.atwho 'load', ':', data.emojis
...@@ -32,10 +32,8 @@ class GitLabDropdownFilter ...@@ -32,10 +32,8 @@ class GitLabDropdownFilter
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS $inputContainer.removeClass HAS_VALUE_CLASS
if keyCode is 13 and @input.val() isnt "" if keyCode is 13
if @options.enterCallback return false
@options.enterCallback()
return
clearTimeout timeout clearTimeout timeout
timeout = setTimeout => timeout = setTimeout =>
...@@ -122,7 +120,9 @@ class GitLabDropdown ...@@ -122,7 +120,9 @@ class GitLabDropdown
FILTER_INPUT = '.dropdown-input .dropdown-input-field' FILTER_INPUT = '.dropdown-input .dropdown-input-field'
constructor: (@el, @options) -> constructor: (@el, @options) ->
@dropdown = $(@el).parent() self = @
selector = $(@el).data "target"
@dropdown = if selector? then $(selector) else $(@el).parent()
# Set Defaults # Set Defaults
{ {
...@@ -130,7 +130,6 @@ class GitLabDropdown ...@@ -130,7 +130,6 @@ class GitLabDropdown
@filterInput = @getElement(FILTER_INPUT) @filterInput = @getElement(FILTER_INPUT)
@highlight = false @highlight = false
@filterInputBlur = true @filterInputBlur = true
@enterCallback = true
} = @options } = @options
self = @ self = @
...@@ -155,6 +154,9 @@ class GitLabDropdown ...@@ -155,6 +154,9 @@ class GitLabDropdown
@fullData = data @fullData = data
@parseData @fullData @parseData @fullData
if @options.filterable
@filterInput.trigger 'keyup'
} }
# Init filterable # Init filterable
...@@ -176,9 +178,6 @@ class GitLabDropdown ...@@ -176,9 +178,6 @@ class GitLabDropdown
callback: (data) => callback: (data) =>
currentIndex = -1 currentIndex = -1
@parseData data @parseData data
enterCallback: =>
if @enterCallback
@selectRowAtIndex 0
# Event listeners # Event listeners
...@@ -387,13 +386,13 @@ class GitLabDropdown ...@@ -387,13 +386,13 @@ class GitLabDropdown
else else
selectedObject selectedObject
else else
if !value? if not @options.multiSelect or el.hasClass('dropdown-clear-active')
field.remove()
if not @options.multiSelect
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
@dropdown.parent().find("input[name='#{fieldName}']").remove() @dropdown.parent().find("input[name='#{fieldName}']").remove()
if !value?
field.remove()
# Toggle active class for the tick mark # Toggle active class for the tick mark
el.addClass ACTIVE_CLASS el.addClass ACTIVE_CLASS
......
class @GLForm
constructor: (@form) ->
@textarea = @form.find('textarea.js-gfm-input')
# Before we start, we should clean up any previous data for this form
@destroy()
# Setup the form
@setupForm()
@form.data 'gl-form', @
destroy: ->
# Clean form listeners
@clearEventListeners()
@form.data 'gl-form', null
setupForm: ->
isNewForm = @form.is(':not(.gfm-form)')
@form.removeClass 'js-new-note-form'
if isNewForm
@form.find('.div-dropzone').remove()
@form.addClass('gfm-form')
disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(@form)
autosize(@textarea)
# form and textarea event listeners
@addEventListeners()
# hide discard button
@form.find('.js-note-discard').hide()
@form.show()
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
addEventListeners: ->
@textarea.on 'focus', ->
$(@).closest('.md-area').addClass 'is-focused'
@textarea.on 'blur', ->
$(@).closest('.md-area').removeClass 'is-focused'
...@@ -4,18 +4,33 @@ class @ImporterStatus ...@@ -4,18 +4,33 @@ class @ImporterStatus
this.setAutoUpdate() this.setAutoUpdate()
initStatusPage: -> initStatusPage: ->
$(".js-add-to-import").click (event) => $('.js-add-to-import')
new_namespace = null .off 'click'
tr = $(event.currentTarget).closest("tr") .on 'click', (e) =>
id = tr.attr("id").replace("repo_", "") new_namespace = null
if tr.find(".import-target input").length > 0 $btn = $(e.currentTarget)
new_namespace = tr.find(".import-target input").prop("value") $tr = $btn.closest('tr')
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) id = $tr.attr('id').replace('repo_', '')
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' if $tr.find('.import-target input').length > 0
new_namespace = $tr.find('.import-target input').prop('value')
$(".js-import-all").click (event) => $tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
$(".js-add-to-import").each ->
$(this).click() $btn
.disable()
.addClass 'is-loading'
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
$('.js-import-all')
.off 'click'
.on 'click', (e) ->
$btn = $(@)
$btn
.disable()
.addClass 'is-loading'
$('.js-add-to-import').each ->
$(this).trigger('click')
setAutoUpdate: -> setAutoUpdate: ->
setInterval (=> setInterval (=>
......
@Issuable =
init: ->
Issuable.initTemplates()
Issuable.initSearch()
initTemplates: ->
Issuable.labelRow = _.template(
'<% _.each(labels, function(label){ %>
<span class="label-row">
<a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a>
</span>
<% }); %>'
)
initSearch: ->
@timer = null
$('#issue_search')
.off 'keyup'
.on 'keyup', ->
clearTimeout(@timer)
@timer = setTimeout( ->
Issuable.filterResults $('#issue_search_form')
, 500)
toggleLabelFilters: ->
$filteredLabels = $('.filtered-labels')
if $filteredLabels.find('.label-row').length > 0
$filteredLabels.removeClass('hidden')
else
$filteredLabels.addClass('hidden')
filterResults: (form) =>
formData = form.serialize()
$('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
formAction = form.attr('action')
issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
issuesUrl += formData
$.ajax
type: 'GET'
url: formAction
data: formData
complete: ->
$('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
success: (data) ->
$('.issues-holder, .merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issuable.reload()
Issuable.updateStateFilters()
$filteredLabels = $('.filtered-labels')
if typeof Issuable.labelRow is 'function'
$filteredLabels.html(Issuable.labelRow(data))
Issuable.toggleLabelFilters()
dataType: "json"
reload: ->
if Issues.created
Issues.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
updateStateFilters: ->
stateFilters = $('.issues-state-filters')
newParams = {}
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
if stateFilters.length
stateFilters.find('a').each ->
initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
labelNameValues = gl.utils.getParameterValues('label_name[]')
if labelNameValues
labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
else
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
$(this).attr 'href', newUrl
...@@ -9,21 +9,29 @@ class @IssuableContext ...@@ -9,21 +9,29 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit() $(this).submit()
$(document).off("click", ".edit-link").on "click",".edit-link", (e) -> $(document)
$block = $(@).parents('.block') .off 'click', '.dropdown-content a'
$selectbox = $block.find('.selectbox') .on 'click', '.dropdown-content a', (e) ->
if $selectbox.is(':visible') e.preventDefault()
$selectbox.hide()
$block.find('.value').show() $(document)
else .off 'click', '.edit-link'
$selectbox.show() .on 'click', '.edit-link', (e) ->
$block.find('.value').hide() e.preventDefault()
if $selectbox.is(':visible') $block = $(@).parents('.block')
setTimeout (-> $selectbox = $block.find('.selectbox')
$block.find('.dropdown-menu-toggle').trigger 'click' if $selectbox.is(':visible')
), 0 $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() $(".right-sidebar").niceScroll()
......
...@@ -10,6 +10,9 @@ class @Issue ...@@ -10,6 +10,9 @@ class @Issue
@initTaskList() @initTaskList()
@initIssueBtnEventListeners() @initIssueBtnEventListeners()
@initMergeRequests()
@initRelatedBranches()
initTaskList: -> initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable') $('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
...@@ -69,3 +72,23 @@ class @Issue ...@@ -69,3 +72,23 @@ class @Issue
type: 'PATCH' type: 'PATCH'
url: $('form.js-issuable-update').attr('action') url: $('form.js-issuable-update').attr('action')
data: patchData data: patchData
initMergeRequests: ->
$container = $('#merge-requests')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load referenced merge requests', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
initRelatedBranches: ->
$container = $('#related-branches')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load related branches', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
@Issues = @Issues =
init: -> init: ->
Issues.initSearch() Issues.created = true
Issues.initChecks() Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", -> $("body").on "ajax:success", ".close_issue, .reopen_issue", ->
...@@ -15,10 +15,6 @@ ...@@ -15,10 +15,6 @@
else else
$(this).html totalIssues - 1 $(this).html totalIssues - 1
reload: ->
Issues.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
initChecks: -> initChecks: ->
$(".check_all_issues").click -> $(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked) $(".selected_issue").prop("checked", @checked)
...@@ -26,51 +22,6 @@ ...@@ -26,51 +22,6 @@
$(".selected_issue").bind "change", Issues.checkChanged $(".selected_issue").bind "change", Issues.checkChanged
# Update state filters if present in page
updateStateFilters: ->
stateFilters = $('.issues-state-filters')
newParams = {}
paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
if stateFilters.length
stateFilters.find('a').each ->
initialUrl = $(this).attr 'href'
$(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
@timer = null
$("#issue_search").keyup ->
clearTimeout(@timer)
@timer = setTimeout( ->
Issues.filterResults $("#issue_search_form")
, 500)
filterResults: (form) =>
$('.issues-holder, .merge-requests-holder').css("opacity", '0.5')
formAction = form.attr('action')
formData = form.serialize()
issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}")
issuesUrl += formData
$.ajax
type: "GET"
url: formAction
data: formData
complete: ->
$('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
success: (data) ->
$('.issues-holder, .merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issues.reload()
Issues.updateStateFilters()
dataType: "json"
checkChanged: -> checkChanged: ->
checked_issues = $(".selected_issue:checked") checked_issues = $(".selected_issue:checked")
if checked_issues.length > 0 if checked_issues.length > 0
......
...@@ -6,7 +6,7 @@ class @LabelsSelect ...@@ -6,7 +6,7 @@ class @LabelsSelect
labelUrl = $dropdown.data('labels') labelUrl = $dropdown.data('labels')
issueUpdateURL = $dropdown.data('issueUpdate') issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected') selectedLabel = $dropdown.data('selected')
if selectedLabel? if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
selectedLabel = selectedLabel.split(',') selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name') newLabelField = $('#new_label_name')
newColorField = $('#new_label_color') newColorField = $('#new_label_color')
...@@ -16,6 +16,7 @@ class @LabelsSelect ...@@ -16,6 +16,7 @@ class @LabelsSelect
abilityName = $dropdown.data('ability-name') abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox') $selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block') $block = $selectbox.closest('.block')
$form = $dropdown.closest('form')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span') $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value') $value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut() $loading = $block.find('.block-loading').fadeOut()
...@@ -33,13 +34,13 @@ class @LabelsSelect ...@@ -33,13 +34,13 @@ class @LabelsSelect
if issueUpdateURL if issueUpdateURL
labelHTMLTemplate = _.template( labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %> '<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>"> <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= _.escape(label.title) %>">
<span class="label color-label" style="background-color: <%= label.color %>;"> <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>;">
<%= label.title %> <%= _.escape(label.title) %>
</span> </span>
</a> </a>
<% }); %>' <% }); %>'
); )
labelNoneHTMLTemplate = _.template('<div class="light">None</div>') labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
if newLabelField.length and $dropdown.hasClass 'js-extra-options' if newLabelField.length and $dropdown.hasClass 'js-extra-options'
...@@ -165,11 +166,13 @@ class @LabelsSelect ...@@ -165,11 +166,13 @@ class @LabelsSelect
.html(template) .html(template)
$sidebarCollapsedValue.text(labelCount) $sidebarCollapsedValue.text(labelCount)
$('.has-tooltip', $value).tooltip(container: 'body')
$value $value
.find('a') .find('a')
.each((i) -> .each((i) ->
setTimeout(=> setTimeout(=>
glAnimate($(@), 'pulse') gl.animate.animate($(@), 'pulse')
,200 * i ,200 * i
) )
) )
...@@ -198,18 +201,23 @@ class @LabelsSelect ...@@ -198,18 +201,23 @@ class @LabelsSelect
callback data callback data
renderRow: (label) -> renderRow: (label) ->
selectedClass = '' removesAll = label.id is 0 or not label.id?
if $selectbox.find("input[type='hidden']\
[name='#{$dropdown.data('field-name')}']\ selectedClass = []
[value='#{label.id}']").length if $form.find("input[type='hidden']\
selectedClass = 'is-active' [name='#{$dropdown.data('fieldName')}']\
[value='#{this.id(label)}']").length
selectedClass.push 'is-active'
if $dropdown.hasClass('js-multiselect') and removesAll
selectedClass.push 'dropdown-clear-active'
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else "" color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
"<li> "<li>
<a href='#' class='#{selectedClass}'> <a href='#' class='#{selectedClass.join(' ')}'>
#{color} #{color}
#{label.title} #{_.escape(label.title)}
</a> </a>
</li>" </li>"
filterable: true filterable: true
...@@ -217,37 +225,56 @@ class @LabelsSelect ...@@ -217,37 +225,56 @@ class @LabelsSelect
fields: ['title'] fields: ['title']
selectable: true selectable: true
toggleLabel: (selected) -> toggleLabel: (selected, el) ->
if selected and selected.title isnt 'Any Label' selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
selected.title
if selected and selected.title?
if selected_labels.length > 1
"#{selected.title} +#{selected_labels.length - 1} more"
else
selected.title
else if not selected and selected_labels.length isnt 0
if selected_labels.length > 1
"#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
else if selected_labels.length is 1
$(selected_labels).text()
else else
defaultLabel defaultLabel
fieldName: $dropdown.data('field-name') fieldName: $dropdown.data('field-name')
id: (label) -> id: (label) ->
if label.isAny? if $dropdown.hasClass("js-filter-submit") and not label.isAny?
''
else if $dropdown.hasClass "js-filter-submit"
label.title label.title
else else
label.id label.id
hidden: -> hidden: ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is 'projects:merge_requests:index'
$selectbox.hide() $selectbox.hide()
# display:block overrides the hide-collapse rule # display:block overrides the hide-collapse rule
$value.removeAttr('style') $value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect' if $dropdown.hasClass 'js-multiselect'
saveLabelData() if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedLabels = $dropdown
.closest('form')
.find("input:hidden[name='#{$dropdown.data('fieldName')}']")
Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect' multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) -> clicked: (label) ->
page = $('body').data 'page' page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index' isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index' isMRIndex = page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedLabel = label.title if not $dropdown.hasClass 'js-multiselect'
selectedLabel = label.title
Issues.filterResults $dropdown.closest('form') Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit' else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit() $dropdown.closest('form').submit()
else else
......
((w) -> ((w) ->
if not w.gl? then w.gl = {}
if not gl.animate? then gl.animate = {}
w.glAnimate = ($el, animation, done) -> gl.animate.animate = ($el, animation, options, done) ->
if options?.cssStart?
$el.css(options.cssStart)
$el $el
.removeClass() .removeClass(animation + ' animated')
.addClass(animation + ' animated') .addClass(animation + ' animated')
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', -> .one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
$(this).removeClass() $(this).removeClass(animation + ' animated')
if done?
done()
if options?.cssEnd?
$el.css(options.cssEnd)
return return
return return
return
gl.animate.animateEach = ($els, animation, time, options, done) ->
dfd = $.Deferred()
if not $els.length
dfd.resolve()
$els.each((i) ->
setTimeout(=>
$this = $(@)
gl.animate.animate($this, animation, options, =>
if i is $els.length - 1
dfd.resolve()
if done?
done()
)
,time * i
)
return
)
return dfd.promise()
return
) window ) window
\ No newline at end of file
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.formatDate = (datetime) ->
dateFormat(datetime, 'mmm d, yyyy h:MMtt Z')
w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) ->
$timeagoEls.each( ->
$el = $(@)
$el.attr('title', gl.utils.formatDate($el.attr('datetime')))
)
$timeagoEls.timeago() if setTimeago
) window
...@@ -3,16 +3,20 @@ ...@@ -3,16 +3,20 @@
w.gl ?= {} w.gl ?= {}
w.gl.utils ?= {} w.gl.utils ?= {}
w.gl.utils.getUrlParameter = (sParam) -> # Returns an array containing the value(s) of the
# of the key passed as an argument
w.gl.utils.getParameterValues = (sParam) ->
sPageURL = decodeURIComponent(window.location.search.substring(1)) sPageURL = decodeURIComponent(window.location.search.substring(1))
sURLVariables = sPageURL.split('&') sURLVariables = sPageURL.split('&')
sParameterName = undefined sParameterName = undefined
values = []
i = 0 i = 0
while i < sURLVariables.length while i < sURLVariables.length
sParameterName = sURLVariables[i].split('=') sParameterName = sURLVariables[i].split('=')
if sParameterName[0] is sParam if sParameterName[0] is sParam
return if sParameterName[1] is undefined then true else sParameterName[1] values.push(sParameterName[1])
i++ i++
values
# # # #
# @param {Object} params - url keys and value to merge # @param {Object} params - url keys and value to merge
...@@ -28,4 +32,12 @@ ...@@ -28,4 +32,12 @@
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}" newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
newUrl newUrl
# removes parameter query string from url. returns the modified url
w.gl.utils.removeParamQueryString = (url, param) ->
url = decodeURIComponent(url)
urlVariables = url.split('&')
(
variables for variables in urlVariables when variables.indexOf(param) is -1
).join('&')
) window ) window
...@@ -85,8 +85,10 @@ class @MergeRequestTabs ...@@ -85,8 +85,10 @@ class @MergeRequestTabs
scrollToElement: (container) -> scrollToElement: (container) ->
if window.location.hash if window.location.hash
$el = $("div#{container} #{window.location.hash}") navBarHeight = $('.navbar-gitlab').outerHeight()
$('body').scrollTo($el.offset().top) if $el.length
$el = $("#{container} #{window.location.hash}")
$.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action # Activate a tab based on the current action
activateTab: (action) -> activateTab: (action) ->
...@@ -142,7 +144,7 @@ class @MergeRequestTabs ...@@ -142,7 +144,7 @@ class @MergeRequestTabs
url: "#{source}.json" url: "#{source}.json"
success: (data) => success: (data) =>
document.querySelector("div#commits").innerHTML = data.html document.querySelector("div#commits").innerHTML = data.html
$('.js-timeago').timeago() gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
@commitsLoaded = true @commitsLoaded = true
@scrollToElement("#commits") @scrollToElement("#commits")
...@@ -152,12 +154,39 @@ class @MergeRequestTabs ...@@ -152,12 +154,39 @@ class @MergeRequestTabs
@_get @_get
url: "#{source}.json" + @_location.search url: "#{source}.json" + @_location.search
success: (data) => success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html $('#diffs').html data.html
$('.js-timeago').timeago() gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('div#diffs .js-syntax-highlight').syntaxHighlight() $('#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel' @expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true @diffsLoaded = true
@scrollToElement("#diffs") @scrollToElement("#diffs")
@highlighSelectedLine()
$(document)
.off 'click', '.diff-line-num a'
.on 'click', '.diff-line-num a', (e) =>
e.preventDefault()
window.location.hash = $(e.currentTarget).attr 'href'
@highlighSelectedLine()
@scrollToElement("#diffs")
highlighSelectedLine: ->
$('.hll').removeClass 'hll'
locationHash = window.location.hash
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
$diffLine = $(locationHash)
if $diffLine.is ':not(tr)'
$diffLine = $("td#{locationHash}, td#{hashClassString}")
else
$diffLine = $('td', $diffLine)
if $diffLine.length
$diffLine.addClass 'hll'
diffLineTop = $diffLine.offset().top
navBarHeight = $('.navbar-gitlab').outerHeight()
loadBuilds: (source) -> loadBuilds: (source) ->
return if @buildsLoaded return if @buildsLoaded
...@@ -166,7 +195,7 @@ class @MergeRequestTabs ...@@ -166,7 +195,7 @@ class @MergeRequestTabs
url: "#{source}.json" url: "#{source}.json"
success: (data) => success: (data) =>
document.querySelector("div#builds").innerHTML = data.html document.querySelector("div#builds").innerHTML = data.html
$('.js-timeago').timeago() gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
@buildsLoaded = true @buildsLoaded = true
@scrollToElement("#builds") @scrollToElement("#builds")
......
#
# * Filter merge requests
#
@MergeRequests =
init: ->
MergeRequests.initSearch()
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
@timer = null
$("#issue_search").keyup ->
clearTimeout(@timer)
@timer = setTimeout(MergeRequests.filterResults, 500)
filterResults: =>
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.merge-requests-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.merge-requests-holder').css("opacity", '1.0')
success: (data) ->
$('.merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issues_url}, document.title, issues_url
MergeRequests.reload()
dataType: "json"
reload: ->
$('#filter_issue_search').val($('#issue_search').val())
...@@ -24,7 +24,7 @@ class @MilestoneSelect ...@@ -24,7 +24,7 @@ class @MilestoneSelect
if issueUpdateURL if issueUpdateURL
milestoneLinkTemplate = _.template( milestoneLinkTemplate = _.template(
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>' '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>'
) )
milestoneLinkNoneTemplate = '<div class="light">None</div>' milestoneLinkNoneTemplate = '<div class="light">None</div>'
...@@ -71,7 +71,7 @@ class @MilestoneSelect ...@@ -71,7 +71,7 @@ class @MilestoneSelect
defaultLabel defaultLabel
fieldName: $dropdown.data('field-name') fieldName: $dropdown.data('field-name')
text: (milestone) -> text: (milestone) ->
milestone.title _.escape(milestone.title)
id: (milestone) -> id: (milestone) ->
if !useId if !useId
milestone.name milestone.name
...@@ -97,7 +97,7 @@ class @MilestoneSelect ...@@ -97,7 +97,7 @@ class @MilestoneSelect
selectedMilestone = selected.name selectedMilestone = selected.name
else else
selectedMilestone = '' selectedMilestone = ''
Issues.filterResults $dropdown.closest('form') Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit') else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit() $dropdown.closest('form').submit()
else else
......
...@@ -75,6 +75,9 @@ class @Notes ...@@ -75,6 +75,9 @@ class @Notes
# when issue status changes, we need to refresh data # when issue status changes, we need to refresh data
$(document).on "issuable:change", @refresh $(document).on "issuable:change", @refresh
# when a key is clicked on the notes
$(document).on "keydown", ".js-note-text", @keydownNoteText
cleanBinding: -> cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", ".js-discussion-note-form"
...@@ -92,10 +95,19 @@ class @Notes ...@@ -92,10 +95,19 @@ class @Notes
$(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close" $(document).off "click", ".js-note-target-close"
$(document).off "click", ".js-note-discard" $(document).off "click", ".js-note-discard"
$(document).off "keydown", ".js-note-text"
$('.note .js-task-list-container').taskList('disable') $('.note .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.note .js-task-list-container' $(document).off 'tasklist:changed', '.note .js-task-list-container'
keydownNoteText: (e) ->
$this = $(this)
if $this.val() is '' and e.which is 38 #aka the up key
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])
initRefresh: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
...@@ -163,9 +175,15 @@ class @Notes ...@@ -163,9 +175,15 @@ class @Notes
else if @isNewNote(note) else if @isNewNote(note)
@note_ids.push(note.id) @note_ids.push(note.id)
$('ul.main-notes-list') $notesList = $('ul.main-notes-list')
$notesList
.append(note.html) .append(note.html)
.syntaxHighlight() .syntaxHighlight()
# Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false)
@initTaskList() @initTaskList()
@updateNotesCount(1) @updateNotesCount(1)
...@@ -217,6 +235,8 @@ class @Notes ...@@ -217,6 +235,8 @@ class @Notes
# append new note to all matching discussions # append new note to all matching discussions
discussionContainer.append note_html discussionContainer.append note_html
gl.utils.localTimeAgo($('.js-timeago', note_html), false)
@updateNotesCount(1) @updateNotesCount(1)
### ###
...@@ -275,32 +295,10 @@ class @Notes ...@@ -275,32 +295,10 @@ class @Notes
show the form show the form
### ###
setupNoteForm: (form) -> setupNoteForm: (form) ->
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") new GLForm form
form.removeClass "js-new-note-form"
form.find('.div-dropzone').remove()
# hide discard button
form.find('.js-note-discard').hide()
# setup preview buttons
previewButton = form.find(".js-md-preview-button")
textarea = form.find(".js-note-text") textarea = form.find(".js-note-text")
textarea.on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
else
previewButton.removeClass("turn-on").addClass "turn-off"
textarea.on 'focus', ->
$(this).closest('.md-area').addClass 'is-focused'
textarea.on 'blur', ->
$(this).closest('.md-area').removeClass 'is-focused'
autosize(textarea)
new Autosave textarea, [ new Autosave textarea, [
"Note" "Note"
form.find("#note_commit_id").val() form.find("#note_commit_id").val()
...@@ -309,11 +307,6 @@ class @Notes ...@@ -309,11 +307,6 @@ class @Notes
form.find("#note_noteable_id").val() form.find("#note_noteable_id").val()
] ]
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(form)
form.show()
### ###
Called in response to the new note form being submitted Called in response to the new note form being submitted
...@@ -345,7 +338,9 @@ class @Notes ...@@ -345,7 +338,9 @@ class @Notes
updateNote: (_xhr, note, _status) => updateNote: (_xhr, note, _status) =>
# Convert returned HTML to a jQuery object so we can modify it further # Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html) $html = $(note.html)
$('.js-timeago', $html).timeago()
gl.utils.localTimeAgo($('.js-timeago', $html))
$html.syntaxHighlight() $html.syntaxHighlight()
$html.find('.js-task-list-container').taskList('enable') $html.find('.js-task-list-container').taskList('enable')
...@@ -360,39 +355,38 @@ class @Notes ...@@ -360,39 +355,38 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels if the user cancels
### ###
showEditForm: (e) -> showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault() e.preventDefault()
note = $(this).closest(".note") note = $(this).closest(".note")
note.addClass "is-editting" note.addClass "is-editting"
form = note.find(".note-edit-form") form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)')
if isNewForm
form.addClass('gfm-form')
form.addClass('current-note-edit-form') form.addClass('current-note-edit-form')
# Show the attachment delete link # Show the attachment delete link
note.find(".js-note-attachment-delete").show() note.find(".js-note-attachment-delete").show()
# Setup markdown form done = ($noteText) ->
if isNewForm # Neat little trick to put the cursor at the end
GitLab.GfmAutoComplete.setup() noteTextVal = $noteText.val()
new DropzoneInput(form) $noteText.val('').val(noteTextVal);
textarea = form.find("textarea") new GLForm form
textarea.focus() if scrollTo? and myLastNote?
# scroll to the bottom
if isNewForm # so the open of the last element doesn't make a jump
autosize(textarea) $('html, body').scrollTop($(document).height());
$('html, body').animate({
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?). scrollTop: myLastNote.offset().top - 150
# The textarea has the correct value, Chrome just won't show it unless we }, 500, ->
# modify it, so let's clear it and re-set it! $noteText = form.find(".js-note-text")
value = textarea.val() $noteText.focus()
textarea.val "" done($noteText)
textarea.val value );
else
if isNewForm $noteText = form.find('.js-note-text')
disableButtonIfEmptyField textarea, form.find(".js-comment-button") $noteText.focus()
done($noteText)
### ###
Called in response to clicking the edit note link Called in response to clicking the edit note link
...@@ -549,6 +543,9 @@ class @Notes ...@@ -549,6 +543,9 @@ class @Notes
removeDiscussionNoteForm: (form)-> removeDiscussionNoteForm: (form)->
row = form.closest("tr") row = form.closest("tr")
glForm = form.data 'gl-form'
glForm.destroy()
form.find(".js-note-text").data("autosave").reset() form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies) # show the reply button (will only work for replies)
...@@ -560,7 +557,6 @@ class @Notes ...@@ -560,7 +557,6 @@ class @Notes
# only remove the form # only remove the form
form.remove() form.remove()
cancelDiscussionForm: (e) => cancelDiscussionForm: (e) =>
e.preventDefault() e.preventDefault()
form = $(e.target).closest(".js-discussion-note-form") form = $(e.target).closest(".js-discussion-note-form")
......
...@@ -18,8 +18,11 @@ class @Profile ...@@ -18,8 +18,11 @@ class @Profile
$(this).find('.btn-save').enable() $(this).find('.btn-save').enable()
$(this).find('.loading-gif').hide() $(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', -> $('.update-notifications').on 'ajax:success', (e, data) ->
$(this).find('.btn-save').enable() if data.saved
new Flash("Notification settings saved", "notice")
else
new Flash("Failed to save new settings", "alert")
@bindEvents() @bindEvents()
...@@ -42,9 +45,10 @@ class @Profile ...@@ -42,9 +45,10 @@ class @Profile
saveForm: -> saveForm: ->
self = @ self = @
formData = new FormData(@form[0]) formData = new FormData(@form[0])
formData.append('user[avatar]', @avatarGlCrop.getBlob(), 'avatar.png')
avatarBlob = @avatarGlCrop.getBlob()
formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
$.ajax $.ajax
url: @form.attr('action') url: @form.attr('action')
......
...@@ -37,19 +37,20 @@ class @Project ...@@ -37,19 +37,20 @@ class @Project
$('.update-notification').on 'click', (e) -> $('.update-notification').on 'click', (e) ->
e.preventDefault() e.preventDefault()
notification_level = $(@).data 'notification-level' notification_level = $(@).data 'notification-level'
$('#notification_level').val(notification_level) label = $(@).data 'notification-title'
$('#notification_setting_level').val(notification_level)
$('#notification-form').submit() $('#notification-form').submit()
label = null
switch notification_level
when 0 then label = ' Disabled '
when 1 then label = ' Participating '
when 2 then label = ' Watching '
when 3 then label = ' Global '
when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") $('#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' $(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass '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()
projectSelectDropdown: -> projectSelectDropdown: ->
......
class @ProjectSelect class @ProjectSelect
constructor: -> constructor: ->
$('.js-projects-dropdown-toggle').each (i, dropdown) ->
$dropdown = $(dropdown)
$dropdown.glDropdown(
filterable: true
filterRemote: true
search:
fields: ['name_with_namespace']
data: (term, callback) ->
finalCallback = (projects) ->
callback projects
if @includeGroups
projectsCallback = (projects) ->
groupsCallback = (groups) ->
data = groups.concat(projects)
finalCallback(data)
Api.groups term, false, groupsCallback
else
projectsCallback = finalCallback
if @groupId
Api.groupProjects @groupId, term, projectsCallback
else
Api.projects term, @orderBy, projectsCallback
url: (project) ->
project.web_url
text: (project) ->
project.name_with_namespace
)
$('.ajax-project-select').each (i, select) -> $('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups') @includeGroups = $(select).data('include-groups')
......
...@@ -2,7 +2,7 @@ class @Subscription ...@@ -2,7 +2,7 @@ class @Subscription
constructor: (container) -> constructor: (container) ->
$container = $(container) $container = $(container)
@url = $container.attr('data-url') @url = $container.attr('data-url')
@subscribe_button = $container.find('.subscribe-button') @subscribe_button = $container.find('.js-subscribe-button')
@subscription_status = $container.find('.subscription-status') @subscription_status = $container.find('.subscription-status')
@subscribe_button.unbind('click').click(@toggleSubscription) @subscribe_button.unbind('click').click(@toggleSubscription)
......
class @Todos class @Todos
constructor: (@name) -> constructor: (opts = {}) ->
{
@el = $('.js-todos-options')
} = opts
@perPage = @el.data('perPage')
@clearListeners() @clearListeners()
@initBtnListeners() @initBtnListeners()
...@@ -26,6 +32,7 @@ class @Todos ...@@ -26,6 +32,7 @@ class @Todos
dataType: 'json' dataType: 'json'
data: '_method': 'delete' data: '_method': 'delete'
success: (data) => success: (data) =>
@redirectIfNeeded data.count
@clearDone $this.closest('li') @clearDone $this.closest('li')
@updateBadges data @updateBadges data
...@@ -57,8 +64,44 @@ class @Todos ...@@ -57,8 +64,44 @@ class @Todos
$('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count $('.todos-done .badge').text data.done_count
getTotalPages: ->
@el.data('totalPages')
getCurrentPage: ->
@el.data('currentPage')
getTodosPerPage: ->
@el.data('perPage')
redirectIfNeeded: (total) ->
currPages = @getTotalPages()
currPage = @getCurrentPage()
# Refresh if no remaining Todos
if not total
location.reload()
return
# Do nothing if no pagination
return if not currPages
newPages = Math.ceil(total / @getTodosPerPage())
url = location.href # Includes query strings
# If new total of pages is different than we have now
if newPages isnt currPages
# Redirect to previous page if there's one available
if currPages > 1 and currPage is currPages
pageParams =
page: currPages - 1
url = gl.utils.mergeUrlParams(pageParams, url)
Turbolinks.visit(url)
goToTodoUrl: (e)-> goToTodoUrl: (e)->
todoLink = $(this).data('url') todoLink = $(this).data('url')
return unless todoLink
if e.metaKey if e.metaKey
e.preventDefault() e.preventDefault()
window.open(todoLink,'_blank') window.open(todoLink,'_blank')
......
...@@ -12,6 +12,7 @@ class @UsersSelect ...@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user') showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user') showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user') firstUser = $dropdown.data('first-user')
@authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected') selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label') defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate') issueURL = $dropdown.data('issueUpdate')
...@@ -157,7 +158,7 @@ class @UsersSelect ...@@ -157,7 +158,7 @@ class @UsersSelect
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedId = user.id selectedId = user.id
Issues.filterResults $dropdown.closest('form') Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit' else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit() $dropdown.closest('form').submit()
else else
...@@ -207,6 +208,7 @@ class @UsersSelect ...@@ -207,6 +208,7 @@ class @UsersSelect
@projectId = $(select).data('project-id') @projectId = $(select).data('project-id')
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user') @showCurrentUser = $(select).data('current-user')
@authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user') showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user') showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user') showEmailUser = $(select).data('email-user')
...@@ -312,6 +314,7 @@ class @UsersSelect ...@@ -312,6 +314,7 @@ class @UsersSelect
project_id: @projectId project_id: @projectId
group_id: @groupId group_id: @groupId
current_user: @showCurrentUser current_user: @showCurrentUser
author_id: @authorId
dataType: "json" dataType: "json"
).done (users) -> ).done (users) ->
callback(users) callback(users)
......
...@@ -248,7 +248,7 @@ ...@@ -248,7 +248,7 @@
.dropdown-title { .dropdown-title {
position: relative; position: relative;
padding: 0 25px 15px; padding: 0 25px 10px;
margin: 0 10px 10px; margin: 0 10px 10px;
font-weight: 600; font-weight: 600;
line-height: 1; line-height: 1;
...@@ -278,7 +278,7 @@ ...@@ -278,7 +278,7 @@
right: 5px; right: 5px;
width: 20px; width: 20px;
height: 20px; height: 20px;
top: -1px; top: -3px;
} }
.dropdown-menu-back { .dropdown-menu-back {
...@@ -358,6 +358,13 @@ ...@@ -358,6 +358,13 @@
border-top: 1px solid $dropdown-divider-color; border-top: 1px solid $dropdown-divider-color;
} }
.dropdown-due-date-footer {
padding-top: 0;
margin-left: 10px;
margin-right: 10px;
border-top: 0;
}
.dropdown-footer-list { .dropdown-footer-list {
font-size: 14px; font-size: 14px;
...@@ -395,3 +402,122 @@ ...@@ -395,3 +402,122 @@
height: 15px; height: 15px;
border-radius: $border-radius-base; border-radius: $border-radius-base;
} }
.dropdown-menu-due-date {
.dropdown-content {
max-height: 230px;
}
.ui-widget {
table {
margin: 0;
}
&.ui-datepicker-inline {
padding: 0 10px;
border: 0;
width: 100%;
}
.ui-datepicker-header {
padding: 0 8px 10px;
border: 0;
.ui-icon {
background: none;
font-size: 20px;
text-indent: 0;
&:before {
display: block;
position: relative;
top: -2px;
color: $dropdown-title-btn-color;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}
.ui-state-active,
.ui-state-hover {
color: $md-link-color;
background-color: $calendar-hover-bg;
}
.ui-datepicker-prev,
.ui-datepicker-next {
top: 0;
height: 15px;
cursor: pointer;
&:hover {
background-color: transparent;
border: 0;
.ui-icon:before {
color: $md-link-color;
}
}
}
.ui-datepicker-prev {
left: 0;
.ui-icon:before {
content: '\f104';
text-align: left;
}
}
.ui-datepicker-next {
right: 0;
.ui-icon:before {
content: '\f105';
text-align: right;
}
}
td {
padding: 0;
border: 1px solid $calendar-border-color;
&:first-child {
border-left: 0;
}
&:last-child {
border-right: 0;
}
a {
line-height: 17px;
border: 0;
border-radius: 0;
}
}
.ui-datepicker-title {
color: $gl-gray;
font-size: 15px;
line-height: 1;
font-weight: normal;
}
}
th {
padding: 2px 0;
color: $calendar-header-color;
font-weight: normal;
text-transform: lowercase;
border-top: 1px solid $calendar-border-color;
}
.ui-datepicker-unselectable {
background-color: $calendar-unselectable-bg;
}
}
/** /**
* Styles that apply to all GFM related forms. * Styles that apply to all GFM related forms.
*/ */
.issue-form, .merge-request-form, .wiki-form {
.description {
height: 16em;
border-top-left-radius: 0;
}
}
.wiki-form {
.description {
height: 26em;
}
}
.milestone-form {
.description {
height: 14em;
}
}
.gfm-commit, .gfm-commit_range { .gfm-commit, .gfm-commit_range {
font-family: $monospace_font; font-family: $monospace_font;
......
...@@ -69,13 +69,18 @@ header { ...@@ -69,13 +69,18 @@ header {
} }
.header-content { .header-content {
position: relative;
height: $header-height; height: $header-height;
padding-right: 20px; padding-right: 40px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: 0; padding-right: 0;
} }
.dropdown-menu {
margin-top: -5px;
}
.title { .title {
margin: 0; margin: 0;
font-size: 19px; font-size: 19px;
...@@ -117,6 +122,10 @@ header { ...@@ -117,6 +122,10 @@ header {
} }
} }
.project-item-select-holder {
display: inline;
}
.impersonation i { .impersonation i {
color: $red-normal; color: $red-normal;
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
.status-box { .status-box {
/* Extra small devices (phones, less than 768px) */ /* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */ /* No media query since this is the default in Bootstrap */
padding: 5px 11px; padding: 5px 11px;
......
.div-dropzone-wrapper { .div-dropzone-wrapper {
.div-dropzone { .div-dropzone {
position: relative; position: relative;
margin-bottom: -5px;
.div-dropzone-focus {
border-color: #66afe9 !important;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important;
outline: 0 !important;
}
.div-dropzone-hover { .div-dropzone-hover {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin-top: -0.5em; margin-top: -11.5px;
margin-left: -0.6em; margin-left: -15px;
opacity: 0; opacity: 0;
font-size: 50px; font-size: 30px;
transition: opacity 200ms ease-in-out; transition: opacity 200ms ease-in-out;
pointer-events: none; pointer-events: none;
} }
...@@ -97,3 +90,12 @@ ...@@ -97,3 +90,12 @@
box-shadow: none; box-shadow: none;
width: 100%; width: 100%;
} }
.md {
&.md-preview-holder {
code {
white-space: pre-wrap;
word-break: break-all;
}
}
}
...@@ -70,13 +70,6 @@ ...@@ -70,13 +70,6 @@
display: none; display: none;
} }
.issue-details {
.creator,
.page-title .btn-close {
display: none;
}
}
%ul.notes .note-role, .note-actions { %ul.notes .note-role, .note-actions {
display: none; display: none;
} }
......
...@@ -58,12 +58,12 @@ ...@@ -58,12 +58,12 @@
.nav-search { .nav-search {
display: inline-block; display: inline-block;
width: 50%; width: 100%;
padding: 11px 0; padding: 11px 0;
/* Small devices (phones, tablets, 768px and lower) */ /* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
width: 100%; width: 50%;
} }
} }
......
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
.diff-file { .diff-file {
border: 1px solid $border-color; border: 1px solid $border-color;
border-bottom: none; border-bottom: none;
margin-left: 0; margin: 0;
margin-right: 0;
} }
} }
......
...@@ -43,7 +43,6 @@ ...@@ -43,7 +43,6 @@
@import "bootstrap/modals"; @import "bootstrap/modals";
@import "bootstrap/tooltip"; @import "bootstrap/tooltip";
@import "bootstrap/popovers"; @import "bootstrap/popovers";
@import "bootstrap/carousel";
// Utility classes // Utility classes
.clearfix { .clearfix {
......
...@@ -250,14 +250,6 @@ a > code { ...@@ -250,14 +250,6 @@ a > code {
* Textareas intended for GFM * Textareas intended for GFM
* *
*/ */
.js-gfm-input {
font-family: $monospace_font;
color: $gl-text-color;
}
.md-preview {
}
.strikethrough { .strikethrough {
text-decoration: line-through; text-decoration: line-through;
} }
......
...@@ -28,6 +28,7 @@ $gl-link-color: #3084bb; ...@@ -28,6 +28,7 @@ $gl-link-color: #3084bb;
$gl-dark-link-color: #333; $gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f; $gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color; $gl-icon-color: $gl-placeholder-color;
$gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color; $gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color; $gl-header-color: $gl-title-color;
...@@ -149,6 +150,7 @@ $light-grey-header: #faf9f9; ...@@ -149,6 +150,7 @@ $light-grey-header: #faf9f9;
*/ */
$gl-primary: $blue-normal; $gl-primary: $blue-normal;
$gl-success: $green-normal; $gl-success: $green-normal;
$gl-success-focus: rgba($gl-success, .4);
$gl-info: $blue-normal; $gl-info: $blue-normal;
$gl-warning: $orange-normal; $gl-warning: $orange-normal;
$gl-danger: $red-normal; $gl-danger: $red-normal;
...@@ -239,3 +241,8 @@ $note-form-border-color: #e5e5e5; ...@@ -239,3 +241,8 @@ $note-form-border-color: #e5e5e5;
$note-toolbar-color: #959494; $note-toolbar-color: #959494;
$zen-control-hover-color: #111; $zen-control-hover-color: #111;
$calendar-header-color: #b8b8b8;
$calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: #faf9f9;
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #557;
border-color: darken(#557, 15%);
}
.diff-line-num.new, .line_content.new { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080); @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #49483e;
border-color: darken(#49483e, 15%);
}
.diff-line-num.new, .line_content.new { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080); @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #174652;
border-color: darken(#174652, 15%);
}
.diff-line-num.new, .line_content.new { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46); @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #ddd8c5;
border-color: darken(#ddd8c5, 15%);
}
.diff-line-num.new, .line_content.new { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4); @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #f8eec7;
border-color: darken(#f8eec7, 15%);
}
.diff-line-num { .diff-line-num {
&.old { &.old {
background-color: $line-number-old; background-color: $line-number-old;
......
...@@ -75,6 +75,11 @@ li.commit { ...@@ -75,6 +75,11 @@ li.commit {
} }
} }
.item-title {
display: inline-block;
max-width: 70%;
}
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
border-left: 1px solid #eee; border-left: 1px solid #eee;
......
.detail-page-header { .detail-page-header {
padding: 11px 0; padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: #5c5d5e; color: #5c5d5e;
font-size: 16px; font-size: 16px;
...@@ -16,11 +16,6 @@ ...@@ -16,11 +16,6 @@
.issue_created_ago, .author_link { .issue_created_ago, .author_link {
white-space: nowrap; white-space: nowrap;
} }
.issue-meta {
display: inline-block;
line-height: 20px;
}
} }
.detail-page-description { .detail-page-description {
...@@ -41,4 +36,10 @@ ...@@ -41,4 +36,10 @@
} }
} }
} }
.wiki {
code {
white-space: pre-wrap;
}
}
} }
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
background: #fff; background: #fff;
color: #333; color: #333;
border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px;
-webkit-overflow-scrolling: auto;
.unfold { .unfold {
cursor: pointer; cursor: pointer;
...@@ -67,8 +68,26 @@ ...@@ -67,8 +68,26 @@
line-height: $code_line_height; line-height: $code_line_height;
font-size: $code_font_size; font-size: $code_font_size;
&.noteable_line {
position: relative;
&.old {
&:before {
content: '-';
position: absolute;
}
}
&.new {
&:before {
content: '+';
position: absolute;
}
}
}
span { span {
white-space: pre; white-space: pre-wrap;
} }
} }
} }
...@@ -317,7 +336,7 @@ ...@@ -317,7 +336,7 @@
} }
.diff-file .line_content { .diff-file .line_content {
white-space: pre; white-space: pre-wrap;
} }
.diff-wrap-lines .line_content { .diff-wrap-lines .line_content {
...@@ -391,3 +410,23 @@ ...@@ -391,3 +410,23 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
.file-holder {
.diff-line-num:not(.js-unfold-bottom) {
a {
&:before {
content: attr(data-linenumber);
}
}
}
}
.discussion {
.diff-content {
.diff-line-num {
&:before {
content: attr(data-linenumber);
}
}
}
}
...@@ -26,6 +26,10 @@ ...@@ -26,6 +26,10 @@
line-height: 42px; line-height: 42px;
padding-top: 7px; padding-top: 7px;
padding-bottom: 7px; padding-bottom: 7px;
.pull-right {
height: 20px;
}
} }
.editor-ref { .editor-ref {
...@@ -53,4 +57,9 @@ ...@@ -53,4 +57,9 @@
.select2 { .select2 {
float: right; float: right;
} }
.encoding-selector,
.license-selector {
display: inline-block;
}
} }
...@@ -41,8 +41,17 @@ ...@@ -41,8 +41,17 @@
word-wrap: break-word; word-wrap: break-word;
.md { .md {
color: #7f8fa4; color: $gl-grayish-blue;
font-size: $gl-font-size; font-size: $gl-font-size;
.label {
color: $gl-text-color;
font-size: inherit;
}
iframe.twitter-share-button {
vertical-align: bottom;
}
} }
pre { pre {
......
...@@ -59,8 +59,10 @@ ...@@ -59,8 +59,10 @@
position: relative; position: relative;
overflow-y: auto; overflow-y: auto;
padding: 15px; padding: 15px;
.form-actions { .form-actions {
margin: -$gl-padding+1; margin: -$gl-padding+1;
margin-top: 15px;
} }
} }
......
...@@ -16,3 +16,24 @@ i.icon-gitorious-big { ...@@ -16,3 +16,24 @@ i.icon-gitorious-big {
width: 18px; width: 18px;
height: 18px; height: 18px;
} }
.import-jobs-from-col,
.import-jobs-to-col {
width: 40%;
}
.import-jobs-status-col {
width: 20%;
}
.btn-import {
.loading-icon {
display: none;
}
&.is-loading {
.loading-icon {
display: inline-block;
}
}
}
...@@ -128,6 +128,7 @@ ...@@ -128,6 +128,7 @@
top: 58px; top: 58px;
bottom: 0; bottom: 0;
right: 0; right: 0;
z-index: 10;
transition: width .3s; transition: width .3s;
background: $gray-light; background: $gray-light;
padding: 10px 20px; padding: 10px 20px;
...@@ -173,12 +174,6 @@ ...@@ -173,12 +174,6 @@
} }
} }
.subscribe-button {
span {
margin-top: 0;
}
}
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */ /* Extra small devices (phones, less than 768px) */
display: none; display: none;
...@@ -247,7 +242,7 @@ ...@@ -247,7 +242,7 @@
} }
} }
.btn { .issuable-pager {
background: $gray-normal; background: $gray-normal;
border: 1px solid $border-gray-normal; border: 1px solid $border-gray-normal;
&:hover { &:hover {
...@@ -256,13 +251,19 @@ ...@@ -256,13 +251,19 @@
} }
} }
a:not(.btn) { a:not(.issuable-pager) {
&:hover { &:hover {
color: $md-link-color; color: $md-link-color;
text-decoration: none; text-decoration: none;
} }
} }
.dropdown-content {
a:hover {
color: inherit;
}
}
.dropdown-menu-toggle { .dropdown-menu-toggle {
width: 100%; width: 100%;
padding-top: 6px; padding-top: 6px;
...@@ -273,10 +274,6 @@ ...@@ -273,10 +274,6 @@
} }
} }
.btn-default.gutter-toggle {
margin-top: 4px;
}
.detail-page-description { .detail-page-description {
small { small {
color: $gray-darkest; color: $gray-darkest;
...@@ -316,3 +313,56 @@ ...@@ -316,3 +313,56 @@
color: #8c8c8c; color: #8c8c8c;
} }
} }
.issuable-form-padding-top {
@media (min-width: $screen-sm-min) {
padding-top: 7px;
}
}
.issuable-status-box {
float: none;
display: inline-block;
margin-top: 0;
@media (max-width: $screen-xs-max) {
position: absolute;
top: 0;
left: 0;
}
}
.issuable-header {
position: relative;
padding-left: 45px;
padding-right: 45px;
line-height: 35px;
@media (min-width: $screen-sm-min) {
float: left;
padding-left: 0;
padding-right: 0;
}
}
.issuable-actions {
padding-top: 10px;
@media (min-width: $screen-sm-min) {
float: right;
padding-top: 0;
}
}
.issuable-gutter-toggle {
@media (max-width: $screen-sm-max) {
position: absolute;
top: 0;
right: 0;
}
}
.issuable-meta {
display: inline-block;
line-height: 18px;
}
...@@ -86,41 +86,9 @@ form.edit-issue { ...@@ -86,41 +86,9 @@ form.edit-issue {
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.issue-btn-group { .issue-btn-group {
width: 100%; width: 100%;
margin-top: 5px;
.btn-group {
width: 100%;
ul {
width: 100%;
text-align: center;
}
}
.btn { .btn {
width: 100%; width: 100%;
&:first-child:not(:last-child) {
}
&:not(:first-child):not(:last-child) {
margin-top: 10px;
}
&:last-child:not(:first-child) {
margin-top: 10px;
}
}
}
.issue {
&:hover .issue-actions {
display: none !important;
}
.issue-updated-at {
display: none;
} }
} }
} }
...@@ -133,11 +101,3 @@ form.edit-issue { ...@@ -133,11 +101,3 @@ form.edit-issue {
color: $gl-text-color; color: $gl-text-color;
margin-left: 52px; margin-left: 52px;
} }
.editor-details {
display: block;
@media (min-width: $screen-sm-min) {
display: inline-block;
}
}
...@@ -79,19 +79,30 @@ ...@@ -79,19 +79,30 @@
color: $white-light; color: $white-light;
} }
@mixin labels-mobile {
@media (max-width: $screen-xs-min) {
display: block;
width: 100%;
margin-left: 0;
padding: 10px 0;
}
}
.manage-labels-list { .manage-labels-list {
.prepend-left-10 { .prepend-left-10, .prepend-description-left {
display: inline-block; display: inline-block;
width: 40%; width: 40%;
vertical-align: middle; vertical-align: middle;
@media (max-width: $screen-xs-min) { @include labels-mobile;
display: block; }
width: 100%;
margin-left: 0; .prepend-description-left {
padding: 10px 0; width: 57%;
}
@include labels-mobile;
} }
.pull-info-right { .pull-info-right {
...@@ -106,7 +117,7 @@ ...@@ -106,7 +117,7 @@
padding: 6px; padding: 6px;
color: $gl-text-color; color: $gl-text-color;
&.subscribe-button { &.label-subscribe-button {
padding-left: 0; padding-left: 0;
} }
} }
......
...@@ -142,6 +142,7 @@ ...@@ -142,6 +142,7 @@
overflow: hidden; overflow: hidden;
font-size: 90%; font-size: 90%;
margin: 0 3px; margin: 0 3px;
word-break: break-all;
} }
.mr-list { .mr-list {
......
...@@ -40,7 +40,9 @@ ...@@ -40,7 +40,9 @@
} }
.note-textarea { .note-textarea {
display: block;
padding: 10px 0; padding: 10px 0;
color: $gl-gray;
font-family: $regular_font; font-family: $regular_font;
border: 0; border: 0;
...@@ -60,23 +62,34 @@ ...@@ -60,23 +62,34 @@
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
border: 1px solid $note-form-border-color; border: 1px solid $note-form-border-color;
border-radius: $border-radius-base; border-radius: $border-radius-base;
transition: border-color ease-in-out 0.15s,
box-shadow ease-in-out 0.15s;
&.is-focused { &.is-focused {
border-color: $focus-border-color; @extend .form-control:focus;
box-shadow: 0 0 2px rgba(#000, .2),
0 0 4px rgba($focus-border-color, .4);
.comment-toolbar, .comment-toolbar,
.nav-links { .nav-links {
border-color: $focus-border-color; border-color: $focus-border-color;
} }
} }
&.is-dropzone-hover {
border-color: $gl-success;
box-shadow: 0 0 2px $black-transparent,
0 0 4px $gl-success-focus;
.comment-toolbar,
.nav-links {
border-color: $gl-success;
}
}
} }
} }
.discussion-form { .discussion-form {
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
background-color: #fff; background-color: $white-light;
} }
.note-edit-form { .note-edit-form {
......
...@@ -81,10 +81,8 @@ ul.notes { ...@@ -81,10 +81,8 @@ ul.notes {
@include md-typography; @include md-typography;
// On diffs code should wrap nicely and not overflow // On diffs code should wrap nicely and not overflow
pre { code {
code { white-space: pre-wrap;
white-space: pre;
}
} }
// Reset ul style types since we're nested inside a ul already // Reset ul style types since we're nested inside a ul already
...@@ -112,6 +110,10 @@ ul.notes { ...@@ -112,6 +110,10 @@ ul.notes {
margin: 10px 0; margin: 10px 0;
} }
} }
a {
word-break: break-all;
}
} }
.note-header { .note-header {
...@@ -145,19 +147,27 @@ ul.notes { ...@@ -145,19 +147,27 @@ ul.notes {
background: $background-color; background: $background-color;
color: $text-color; color: $text-color;
} }
&.notes_line2 { &.notes_line2 {
text-align: center; text-align: center;
padding: 10px 0; padding: 10px 0;
border-left: 1px solid #ddd !important; border-left: 1px solid #ddd !important;
} }
&.notes_content { &.notes_content {
background-color: #fff; background-color: $background-color;
border-width: 1px 0; border-width: 1px 0;
padding: 0; padding: 0;
vertical-align: top; vertical-align: top;
white-space: normal;
&.parallel { &.parallel {
border-width: 1px; border-width: 1px;
} }
.notes {
background-color: $white-light;
}
} }
} }
} }
...@@ -180,6 +190,12 @@ ul.notes { ...@@ -180,6 +190,12 @@ ul.notes {
color: $notes-light-color; color: $notes-light-color;
} }
.discussion-headline-light {
a {
color: $gl-link-color;
}
}
/** /**
* Actions for Discussions/Notes * Actions for Discussions/Notes
*/ */
...@@ -191,6 +207,17 @@ ul.notes { ...@@ -191,6 +207,17 @@ ul.notes {
color: $notes-action-color; color: $notes-action-color;
} }
.discussion-actions {
@media (max-width: $screen-sm-max) {
float: none;
margin-left: 0;
.note-action-button {
margin-left: 0;
}
}
}
.note-action-button, .note-action-button,
.discussion-action-button { .discussion-action-button {
display: inline-block; display: inline-block;
...@@ -258,8 +285,7 @@ ul.notes { ...@@ -258,8 +285,7 @@ ul.notes {
.diff-file tr.line_holder { .diff-file tr.line_holder {
@mixin show-add-diff-note { @mixin show-add-diff-note {
filter: alpha(opacity=100); display: inline-block;
opacity: 1.0;
} }
.add-diff-note { .add-diff-note {
...@@ -269,17 +295,12 @@ ul.notes { ...@@ -269,17 +295,12 @@ ul.notes {
padding: 4px; padding: 4px;
font-size: 16px; font-size: 16px;
color: $gl-link-color; color: $gl-link-color;
margin-left: -60px; margin-left: -56px;
position: absolute; position: absolute;
z-index: 10; z-index: 10;
width: 32px; width: 32px;
transition: all 0.2s ease;
// "hide" it by default // "hide" it by default
opacity: 0.0; display: none;
filter: alpha(opacity=0);
&:hover { &:hover {
background: $gl-info; background: $gl-info;
color: #fff; color: #fff;
......
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
color: #7f8fa4; color: #7f8fa4;
font-size: $gl-font-size; font-size: $gl-font-size;
.label {
color: $gl-text-color;
font-size: inherit;
}
p { p {
color: #5c5d5e; color: #5c5d5e;
} }
......
/* Generic print styles */
header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
.profiler-results {display: none;}
/* Styles targeted specifically at printing files */
.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
.file-title {display: none;}
.file-holder {border: none;}
.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; } .wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
.wiki h1 {font-size: 30px;} .wiki h1 {font-size: 30px;}
.wiki h2 {font-size: 22px;} .wiki h2 {font-size: 22px;}
.wiki h3 {font-size: 18px; font-weight: bold; } .wiki h3 {font-size: 18px; font-weight: bold; }
.sidebar-wrapper { display: none; } header,
.nav { display: none; } nav,
.btn { display: none; } nav.main-nav,
nav.navbar-collapse,
nav.navbar-collapse.collapse,
.profiler-results,
.tree-ref-holder,
.tree-holder .breadcrumb,
.blob-commit-info,
.file-title,
.file-holder,
.sidebar-wrapper,
.nav,
.btn,
ul.notes-form,
.merge-request-ci-status .ci-status-link:after,
.issuable-gutter-toggle,
.gutter-toggle,
.issuable-details .content-block-small,
.edit-link,
.note-action-button {
display: none!important;
}
.page-gutter {
padding-top: 0;
padding-left: 0;
}
.right-sidebar {
top: 0;
}
...@@ -19,6 +19,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -19,6 +19,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path redirect_to admin_runners_path
end end
def clear_repository_check_states
RepositoryCheck::ClearWorker.perform_async
redirect_to(
admin_application_settings_path,
notice: 'Started asynchronous removal of all repository check states.'
)
end
private private
def set_application_setting def set_application_setting
...@@ -66,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -66,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:admin_notification_email, :admin_notification_email,
:user_oauth_applications, :user_oauth_applications,
:shared_runners_enabled, :shared_runners_enabled,
:shared_runners_text,
:max_artifacts_size, :max_artifacts_size,
:metrics_enabled, :metrics_enabled,
:metrics_host, :metrics_host,
...@@ -82,6 +92,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -82,6 +92,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_enabled, :akismet_enabled,
:akismet_api_key, :akismet_api_key,
:email_author_in_body, :email_author_in_body,
:repository_checks_enabled,
:metrics_packet_size,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
) )
......
...@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController
end end
def hook_params def hook_params
params.require(:hook).permit(:url, :enable_ssl_verification) params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events)
end end
end end
class Admin::ProjectsController < Admin::ApplicationController class Admin::ProjectsController < Admin::ApplicationController
before_action :project, only: [:show, :transfer] before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer] before_action :group, only: [:show, :transfer]
def index def index
...@@ -8,6 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -8,6 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present? @projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present?
@projects = @projects.non_archived unless params[:with_archived].present? @projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
...@@ -30,6 +31,15 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -30,6 +31,15 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to admin_namespace_project_path(@project.namespace, @project) redirect_to admin_namespace_project_path(@project.namespace, @project)
end end
def repository_check
RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
redirect_to(
admin_namespace_project_path(@project.namespace, @project),
notice: 'Repository check was triggered.'
)
end
protected protected
def project def project
......
...@@ -3,6 +3,7 @@ require 'fogbugz' ...@@ -3,6 +3,7 @@ require 'fogbugz'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::GonHelper
include GitlabRoutingHelper include GitlabRoutingHelper
include PageLayoutHelper include PageLayoutHelper
...@@ -13,7 +14,7 @@ class ApplicationController < ActionController::Base ...@@ -13,7 +14,7 @@ class ApplicationController < ActionController::Base
before_action :check_password_expiration before_action :check_password_expiration
before_action :check_2fa_requirement before_action :check_2fa_requirement
before_action :ldap_security_check before_action :ldap_security_check
before_action :sentry_user_context before_action :sentry_context
before_action :default_headers before_action :default_headers
before_action :add_gon_variables before_action :add_gon_variables
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
...@@ -40,13 +41,15 @@ class ApplicationController < ActionController::Base ...@@ -40,13 +41,15 @@ class ApplicationController < ActionController::Base
protected protected
def sentry_user_context def sentry_context
if Rails.env.production? && current_application_settings.sentry_enabled && current_user if Rails.env.production? && current_application_settings.sentry_enabled
Raven.user_context( if current_user
id: current_user.id, Raven.user_context(
email: current_user.email, id: current_user.id,
username: current_user.username, email: current_user.email,
) username: current_user.username,
)
end
Raven.tags_context(program: sentry_program_context) Raven.tags_context(program: sentry_program_context)
end end
...@@ -158,20 +161,6 @@ class ApplicationController < ActionController::Base ...@@ -158,20 +161,6 @@ class ApplicationController < ActionController::Base
end end
end end
def add_gon_variables
gon.api_version = API::API.version
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
if current_user
gon.current_user_id = current_user.id
gon.api_token = current_user.private_token
end
end
def validate_user_service_ticket! def validate_user_service_ticket!
return unless signed_in? && session[:service_tickets] return unless signed_in? && session[:service_tickets]
......
...@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController ...@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
if params[:current_user] && current_user if params[:current_user] && current_user
@users = [*@users, current_user].uniq @users = [*@users, current_user]
end end
if params[:author_id].present?
author = User.find_by_id(params[:author_id])
@users = [author, *@users] if author
end
@users.uniq!
end end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
......
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
...@@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController ...@@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank? @projects = @projects.page(params[:page]) if params[:filter_projects].blank?
......
...@@ -51,6 +51,7 @@ class HelpController < ApplicationController ...@@ -51,6 +51,7 @@ class HelpController < ApplicationController
end end
def ui def ui
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
end end
private private
......
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::GonHelper
include PageLayoutHelper include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user! before_action :authenticate_user!
before_action :add_gon_variables
layout 'profile' layout 'profile'
......
...@@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id]) @key = current_user.keys.find(params[:id])
end end
# Back-compat: We need to support this URL since git-annex webapp points to it
def new
redirect_to profile_keys_path
end
def create def create
@key = current_user.keys.new(key_params) @key = current_user.keys.new(key_params)
......
class Profiles::NotificationsController < Profiles::ApplicationController class Profiles::NotificationsController < Profiles::ApplicationController
def show def show
@user = current_user @user = current_user
@notification = current_user.notification @group_notifications = current_user.notification_settings.for_groups
@project_members = current_user.project_members @project_notifications = current_user.notification_settings.for_projects
@group_members = current_user.group_members
end end
def update def update
type = params[:notification_type] if current_user.update_attributes(user_params)
flash[:notice] = "Notification settings saved"
@saved = if type == 'global' else
current_user.update_attributes(user_params) flash[:alert] = "Failed to save new settings"
elsif type == 'group'
group_member = current_user.group_members.find(params[:notification_id])
group_member.notification_level = params[:notification_level]
group_member.save
else
project_member = current_user.project_members.find(params[:notification_id])
project_member.notification_level = params[:notification_level]
project_member.save
end
respond_to do |format|
format.html do
if @saved
flash[:notice] = "Notification settings saved"
else
flash[:alert] = "Failed to save new settings"
end
redirect_back_or_default(default: profile_notifications_path)
end
format.js
end end
redirect_back_or_default(default: profile_notifications_path)
end end
def user_params def user_params
......
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry] before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
before_action :authorize_update_build!, except: [:index, :show, :status] before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project' layout 'project'
def index def index
...@@ -62,6 +62,14 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -62,6 +62,14 @@ class Projects::BuildsController < Projects::ApplicationController
notice: "Build has been sucessfully erased!" notice: "Build has been sucessfully erased!"
end end
def raw
if @build.has_trace?
send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline'
else
render_404
end
end
private private
def build def build
......
...@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_read_commit_status!, only: [:builds] before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit before_action :commit
before_action :define_show_vars, only: [:show, :builds] before_action :define_show_vars, only: [:show, :builds]
before_action :authorize_edit_tree!, only: [:revert] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
def show def show
apply_diff_view_cookie! apply_diff_view_cookie!
...@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController
end end
def cancel_builds def cancel_builds
ci_commit.builds.running_or_pending.each(&:cancel) ci_builds.running_or_pending.each(&:cancel)
redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha) redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end end
def retry_builds def retry_builds
ci_commit.builds.latest.failed.each do |build| ci_builds.latest.failed.each do |build|
if build.retryable? if build.retryable?
Ci::Build.retry(build) Ci::Build.retry(build)
end end
...@@ -60,27 +60,32 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -60,27 +60,32 @@ class Projects::CommitController < Projects::ApplicationController
end end
def revert def revert
assign_revert_commit_vars assign_change_commit_vars(@commit.revert_branch_name)
return render_404 if @target_branch.blank? return render_404 if @target_branch.blank?
create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.", create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
success_path: successful_revert_path, failure_path: failed_revert_path) success_path: successful_change_path, failure_path: failed_change_path)
end end
def cherry_pick
assign_change_commit_vars(@commit.cherry_pick_branch_name)
return render_404 if @target_branch.blank?
private create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
success_path: successful_change_path, failure_path: failed_change_path)
def revert_type_title
@commit.merged_merge_request ? 'merge request' : 'commit'
end end
def successful_revert_path private
def successful_change_path
return referenced_merge_request_url if @commit.merged_merge_request return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commits_url(@project.namespace, @project, @target_branch) namespace_project_commits_url(@project.namespace, @project, @target_branch)
end end
def failed_revert_path def failed_change_path
return referenced_merge_request_url if @commit.merged_merge_request return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commit_url(@project.namespace, @project, params[:id]) namespace_project_commit_url(@project.namespace, @project, params[:id])
...@@ -94,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -94,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id]) @commit ||= @project.commit(params[:id])
end end
def ci_commit def ci_commits
@ci_commit ||= project.ci_commit(commit.sha) @ci_commits ||= project.ci_commits.where(sha: commit.sha)
end
def ci_builds
@ci_builds ||= Ci::Build.where(commit: ci_commits)
end end
def define_show_vars def define_show_vars
...@@ -108,17 +117,17 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -108,17 +117,17 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit] @diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count @notes_count = commit.notes.count
@statuses = ci_commit.statuses if ci_commit @statuses = CommitStatus.where(commit: ci_commits)
@builds = Ci::Build.where(commit: ci_commits)
end end
def assign_revert_commit_vars def assign_change_commit_vars(mr_source_branch)
@commit = project.commit(params[:id]) @commit = project.commit(params[:id])
@target_branch = params[:target_branch] @target_branch = params[:target_branch]
@mr_source_branch = @commit.revert_branch_name @mr_source_branch = mr_source_branch
@mr_target_branch = @target_branch @mr_target_branch = @target_branch
@commit_params = { @commit_params = {
commit: @commit, commit: @commit,
revert_type_title: revert_type_title,
create_merge_request: params[:create_merge_request].present? || different_project? create_merge_request: params[:create_merge_request].present? || different_project?
} }
end end
......
...@@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController
end end
def create def create
link = project.project_group_links.new group = Group.find(params[:link_group_id])
link.group_id = params[:link_group_id] return render_404 unless can?(current_user, :read_group, group)
link.group_access = params[:link_group_access]
link.save project.project_group_links.create(
group: group, group_access: params[:link_group_access]
)
redirect_to namespace_project_group_links_path(project.namespace, project) redirect_to namespace_project_group_links_path(project.namespace, project)
end end
......
...@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions include IssuableActions
before_action :module_enabled before_action :module_enabled
before_action :issue, only: [:edit, :update, :show] before_action :issue,
only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
# Allow read any issue # Allow read any issue
before_action :authorize_read_issue!, only: [:show] before_action :authorize_read_issue!, only: [:show]
...@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update # Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update] before_action :authorize_admin_issues!, only: [:bulk_update]
# Cross-reference merge requests
before_action :closed_by_merge_requests, only: [:show]
respond_to :html respond_to :html
def index def index
...@@ -35,14 +33,15 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -35,14 +33,15 @@ class Projects::IssuesController < Projects::ApplicationController
end end
@issues = @issues.page(params[:page]) @issues = @issues.page(params[:page])
@label = @project.labels.find_by(title: params[:label_name]) @labels = @project.labels.where(title: params[:label_name])
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
format.json do format.json do
render json: { render json: {
html: view_to_html_string("projects/issues/_issues") html: view_to_html_string("projects/issues/_issues"),
labels: @labels.as_json(methods: :text_color)
} }
end end
end end
...@@ -62,11 +61,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -62,11 +61,9 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue @noteable = @issue
@merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -118,15 +115,36 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -118,15 +115,36 @@ class Projects::IssuesController < Projects::ApplicationController
end end
end end
def referenced_merge_requests
@merge_requests = @issue.referenced_merge_requests(current_user)
@closed_by_merge_requests = @issue.closed_by_merge_requests(current_user)
respond_to do |format|
format.json do
render json: {
html: view_to_html_string('projects/issues/_merge_requests')
}
end
end
end
def related_branches
@related_branches = @issue.related_branches(current_user)
respond_to do |format|
format.json do
render json: {
html: view_to_html_string('projects/issues/_related_branches')
}
end
end
end
def bulk_update def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" }) redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end end
def closed_by_merge_requests
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
end
protected protected
def issue def issue
...@@ -174,7 +192,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -174,7 +192,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params def issue_params
params.require(:issue).permit( params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential, :title, :assignee_id, :position, :description, :confidential,
:milestone_id, :state_event, :task_num, label_ids: [] :milestone_id, :due_date, :state_event, :task_num, label_ids: []
) )
end end
......
...@@ -38,13 +38,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -38,13 +38,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project) @merge_requests = @merge_requests.preload(:target_project)
@label = @project.labels.find_by(title: params[:label_name]) @labels = @project.labels.where(title: params[:label_name])
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
render json: { render json: {
html: view_to_html_string("projects/merge_requests/_merge_requests") html: view_to_html_string("projects/merge_requests/_merge_requests"),
labels: @labels.as_json(methods: :text_color)
} }
end end
end end
...@@ -320,6 +321,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -320,6 +321,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars def define_widget_vars
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
@ci_commits = [@ci_commit].compact
closes_issues closes_issues
end end
......
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
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project_member!, except: :leave before_action :authorize_admin_project_member!, except: [:leave, :index]
def index def index
@project_members = @project.project_members @project_members = @project.project_members
......
...@@ -11,7 +11,6 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -11,7 +11,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
end end
def archive def archive
RepositoryArchiveCacheWorker.perform_async
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format])) headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
head :ok head :ok
rescue => ex rescue => ex
......
...@@ -6,7 +6,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -6,7 +6,7 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels, :colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events, :push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :build_events, :note_events, :build_events, :wiki_page_events,
:notify_only_broken_builds, :add_pusher, :notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
......
...@@ -44,7 +44,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -44,7 +44,7 @@ class Projects::WikisController < Projects::ApplicationController
return render('empty') unless can?(current_user, :create_wiki, @project) return render('empty') unless can?(current_user, :create_wiki, @project)
if @page.update(content, format, message) if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
redirect_to( redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page), namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.' notice: 'Wiki was successfully updated.'
...@@ -55,9 +55,9 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -55,9 +55,9 @@ class Projects::WikisController < Projects::ApplicationController
end end
def create def create
@page = WikiPage.new(@project_wiki) @page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute
if @page.create(wiki_params) if @page.persisted?
redirect_to( redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page), namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.' notice: 'Wiki was successfully updated.'
...@@ -122,15 +122,4 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -122,15 +122,4 @@ class Projects::WikisController < Projects::ApplicationController
params[:wiki].slice(:title, :content, :format, :message) params[:wiki].slice(:title, :content, :format, :message)
end end
def content
params[:wiki][:content]
end
def format
params[:wiki][:format]
end
def message
params[:wiki][:message]
end
end end
...@@ -101,14 +101,18 @@ class ProjectsController < Projects::ApplicationController ...@@ -101,14 +101,18 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
if current_user
@membership = @project.team.find_member(current_user.id)
if @membership
@notification_setting = current_user.notification_settings_for(@project)
end
end
if @project.repository_exists? if @project.repository_exists?
if @project.empty_repo? if @project.empty_repo?
render 'projects/empty' render 'projects/empty'
else else
if current_user
@membership = @project.team.find_member(current_user.id)
end
render :show render :show
end end
else else
......
class UsersController < ApplicationController class UsersController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :set_user before_action :user
before_action :authorize_read_user!, only: [:show]
def show def show
respond_to do |format| respond_to do |format|
...@@ -75,22 +76,26 @@ class UsersController < ApplicationController ...@@ -75,22 +76,26 @@ class UsersController < ApplicationController
private private
def set_user def authorize_read_user!
@user = User.find_by_username!(params[:username]) render_404 unless can?(current_user, :read_user, user)
end
def user
@user ||= User.find_by_username!(params[:username])
end end
def contributed_projects def contributed_projects
ContributedProjectsFinder.new(@user).execute(current_user) ContributedProjectsFinder.new(user).execute(current_user)
end end
def contributions_calendar def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar. @contributions_calendar ||= Gitlab::ContributionsCalendar.
new(contributed_projects, @user) new(contributed_projects, user)
end end
def load_events def load_events
# Get user activity feed for projects common for both users # Get user activity feed for projects common for both users
@events = @user.recent_events. @events = user.recent_events.
merge(projects_for_current_user). merge(projects_for_current_user).
references(:project). references(:project).
with_associations. with_associations.
...@@ -99,16 +104,16 @@ class UsersController < ApplicationController ...@@ -99,16 +104,16 @@ class UsersController < ApplicationController
def load_projects def load_projects
@projects = @projects =
PersonalProjectsFinder.new(@user).execute(current_user) PersonalProjectsFinder.new(user).execute(current_user)
.page(params[:page]) .page(params[:page])
end end
def load_contributed_projects def load_contributed_projects
@contributed_projects = contributed_projects.joined(@user) @contributed_projects = contributed_projects.joined(user)
end end
def load_groups def load_groups
@groups = JoinedGroupsFinder.new(@user).execute(current_user) @groups = JoinedGroupsFinder.new(user).execute(current_user)
end end
def projects_for_current_user def projects_for_current_user
......
...@@ -39,6 +39,7 @@ class IssuableFinder ...@@ -39,6 +39,7 @@ class IssuableFinder
items = by_assignee(items) items = by_assignee(items)
items = by_author(items) items = by_author(items)
items = by_label(items) items = by_label(items)
items = by_due_date(items)
sort(items) sort(items)
end end
...@@ -117,7 +118,7 @@ class IssuableFinder ...@@ -117,7 +118,7 @@ class IssuableFinder
end end
def filter_by_no_label? def filter_by_no_label?
labels? && params[:label_name] == Label::None.title labels? && params[:label_name].include?(Label::None.title)
end end
def labels def labels
...@@ -271,18 +272,55 @@ class IssuableFinder ...@@ -271,18 +272,55 @@ class IssuableFinder
items = items.without_label items = items.without_label
else else
items = items.with_label(label_names) items = items.with_label(label_names)
if projects if projects
items = items.where(labels: { project_id: projects }) items = items.where(labels: { project_id: projects })
end end
end end
end end
# When filtering by multiple labels we may end up duplicating issues (if one
# has multiple labels). This ensures we only return unique issues.
items.distinct
end
def by_due_date(items)
if due_date?
if filter_by_no_due_date?
items = items.without_due_date
elsif filter_by_overdue?
items = items.due_before(Date.today)
elsif filter_by_due_this_week?
items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
elsif filter_by_due_this_month?
items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
end
end
items items
end end
def filter_by_no_due_date?
due_date? && params[:due_date] == Issue::NoDueDate.name
end
def filter_by_overdue?
due_date? && params[:due_date] == Issue::Overdue.name
end
def filter_by_due_this_week?
due_date? && params[:due_date] == Issue::DueThisWeek.name
end
def filter_by_due_this_month?
due_date? && params[:due_date] == Issue::DueThisMonth.name
end
def due_date?
params[:due_date].present? && klass.column_names.include?('due_date')
end
def label_names def label_names
params[:label_name].split(',') params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
end end
def current_user_related? def current_user_related?
......
...@@ -184,7 +184,7 @@ module ApplicationHelper ...@@ -184,7 +184,7 @@ module ApplicationHelper
element = content_tag :time, time.to_s, element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}", class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}",
datetime: time.to_time.getutc.iso8601, datetime: time.to_time.getutc.iso8601,
title: time.in_time_zone.to_s(:medium), title: time.to_time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' } data: { toggle: 'tooltip', placement: placement, container: 'body' }
unless skip_js unless skip_js
...@@ -254,11 +254,11 @@ module ApplicationHelper ...@@ -254,11 +254,11 @@ module ApplicationHelper
def page_filter_path(options = {}) def page_filter_path(options = {})
without = options.delete(:without) without = options.delete(:without)
add_label = options.delete(:label)
exist_opts = { exist_opts = {
state: params[:state], state: params[:state],
scope: params[:scope], scope: params[:scope],
label_name: params[:label_name],
milestone_title: params[:milestone_title], milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id], assignee_id: params[:assignee_id],
author_id: params[:author_id], author_id: params[:author_id],
...@@ -275,6 +275,13 @@ module ApplicationHelper ...@@ -275,6 +275,13 @@ module ApplicationHelper
path = request.path path = request.path
path << "?#{options.to_param}" path << "?#{options.to_param}"
if add_label
if params[:label_name].present? and params[:label_name].respond_to?('any?')
params[:label_name].each do |label|
path << "&label_name[]=#{label}"
end
end
end
path path
end end
......
...@@ -15,6 +15,10 @@ module ApplicationSettingsHelper ...@@ -15,6 +15,10 @@ module ApplicationSettingsHelper
current_application_settings.sign_in_text current_application_settings.sign_in_text
end end
def shared_runners_text
current_application_settings.shared_runners_text
end
def user_oauth_applications? def user_oauth_applications?
current_application_settings.user_oauth_applications current_application_settings.user_oauth_applications
end end
......
...@@ -173,4 +173,15 @@ module BlobHelper ...@@ -173,4 +173,15 @@ module BlobHelper
response.etag = @blob.id response.etag = @blob.id
!stale !stale
end end
def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select)
licenses = Licensee::License.all
@licenses_for_select = {
Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
}
end
end end
...@@ -4,14 +4,6 @@ module CiStatusHelper ...@@ -4,14 +4,6 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end end
def ci_status_icon(ci_commit)
ci_icon_for_status(ci_commit.status)
end
def ci_status_label(ci_commit)
ci_label_for_status(ci_commit.status)
end
def ci_status_with_icon(status, target = nil) def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status) content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
klass = "ci-status ci-#{status}" klass = "ci-status ci-#{status}"
...@@ -47,10 +39,13 @@ module CiStatusHelper ...@@ -47,10 +39,13 @@ module CiStatusHelper
end end
def render_ci_status(ci_commit, tooltip_placement: 'auto left') def render_ci_status(ci_commit, tooltip_placement: 'auto left')
link_to ci_status_icon(ci_commit), # TODO: split this method into
# - render_commit_status
# - render_pipeline_status
link_to ci_icon_for_status(ci_commit.status),
ci_status_path(ci_commit), ci_status_path(ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
title: "Build #{ci_status_label(ci_commit)}", title: "Build #{ci_label_for_status(ci_commit.status)}",
data: { toggle: 'tooltip', placement: tooltip_placement } data: { toggle: 'tooltip', placement: tooltip_placement }
end end
......
...@@ -126,12 +126,10 @@ module CommitsHelper ...@@ -126,12 +126,10 @@ module CommitsHelper
def revert_commit_link(commit, continue_to_path, btn_class: nil) def revert_commit_link(commit, continue_to_path, btn_class: nil)
return unless current_user return unless current_user
tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request" tooltip = "Revert this #{commit.change_type_title} in a new merge request"
if can_collaborate_with_project? if can_collaborate_with_project?
content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}"
end
elsif can?(current_user, :fork_project, @project) elsif can?(current_user, :fork_project, @project)
continue_params = { continue_params = {
to: continue_to_path, to: continue_to_path,
...@@ -146,11 +144,24 @@ module CommitsHelper ...@@ -146,11 +144,24 @@ module CommitsHelper
end end
end end
def revert_commit_type(commit) def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil)
if commit.merged_merge_request return unless current_user
'merge request'
else tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
'commit'
if can_collaborate_with_project?
link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
elsif can?(current_user, :fork_project, @project)
continue_params = {
to: continue_to_path,
notice: edit_in_new_fork_notice + ' Try to cherry-pick this commit again.',
notice_now: edit_in_new_fork_notice_now
}
fork_path = namespace_project_forks_path(@project.namespace, @project,
namespace_key: current_user.namespace.id,
continue: continue_params)
link_to 'Cherry-pick', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip
end end
end end
...@@ -183,7 +194,7 @@ module CommitsHelper ...@@ -183,7 +194,7 @@ module CommitsHelper
options = { options = {
class: "commit-#{options[:source]}-link has-tooltip", class: "commit-#{options[:source]}-link has-tooltip",
data: { 'original-title'.to_sym => sanitize(source_email) } title: source_email
} }
if user.nil? if user.nil?
......
...@@ -40,10 +40,11 @@ module DiffHelper ...@@ -40,10 +40,11 @@ module DiffHelper
(unfold) ? 'unfold js-unfold' : '' (unfold) ? 'unfold js-unfold' : ''
end end
def diff_line_content(line) def diff_line_content(line, line_type = nil)
if line.blank? if line.blank?
" &nbsp;".html_safe " &nbsp;".html_safe
else else
line[0] = ' ' if %w[new old].include?(line_type)
line line
end end
end end
......
...@@ -25,6 +25,10 @@ module GitlabRoutingHelper ...@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end end
def project_pipelines_path(project, *args)
namespace_project_pipelines_path(project.namespace, project, *args)
end
def project_builds_path(project, *args) def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args) namespace_project_builds_path(project.namespace, project, *args)
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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