summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorDouglas Barbosa Alexandre <dbalexandre@gmail.com>2016-04-08 15:48:09 -0300
committerDouglas Barbosa Alexandre <dbalexandre@gmail.com>2016-04-08 15:48:09 -0300
commit7afeace35474249c9aeb898f3a56180745106169 (patch)
tree36bd5104fb42d739b54378fc2fb5e0cc9714e538 /app
parent877f56c9370eef9affef3cc3438c5d4fe11b123c (diff)
parent0d216c194c5a7b72c98f0f06b4fc7fd0ba358c0e (diff)
downloadgitlab-ce-7afeace35474249c9aeb898f3a56180745106169.tar.gz
Merge branch 'master' into decouple-member-notification
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/awards_handler.coffee4
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee8
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee226
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee2
-rw-r--r--app/assets/javascripts/issue.js.coffee17
-rw-r--r--app/assets/javascripts/issues.js.coffee15
-rw-r--r--app/assets/javascripts/labels_select.js.coffee22
-rw-r--r--app/assets/javascripts/lib/notify.js.coffee30
-rw-r--r--app/assets/javascripts/lib/url_utility.js.coffee31
-rw-r--r--app/assets/javascripts/merge_request.js.coffee16
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee78
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee17
-rw-r--r--app/assets/javascripts/notes.js.coffee38
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee55
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee305
-rw-r--r--app/assets/javascripts/todos.js.coffee10
-rw-r--r--app/assets/javascripts/users_select.js.coffee23
-rw-r--r--app/assets/javascripts/zen_mode.js.coffee2
-rw-r--r--app/assets/stylesheets/behaviors.scss4
-rw-r--r--app/assets/stylesheets/framework/calendar.scss6
-rw-r--r--app/assets/stylesheets/framework/common.scss11
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss60
-rw-r--r--app/assets/stylesheets/framework/files.scss1
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/forms.scss36
-rw-r--r--app/assets/stylesheets/framework/header.scss28
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss35
-rw-r--r--app/assets/stylesheets/framework/mobile.scss4
-rw-r--r--app/assets/stylesheets/framework/nav.scss14
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss8
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss8
-rw-r--r--app/assets/stylesheets/framework/variables.scss48
-rw-r--r--app/assets/stylesheets/framework/zen.scss101
-rw-r--r--app/assets/stylesheets/pages/awards.scss2
-rw-r--r--app/assets/stylesheets/pages/ci_projects.scss2
-rw-r--r--app/assets/stylesheets/pages/commit.scss10
-rw-r--r--app/assets/stylesheets/pages/commits.scss2
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss8
-rw-r--r--app/assets/stylesheets/pages/diff.scss9
-rw-r--r--app/assets/stylesheets/pages/editor.scss2
-rw-r--r--app/assets/stylesheets/pages/events.scss4
-rw-r--r--app/assets/stylesheets/pages/lint.scss4
-rw-r--r--app/assets/stylesheets/pages/login.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss36
-rw-r--r--app/assets/stylesheets/pages/note_form.scss127
-rw-r--r--app/assets/stylesheets/pages/notes.scss163
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/assets/stylesheets/pages/search.scss142
-rw-r--r--app/assets/stylesheets/pages/status.scss2
-rw-r--r--app/assets/stylesheets/pages/todos.scss8
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/projects_controller.rb2
-rw-r--r--app/controllers/groups/milestones_controller.rb31
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb4
-rw-r--r--app/controllers/projects/badges_controller.rb14
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb22
-rw-r--r--app/controllers/projects/project_members_controller.rb11
-rw-r--r--app/controllers/projects/refs_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb14
-rw-r--r--app/controllers/projects_controller.rb18
-rw-r--r--app/controllers/sessions_controller.rb15
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/events_helper.rb8
-rw-r--r--app/helpers/search_helper.rb57
-rw-r--r--app/mailers/notify.rb4
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/commit_range.rb4
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/merge_request.rb13
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb16
-rw-r--r--app/models/project_services/builds_email_service.rb11
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/repository.rb27
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/git_push_service.rb18
-rw-r--r--app/services/issues/move_service.rb28
-rw-r--r--app/services/milestones/create_service.rb2
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/projects/unlink_fork_service.rb19
-rw-r--r--app/services/system_hooks_service.rb22
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/services/todo_service.rb26
-rw-r--r--app/uploaders/file_uploader.rb17
-rw-r--r--app/views/admin/application_settings/_form.html.haml7
-rw-r--r--app/views/admin/builds/_build.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/deploy_keys/index.html.haml2
-rw-r--r--app/views/admin/groups/_group.html.haml28
-rw-r--r--app/views/admin/groups/index.html.haml45
-rw-r--r--app/views/admin/labels/index.html.haml12
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/dashboard/todos/_todo.html.haml7
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/event/_created_project.html.haml18
-rw-r--r--app/views/groups/milestones/new.html.haml8
-rw-r--r--app/views/layouts/_search.html.haml40
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml2
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml7
-rw-r--r--app/views/layouts/project.html.haml6
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml2
-rw-r--r--app/views/projects/_md_preview.html.haml17
-rw-r--r--app/views/projects/_zen.html.haml20
-rw-r--r--app/views/projects/badges/index.html.haml24
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml17
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/diffs/_image.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml24
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml19
-rw-r--r--app/views/projects/notes/_edit_form.html.haml7
-rw-r--r--app/views/projects/notes/_form.html.haml4
-rw-r--r--app/views/projects/notes/_hints.html.haml15
-rw-r--r--app/views/projects/notes/_note.html.haml29
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml31
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml16
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml15
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml15
-rw-r--r--app/views/search/results/_note.html.haml18
-rw-r--r--app/views/shared/issuable/_filter.html.haml4
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml15
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/views/votes/_votes_block.html.haml2
-rw-r--r--app/workers/project_cache_worker.rb3
-rw-r--r--app/workers/project_destroy_worker.rb2
137 files changed, 1871 insertions, 882 deletions
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 47b080406d4..6a670d5e887 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,5 +1,5 @@
class @AwardsHandler
- constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
+ constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
$(".js-add-award").on "click", (event) =>
event.stopPropagation()
event.preventDefault()
@@ -34,7 +34,7 @@ class @AwardsHandler
$("#emoji_search").focus()
else
$('.js-add-award').addClass "is-loading"
- $.get "/emojis", (response) =>
+ $.get @get_emojis_url, (response) =>
$('.js-add-award').removeClass "is-loading"
$(".js-award-holder").append response
setTimeout =>
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index f5e1ca9860d..70fd6f50e9c 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -146,15 +146,11 @@ class Dispatcher
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
-
# If we haven't installed a custom shortcut handler, install the default one
if not shortcut_handler
new Shortcuts()
initSearch: ->
- opts = $('.search-autocomplete-opts')
- path = opts.data('autocomplete-path')
- project_id = opts.data('autocomplete-project-id')
- project_ref = opts.data('autocomplete-project-ref')
- new SearchAutocomplete(path, project_id, project_ref)
+ # Only when search form is present
+ new SearchAutocomplete() if $('.search').length
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index dd0465b9358..e8d25591f63 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -1,8 +1,13 @@
class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40]
+ ARROW_KEY_CODES = [38, 40]
HAS_VALUE_CLASS = "has-value"
constructor: (@input, @options) ->
+ {
+ @filterInputBlur = true
+ } = @options
+
$inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
@@ -18,22 +23,26 @@ class GitLabDropdownFilter
# Key events
timeout = ""
@input.on "keyup", (e) =>
+ keyCode = e.which
+
+ return if ARROW_KEY_CODES.indexOf(keyCode) >= 0
+
if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.addClass HAS_VALUE_CLASS
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS
- if e.keyCode is 13 and @input.val() isnt ""
+ if keyCode is 13 and @input.val() isnt ""
if @options.enterCallback
@options.enterCallback()
return
clearTimeout timeout
timeout = setTimeout =>
- blur_field = @shouldBlur e.keyCode
+ blur_field = @shouldBlur keyCode
search_text = @input.val()
- if blur_field
+ if blur_field and @filterInputBlur
@input.blur()
if @options.remote
@@ -92,38 +101,61 @@ class GitLabDropdown
LOADING_CLASS = "is-loading"
PAGE_TWO_CLASS = "is-page-two"
ACTIVE_CLASS = "is-active"
+ currentIndex = -1
+
+ FILTER_INPUT = '.dropdown-input .dropdown-input-field'
constructor: (@el, @options) ->
- self = @
@dropdown = $(@el).parent()
+
+ # Set Defaults
+ {
+ # If no input is passed create a default one
+ @filterInput = @getElement(FILTER_INPUT)
+ @highlight = false
+ @filterInputBlur = true
+ @enterCallback = true
+ } = @options
+
+ self = @
+
+ # If selector was passed
+ if _.isString(@filterInput)
+ @filterInput = @getElement(@filterInput)
+
search_fields = if @options.search then @options.search.fields else [];
if @options.data
- # Remote data
- @remote = new GitLabDropdownRemote @options.data, {
- dataType: @options.dataType,
- beforeSend: @toggleLoading.bind(@)
- success: (data) =>
- @fullData = data
+ # If data is an array
+ if _.isArray @options.data
+ @fullData = @options.data
+ @parseData @options.data
+ else
+ # Remote data
+ @remote = new GitLabDropdownRemote @options.data, {
+ dataType: @options.dataType,
+ beforeSend: @toggleLoading.bind(@)
+ success: (data) =>
+ @fullData = data
- @parseData @fullData
- }
+ @parseData @fullData
+ }
- # Init filiterable
+ # Init filterable
if @options.filterable
- @input = @dropdown.find('.dropdown-input .dropdown-input-field')
-
- @filter = new GitLabDropdownFilter @input,
+ @filter = new GitLabDropdownFilter @filterInput,
+ filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
data: =>
return @fullData
callback: (data) =>
+ currentIndex = -1
@parseData data
- @highlightRow 1
enterCallback: =>
- @selectFirstRow()
+ if @enterCallback
+ @selectRowAtIndex 0
# Event listeners
@@ -145,10 +177,15 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) ->
- selected = self.rowClicked $(@)
+ $el = $(@)
+ selected = self.rowClicked $el
if self.options.clicked
- self.options.clicked(selected)
+ self.options.clicked(selected, $el, e)
+
+ # Finds an element inside wrapper element
+ getElement: (selector) ->
+ @dropdown.find selector
toggleLoading: ->
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
@@ -188,14 +225,19 @@ class GitLabDropdown
return true
opened: =>
+ @addArrowKeyEvent()
+
contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is ""
@remote.execute()
if @options.filterable
- @dropdown.find(".dropdown-input-field").focus()
+ @filterInput.focus()
+
+ @dropdown.trigger('shown.gl.dropdown')
hidden: (e) =>
+ @removeArrayKeyEvent()
if @options.filterable
@dropdown
.find(".dropdown-input-field")
@@ -209,6 +251,8 @@ class GitLabDropdown
if @options.hidden
@options.hidden.call(@,e)
+ @dropdown.trigger('hidden.gl.dropdown')
+
# Render the full menu
renderMenu: (html) ->
@@ -233,13 +277,19 @@ class GitLabDropdown
renderItem: (data) ->
html = ""
+ # Divider
return "<li class='divider'></li>" if data is "divider"
+ # Separator is a full-width divider
+ return "<li class='separator'></li>" if data is "separator"
+
+ # Header
+ return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
+
if @options.renderRow
# Call the render function
html = @options.renderRow(data)
else
- selected = if @options.isSelected then @options.isSelected(data) else false
if not selected
value = if @options.id then @options.id(data) else data.id
fieldName = @options.fieldName
@@ -247,35 +297,54 @@ class GitLabDropdown
if field.length
selected = true
- url = if @options.url then @options.url(data) else "#"
- text = if @options.text then @options.text(data) else ""
+ # Set URL
+ if @options.url?
+ url = @options.url(data)
+ else
+ url = if data.url? then data.url else '#'
+
+ # Set Text
+ if @options.text?
+ text = @options.text(data)
+ else
+ text = if data.text? then data.text else ''
+
cssClass = "";
if selected
cssClass = "is-active"
- html = "<li>"
- html += "<a href='#{url}' class='#{cssClass}'>"
- html += text
- html += "</a>"
- html += "</li>"
+ if @highlight
+ text = @highlightTextMatches(text, @filterInput.val())
+
+ html = "<li>
+ <a href='#{url}' class='#{cssClass}'>
+ #{text}
+ </a>
+ </li>"
return html
+ highlightTextMatches: (text, term) ->
+ occurrences = fuzzaldrinPlus.match(text, term)
+ text.split('').map((character, i) ->
+ if i in occurrences then "<b>#{character}</b>" else character
+ ).join('')
+
noResults: ->
- html = "<li>"
- html += "<a href='#' class='dropdown-menu-empty-link is-focused'>"
- html += "No matching results."
- html += "</a>"
- html += "</li>"
+ html = "<li class='dropdown-menu-empty-link'>
+ <a href='#' class='is-focused'>
+ No matching results.
+ </a>
+ </li>"
highlightRow: (index) ->
- if @input.val() isnt ""
+ if @filterInput.val() isnt ""
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
- $(selector).addClass 'is-focused'
+ @getElement(selector).addClass 'is-focused'
rowClicked: (el) ->
fieldName = @options.fieldName
@@ -284,7 +353,7 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-
+
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
@@ -292,6 +361,8 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
+ else
+ selectedObject
else
if !value?
field.remove()
@@ -307,24 +378,93 @@ class GitLabDropdown
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
if value?
- if !field.length
+ if !field.length and fieldName
# Create hidden input for form
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
if @options.inputId?
input = $(input)
.attr('id', @options.inputId)
@dropdown.before input
+ else
+ field.val value
return selectedObject
- selectFirstRow: ->
- selector = '.dropdown-content li:first-child a'
+ selectRowAtIndex: (index) ->
+ selector = ".dropdown-content li:not(.divider):eq(#{index}) a"
+
if @dropdown.find(".dropdown-toggle-page").length
- selector = ".dropdown-page-one .dropdown-content li:first-child a"
+ selector = ".dropdown-page-one #{selector}"
# simulate a click on the first link
- $(selector).trigger "click"
+ $(selector, @dropdown).trigger "click"
+
+ addArrowKeyEvent: ->
+ ARROW_KEY_CODES = [38, 40]
+ $input = @dropdown.find(".dropdown-input-field")
+
+ selector = '.dropdown-content li:not(.divider)'
+ if @dropdown.find(".dropdown-toggle-page").length
+ selector = ".dropdown-page-one #{selector}"
+
+ $('body').on 'keydown', (e) =>
+ currentKeyCode = e.which
+
+ if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0
+ e.preventDefault()
+ e.stopImmediatePropagation()
+
+ PREV_INDEX = currentIndex
+ $listItems = $(selector, @dropdown)
+
+ # if @options.filterable
+ # $input.blur()
+
+ if currentKeyCode is 40
+ # Move down
+ currentIndex += 1 if currentIndex < ($listItems.length - 1)
+ else if currentKeyCode is 38
+ # Move up
+ currentIndex -= 1 if currentIndex > 0
+
+ @highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX
+
+ return false
+
+ if currentKeyCode is 13
+ @selectRowAtIndex currentIndex
+
+ removeArrayKeyEvent: ->
+ $('body').off 'keydown'
+
+ highlightRowAtIndex: ($listItems, index) ->
+ # Remove the class for the previously focused row
+ $('.is-focused', @dropdown).removeClass 'is-focused'
+
+ # Update the class for the row at the specific index
+ $listItem = $listItems.eq(index)
+ $listItem.find('a:first-child').addClass "is-focused"
+
+ # Dropdown content scroll area
+ $dropdownContent = $listItem.closest('.dropdown-content')
+ dropdownScrollTop = $dropdownContent.scrollTop()
+ dropdownContentHeight = $dropdownContent.outerHeight()
+ dropdownContentTop = $dropdownContent.prop('offsetTop')
+ dropdownContentBottom = dropdownContentTop + dropdownContentHeight
+
+ # Get the offset bottom of the list item
+ listItemHeight = $listItem.outerHeight()
+ listItemTop = $listItem.prop('offsetTop')
+ listItemBottom = listItemTop + listItemHeight
+
+ if listItemBottom > dropdownContentBottom + dropdownScrollTop
+ # Scroll the dropdown content down
+ $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom)
+ else if listItemTop < dropdownContentTop + dropdownScrollTop
+ # Scroll the dropdown content up
+ $dropdownContent.scrollTop(listItemTop - dropdownContentTop)
$.fn.glDropdown = (opts) ->
return @.each ->
- new GitLabDropdown @, opts
+ if (!$.data @, 'glDropdown')
+ $.data(@, 'glDropdown', new GitLabDropdown @, opts)
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 6fc924d3d66..2f19513a831 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -9,7 +9,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
- $(document).on "click",".edit-link", (e) ->
+ $(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
$block = $(@).parents('.block')
$selectbox = $block.find('.selectbox')
if $selectbox.is(':visible')
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index d663e34871c..946d83b7bdd 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -6,25 +6,10 @@ class @Issue
constructor: ->
# Prevent duplicate event bindings
@disableTaskList()
- @fixAffixScroll()
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
- fixAffixScroll: ->
- fixAffix = ->
- $discussion = $('.issuable-discussion')
- $sidebar = $('.issuable-sidebar')
- if $sidebar.hasClass('no-affix')
- $sidebar.removeClass(['affix-top','affix'])
- discussionHeight = $discussion.height()
- sidebarHeight = $sidebar.height()
- if sidebarHeight > discussionHeight
- $discussion.height(sidebarHeight + 50)
- $sidebar.addClass('no-affix')
- $(window).on('resize', fixAffix)
- fixAffix()
-
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
@@ -49,7 +34,7 @@ class @Issue
issueStatus = if isClose then 'close' else 'open'
new Flash(issueFailMessage, 'alert')
success: (data, textStatus, jqXHR) ->
- if data.saved
+ if 'id' of data
$(document).trigger('issuable:change');
if isClose
$('a.btn-close').addClass('hidden')
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index b1479bfb449..0d9f2094c2a 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -26,6 +26,20 @@
$(".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
@@ -54,6 +68,7 @@
# 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: ->
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index e69a9e3e4b1..d1fe116397a 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -16,6 +16,7 @@ class @LabelsSelect
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
+ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut()
@@ -142,6 +143,7 @@ class @LabelsSelect
if not selected.length
data[abilityName].label_ids = ['']
$loading.fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
url: issueUpdateURL
@@ -149,15 +151,20 @@ class @LabelsSelect
data: data
).done (data) ->
$loading.fadeOut()
+ $dropdown.trigger('loaded.gl.dropdown')
$selectbox.hide()
data.issueURLSplit = issueURLSplit
- if not data.labels.length
- template = labelNoneHTMLTemplate()
- else
+ labelCount = 0
+ if data.labels.length
template = labelHTMLTemplate(data)
- href = $value
- .show()
- .html(template)
+ labelCount = data.labels.length
+ else
+ template = labelNoneHTMLTemplate()
+ $value
+ .removeAttr('style')
+ .html(template)
+ $sidebarCollapsedValue.text(labelCount)
+
$value
.find('a')
.each((i) ->
@@ -226,7 +233,8 @@ class @LabelsSelect
hidden: ->
$selectbox.hide()
- $value.show()
+ # display:block overrides the hide-collapse rule
+ $value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect'
saveLabelData()
diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/notify.js.coffee
new file mode 100644
index 00000000000..3f9ca39912c
--- /dev/null
+++ b/app/assets/javascripts/lib/notify.js.coffee
@@ -0,0 +1,30 @@
+((w) ->
+ notificationGranted = (message, opts, onclick) ->
+ notification = new Notification(message, opts)
+
+ if onclick
+ notification.onclick = onclick
+
+ notifyPermissions = ->
+ if 'Notification' of window
+ Notification.requestPermission()
+
+ notifyMe = (message, body, icon, onclick) ->
+ opts =
+ body: body
+ icon: icon
+ # Let's check if the browser supports notifications
+ if !('Notification' of window)
+ # do nothing
+ else if Notification.permission == 'granted'
+ # If it's okay let's create a notification
+ notificationGranted message, opts, onclick
+ else if Notification.permission != 'denied'
+ Notification.requestPermission (permission) ->
+ # If the user accepts, let's create a notification
+ if permission == 'granted'
+ notificationGranted message, opts, onclick
+
+ w.notify = notifyMe
+ w.notifyPermissions = notifyPermissions
+) window
diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/url_utility.js.coffee
new file mode 100644
index 00000000000..abd556e0b4e
--- /dev/null
+++ b/app/assets/javascripts/lib/url_utility.js.coffee
@@ -0,0 +1,31 @@
+((w) ->
+
+ w.gl ?= {}
+ w.gl.utils ?= {}
+
+ w.gl.utils.getUrlParameter = (sParam) ->
+ sPageURL = decodeURIComponent(window.location.search.substring(1))
+ sURLVariables = sPageURL.split('&')
+ sParameterName = undefined
+ i = 0
+ while i < sURLVariables.length
+ sParameterName = sURLVariables[i].split('=')
+ if sParameterName[0] is sParam
+ return if sParameterName[1] is undefined then true else sParameterName[1]
+ i++
+
+ # #
+ # @param {Object} params - url keys and value to merge
+ # @param {String} url
+ # #
+ w.gl.utils.mergeUrlParams = (params, url) ->
+ newUrl = decodeURIComponent(url)
+ for paramName, paramValue of params
+ pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
+ if url.search(pattern) >= 0
+ newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
+ else
+ newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
+ newUrl
+
+) window
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 6af5a48a0bb..1f46e331427 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -15,8 +15,6 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
- @fixAffixScroll();
-
@initTabs()
# Prevent duplicate event bindings
@@ -30,20 +28,6 @@ class @MergeRequest
$: (selector) ->
this.$el.find(selector)
- fixAffixScroll: ->
- fixAffix = ->
- $discussion = $('.issuable-discussion')
- $sidebar = $('.issuable-sidebar')
- if $sidebar.hasClass('no-affix')
- $sidebar.removeClass(['affix-top','affix'])
- discussionHeight = $discussion.height()
- sidebarHeight = $sidebar.height()
- if sidebarHeight > discussionHeight
- $discussion.height(sidebarHeight + 50)
- $sidebar.addClass('no-affix')
- $(window).on('resize', fixAffix)
- fixAffix()
-
initTabs: ->
if @opts.action != 'new'
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 738ffc8343b..84a8887fbce 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -2,13 +2,20 @@ class @MergeRequestWidget
# Initialize MergeRequestWidget behavior
#
# check_enable - Boolean, whether to check automerge status
- # url_to_automerge_check - String, URL to use to check automerge status
- # current_status - String, current automerge status
- # ci_enable - Boolean, whether a CI service is enabled
- # url_to_ci_check - String, URL to use to check CI status
+ # merge_check_url - String, URL to use to check automerge status
+ # ci_status_url - String, URL to use to check CI status
#
+
constructor: (@opts) ->
- modal = $('#modal_merge_info').modal(show: false)
+ $('#modal_merge_info').modal(show: false)
+ @firstCICheck = true
+ @readyForCICheck = true
+ clearInterval @fetchBuildStatusInterval
+
+ @pollCIStatus()
+ notifyPermissions()
+
+ setOpts: (@opts) ->
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
@@ -27,18 +34,61 @@ class @MergeRequestWidget
dataType: 'json'
getMergeStatus: ->
- $.get @opts.url_to_automerge_check, (data) ->
+ $.get @opts.merge_check_url, (data) ->
$('.mr-state-widget').replaceWith(data)
- getCiStatus: ->
- if @opts.ci_enable
- $.get @opts.url_to_ci_check, (data) =>
- this.showCiState data.status
+ ciLabelForStatus: (status) ->
+ if status == 'success'
+ 'passed'
+ else
+ status
+
+ pollCIStatus: ->
+ @fetchBuildStatusInterval = setInterval ( =>
+ return if not @readyForCICheck
+
+ @getCIStatus(true)
+
+ @readyForCICheck = false
+ ), 10000
+
+ getCIStatus: (showNotification) ->
+ _this = @
+ $('.ci-widget-fetching').show()
+
+ $.getJSON @opts.ci_status_url, (data) =>
+ @readyForCICheck = true
+
+ if @firstCICheck
+ @firstCICheck = false
+ @opts.ci_status = data.status
+
+ if @opts.ci_status is ''
+ @opts.ci_status = data.status
+ return
+
+ if data.status isnt @opts.ci_status
+ @showCIStatus data.status
if data.coverage
- this.showCiCoverage data.coverage
- , 'json'
+ @showCICoverage data.coverage
+
+ if showNotification
+ message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status))
+ message = message.replace('{{sha}}', data.sha)
+ message = message.replace('{{title}}', data.title)
+
+ notify(
+ "Build #{@ciLabelForStatus(data.status)}",
+ message,
+ @opts.gitlab_icon,
+ ->
+ @close()
+ Turbolinks.visit _this.opts.builds_path
+ )
+
+ @opts.ci_status = data.status
- showCiState: (state) ->
+ showCIStatus: (state) ->
$('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states
@@ -52,7 +102,7 @@ class @MergeRequestWidget
$('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger')
- showCiCoverage: (coverage) ->
+ showCICoverage: (coverage) ->
text = 'Coverage ' + coverage + '%'
$('.ci_widget:visible .ci-coverage').text(text)
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index d61d03791fa..f73127f49f0 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -18,6 +18,7 @@ class @MilestoneSelect
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
+ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon')
$value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut()
@@ -80,7 +81,9 @@ class @MilestoneSelect
milestone.name is selectedMilestone
hidden: ->
$selectbox.hide()
- $value.show()
+
+ # display:block overrides the hide-collapse rule
+ $value.removeAttr('style')
clicked: (selected) ->
if $dropdown.hasClass 'js-filter-bulk-update'
return
@@ -88,11 +91,9 @@ class @MilestoneSelect
if $dropdown.hasClass('js-filter-submit')
if selected.name?
selectedMilestone = selected.name
- else if selected.title?
- selectedMilestone = selected.title
else
selectedMilestone = ''
- $dropdown.parents('form').submit()
+ Issues.filterResults $dropdown.closest('form')
else
selected = $selectbox
.find('input[type="hidden"]')
@@ -102,20 +103,22 @@ class @MilestoneSelect
data[abilityName].milestone_id = selected
$loading
.fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
url: issueUpdateURL
data: data
).done (data) ->
+ $dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
- $milestoneLink = $value
- .show()
- .find('a')
+ $value.removeAttr('style')
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
$value.html(milestoneLinkTemplate(data.milestone))
+ $sidebarCollapsedValue.find('span').text(data.milestone.title)
else
$value.html(milestoneLinkNoneTemplate)
+ $sidebarCollapsedValue.find('span').text('No')
)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index ff06c57f2b5..86e3b860fcb 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -251,13 +251,11 @@ class @Notes
Sets some hidden fields in the form.
###
setupMainTargetNoteForm: ->
-
# find the form
form = $(".js-new-note-form")
- # insert the form after the button
- form.clone().replaceAll $(".js-main-target-form")
- form = form.prev("form")
+ # Set a global clone of the form for later cloning
+ @formClone = form.clone()
# show the form
@setupNoteForm(form)
@@ -266,9 +264,7 @@ class @Notes
form.removeClass "js-new-note-form"
form.addClass "js-main-target-form"
- # remove unnecessary fields and buttons
form.find("#note_line_code").remove()
- form.find(".js-close-discussion-note-form").remove()
###
General note form setup.
@@ -297,7 +293,14 @@ class @Notes
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, [
"Note"
form.find("#note_commit_id").val()
@@ -307,7 +310,6 @@ class @Notes
]
# remove notify commit author checkbox for non-commit notes
- form.find(".js-notify-commit-author").remove() if form.find("#note_noteable_type").val() isnt "Commit"
GitLab.GfmAutoComplete.setup()
new DropzoneInput(form)
form.show()
@@ -455,15 +457,15 @@ class @Notes
Shows the note form below the notes.
###
replyToDiscussionNote: (e) =>
- form = $(".js-new-note-form")
+ form = @formClone.clone()
replyLink = $(e.target).closest(".js-discussion-reply-button")
replyLink.hide()
# insert the form after the button
- form.clone().insertAfter replyLink
+ replyLink.after form
# show the form
- @setupDiscussionNoteForm(replyLink, replyLink.next("form"))
+ @setupDiscussionNoteForm(replyLink, form)
###
Shows the diff or discussion form and does some setup on it.
@@ -488,7 +490,9 @@ class @Notes
.text(form.find('.js-close-discussion-note-form').data('cancel-text'))
@setupNoteForm form
form.find(".js-note-text").focus()
- form.addClass "js-discussion-note-form"
+ form
+ .removeClass('js-main-target-form')
+ .addClass("discussion-form js-discussion-note-form")
###
Called when clicking on the "add a comment" button on the side of a diff line.
@@ -498,9 +502,8 @@ class @Notes
###
addDiffNote: (e) =>
e.preventDefault()
- link = e.currentTarget
- form = $(".js-new-note-form")
- row = $(link).closest("tr")
+ $link = $(e.currentTarget)
+ row = $link.closest("tr")
nextRow = row.next()
hasNotes = nextRow.is(".notes_holder")
addForm = false
@@ -509,7 +512,7 @@ class @Notes
# In parallel view, look inside the correct left/right pane
if @isParallelView()
- lineType = $(link).data("lineType")
+ lineType = $link.data("lineType")
targetContent += "." + lineType
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>"
@@ -531,11 +534,11 @@ class @Notes
addForm = true
if addForm
- newForm = form.clone()
+ newForm = @formClone.clone()
newForm.appendTo row.next().find(targetContent)
# show the form
- @setupDiscussionNoteForm $(link), newForm
+ @setupDiscussionNoteForm $link, newForm
###
Called in response to "cancel" on a diff note form.
@@ -560,7 +563,6 @@ class @Notes
cancelDiscussionForm: (e) =>
e.preventDefault()
- form = $(".js-new-note-form")
form = $(e.target).closest(".js-discussion-note-form")
@removeDiscussionNoteForm(form)
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
new file mode 100644
index 00000000000..67403554340
--- /dev/null
+++ b/app/assets/javascripts/right_sidebar.js.coffee
@@ -0,0 +1,55 @@
+class @Sidebar
+ constructor: (currentUser) ->
+ @addEventListeners()
+
+ addEventListeners: ->
+ $('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
+ $('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
+ $('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
+ $('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
+
+ sidebarDropdownLoading: (e) ->
+ $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
+ img = $sidebarCollapsedIcon.find('img')
+ i = $sidebarCollapsedIcon.find('i')
+ $loading = $('<i class="fa fa-spinner fa-spin"></i>')
+ if img.length
+ img.before($loading)
+ img.hide()
+ else if i.length
+ i.before($loading)
+ i.hide()
+
+ sidebarDropdownLoaded: (e) ->
+ $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
+ img = $sidebarCollapsedIcon.find('img')
+ $sidebarCollapsedIcon.find('i.fa-spin').remove()
+ i = $sidebarCollapsedIcon.find('i')
+ if img.length
+ img.show()
+ else
+ i.show()
+
+
+ sidebarCollapseClicked: (e) ->
+ e.preventDefault()
+ $block = $(@).closest('.block')
+
+ $('aside')
+ .find('.gutter-toggle')
+ .trigger('click')
+ $editLink = $block.find('.edit-link')
+
+ if $editLink.length
+ $editLink.trigger('click')
+ $block.addClass('collapse-after-update')
+ $('.page-with-sidebar').addClass('with-overlay')
+
+ sidebarDropdownHidden: (e) ->
+ $block = $(@).closest('.block')
+ if $block.hasClass('collapse-after-update')
+ $block.removeClass('collapse-after-update')
+ $('.page-with-sidebar').removeClass('with-overlay')
+ $('aside')
+ .find('.gutter-toggle')
+ .trigger('click') \ No newline at end of file
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index c1801365266..6a7b4ad1db7 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -1,11 +1,296 @@
class @SearchAutocomplete
- constructor: (search_autocomplete_path, project_id, project_ref) ->
- project_id = '' unless project_id
- project_ref = '' unless project_ref
- query = "?project_id=" + project_id + "&project_ref=" + project_ref
-
- $("#search").autocomplete
- source: search_autocomplete_path + query
- minLength: 1
- select: (event, ui) ->
- location.href = ui.item.url
+
+ KEYCODE =
+ ESCAPE: 27
+ BACKSPACE: 8
+ ENTER: 13
+
+ constructor: (opts = {}) ->
+ {
+ @wrap = $('.search')
+
+ @optsEl = @wrap.find('.search-autocomplete-opts')
+ @autocompletePath = @optsEl.data('autocomplete-path')
+ @projectId = @optsEl.data('autocomplete-project-id') || ''
+ @projectRef = @optsEl.data('autocomplete-project-ref') || ''
+
+ } = opts
+
+ # Dropdown Element
+ @dropdown = @wrap.find('.dropdown')
+ @dropdownContent = @dropdown.find('.dropdown-content')
+
+ @locationBadgeEl = @getElement('.search-location-badge')
+ @locationText = @getElement('.location-text')
+ @scopeInputEl = @getElement('#scope')
+ @searchInput = @getElement('.search-input')
+ @projectInputEl = @getElement('#search_project_id')
+ @groupInputEl = @getElement('#group_id')
+ @searchCodeInputEl = @getElement('#search_code')
+ @repositoryInputEl = @getElement('#repository_ref')
+ @clearInput = @getElement('.js-clear-input')
+
+ @saveOriginalState()
+
+ # Only when user is logged in
+ @createAutocomplete() if gon.current_user_id
+
+ @searchInput.addClass('disabled')
+
+ @saveTextLength()
+
+ @bindEvents()
+
+ # Finds an element inside wrapper element
+ getElement: (selector) ->
+ @wrap.find(selector)
+
+ saveOriginalState: ->
+ @originalState = @serializeState()
+
+ saveTextLength: ->
+ @lastTextLength = @searchInput.val().length
+
+ createAutocomplete: ->
+ @searchInput.glDropdown
+ filterInputBlur: false
+ filterable: true
+ filterRemote: true
+ highlight: true
+ enterCallback: false
+ filterInput: 'input#search'
+ search:
+ fields: ['text']
+ data: @getData.bind(@)
+ selectable: true
+ clicked: @onClick.bind(@)
+
+ getData: (term, callback) ->
+ _this = @
+
+ # Do not trigger request if input is empty
+ return if @searchInput.val() is ''
+
+ # Prevent multiple ajax calls
+ return if @loadingSuggestions
+
+ @loadingSuggestions = true
+
+ jqXHR = $.get(@autocompletePath, {
+ project_id: @projectId
+ project_ref: @projectRef
+ term: term
+ }, (response) ->
+ # Hide dropdown menu if no suggestions returns
+ if !response.length
+ _this.disableAutocomplete()
+ return
+
+ data = []
+
+ # List results
+ firstCategory = true
+ for suggestion in response
+
+ # Add group header before list each group
+ if lastCategory isnt suggestion.category
+ data.push 'separator' if !firstCategory
+
+ firstCategory = false if firstCategory
+
+ data.push
+ header: suggestion.category
+
+ lastCategory = suggestion.category
+
+ data.push
+ id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
+ category: suggestion.category
+ text: suggestion.label
+ url: suggestion.url
+
+ # Add option to proceed with the search
+ if data.length
+ data.push('separator')
+ data.push
+ text: "Result name contains \"#{term}\""
+ url: "/search?\
+ search=#{term}\
+ &project_id=#{_this.projectInputEl.val()}\
+ &group_id=#{_this.groupInputEl.val()}"
+
+ callback(data)
+ ).always ->
+ _this.loadingSuggestions = false
+
+ serializeState: ->
+ {
+ # Search Criteria
+ search_project_id: @projectInputEl.val()
+ group_id: @groupInputEl.val()
+ search_code: @searchCodeInputEl.val()
+ repository_ref: @repositoryInputEl.val()
+ scope: @scopeInputEl.val()
+
+ # Location badge
+ _location: @locationText.text()
+ }
+
+ bindEvents: ->
+ $(document).on 'click', @onDocumentClick
+ @searchInput.on 'keydown', @onSearchInputKeyDown
+ @searchInput.on 'keyup', @onSearchInputKeyUp
+ @searchInput.on 'click', @onSearchInputClick
+ @searchInput.on 'focus', @onSearchInputFocus
+ @clearInput.on 'click', @onClearInputClick
+
+ onDocumentClick: (e) =>
+ # If clicking outside the search box
+ # And search input is not focused
+ # And we are not clicking inside a suggestion
+ if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
+ @onSearchInputBlur()
+
+ enableAutocomplete: ->
+ # No need to enable anything if user is not logged in
+ return if !gon.current_user_id
+
+ _this = @
+ @loadingSuggestions = false
+
+ @dropdown.addClass('open')
+ @searchInput.removeClass('disabled')
+
+ onSearchInputKeyDown: =>
+ # Saves last length of the entered text
+ @saveTextLength()
+
+ onSearchInputKeyUp: (e) =>
+ switch e.keyCode
+ when KEYCODE.BACKSPACE
+ # when trying to remove the location badge
+ if @lastTextLength is 0 and @badgePresent()
+ @removeLocationBadge()
+
+ # When removing the last character and no badge is present
+ if @lastTextLength is 1
+ @disableAutocomplete()
+
+ # When removing any character from existin value
+ if @lastTextLength > 1
+ @enableAutocomplete()
+
+ when KEYCODE.ESCAPE
+ @restoreOriginalState()
+
+ else
+ # Handle the case when deleting the input value other than backspace
+ # e.g. Pressing ctrl + backspace or ctrl + x
+ if @searchInput.val() is ''
+ @disableAutocomplete()
+ else
+ # We should display the menu only when input is not empty
+ @enableAutocomplete()
+
+ @wrap.toggleClass 'has-value', !!e.target.value
+
+ # Avoid falsy value to be returned
+ return
+
+ onSearchInputClick: (e) =>
+ # Prevents closing the dropdown menu
+ e.stopImmediatePropagation()
+
+ onSearchInputFocus: =>
+ @isFocused = true
+ @wrap.addClass('search-active')
+
+ onClearInputClick: (e) =>
+ e.preventDefault()
+ @searchInput.val('').focus()
+
+ onSearchInputBlur: (e) =>
+ @isFocused = false
+ @wrap.removeClass('search-active')
+
+ # If input is blank then restore state
+ if @searchInput.val() is ''
+ @restoreOriginalState()
+
+ addLocationBadge: (item) ->
+ category = if item.category? then "#{item.category}: " else ''
+ value = if item.value? then item.value else ''
+
+ html = "<span class='location-badge'>
+ <i class='location-text'>#{category}#{value}</i>
+ </span>"
+ @locationBadgeEl.html(html)
+ @wrap.addClass('has-location-badge')
+
+ restoreOriginalState: ->
+ inputs = Object.keys @originalState
+
+ for input in inputs
+ @getElement("##{input}").val(@originalState[input])
+
+
+ if @originalState._location is ''
+ @locationBadgeEl.empty()
+ else
+ @addLocationBadge(
+ value: @originalState._location
+ )
+
+ @dropdown.removeClass 'open'
+
+ badgePresent: ->
+ @locationBadgeEl.children().length
+
+ resetSearchState: ->
+ inputs = Object.keys @originalState
+
+ for input in inputs
+
+ # _location isnt a input
+ break if input is '_location'
+
+ @getElement("##{input}").val('')
+
+ removeLocationBadge: ->
+ @locationBadgeEl.empty()
+
+ # Reset state
+ @resetSearchState()
+
+ @wrap.removeClass('has-location-badge')
+
+ disableAutocomplete: ->
+ @searchInput.addClass('disabled')
+ @dropdown.removeClass('open')
+ @restoreMenu()
+
+ restoreMenu: ->
+ html = "<ul>
+ <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
+ </ul>"
+ @dropdownContent.html(html)
+
+ onClick: (item, $el, e) ->
+ if location.pathname.indexOf(item.url) isnt -1
+ e.preventDefault()
+ if not @badgePresent
+ if item.category is 'Projects'
+ @projectInputEl.val(item.id)
+ @addLocationBadge(
+ value: 'This project'
+ )
+
+ if item.category is 'Groups'
+ @groupInputEl.val(item.id)
+ @addLocationBadge(
+ value: 'This group'
+ )
+
+ $el.removeClass('is-active')
+ @disableAutocomplete()
+ @searchInput.val('').focus()
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index b6b4bd90e6a..886da72e261 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -6,10 +6,12 @@ class @Todos
clearListeners: ->
$('.done-todo').off('click')
$('.js-todos-mark-all').off('click')
+ $('.todo').off('click')
initBtnListeners: ->
$('.done-todo').on('click', @doneClicked)
$('.js-todos-mark-all').on('click', @allDoneClicked)
+ $('.todo').on('click', @goToTodoUrl)
doneClicked: (e) =>
e.preventDefault()
@@ -54,3 +56,11 @@ class @Todos
updateBadges: (data) ->
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
+
+ goToTodoUrl: (e)->
+ todoLink = $(this).data('url')
+ if e.metaKey
+ e.preventDefault()
+ window.open(todoLink,'_blank')
+ else
+ Turbolinks.visit(todoLink)
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index aec13e54c98..eee9b6e690e 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -19,6 +19,7 @@ class @UsersSelect
$block = $selectbox.closest('.block')
abilityName = $dropdown.data('ability-name')
$value = $block.find('.value')
+ $collapsedSidebar = $block.find('.sidebar-collapsed-user')
$loading = $block.find('.block-loading').fadeOut()
$block.on('click', '.js-assign-yourself', (e) =>
@@ -32,12 +33,14 @@ class @UsersSelect
data[abilityName].assignee_id = selected
$loading
.fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
dataType: 'json'
url: issueURL
data: data
).done (data) ->
+ $dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
@@ -51,11 +54,22 @@ class @UsersSelect
name: 'Unassigned'
username: ''
avatar: ''
+ $value.html(assigneeTemplate(user))
+ $collapsedSidebar.html(collapsedAssigneeTemplate(user))
- $value.html(noAssigneeTemplate(user))
- $value.find('a').attr('href')
- noAssigneeTemplate = _.template(
+ collapsedAssigneeTemplate = _.template(
+ '<% if( avatar ) { %>
+ <a class="author_link" href="/u/<%= username %>">
+ <img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>">
+ <span class="author">Toni Boehm</span>
+ </a>
+ <% } else { %>
+ <i class="fa fa-user"></i>
+ <% } %>'
+ )
+
+ assigneeTemplate = _.template(
'<% if (username) { %>
<a class="author_link " href="/u/<%= username %>">
<% if( avatar ) { %>
@@ -131,7 +145,8 @@ class @UsersSelect
hidden: (e) ->
$selectbox.hide()
- $value.show()
+ # display:block overrides the hide-collapse rule
+ $value.removeAttr('style')
clicked: (user) ->
page = $('body').data 'page'
diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
index e1c5446eaac..99f35ecfb0f 100644
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ b/app/assets/javascripts/zen_mode.js.coffee
@@ -42,7 +42,7 @@ class @ZenMode
$(e.currentTarget).trigger('zen_mode:leave')
$(document).on 'zen_mode:enter', (e) =>
- @enter(e.target.parentNode)
+ @enter($(e.target).closest('.md-area').find('.zen-backdrop'))
$(document).on 'zen_mode:leave', (e) =>
@exit()
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index 469f4f296ae..542a53f0377 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -13,10 +13,10 @@
// Toggle between two states.
.js-toggler-container {
- .turn-on { display: block; }
+ .turn-on { display: block; }
.turn-off { display: none; }
&.on {
- .turn-on { display: none; }
+ .turn-on { display: none; }
.turn-off { display: block; }
}
}
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index e3192823a1a..0b3af592d4a 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -1,3 +1,9 @@
+.calender-block {
+ @media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
+ overflow-x: scroll;
+ }
+}
+
.user-calendar-activities {
.calendar_onclick_hr {
padding: 0;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 9b676d759e0..2ade341c9dd 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -121,17 +121,10 @@ p.time {
text-shadow: none;
}
-.thin_area{
+.thin_area {
height: 150px;
}
-// Fixes alignment on notes.
-.new_note {
- label {
- text-align: left;
- }
-}
-
// Fix issue with notes & lists creating a bunch of bottom borders.
li.note {
img { max-width: 100% }
@@ -148,7 +141,7 @@ li.note {
}
}
-.wiki_content code, .readme code{
+.wiki_content code, .readme code {
background-color: inherit;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 6c870ed927e..82dc1acbd01 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -42,7 +42,7 @@
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
- border-radius: 2px;
+ border-radius: $dropdown-border-radius;
outline: 0;
text-overflow: ellipsis;
white-space: nowrap;
@@ -75,12 +75,12 @@
width: 240px;
margin-top: 2px;
margin-bottom: 0;
- padding: 10px;
- font-size: 14px;
+ font-size: 15px;
font-weight: normal;
+ padding: 10px 0;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
- border-radius: $border-radius-base;
+ border-radius: $dropdown-border-radius;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading {
@@ -101,9 +101,17 @@
li {
text-align: left;
list-style: none;
+ padding: 0 10px;
}
.divider {
+ height: 1px;
+ margin: 8px 10px;
+ padding: 0;
+ background-color: $dropdown-divider-color;
+ }
+
+ .separator {
width: 100%;
height: 1px;
margin-top: 8px;
@@ -141,6 +149,17 @@
line-height: 16px;
}
}
+
+ .dropdown-header {
+ color: $dropdown-header-color;
+ font-size: 13px;
+ line-height: 22px;
+ padding: 0 10px 10px;
+ }
+
+ .separator + .dropdown-header {
+ padding-top: 2px;
+ }
}
.dropdown-menu-paging {
@@ -158,6 +177,10 @@
.dropdown-menu-back {
display: block;
}
+
+ .dropdown-content {
+ padding: 0 10px;
+ }
}
}
@@ -193,7 +216,7 @@
}
.dropdown-select {
- width: 300px;
+ width: $dropdown-width;
}
.dropdown-menu-align-right {
@@ -222,20 +245,11 @@
}
}
-.dropdown-header {
- padding-left: 5px;
- padding-right: 5px;
- color: $dropdown-header-color;
- font-size: 13px;
- line-height: 22px;
-}
.dropdown-title {
position: relative;
- margin-bottom: 10px;
- padding-left: 30px;
- padding-right: 30px;
- padding-bottom: 10px;
+ padding: 0 0 15px;
+ margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
text-align: center;
@@ -261,21 +275,26 @@
}
.dropdown-menu-close {
- right: 0;
+ right: 7px;
+ width: 20px;
+ height: 20px;
+ top: -1px;
}
.dropdown-menu-back {
- left: 0;
+ left: 7px;
+ top: 2px;
}
.dropdown-input {
position: relative;
margin-bottom: 10px;
+ padding: 0 10px;
.fa {
position: absolute;
top: 10px;
- right: 10px;
+ right: 20px;
color: #c7c7c7;
font-size: 12px;
pointer-events: none;
@@ -285,6 +304,9 @@
display: none;
cursor: pointer;
pointer-events: all;
+ right: 22px;
+ top: 9px;
+ font-size: 14px;
}
&.has-value {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index a26ace5cc19..b15f4e7bd5e 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -20,6 +20,7 @@
margin: 0;
text-align: left;
padding: 10px $gl-padding;
+ word-wrap: break-word;
.file-actions {
float: right;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index b05c5df1bd8..9209347f9bc 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -3,7 +3,7 @@
vertical-align: top;
}
-@media (min-width: $screen-sm-min) {
+@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
.dropdown-menu-toggle {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 4cb4129b71b..54cb5461113 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -6,40 +6,6 @@ input {
border-radius: $border-radius-base;
}
-input[type='search'] {
- background-color: white;
- padding-left: 10px;
-}
-
-input[type='search'].search-input {
- background-repeat: no-repeat;
- background-position: 10px;
- background-size: 16px;
- background-position-x: 30%;
- padding-left: 10px;
- background-color: $gray-light;
-
- &.search-input[value=""] {
- background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
- }
-
- &.search-input::-webkit-input-placeholder {
- text-align: center;
- }
-
- &.search-input:-moz-placeholder { /* Firefox 18- */
- text-align: center;
- }
-
- &.search-input::-moz-placeholder { /* Firefox 19+ */
- text-align: center;
- }
-
- &.search-input:-ms-input-placeholder {
- text-align: center;
- }
-}
-
input[type='text'].danger {
background: #f2dede!important;
border-color: #d66;
@@ -125,7 +91,7 @@ label {
}
.form-control::-webkit-input-placeholder {
- color: #7f8fa4;
+ color: $gl-placeholder-color;
}
.input-group {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 6a68bb5c115..b3397d16016 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -36,7 +36,7 @@ header {
padding: 0;
.nav > li > a {
- color: #7f8fa4;
+ color: $gl-icon-color;
font-size: 18px;
padding: 0;
margin: ($header-height - 28) / 2 0;
@@ -62,7 +62,7 @@ header {
background-color: #eee;
}
&.active {
- color: #7f8fa4;
+ color: $gl-icon-color;
}
}
}
@@ -81,14 +81,14 @@ header {
font-size: 19px;
line-height: $header-height;
font-weight: normal;
- color: #4c4e54;
+ color: $gl-text-color;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
a {
- color: #4c4e54;
+ color: $gl-text-color;
&:hover {
text-decoration: underline;
}
@@ -117,26 +117,6 @@ header {
}
}
- .search {
- margin-right: 10px;
- margin-left: 10px;
- margin-top: ($header-height - 36) / 2;
-
- form {
- margin: 0;
- padding: 0;
- }
-
- .search-input {
- width: 220px;
-
- &:focus {
- @include box-shadow(none);
- outline: none;
- }
- }
- }
-
.impersonation i {
color: $red-normal;
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 8328aac4e7a..c8f86d60e3b 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -1,9 +1,7 @@
.div-dropzone-wrapper {
.div-dropzone {
position: relative;
- padding: 0;
- border: 0;
- margin-bottom: 5px;
+ margin-bottom: -5px;
.div-dropzone-focus {
border-color: #66afe9 !important;
@@ -25,12 +23,10 @@
.div-dropzone-spinner {
position: absolute;
- top: 100%;
- left: 100%;
- margin-top: -1.1em;
- margin-left: -1.1em;
+ bottom: 10px;
+ right: 5px;
opacity: 0;
- font-size: 30px;
+ font-size: 20px;
transition: opacity 200ms ease-in-out;
}
@@ -65,17 +61,30 @@
position: relative;
}
+.md-header {
+ .nav-links {
+ .active {
+ a {
+ border-bottom-color: #000;
+ }
+ }
+
+ a {
+ padding-top: 0;
+ line-height: 1;
+ }
+ }
+}
+
.referenced-users {
color: #4c4e54;
padding-top: 10px;
}
.md-preview-holder {
- background: #fff;
- border: 1px solid #ddd;
- min-height: 169px;
- padding: 5px;
- box-shadow: none;
+ min-height: 167px;
+ padding: 10px 0;
+ overflow-x: auto;
}
.markdown-area {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 5ea4f9a49db..66180f38a4f 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -107,7 +107,7 @@
}
.page-title {
- .note_created_ago, .new-issue-link {
+ .note-created-ago, .new-issue-link {
display: none;
}
}
@@ -116,7 +116,7 @@
display: none;
}
- aside:not(.right-sidebar){
+ aside:not(.right-sidebar) {
display: none;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 95bdd6d1ea3..94f5a12ff6a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -56,6 +56,17 @@
}
}
+ .nav-search {
+ display: inline-block;
+ width: 50%;
+ padding: 11px 0;
+
+ /* Small devices (phones, tablets, 768px and lower) */
+ @media (max-width: $screen-sm-min) {
+ width: 100%;
+ }
+ }
+
.nav-links {
display: inline-block;
width: 50%;
@@ -100,6 +111,7 @@
> form {
display: inline-block;
+ margin-top: -1px;
}
.icon-label {
@@ -110,7 +122,7 @@
height: 34px;
display: inline-block;
position: relative;
- top: 1px;
+ top: 2px;
margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index e82d052f45a..b2fab387e17 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -51,7 +51,7 @@
padding: 10px 15px;
}
-.select2-drop{
+.select2-drop {
color: #7f8fa4;
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 9d188317783..18189e985c4 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -288,6 +288,10 @@
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
+
+ .sidebar-collapsed-icon {
+ cursor: pointer;
+ }
}
.right-sidebar-expanded {
@@ -300,4 +304,8 @@
@media (min-width: $screen-md-min) {
padding-right: $gutter_width;
}
+
+ &.with-overlay {
+ padding-right: $sidebar_collapsed_width;
+ }
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index f63ac033234..c72af5dad0a 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -56,8 +56,8 @@ $component-active-bg: $brand-info;
//##
$input-color: $text-color;
-$input-border: #e7e9ed;
-$input-border-focus: #7f8fa4;
+$input-border: $border-color;
+$input-border-focus: $focus-border-color;
$legend-color: $text-color;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index b1886fbe67b..7b2aada5a0d 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -138,6 +138,12 @@
}
}
+ a.no-attachment-icon {
+ &:before {
+ display: none;
+ }
+ }
+
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
@@ -244,7 +250,7 @@ a > code {
* Textareas intended for GFM
*
*/
-textarea.js-gfm-input {
+.js-gfm-input {
font-family: $monospace_font;
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 61e0dd4d672..8d3ad934a50 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -11,6 +11,7 @@ $gutter_inner_width: 258px;
* UI elements
*/
$border-color: #efeff1;
+$focus-border-color: #3aabf0;
$table-border-color: #eef0f2;
$background-color: #faf9f9;
@@ -26,6 +27,7 @@ $gl-text-orange: #d90;
$gl-link-color: #3084bb;
$gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f;
+$gl-icon-color: $gl-placeholder-color;
$gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color;
@@ -66,7 +68,7 @@ $header-height: 58px;
$fixed-layout-width: 1280px;
$gl-avatar-size: 40px;
$error-exclamation-point: #e62958;
-$border-radius-default: 3px;
+$border-radius-default: 2px;
$btn-transparent-color: #8f8f8f;
$ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px;
@@ -102,9 +104,9 @@ $orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #e75e40;
$orange-dark: #ce5237;
-$red-light: #f06559;
-$red-normal: #e52c5a;
-$red-dark: #d22852;
+$red-light: #e52c5a;
+$red-normal: #d22852;
+$red-dark: darken($red-normal, 5%);
$border-white-light: #f1f2f4;
$border-white-normal: #d6dae2;
@@ -126,9 +128,9 @@ $border-orange-light: #fc6d26;
$border-orange-normal: #ce5237;
$border-orange-dark: #c14e35;
-$border-red-light: #f24f41;
-$border-red-normal: #d22852;
-$border-red-dark: #ca264f;
+$border-red-light: #d22852;
+$border-red-normal: #ca264f;
+$border-red-dark: darken($border-red-normal, 5%);
$help-well-bg: #fafafa;
$help-well-border: #e5e5e5;
@@ -166,6 +168,8 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
/*
* Dropdowns
*/
+$dropdown-border-radius: 2px;
+$dropdown-width: 300px;
$dropdown-bg: #fff;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover;
@@ -176,8 +180,8 @@ $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555;
-$dropdown-input-focus-border: rgb(58, 171, 240);
-$dropdown-input-focus-shadow: rgba(#000, .2);
+$dropdown-input-focus-border: $focus-border-color;
+$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6);
$dropdown-toggle-bg: #fff;
@@ -193,3 +197,29 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
$award-emoji-menu-bg: #fff;
$award-emoji-menu-border: #f1f2f4;
$award-emoji-new-btn-icon-color: #dcdcdc;
+
+/*
+ * Search Box
+ */
+$search-input-border-color: rgba(#4688f1, .8);
+$search-input-focus-shadow-color: $dropdown-input-focus-shadow;
+$search-input-width: 244px;
+$location-badge-color: #aaa;
+$location-badge-bg: $gray-normal;
+$location-badge-active-bg: #4f91f8;
+$location-icon-color: #e7e9ed;
+$location-icon-active-color: #807e7e;
+
+/*
+ * Notes
+ */
+$notes-light-color: #8e8e8e;
+$notes-action-color: #c3c3c3;
+$notes-role-color: #8e8e8e;
+$notes-role-border-color: #e4e4e4;
+
+$note-disabled-comment-color: #b2b2b2;
+$note-form-border-color: #e5e5e5;
+$note-toolbar-color: #959494;
+
+$zen-control-hover-color: #111;
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index 02e24ec7c4d..f870ea0d87f 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -1,61 +1,62 @@
-.zennable {
- a.js-zen-enter {
- color: $gl-gray;
- position: absolute;
+.zen-backdrop {
+ &.fullscreen {
+ background-color: white;
+ position: fixed;
top: 0;
- right: 4px;
- line-height: 56px;
- }
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1031;
- a.js-zen-leave {
- display: none;
- color: $gl-text-color;
- position: absolute;
- top: 10px;
- right: 10px;
- padding: 5px;
- font-size: 36px;
+ textarea {
+ border: none;
+ box-shadow: none;
+ border-radius: 0;
+ color: #000;
+ font-size: 20px;
+ line-height: 26px;
+ padding: 30px;
+ display: block;
+ outline: none;
+ resize: none;
+ height: 100vh;
+ max-width: 900px;
+ margin: 0 auto;
+ }
- &:hover {
- color: #111;
+ .zen-control-leave {
+ display: block;
+ position: absolute;
+ top: 0;
}
}
+}
- .zen-backdrop {
- &.fullscreen {
- background-color: white;
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 1031;
+.zen-cotrol {
+ padding: 0;
+ color: #555;
+ background: none;
+ border: 0;
+}
- textarea {
- border: none;
- box-shadow: none;
- border-radius: 0;
- color: #000;
- font-size: 20px;
- line-height: 26px;
- padding: 30px;
- display: block;
- outline: none;
- resize: none;
- height: 100vh;
- max-width: 900px;
- margin: 0 auto;
- }
+.zen-control-full {
+ color: $note-toolbar-color;
- a.js-zen-enter {
- display: none;
- }
+ &:hover {
+ color: $gl-link-color;
+ text-decoration: none;
+ }
+}
- a.js-zen-leave {
- display: block;
- position: absolute;
- top: 0;
- }
- }
+.zen-control-leave {
+ display: none;
+ color: $gl-text-color;
+ position: absolute;
+ right: 10px;
+ padding: 5px;
+ font-size: 36px;
+
+ &:hover {
+ color: $zen-control-hover-color;
}
}
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 28994e60baa..37bf38fa65d 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -37,7 +37,7 @@
height: 300px;
overflow-y: scroll;
- input.emoji-search{
+ input.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index 2a7b5cfc7fd..67a9d7d2cf7 100644
--- a/app/assets/stylesheets/pages/ci_projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -42,7 +42,7 @@
}
}
- .loading{
+ .loading {
font-size: 20px;
}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 971656feb42..358d2f4ab9d 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -1,15 +1,15 @@
-.commit-title{
+.commit-title {
display: block;
}
-.commit-author, .commit-committer{
+.commit-author, .commit-committer {
display: block;
color: #999;
font-weight: normal;
font-style: italic;
}
-.commit-author strong, .commit-committer strong{
+.commit-author strong, .commit-committer strong {
font-weight: bold;
font-style: normal;
}
@@ -20,6 +20,8 @@
margin: 0;
padding: 0;
margin-top: 10px;
+ word-break: normal;
+ white-space: pre-wrap;
}
.commit-info-row {
@@ -74,7 +76,7 @@
color: $gl-text-red;
}
}
- .edit-file{
+ .edit-file {
a {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index b6011fe7679..8272615768d 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -1,4 +1,4 @@
-.commits-compare-switch{
+.commits-compare-switch {
@include btn-default;
@include btn-white;
background: image-url("switch_icon.png") no-repeat center center;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index d3eda1a57e6..5917f089720 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -33,8 +33,12 @@
.description {
margin-top: 6px;
- p:last-child {
- margin-bottom: 0;
+ p {
+ overflow-x: auto;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index f1368d74b3b..97f4485beb8 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -59,9 +59,14 @@
border-collapse: separate;
margin: 0;
padding: 0;
+
.line_holder td {
line-height: $code_line_height;
font-size: $code_font_size;
+
+ span {
+ white-space: pre;
+ }
}
}
@@ -104,6 +109,10 @@
display: table-cell;
}
}
+
+ .text-file.diff-wrap-lines table .line_holder td span {
+ white-space: pre-wrap;
+ }
}
.image {
background: #ddd;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 43be5e38ba8..0f0592a0ab8 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -1,5 +1,5 @@
.file-editor {
- #editor{
+ #editor {
border: none;
@include border-radius(0);
height: 500px;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 84eefd01cfe..c66efe978cd 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -43,10 +43,6 @@
.md {
color: #7f8fa4;
font-size: $gl-font-size;
-
- iframe.twitter-share-button {
- vertical-align: bottom;
- }
}
pre {
diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss
index 6d2bd33b28b..6926448519e 100644
--- a/app/assets/stylesheets/pages/lint.scss
+++ b/app/assets/stylesheets/pages/lint.scss
@@ -1,9 +1,9 @@
.ci-body {
- .incorrect-syntax{
+ .incorrect-syntax {
font-size: 19px;
color: red;
}
- .correct-syntax{
+ .correct-syntax {
font-size: 19px;
color: #47a447;
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 777bcbca5c3..403171d4532 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -36,7 +36,7 @@
}
}
- .login-box{
+ .login-box {
background: #fafafa;
border-radius: 10px;
box-shadow: 0 0 2px #ccc;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 7ff63ca20b6..1c6a4208974 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -195,42 +195,6 @@
line-height: 31px;
}
-.disabled-comment-area {
- padding: 16px 0;
-
- .disabled-profile {
- width: 40px;
- height: 40px;
- background: $border-gray-dark;
- border-radius: 20px;
- display: inline-block;
- margin-right: 10px;
- }
-
- .disabled-comment {
- background: $gray-light;
- display: inline-block;
- vertical-align: top;
- height: 200px;
- border-radius: 4px;
- border: 1px solid $border-gray-normal;
- padding-top: 90px;
- text-align: center;
- right: 20px;
- position: absolute;
- left: 70px;
- margin-bottom: 20px;
-
- span {
- color: #b2b2b2;
-
- a {
- color: $md-link-color;
- }
- }
- }
-}
-
.builds {
.table-holder {
overflow-x: scroll;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 655f88b0c2c..a909776b437 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -1,10 +1,6 @@
/**
* Note Form
*/
-
-.comment-btn {
- @extend .btn-create;
-}
.reply-btn {
@extend .btn-primary;
margin: 10px $gl-padding;
@@ -17,16 +13,17 @@
}
.diff-file,
.discussion {
- .new_note {
+ .new-note {
margin: 0;
border: none;
}
}
-.new_note {
+
+.new-note {
display: none;
}
-.new_note, .note-edit-form {
+.new-note, .note-edit-form {
.note-form-actions {
margin-top: $gl-padding;
}
@@ -40,21 +37,18 @@
img {
max-width: 100%;
}
+}
- .note_text {
- width: 100%;
- }
+.note-textarea {
+ padding: 10px 0;
+ font-family: $regular_font;
+ border: 0;
- .comment-hints {
- margin-top: -12px;
+ &:focus {
+ outline: 0;
}
}
-/* loading indicator */
-.notes-busy {
- margin: 18px;
-}
-
.note-image-attach {
@extend .col-md-4;
margin-left: 45px;
@@ -62,38 +56,29 @@
}
.common-note-form {
- margin: 0;
- background: #fff;
- padding: $gl-padding;
- margin-left: -$gl-padding;
- margin-right: -$gl-padding;
- margin-bottom: -$gl-padding;
-}
-
-.note-form-actions {
- .note-form-option {
- margin-top: 8px;
- margin-left: 30px;
- @extend .pull-left;
- }
-
- .js-notify-commit-author {
- float: left;
- }
-
- .write-preview-btn {
- // makes the "absolute" position for links relative to this
- position: relative;
-
- // preview/edit buttons
- > a {
- position: absolute;
- right: 5px;
- top: 8px;
+ .md-area {
+ padding: $gl-padding-top $gl-padding;
+ border: 1px solid $note-form-border-color;
+ border-radius: $border-radius-base;
+
+ &.is-focused {
+ border-color: $focus-border-color;
+ box-shadow: 0 0 2px rgba(#000, .2),
+ 0 0 4px rgba($focus-border-color, .4);
+
+ .comment-toolbar,
+ .nav-links {
+ border-color: $focus-border-color;
+ }
}
}
}
+.discussion-form {
+ padding: $gl-padding-top $gl-padding;
+ background-color: #fff;
+}
+
.note-edit-form {
display: none;
font-size: 15px;
@@ -152,11 +137,49 @@
}
}
-.comment-hints {
- color: #999;
- background: #fff;
- padding: 7px;
- margin-top: -7px;
- border: 1px solid $border-color;
- font-size: 13px;
+.comment-toolbar {
+ padding-top: $gl-padding-top;
+ color: $note-toolbar-color;
+ border-top: 1px solid $border-color;
+}
+
+.toolbar-button {
+ padding: 0;
+ background: none;
+ border: 0;
+ font-size: 14px;
+ line-height: 16px;
+
+ &:hover,
+ &:focus {
+ color: $gl-link-color;
+ outline: 0;
+ }
+
+ @media (min-width: $screen-md-min) {
+ float: left;
+ margin-right: $gl-padding;
+
+ &:last-child {
+ float: right;
+ margin-right: 0;
+ }
+ }
+}
+
+.toolbar-button-icon {
+ position: relative;
+ top: 1px;
+ margin-right: 3px;
+ color: inherit;
+ font-size: 16px;
+}
+
+.toolbar-text {
+ font-size: 14px;
+ line-height: 16px;
+
+ @media (min-width: $screen-md-min) {
+ float: left;
+ }
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4bd2016bdcf..aca86457c70 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -20,9 +20,15 @@ ul.notes {
.timeline-content {
margin-left: 55px;
+
+ &.timeline-content-form {
+ @media (max-width: $screen-sm-max) {
+ margin-left: 0;
+ }
+ }
}
- .note_created_ago, .note-updated-at {
+ .note-created-ago, .note-updated-at {
white-space: nowrap;
}
@@ -39,53 +45,6 @@ ul.notes {
}
}
- .discussion-header,
- .note-header {
- @extend .cgray;
-
- a:hover {
- text-decoration: none;
- }
-
- .avatar {
- float: left;
- margin-right: 10px;
- }
-
- .discussion-last-update,
- .note-last-update {
- &:before {
- content: "\00b7";
- }
-
- a {
- color: $gl-gray;
-
- &:hover {
- text-decoration: underline;
- }
- }
- }
- .author {
- color: #4c4e54;
- margin-right: 3px;
-
- &:hover {
- color: $gl-link-color;
- }
- }
- .author-username {
- }
-
- .note-role {
- float: right;
- margin-top: 1px;
- border: 1px solid #bbb;
- background-color: transparent;
- color: $gl-gray;
- }
- }
-
.discussion-body {
padding-top: 15px;
}
@@ -123,7 +82,7 @@ ul.notes {
// On diffs code should wrap nicely and not overflow
pre {
code {
- white-space: pre-wrap;
+ white-space: pre;
}
}
@@ -196,42 +155,90 @@ ul.notes {
&.notes_content {
background-color: #fff;
border-width: 1px 0;
- padding-top: 0;
+ padding: 0;
vertical-align: top;
- &.parallel{
+ &.parallel {
border-width: 1px;
}
}
}
}
+.discussion-header,
+.note-header {
+ a {
+ color: inherit;
+
+ &:hover {
+ color: $gl-link-color;
+ text-decoration: none;
+ }
+ }
+
+ .author_link {
+ font-weight: 600;
+ }
+}
+
+.note-headline-light,
+.discussion-headline-light {
+ color: $notes-light-color;
+}
+
/**
* Actions for Discussions/Notes
*/
-.discussion,
-.note {
- .discussion-actions,
- .note-actions {
- float: right;
- margin-left: 10px;
+.discussion-actions,
+.note-actions {
+ float: right;
+ margin-left: 10px;
+ color: $notes-action-color;
+}
- a {
- margin-left: 5px;
- color: $gl-gray;
+.note-action-button,
+.discussion-action-button {
+ display: inline-block;
+ margin-left: 10px;
+ line-height: 24px;
- i.fa {
- font-size: 16px;
- line-height: 16px;
- }
+ .fa {
+ position: relative;
+ top: 1px;
+ font-size: 17px;
+ }
- &:hover {
- @extend .cgray;
- &.danger { @extend .cred; }
- }
- }
+ .fa-trash-o {
+ top: 0;
+ font-size: 16px;
}
}
+
+.discussion-toggle-button {
+ line-height: 20px;
+ font-size: 13px;
+
+ .fa {
+ margin-right: 3px;
+ font-size: 10px;
+ line-height: 18px;
+ vertical-align: top;
+ }
+}
+
+.note-role {
+ position: relative;
+ top: -2px;
+ display: inline-block;
+ padding-left: 4px;
+ padding-right: 4px;
+ color: $notes-role-color;
+ font-size: 12px;
+ line-height: 20px;
+ border: 1px solid $notes-role-border-color;
+ border-radius: $border-radius-base;
+}
+
.diff-file .note .note-actions {
right: 0;
top: 0;
@@ -280,3 +287,21 @@ ul.notes {
}
}
}
+
+.disabled-comment {
+ margin-left: -$gl-padding-top;
+ margin-right: -$gl-padding-top;
+ background-color: $gray-light;
+ border-radius: $border-radius-base;
+ border: 1px solid $border-gray-normal;
+ color: $note-disabled-comment-color;
+ line-height: 200px;
+
+ .disabled-comment-text {
+ line-height: normal;
+ }
+
+ a {
+ color: $gl-link-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 4e6aa8cd1a6..fcca9d4faf5 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -315,7 +315,7 @@ pre.light-well {
}
.git-empty {
- margin: 0 7px;
+ margin: 0 7px 7px;
h5 {
color: #5c5d5e;
@@ -401,7 +401,7 @@ pre.light-well {
}
.commit_short_id {
- margin-right: 5px;
+ margin: 0 5px;
color: $gl-link-color;
font-weight: 600;
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index b6e45024644..f0f3744c6fa 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -21,3 +21,145 @@
}
}
+.search {
+ margin-right: 10px;
+ margin-left: 10px;
+ margin-top: ($header-height - 35) / 2;
+
+ form {
+ @extend .form-control;
+ margin: 0;
+ padding: 4px;
+ width: $search-input-width;
+ line-height: 24px;
+ }
+
+ .location-text {
+ font-style: normal;
+ }
+
+ .search-input {
+ border: none;
+ font-size: 14px;
+ outline: none;
+ padding: 0;
+ margin-left: 5px;
+ line-height: 25px;
+ width: 98%;
+ }
+
+ .location-badge {
+ line-height: 25px;
+ padding: 0 5px;
+ border-radius: $border-radius-default;
+ font-size: 14px;
+ font-style: normal;
+ color: $location-badge-color;
+ display: inline-block;
+ background-color: $location-badge-bg;
+ vertical-align: top;
+ }
+
+ .search-input-container {
+ display: -webkit-flex;
+ display: flex;
+ position: relative;
+ }
+
+ .search-location-badge, .search-input-wrap {
+ // Fallback if flexbox is not supported
+ display: inline-block;
+ }
+
+ .search-input-wrap {
+ width: 100%;
+
+ .search-icon, .clear-icon {
+ position: absolute;
+ right: 5px;
+ top: 0;
+ color: $location-icon-color;
+
+ &:before {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ }
+ }
+
+ .search-icon {
+ @extend .fa-search;
+ @include transition(color .15s);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ }
+
+ .clear-icon {
+ @extend .fa-times;
+ display: none;
+ }
+
+ // Rewrite position. Dropdown menu should be relative to .search-input-container
+ .dropdown {
+ position: static;
+ }
+
+ .dropdown-header {
+ text-transform: uppercase;
+ font-size: 11px;
+ }
+
+ // Custom dropdown positioning
+ .dropdown-menu {
+ top: 30px;
+ left: -5px;
+ padding: 0;
+
+ ul {
+ padding: 10px 0;
+ }
+ }
+
+ .dropdown-content {
+ max-height: 350px;
+ }
+ }
+
+ &.search-active {
+ form {
+ @extend .form-control:focus;
+ border-color: $dropdown-input-focus-border;
+ box-shadow: 0 0 4px $search-input-focus-shadow-color;
+ }
+
+ .location-badge {
+ @include transition(all .15s);
+ background-color: $location-badge-active-bg;
+ color: $white-light;
+ }
+
+ .search-input-wrap {
+ i {
+ color: $location-icon-active-color;
+ }
+ }
+ }
+
+ &.has-value {
+ .search-icon {
+ display: none;
+ }
+
+ .clear-icon {
+ cursor: pointer;
+ display: block;
+ }
+ }
+
+ &.has-location-badge {
+ .search-input-wrap {
+ width: 78%;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 5e5e38a0ba6..dbb6daf0d70 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -1,4 +1,4 @@
-.container-fluid .content {
+.container-fluid {
.ci-status {
padding: 2px 7px;
margin-right: 5px;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index f983e9829e6..e83fa9e3d52 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,13 +6,19 @@
.navbar-nav {
li {
.badge.todos-pending-count {
- background-color: #7f8fa4;
+ background-color: $gl-icon-color;
margin-top: -5px;
font-weight: normal;
}
}
}
+.todo {
+ &:hover {
+ cursor: pointer;
+ }
+}
+
.todo-item {
.todo-title {
@include str-truncated(calc(100% - 174px));
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index ed9f6031389..f010436bd36 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication,
:two_factor_grace_period,
:gravatar_enabled,
- :twitter_sharing_enabled,
:sign_in_text,
:help_page_text,
:home_page_url,
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 4089091d569..c6b3105544a 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController
def index
@projects = Project.all
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
- @projects = @projects.where("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.abandoned if params[:abandoned].present?
@projects = @projects.non_archived unless params[:with_archived].present?
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index b23c3022fb5..9d5a28e8d4d 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -18,14 +18,14 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def create
- project_ids = params[:milestone][:project_ids]
+ project_ids = params[:milestone][:project_ids].reject(&:blank?)
title = milestone_params[:title]
- @projects.where(id: project_ids).each do |project|
- Milestones::CreateService.new(project, current_user, milestone_params).execute
+ if create_milestones(project_ids)
+ redirect_to milestone_path(title)
+ else
+ render_new_with_error(project_ids.empty?)
end
-
- redirect_to milestone_path(title)
end
def show
@@ -41,6 +41,27 @@ class Groups::MilestonesController < Groups::ApplicationController
private
+ def create_milestones(project_ids)
+ return false unless project_ids.present?
+
+ ActiveRecord::Base.transaction do
+ @projects.where(id: project_ids).each do |project|
+ Milestones::CreateService.new(project, current_user, milestone_params).execute
+ end
+ end
+
+ true
+ rescue ActiveRecord::ActiveRecordError => e
+ flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}"
+ false
+ end
+
+ def render_new_with_error(empty_project_ids)
+ @milestone = Milestone.new(milestone_params)
+ @milestone.errors.add(:project_id, "Please select at least one project.") if empty_project_ids
+ render :new
+ end
+
def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group)
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 21135f7d607..d28e96c3f18 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -55,7 +55,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
else
saml_user = Gitlab::Saml::User.new(oauth)
- saml_user.save
+ saml_user.save if saml_user.changed?
@user = saml_user.gl_user
continue_login_process
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 657ee94cfd7..74150ad606b 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -68,7 +68,9 @@ class Projects::ApplicationController < ApplicationController
end
def require_non_empty_project
- redirect_to namespace_project_path(@project.namespace, @project) if @project.empty_repo?
+ # Be sure to return status code 303 to avoid a double DELETE:
+ # http://api.rubyonrails.org/classes/ActionController/Redirecting.html
+ redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo?
end
def require_branch_head
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 6ff47c4033a..824aa41db51 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -1,12 +1,20 @@
class Projects::BadgesController < Projects::ApplicationController
- before_action :no_cache_headers
+ layout 'project_settings'
+ before_action :authorize_admin_project!, only: [:index]
+ before_action :no_cache_headers, except: [:index]
+
+ def index
+ @ref = params[:ref] || @project.default_branch || 'master'
+ @build_badge = Gitlab::Badge::Build.new(@project, @ref)
+ end
def build
+ badge = Gitlab::Badge::Build.new(project, params[:ref])
+
respond_to do |format|
format.html { render_404 }
format.svg do
- image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
- send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
+ send_data(badge.data, type: badge.type, disposition: 'inline')
end
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index c0a53734921..d09e7375b67 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -48,7 +48,7 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to namespace_project_branches_path(@project.namespace,
- @project)
+ @project), status: 303
end
format.js { render status: status[:return_code] }
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 6189de09f27..49064f5d505 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -57,8 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json { render json: @merge_request }
- format.diff { render text: @merge_request.to_diff(current_user) }
- format.patch { render text: @merge_request.to_patch(current_user) }
+ format.diff { render text: @merge_request.to_diff }
+ format.patch { render text: @merge_request.to_patch }
end
end
@@ -154,7 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.target_project, @merge_request])
end
format.json do
- render json: @merge_request.to_json(include: [:milestone, :labels, :assignee])
+ render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
end
end
else
@@ -224,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
- ci_service = @merge_request.source_project.ci_service
- status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch)
+ ci_commit = @merge_request.ci_commit
+ if ci_commit
+ status = ci_commit.status
+ coverage = ci_commit.try(:coverage)
+ else
+ ci_service = @merge_request.source_project.ci_service
+ status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
- if ci_service.respond_to?(:commit_coverage)
- coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
+ if ci_service.respond_to?(:commit_coverage)
+ coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
+ end
end
response = {
+ title: merge_request.title,
+ sha: merge_request.last_commit_short_sha,
status: status,
coverage: coverage
}
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index e7bddc4a6f1..e457db2f0b7 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -94,9 +94,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def apply_import
- giver = Project.find(params[:source_project_id])
- status = @project.team.import(giver, current_user)
- notice = status ? "Successfully imported" : "Import failed"
+ source_project = Project.find(params[:source_project_id])
+
+ if can?(current_user, :read_project_member, source_project)
+ status = @project.team.import(source_project, current_user)
+ notice = status ? "Successfully imported" : "Import failed"
+ else
+ return render_404
+ end
redirect_to(namespace_project_project_members_path(project.namespace, project),
notice: notice)
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 00df1c9c965..d79f16e6a5a 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -24,6 +24,8 @@ class Projects::RefsController < Projects::ApplicationController
namespace_project_find_file_path(@project.namespace, @project, @id)
when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id)
+ when "badges"
+ namespace_project_badges_path(@project.namespace, @project, ref: @id)
else
namespace_project_commits_path(@project.namespace, @project, @id)
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 02ceb8f4334..9f3a4a69721 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -88,6 +88,20 @@ class Projects::WikisController < Projects::ApplicationController
)
end
+ def markdown_preview
+ text = params[:text]
+
+ ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
+ ext.analyze(text)
+
+ render json: {
+ body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki),
+ references: {
+ users: ext.users.map(&:username)
+ }
+ }
+ end
+
def git_access
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ec5318f2d2c..41a4c41cf80 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -40,6 +40,9 @@ class ProjectsController < Projects::ApplicationController
def update
status = ::Projects::UpdateService.new(@project, current_user, project_params).execute
+ # Refresh the repo in case anything changed
+ @repository = project.repository
+
respond_to do |format|
if status
flash[:notice] = "Project '#{@project.name}' was successfully updated."
@@ -71,7 +74,7 @@ class ProjectsController < Projects::ApplicationController
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
- if @project.unlink_fork
+ if ::Projects::UnlinkForkService.new(@project, current_user).execute
flash[:notice] = 'The fork relationship has been removed.'
end
end
@@ -143,7 +146,7 @@ class ProjectsController < Projects::ApplicationController
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
- emojis: autocomplete_emojis,
+ emojis: AwardEmoji.urls,
issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests,
members: participants
@@ -240,17 +243,6 @@ class ProjectsController < Projects::ApplicationController
)
end
- def autocomplete_emojis
- Rails.cache.fetch("autocomplete-emoji-#{Gemojione::VERSION}") do
- Emoji.emojis.map do |name, emoji|
- {
- name: name,
- path: view_context.image_url("#{emoji["unicode"]}.png")
- }
- end
- end
- end
-
def repo_exists?
project.repository_exists? && !project.empty_repo?
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 65677a3dd3c..c29f4609e93 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -5,7 +5,8 @@ class SessionsController < Devise::SessionsController
skip_before_action :check_2fa_requirement, only: [:destroy]
prepend_before_action :check_initial_setup, only: [:new]
- prepend_before_action :authenticate_with_two_factor, only: [:create]
+ prepend_before_action :authenticate_with_two_factor,
+ if: :two_factor_enabled?, only: [:create]
prepend_before_action :store_redirect_path, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new]
@@ -56,10 +57,10 @@ class SessionsController < Devise::SessionsController
end
def find_user
- if user_params[:login]
- User.by_login(user_params[:login])
- elsif user_params[:otp_attempt] && session[:otp_user_id]
+ if session[:otp_user_id]
User.find(session[:otp_user_id])
+ elsif user_params[:login]
+ User.by_login(user_params[:login])
end
end
@@ -83,11 +84,13 @@ class SessionsController < Devise::SessionsController
end
end
+ def two_factor_enabled?
+ find_user.try(:two_factor_enabled?)
+ end
+
def authenticate_with_two_factor
user = self.resource = find_user
- return unless user && user.two_factor_enabled?
-
if user_params[:otp_attempt].present? && session[:otp_user_id]
if valid_otp_attempt?(user)
# Remove any lingering user data from login
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 23693629a4c..60a0ff32c9c 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -3,10 +3,6 @@ module ApplicationSettingsHelper
current_application_settings.gravatar_enabled?
end
- def twitter_sharing_enabled?
- current_application_settings.twitter_sharing_enabled?
- end
-
def signup_enabled?
current_application_settings.signup_enabled?
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index d3e5e3aa8b9..592bad8ba24 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -214,4 +214,12 @@ module EventsHelper
end
end
end
+
+ def event_row_class(event)
+ if event.body?
+ "event-block"
+ else
+ "event-inline"
+ end
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 494dad0b41e..8a97a74ad73 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -1,4 +1,5 @@
module SearchHelper
+
def search_autocomplete_opts(term)
return unless current_user
@@ -23,45 +24,44 @@ module SearchHelper
# Autocomplete results for various settings pages
def default_autocomplete
[
- { label: "Profile settings", url: profile_path },
- { label: "SSH Keys", url: profile_keys_path },
- { label: "Dashboard", url: root_path },
- { label: "Admin Section", url: admin_root_path },
+ { category: "Settings", label: "Profile settings", url: profile_path },
+ { category: "Settings", label: "SSH Keys", url: profile_keys_path },
+ { category: "Settings", label: "Dashboard", url: root_path },
+ { category: "Settings", label: "Admin Section", url: admin_root_path },
]
end
# Autocomplete results for internal help pages
def help_autocomplete
[
- { label: "help: API Help", url: help_page_path("api", "README") },
- { label: "help: Markdown Help", url: help_page_path("markdown", "markdown") },
- { label: "help: Permissions Help", url: help_page_path("permissions", "permissions") },
- { label: "help: Public Access Help", url: help_page_path("public_access", "public_access") },
- { label: "help: Rake Tasks Help", url: help_page_path("raketasks", "README") },
- { label: "help: SSH Keys Help", url: help_page_path("ssh", "README") },
- { label: "help: System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
- { label: "help: Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
- { label: "help: Workflow Help", url: help_page_path("workflow", "README") },
+ { category: "Help", label: "API Help", url: help_page_path("api", "README") },
+ { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") },
+ { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") },
+ { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") },
+ { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") },
+ { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") },
+ { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
+ { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
+ { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") },
]
end
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
- prefix = search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref
[
- { label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) },
- { label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
- { label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
- { label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
- { label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) },
- { label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
+ { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
]
else
[]
@@ -72,7 +72,9 @@ module SearchHelper
def groups_autocomplete(term, limit = 5)
current_user.authorized_groups.search(term).limit(limit).map do |group|
{
- label: "group: #{search_result_sanitize(group.name)}",
+ category: "Groups",
+ id: group.id,
+ label: "#{search_result_sanitize(group.name)}",
url: group_path(group)
}
end
@@ -83,7 +85,10 @@ module SearchHelper
current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
- label: "project: #{search_result_sanitize(p.name_with_namespace)}",
+ category: "Projects",
+ id: p.id,
+ value: "#{search_result_sanitize(p.name)}",
+ label: "#{search_result_sanitize(p.name_with_namespace)}",
url: namespace_project_path(p.namespace, p)
}
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 8cbc9eefc7b..826e5f96fa1 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -110,6 +110,10 @@ class Notify < BaseMailer
headers['Reply-To'] = address
+ fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>".freeze
+ headers['References'] ||= ''
+ headers['References'] << ' ' << fallback_reply_message_id
+
@reply_by_email = true
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c4879598c4e..052cd874733 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -12,7 +12,6 @@
# updated_at :datetime
# home_page_url :string(255)
# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
@@ -140,7 +139,6 @@ class ApplicationSetting < ActiveRecord::Base
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
- twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d0dbe009d0d..d09876a07d9 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -74,14 +74,14 @@ class Commit
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit>\h{7,40})
}x
end
def self.link_reference_pattern
- super("commit", /(?<commit>\h{7,40})/)
+ @link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
end
def to_reference(from_project = nil)
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 289dbc57287..51673897d98 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -43,14 +43,14 @@ class CommitRange
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{STRICT_PATTERN})
}x
end
def self.link_reference_pattern
- super("compare", /(?<commit_range>#{PATTERN})/)
+ @link_reference_pattern ||= super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index cf5b2c71675..afa2ca039ae 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -19,6 +19,7 @@ module Issuable
has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
+ has_many :todos, as: :target, dependent: :destroy
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
@@ -41,7 +42,7 @@ module Issuable
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
- scope :non_archived, -> { join_project.merge(Project.non_archived.only(:where)) }
+ scope :non_archived, -> { join_project.where(projects: { archived: false }) }
delegate :name,
:email,
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 2ca79df0a29..b8585d4e577 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -31,7 +31,7 @@ class ExternalIssue
# Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern
- %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
+ @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
def to_reference(_from_project = nil)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index ed960cb39f4..e064b0f8b95 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -73,14 +73,14 @@ class Issue < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<issue>\d+)
}x
end
def self.link_reference_pattern
- super("issues", /(?<issue>\d+)/)
+ @link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil)
diff --git a/app/models/label.rb b/app/models/label.rb
index 500d5a35521..55c01cae762 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -56,7 +56,7 @@ class Label < ActiveRecord::Base
# This pattern supports cross-project references.
#
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?:
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7c61a7ae18c..bf185cb5dd8 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -135,6 +135,7 @@ class MergeRequest < ActiveRecord::Base
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
+ scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
@@ -149,14 +150,14 @@ class MergeRequest < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
}x
end
def self.link_reference_pattern
- super("merge_requests", /(?<merge_request>\d+)/)
+ @link_reference_pattern ||= super("merge_requests", /(?<merge_request>\d+)/)
end
# Returns all the merge requests from an ActiveRecord:Relation.
@@ -331,15 +332,15 @@ class MergeRequest < ActiveRecord::Base
# Returns the raw diff for this merge request
#
# see "git diff"
- def to_diff(current_user)
- target_project.repository.diff_text(target_branch, source_sha)
+ def to_diff
+ target_project.repository.diff_text(diff_base_commit.sha, source_sha)
end
# Returns the commit as a series of email patches.
#
# see "git format-patch"
- def to_patch(current_user)
- target_project.repository.format_patch(target_branch, source_sha)
+ def to_patch
+ target_project.repository.format_patch(diff_base_commit.sha, source_sha)
end
def hook_attrs
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index bbd59eab9ae..986184dd301 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -79,7 +79,7 @@ class Milestone < ActiveRecord::Base
end
def self.link_reference_pattern
- super("milestones", /(?<milestone>\d+)/)
+ @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end
def self.upcoming
@@ -89,7 +89,7 @@ class Milestone < ActiveRecord::Base
def to_reference(from_project = nil)
escaped_title = self.title.gsub("]", "\\]")
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
"[#{escaped_title}](#{url})"
diff --git a/app/models/note.rb b/app/models/note.rb
index b0c33f2eec5..87ced65c650 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -311,7 +311,7 @@ class Note < ActiveRecord::Base
for_merge_request? && for_diff_line?
end
- def for_project_snippet?
+ def for_snippet?
noteable_type == "Snippet"
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2f9621809b6..c3d42705902 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -207,6 +207,8 @@ class Project < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
# Scopes
+ default_scope { where(pending_delete: false) }
+
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
@@ -470,7 +472,7 @@ class Project < ActiveRecord::Base
end
def web_url
- Gitlab::Application.routes.url_helpers.namespace_project_url(self.namespace, self)
+ Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
end
def web_url_without_protocol
@@ -591,7 +593,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
- Gitlab::Application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
+ Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
@@ -930,16 +932,6 @@ class Project < ActiveRecord::Base
self.builds_enabled = true
end
- def unlink_fork
- if forked?
- forked_from_project.lfs_objects.find_each do |lfs_object|
- lfs_object.projects << self
- end
-
- forked_project_link.destroy
- end
- end
-
def any_runners?(&block)
if runners.active.any?(&block)
return true
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index f6313255cbb..f9f04838766 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -50,12 +50,15 @@ class BuildsEmailService < Service
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
+ return unless should_build_be_notified?(push_data)
- if should_build_be_notified?(push_data)
+ recipients = all_recipients(push_data)
+
+ if recipients.any?
BuildEmailWorker.perform_async(
push_data[:build_id],
- all_recipients(push_data),
- push_data,
+ recipients,
+ push_data
)
end
end
@@ -84,7 +87,7 @@ class BuildsEmailService < Service
end
def all_recipients(data)
- all_recipients = recipients.split(',')
+ all_recipients = recipients.split(',').compact.reject(&:blank?)
if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}"
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 05436cd0f79..eaa5654b9c6 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -20,7 +20,7 @@
#
class GitlabIssueTrackerService < IssueTrackerService
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index aba37921c09..1ed42c4f3e7 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -21,7 +21,7 @@
class JiraService < IssueTrackerService
include HTTParty
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing.url_helpers
DEFAULT_API_VERSION = 2
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c07e8072043..8dead3a5884 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -72,7 +72,7 @@ class Repository
return @has_visible_content unless @has_visible_content.nil?
@has_visible_content = cache.fetch(:has_visible_content?) do
- raw_repository.branch_count > 0
+ branch_count > 0
end
end
@@ -173,7 +173,7 @@ class Repository
end
def branch_names
- cache.fetch(:branch_names) { raw_repository.branch_names }
+ cache.fetch(:branch_names) { branches.map(&:name) }
end
def tag_names
@@ -191,7 +191,7 @@ class Repository
end
def branch_count
- @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
+ @branch_count ||= cache.fetch(:branch_count) { branches.size }
end
def tag_count
@@ -239,7 +239,7 @@ class Repository
def expire_branches_cache
cache.expire(:branch_names)
- @branches = nil
+ @local_branches = nil
end
def expire_cache(branch_name = nil, revision = nil)
@@ -331,10 +331,14 @@ class Repository
# Runs code after a repository has been created.
def after_create
expire_exists_cache
+ expire_root_ref_cache
+ expire_emptiness_caches
end
# Runs code just before a repository is deleted.
def before_delete
+ expire_exists_cache
+
expire_cache if exists?
expire_root_ref_cache
@@ -362,6 +366,11 @@ class Repository
expire_tag_count_cache
end
+ def before_import
+ expire_emptiness_caches
+ expire_exists_cache
+ end
+
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
@@ -612,10 +621,14 @@ class Repository
refs_contains_sha('tag', sha)
end
- def branches
- @branches ||= raw_repository.branches
+ def local_branches
+ @local_branches ||= rugged.branches.each(:local).map do |branch|
+ Gitlab::Git::Branch.new(branch.name, branch.target)
+ end
end
+ alias_method :branches, :local_branches
+
def tags
@tags ||= raw_repository.tags
end
@@ -818,7 +831,7 @@ class Repository
end
def fetch_ref(source_path, source_ref, target_ref)
- args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
+ args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b9e835a4486..b96e3937281 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -56,14 +56,14 @@ class Snippet < ActiveRecord::Base
#
# This pattern supports cross-project references.
def self.reference_pattern
- %r{
+ @reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<snippet>\d+)
}x
end
def self.link_reference_pattern
- super("snippets", /(?<snippet>\d+)/)
+ @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil)
diff --git a/app/models/user.rb b/app/models/user.rb
index af6b86bfa70..f68379fd050 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -412,6 +412,8 @@ class User < ActiveRecord::Base
end
def owns_notification_email
+ return if self.temp_oauth_email?
+
self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index c007d648dd6..dc74c02760b 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -43,23 +43,27 @@ class GitPushService < BaseService
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages
end
- # Checks if the main language has changed in the project and if so
- # it updates it accordingly
- update_main_language
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
update_merge_requests
+ # Checks if the main language has changed in the project and if so
+ # it updates it accordingly
+ update_main_language
+
perform_housekeeping
end
def update_main_language
- current_language = @project.repository.main_language
+ # Performance can be bad so for now only check main_language once
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937
+ return if @project.main_language.present?
- unless current_language == @project.main_language
- return @project.update_attributes(main_language: current_language)
- end
+ return unless is_default_branch?
+ return unless push_to_new_branch? || push_to_existing_branch?
+ current_language = @project.repository.main_language
+ @project.update_attributes(main_language: current_language)
true
end
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index a5efb21fab6..82e7090f1ea 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -43,7 +43,7 @@ module Issues
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
project: @new_project, author: @old_issue.author,
- description: unfold_references(@old_issue.description) }
+ description: rewrite_content(@old_issue.description) }
new_params = @old_issue.serializable_hash.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
@@ -53,7 +53,7 @@ module Issues
@old_issue.notes.find_each do |note|
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
- note: unfold_references(new_note.note),
+ note: rewrite_content(new_note.note),
created_at: note.created_at,
updated_at: note.updated_at }
@@ -61,6 +61,18 @@ module Issues
end
end
+ def rewrite_content(content)
+ return unless content
+
+ rewriters = [Gitlab::Gfm::ReferenceRewriter,
+ Gitlab::Gfm::UploadsRewriter]
+
+ rewriters.inject(content) do |text, klass|
+ rewriter = klass.new(text, @old_project, @current_user)
+ rewriter.rewrite(@new_project)
+ end
+ end
+
def close_issue
close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false)
@@ -78,20 +90,12 @@ module Issues
direction: :to)
end
- def unfold_references(content)
- return unless content
-
- rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
- @current_user)
- rewriter.rewrite(@new_project)
+ def mark_as_moved
+ @old_issue.update(moved_to: @new_issue)
end
def notify_participants
notification_service.issue_moved(@old_issue, @new_issue, @current_user)
end
-
- def mark_as_moved
- @old_issue.update(moved_to: @new_issue)
- end
end
end
diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb
index b8e08c9f1eb..3b90399af64 100644
--- a/app/services/milestones/create_service.rb
+++ b/app/services/milestones/create_service.rb
@@ -3,7 +3,7 @@ module Milestones
def execute
milestone = project.milestones.new(params)
- if milestone.save
+ if milestone.save!
event_service.open_milestone(milestone, current_user)
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 2015897dd19..ef15ef6a473 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -46,6 +46,8 @@ module Projects
def import_data
return unless has_importer?
+ project.repository.before_import
+
unless importer.execute
raise Error, 'The remote data could not be imported.'
end
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
new file mode 100644
index 00000000000..315c3e16292
--- /dev/null
+++ b/app/services/projects/unlink_fork_service.rb
@@ -0,0 +1,19 @@
+module Projects
+ class UnlinkForkService < BaseService
+ def execute
+ return unless @project.forked?
+
+ @project.forked_from_project.lfs_objects.find_each do |lfs_object|
+ lfs_object.projects << @project
+ end
+
+ merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project)
+
+ merge_requests.each do |mr|
+ MergeRequests::CloseService.new(@project, @current_user).execute(mr)
+ end
+
+ @project.forked_project_link.destroy
+ end
+ end
+end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index ea2b26ccb52..f0615ec7420 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -95,17 +95,19 @@ class SystemHooksService
end
def project_member_data(model)
+ project = model.project || Project.unscoped.find(model.source_id)
+
{
- project_name: model.project.name,
- project_path: model.project.path,
- project_path_with_namespace: model.project.path_with_namespace,
- project_id: model.project.id,
- user_username: model.user.username,
- user_name: model.user.name,
- user_email: model.user.email,
- user_id: model.user.id,
- access_level: model.human_access,
- project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
+ project_name: project.name,
+ project_path: project.path,
+ project_path_with_namespace: project.path_with_namespace,
+ project_id: project.id,
+ user_username: model.user.username,
+ user_name: model.user.name,
+ user_email: model.user.email,
+ user_id: model.user.id,
+ access_level: model.human_access,
+ project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase
}
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index e022a046c48..658b086496f 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -224,7 +224,7 @@ class SystemNoteService
#
# "Started branch `issue-branch-button-201`"
def self.new_issue_branch(issue, project, author, branch)
- h = Gitlab::Application.routes.url_helpers
+ h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "Started branch [`#{branch}`](#{link})"
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index f2662922e90..42c5bca90fd 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -123,7 +123,7 @@ class TodoService
def handle_note(note, author)
# Skip system notes, and notes on project snippet
- return if note.system? || note.for_project_snippet?
+ return if note.system? || note.for_snippet?
project = note.project
target = note.noteable
@@ -170,14 +170,30 @@ class TodoService
end
def filter_mentioned_users(project, target, author)
- mentioned_users = target.mentioned_users.select do |user|
- user.can?(:read_project, project)
- end
-
+ mentioned_users = target.mentioned_users
+ mentioned_users = reject_users_without_access(mentioned_users, project, target)
mentioned_users.delete(author)
mentioned_users.uniq
end
+ def reject_users_without_access(users, project, target)
+ if target.is_a?(Note) && target.for_issue?
+ target = target.noteable
+ end
+
+ if target.is_a?(Issue)
+ select_users(users, :read_issue, target)
+ else
+ select_users(users, :read_project, project)
+ end
+ end
+
+ def select_users(users, ability, subject)
+ users.select do |user|
+ user.can?(ability.to_sym, subject)
+ end
+ end
+
def pending_todos(user, criteria = {})
valid_keys = [:project_id, :target_id, :target_type, :commit_id]
user.todos.pending.where(criteria.slice(*valid_keys))
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 86d24469e05..1af9e9b0edb 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -1,14 +1,15 @@
# encoding: utf-8
class FileUploader < CarrierWave::Uploader::Base
include UploaderHelper
+ MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
storage :file
attr_accessor :project, :secret
- def initialize(project, secret = self.class.generate_secret)
+ def initialize(project, secret = nil)
@project = project
- @secret = secret
+ @secret = secret || self.class.generate_secret
end
def base_dir
@@ -23,14 +24,14 @@ class FileUploader < CarrierWave::Uploader::Base
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
- def self.generate_secret
- SecureRandom.hex
- end
-
def secure_url
File.join("/uploads", @secret, file.filename)
end
+ def to_markdown
+ to_h[:markdown]
+ end
+
def to_h
filename = image? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
@@ -45,4 +46,8 @@ class FileUploader < CarrierWave::Uploader::Base
markdown: markdown
}
end
+
+ def self.generate_secret
+ SecureRandom.hex
+ end
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 0350995d03d..de86dacbb12 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -77,13 +77,6 @@
= f.check_box :gravatar_enabled
Gravatar enabled
.form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :twitter_sharing_enabled do
- = f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block'
- Twitter enabled
- %span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter
- .form-group
= f.label :default_projects_limit, class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :default_projects_limit, class: 'form-control'
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
index 588ad767426..3571eefd570 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -15,7 +15,7 @@
%td
- if project
- = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace"
+ = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 3274ba5377b..6dd2fef395d 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,4 +1,4 @@
-.admin-dashboard
+.admin-dashboard.prepend-top-default
.row
.col-md-4
%h4 Statistics
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 41c43899978..149593e7f46 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -1,5 +1,5 @@
- page_title "Deploy Keys"
-.panel.panel-default
+.panel.panel-default.prepend-top-default
.panel-heading
Public deploy keys (#{@deploy_keys.count})
.controls
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
new file mode 100644
index 00000000000..9025aaac097
--- /dev/null
+++ b/app/views/admin/groups/_group.html.haml
@@ -0,0 +1,28 @@
+- css_class = '' unless local_assigns[:css_class]
+- css_class += ' no-description' if group.description.blank?
+
+%li.group-row{ class: css_class }
+ .controls.hidden-xs
+ = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm'
+ = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove'
+
+ .stats
+ %span
+ = icon('bookmark')
+ = number_with_delimiter(group.projects.count)
+
+ %span
+ = icon('users')
+ = number_with_delimiter(group.users.count)
+
+ %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
+ = visibility_level_icon(group.visibility_level, fw: false)
+
+ = image_tag group_icon(group), class: 'avatar s40 hidden-xs'
+ .title
+ = link_to [:admin, group], class: 'group-name' do
+ = group.name
+
+ - if group.description.present?
+ .description
+ = markdown(group.description, pipeline: :description)
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 6bdc885a312..775072a7441 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,20 +1,19 @@
- page_title "Groups"
%h3.page-title
Groups (#{number_with_delimiter(@groups.total_count)})
- = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%p.light
Group allows you to keep projects organized.
Use groups for uniting related projects.
-%hr
-= form_tag admin_groups_path, method: :get, class: 'form-inline' do
- = hidden_field_tag :sort, @sort
- .form-group
- = text_field_tag :name, params[:name], class: "form-control"
- = button_tag "Search", class: "btn submit btn-primary"
+.top-area
+ .nav-search
+ = form_tag admin_groups_path, method: :get, class: 'form-inline' do
+ = hidden_field_tag :sort, @sort
+ = text_field_tag :name, params[:name], class: "form-control"
+ = button_tag "Search", class: "btn submit btn-primary"
- .pull-right
+ .nav-controls
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light
@@ -33,34 +32,10 @@
= sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
-%hr
-
-%ul.bordered-list
+%ul.content-list
- @groups.each do |group|
- %li
- .clearfix
- .pull-right.prepend-top-10
- = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-sm"
- = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-sm btn-remove"
-
- %h4
- = link_to [:admin, group] do
- %span{ class: visibility_level_color(group.visibility_level) }
- = visibility_level_icon(group.visibility_level)
-
- %i.fa.fa-folder
- = group.name
-
- &rarr;
- %span.monospace
- %strong #{group.path}/
- .clearfix
- %p
- = truncate group.description, length: 150
- .clearfix
- %p.light
- #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
-
+ = render 'group', group: group
= paginate @groups, theme: "gitlab"
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
index 3c57e3dc174..05d6b9ed238 100644
--- a/app/views/admin/labels/index.html.haml
+++ b/app/views/admin/labels/index.html.haml
@@ -1,8 +1,10 @@
- page_title "Labels"
-= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
- New label
-%h3.page-title
- Labels
+
+%div
+ = link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
+ New label
+ %h3.page-title
+ Labels
%hr
.labels
@@ -13,4 +15,4 @@
- else
.light-well
.nothing-here-block There are no labels yet
-
+
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index c407972cd08..2dad64b8d0f 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,4 +1,4 @@
-%p.lead
+%p.lead.prepend-top-default
%span
To register a new runner you should enter the following registration token.
With this token the runner will request a unique runner token and use that for future communication.
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index e3a4d64df01..aa0aff86d4d 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -1,4 +1,4 @@
-%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
+%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} }
.todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
@@ -10,7 +10,10 @@
(removed)
%span.todo-label
= todo_action_name(todo)
- = todo_target_link(todo)
+ - if todo.target
+ = todo_target_link(todo)
+ - else
+ (removed)
&middot; #{time_ago_with_tooltip(todo.created_at)}
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 42c2764e7e2..4d20dd5830e 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,5 +1,5 @@
- if event.visible_to_user?(current_user)
- .event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
+ .event-item{ class: event_row_class(event) }
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index 8cf36c711b4..5a2a469ba62 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -7,21 +7,3 @@
= link_to_project event.project
- else
= event.project_name
-
-- if !event.project.private? && twitter_sharing_enabled?
- .event-body{"data-user-is" => event.author_id}
- .event-note
- .md
- %p
- Congratulations! Why not share your accomplishment with the world?
-
- %a.twitter-share-button{ |
- href: "https://twitter.com/share", |
- "data-url" => event.project.web_url, |
- "data-text" => "I just #{event.action_name} a new project on GitLab! GitLab is version control on your server.", |
- "data-size" => "medium", |
- "data-related" => "gitlab", |
- "data-hashtags" => "gitlab", |
- "data-count" => "none"}
- Tweet
- %script{src: "//platform.twitter.com/widgets.js"}
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index a8e1ed77da9..4290e0bf72e 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -10,6 +10,14 @@
= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f|
.row
+ - if @milestone.errors.any?
+ #error_explanation
+ .alert.alert-danger
+ %ul
+ - @milestone.errors.full_messages.each do |msg|
+ %li
+ = msg
+
.col-md-6
.form-group
= f.label :title, "Title", class: "control-label"
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 54af2c3063c..6b208c3d0bb 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,10 +1,33 @@
-.search
- = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
- = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1"
+- if controller.controller_path =~ /^groups/ && @group.persisted?
+ - label = 'This group'
+- if controller.controller_path =~ /^projects/ && @project.persisted?
+ - label = 'This project'
+
+.search.search-form{class: "#{'has-location-badge' if label.present?}"}
+ = form_tag search_path, method: :get, class: 'navbar-form' do |f|
+ .search-input-container
+ .search-location-badge
+ - if label.present?
+ %span.location-badge
+ %i.location-text
+ = label
+ .search-input-wrap
+ .dropdown{ data: {url: search_autocomplete_path } }
+ = search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
+ .dropdown-menu.dropdown-select
+ = dropdown_content do
+ %ul
+ %li
+ %a.is-focused.dropdown-menu-empty-link
+ Loading...
+ = dropdown_loading
+ %i.search-icon
+ %i.clear-icon.js-clear-input
+
= hidden_field_tag :group_id, @group.try(:id)
- - if @project && @project.persisted?
- = hidden_field_tag :project_id, @project.id
+ = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
+ - if @project && @project.persisted?
- if current_controller?(:issues)
= hidden_field_tag :scope, 'issues'
- elsif current_controller?(:merge_requests)
@@ -21,10 +44,3 @@
= hidden_field_tag :repository_ref, @ref
= button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
-
-:javascript
- $('.search-input').on('keyup', function(e) {
- if (e.keyCode == 27) {
- $('.search-input').blur();
- }
- });
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 4a0069f18f8..5cef652da14 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -9,7 +9,7 @@
= icon('bell fw')
%span
Todos
- %span.count= number_with_delimiter(todos_pending_count)
+ %span.count.todos-pending-count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
= icon('dashboard fw')
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index dc3050f02e5..d429a928464 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -51,8 +51,13 @@
= icon('code fw')
%span
Variables
- = nav_link path: 'triggers#index' do
+ = nav_link(controller: :triggers) do
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw')
%span
Triggers
+ = nav_link(controller: :badges) do
+ = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
+ = icon('star-half-empty fw')
+ %span
+ Badges
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index ab527e8e438..a7ef31acd3d 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -5,10 +5,14 @@
- content_for :scripts_body_top do
- project = @target_project || @project
+ - if @project_wiki
+ - markdown_preview_path = namespace_project_wikis_markdown_preview_path(project.namespace, project)
+ - else
+ - markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
- if current_user
:javascript
window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
- window.markdown_preview_path = "#{markdown_preview_namespace_project_path(project.namespace, project)}";
+ window.markdown_preview_path = "#{markdown_preview_path}";
- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 5d342ef58e5..69fc81cb45c 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -8,8 +8,6 @@
Increase your account's security by enabling two-factor authentication (2FA).
.col-lg-9
%p
- Status: #{current_user.two_factor_enabled? ? 'enabled' : 'disabled'}
- %p
Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code.
More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
.row.append-bottom-10
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 1fb37ef6621..4920910fee1 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,18 +1,19 @@
.md-area
- .md-header.clearfix
+ .md-header
%ul.nav-links
%li.active
- %a.js-md-write-button(href="#md-write-holder" tabindex="-1")
+ %a.js-md-write-button{ href: "#md-write-holder" }
Write
%li
- %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
+ %a.js-md-preview-button{ href: "#md-preview-holder" }
Preview
+ %li.pull-right
+ %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button' }
+ Go full screen
- %div
- .md-write-holder
- = yield
- .md.md-preview-holder.hide
- .js-md-preview{class: (preview_class if defined?(preview_class))}
+ .md-write-holder
+ = yield
+ .md.md-preview-holder.js-md-preview.hide{class: (preview_class if defined?(preview_class))}
- if defined?(referenced_users) && referenced_users
%div.referenced-users.hide
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index e701253d7de..bddff5cdcbc 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,12 +1,8 @@
-.zennable
- .zen-backdrop
- - classes << ' js-gfm-input js-autosize markdown-area'
- - if defined?(f) && f
- = f.text_area attr, class: classes
- - else
- = text_area_tag attr, nil, class: classes
- %a.js-zen-enter(tabindex="-1" href="#")
- = icon('expand')
- Edit in fullscreen
- %a.js-zen-leave(tabindex="-1" href="#")
- = icon('compress')
+.zen-backdrop
+ - classes << ' js-gfm-input js-autosize markdown-area'
+ - if defined?(f) && f
+ = f.text_area attr, class: classes, placeholder: "Write a comment or drag your files here..."
+ - else
+ = text_area_tag attr, nil, class: classes, placeholder: "Write a comment or drag your files here..."
+ %a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" }
+ = icon('compress')
diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml
new file mode 100644
index 00000000000..c22384ddf46
--- /dev/null
+++ b/app/views/projects/badges/index.html.haml
@@ -0,0 +1,24 @@
+- page_title 'Badges'
+- badges_path = namespace_project_badges_path(@project.namespace, @project)
+- header_title project_title(@project, 'Badges', badges_path)
+
+.prepend-top-10
+ .panel.panel-default
+ .panel-heading
+ %b Builds badge &middot;
+ = @build_badge.to_html
+ .pull-right
+ = render 'shared/ref_switcher', destination: 'badges'
+ .panel-body
+ .row
+ .col-md-2.text-center
+ Markdown
+ .col-md-10.code.js-syntax-highlight
+ = highlight('.md', @build_badge.to_markdown)
+ .row
+ %hr
+ .row
+ .col-md-2.text-center
+ HTML
+ .col-md-10.code.js-syntax-highlight
+ = highlight('.html', @build_badge.to_html)
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index e7c85edff96..1e4c46fca2f 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -3,25 +3,32 @@
%a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- - if can?(current_user, :create_issue, @project)
+ - can_create_issue = can?(current_user, :create_issue, @project)
+ - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+ - can_create_snippet = can?(current_user, :create_snippet, @project)
+
+ - if can_create_issue
%li
= link_to url_for_new_issue(@project, only_path: true) do
= icon('exclamation-circle fw')
New issue
- - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+
- if merge_project
%li
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do
= icon('tasks fw')
New merge request
- - if can?(current_user, :create_snippet, @project)
+
+ - if can_create_snippet
%li
= link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw')
New snippet
- - if can?(current_user, :push_code, @project)
+ - if can_create_issue || merge_project || can_create_snippet
%li.divider
+
+ - if can?(current_user, :push_code, @project)
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
@@ -35,13 +42,11 @@
= icon('tags fw')
New tag
- elsif current_user && current_user.already_forked?(@project)
- %li.divider
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
New file
- elsif can?(current_user, :fork_project, @project)
- %li.divider
%li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index d22d1da8402..2cf9115e4dd 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -39,7 +39,7 @@
%td
= build.name
- .pull-right
+ .label-container
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 8367112a9cb..2731219ccad 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,7 +1,10 @@
- diff = diff_file.diff
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
-- old_commit_id = diff_refs.first.id
-- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+// diff_refs will be nil for orphaned commits (e.g. first commit in repo)
+- if diff_refs
+ - old_commit_id = diff_refs.first.id
+ - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index b05ab869215..2ec0d20a879 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,15 +1,17 @@
- if @ci_commit
.mr-widget-heading
- .ci_widget{class: "ci-#{@ci_commit.status}"}
- = ci_status_icon(@ci_commit)
- %span
- Build
- = ci_status_label(@ci_commit)
- for
- = succeed "." do
- = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
- %span.ci-coverage
- = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
+ - %w[success skipped canceled failed running pending].each do |status|
+ .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @ci_commit.status == status) }
+ = ci_icon_for_status(status)
+ %span
+ CI build
+ = ci_label_for_status(status)
+ for
+ - commit = @merge_request.last_commit
+ = succeed "." do
+ = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
+ %span.ci-coverage
+ = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
- elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
@@ -43,5 +45,5 @@
:javascript
$(function() {
- merge_request_widget.getCiStatus();
+ merge_request_widget.getCIStatus(false);
});
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index a489d4f9b24..92d95358937 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -9,12 +9,19 @@
:javascript
var merge_request_widget;
-
- merge_request_widget = new MergeRequestWidget({
- url_to_automerge_check: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ var opts = {
+ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
- url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
+ ci_status: "",
+ ci_message: "Build {{status}} for \"{{title}}\"",
ci_enable: #{@project.ci_service ? "true" : "false"},
- current_status: "#{@merge_request.gitlab_merge_status}",
- });
+ builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
+ };
+ if(typeof merge_request_widget === 'undefined') {
+ merge_request_widget = new MergeRequestWidget(opts);
+ } else {
+ merge_request_widget.setOpts(opts);
+ }
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 2999befffc6..23e4f93eab5 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -1,10 +1,11 @@
.note-edit-form
- = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note js-quick-submit' } do |f|
+ = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f|
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field'
= render 'projects/notes/hints'
.note-form-actions.clearfix
= f.submit 'Save Comment', class: 'btn btn-nr btn-save btn-grouped js-comment-button'
- = link_to 'Cancel', '#', class: 'btn btn-nr btn-cancel note-edit-cancel'
+ %button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
+ Cancel
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index f675f092da1..c446ecec2c3 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
= note_target_fields(@note)
@@ -8,7 +8,7 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text'
= render 'projects/notes/hints'
.error-alert
diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml
index 6e7929bdab0..0b002043408 100644
--- a/app/views/projects/notes/_hints.html.haml
+++ b/app/views/projects/notes/_hints.html.haml
@@ -1,9 +1,8 @@
-.comment-hints.clearfix
- .pull-left
+.comment-toolbar.clearfix
+ .toolbar-text
+ Styling with
= link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1
- tip:
- = random_markdown_tip
- .pull-right
- = link_to '#', class: 'markdown-selector', tabindex: -1 do
- = icon('paperclip')
- Attach a file
+ is supported
+ %button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' }
+ = icon('file-image-o', class: 'toolbar-button-icon')
+ Attach a file
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 2cf32e6093d..a681d6dece4 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -5,28 +5,21 @@
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
.timeline-content
.note-header
+ = link_to_member(note.project, note.author, avatar: false)
+ .inline.note-headline-light
+ = "#{note.author.to_reference} commented"
+ %a{ href: "##{dom_id(note)}" }
+ = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- if note_editable?(note)
.note-actions
- = link_to '#', title: 'Edit comment', class: 'js-note-edit' do
+ - access = note.project.team.human_max_access(note.author.id)
+ - if access
+ %span.note-role
+ = access
+ = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil-square-o')
-
- = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do
+ = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do
= icon('trash-o')
-
- - unless note.system
- - access = note.project.team.human_max_access(note.author.id)
- - if access
- %span.note-role.label
- = access
-
- = link_to_member(note.project, note.author, avatar: false)
-
- %span.author-username
- = '@' + note.author.username
-
- %span.note-last-update
- %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
- = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 910eb6cf66e..cc42aab5c52 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -1,20 +1,21 @@
%ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes"
-.js-notes-busy
-
-.js-main-target-form
-- if can? current_user, :create_note, @project
- = render "projects/notes/form", view: diff_view
-- else
- .disabled-comment-area
- .disabled-profile
- .disabled-comment
- %span
- Please
- = link_to "register",new_user_session_path
- or
- = link_to "login",new_user_session_path
- to post a comment
+%ul.notes.timeline
+ %li.timeline-entry
+ - if can? current_user, :create_note, @project
+ .timeline-icon.hidden-xs.hidden-sm
+ %a.author_link{ href: user_path(current_user) }
+ = image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40'
+ .timeline-content.timeline-content-form
+ = render "projects/notes/form", view: diff_view
+ - else
+ .disabled-comment.text-center
+ .disabled-comment-text.inline
+ Please
+ = link_to "register",new_user_session_path
+ or
+ = link_to "login",new_user_session_path
+ to post a comment
:javascript
var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index 4f15a99d061..cd8a5f0bd02 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -1,22 +1,20 @@
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{note.author.to_reference} started a discussion"
+ = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
+ on the diff
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- started a discussion
- = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
- %strong on the diff
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
-
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index f67ec8db942..46f2ba4bbcf 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -3,21 +3,20 @@
- commit_description = commit ? 'commit' : 'a deleted commit'
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{note.author.to_reference} started a discussion on #{commit_description}"
+ - if commit
+ = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- %p started a discussion on #{commit_description}
- - if commit
- = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
- if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index 218b0da3977..f8e000b424f 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -1,19 +1,18 @@
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+ .inline.discussion-headline-light
+ = "#{note.author.to_reference} started a discussion"
+ on the outdated diff
.discussion-actions
- = link_to "#", class: "js-toggle-button" do
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down
Show/hide discussion
- %div
- = link_to_member(@project, note.author, avatar: false)
- started a discussion on the
- %strong outdated diff
- %div
+ .last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
- %span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 5fcba2b7e93..9544e3d3e17 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,24 +1,20 @@
- project = note.project
+- note_url = Gitlab::UrlBuilder.new(:note).build(note.id)
+- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
.search-result-row
%h5.note-search-caption.str-truncated
%i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
commented on
+ = link_to project.name_with_namespace, project
+ &middot;
- if note.for_commit?
- = link_to project do
- = project.name_with_namespace
- &middot;
- = link_to namespace_project_commit_path(project.namespace, project, note.commit_id, anchor: dom_id(note)) do
- Commit #{truncate_sha(note.commit_id)}
+ = link_to "Commit #{truncate_sha(note.commit_id)}", note_url
- else
- = link_to project do
- = project.name_with_namespace
- &middot;
- %span #{note.noteable_type.titleize} ##{note.noteable.iid}
+ %span #{note.noteable_type.titleize} ##{noteable_identifier}
&middot;
- = link_to [project.namespace.becomes(Namespace), project, note.noteable, anchor: dom_id(note)] do
- = note.noteable.title
+ = link_to note.noteable.title, note_url
.note-search-result
.term
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index c99da92be9f..921eaefd79a 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -7,13 +7,13 @@
class: "check_all_issues left"
.issues-other-filters
.filter-item.inline
- - if params[:author_id]
+ - if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- - if params[:assignee_id]
+ - if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id])
= dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 006a34a11e3..fd5e58c1f1f 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,4 +1,4 @@
-- if params[:label_name]
+- if params[:label_name].present?
= hidden_field_tag(:label_name, params[:label_name])
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index e52d2e39e6b..2fcf40ece99 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -1,4 +1,4 @@
-- if params[:milestone_title]
+- if params[:milestone_title].present?
= hidden_field_tag(:milestone_title, params[:milestone_title])
= dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 451c64da2c4..94affa4b59a 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,5 +1,6 @@
%aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar
+ - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
%span.issuable-count.hide-collapsed.pull-left
= issuable.iid
@@ -29,7 +30,7 @@
.title.hide-collapsed
Assignee
= icon('spinner spin', class: 'block-loading')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed
- if issuable.assignee
@@ -41,9 +42,10 @@
= issuable.assignee.to_reference
- else
%span.assign-yourself
- No assignee -
- %a.js-assign-yourself{ href: '#' }
- assign yourself
+ No assignee
+ - if can_edit_issuable
+ %a.js-assign-yourself{ href: '#' }
+ \- assign yourself
.selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
@@ -60,7 +62,7 @@
.title.hide-collapsed
Milestone
= icon('spinner spin', class: 'block-loading')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed
- if issuable.milestone
@@ -82,7 +84,7 @@
.title.hide-collapsed
Labels
= icon('spinner spin', class: 'block-loading')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) }
- if issuable.labels.any?
@@ -150,3 +152,4 @@
new LabelsSelect();
new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
new Subscription('.subscription')
+ new Sidebar(); \ No newline at end of file
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index bca816f22cb..0c4b6a5618b 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -87,7 +87,7 @@
%div{ class: container_class }
.tab-content
#activity.tab-pane
- .gray-content-block.white.second-block
+ .gray-content-block.calender-block.white.second-block.hidden-xs
%div{ class: container_class }
.user-calendar{data: {href: user_calendar_path}}
%h4.center.light
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 02647229776..8ffcdc4a327 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -15,12 +15,14 @@
- if current_user
:javascript
+ var get_emojis_url = "#{emojis_path}";
var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
var noteable_type = "#{votable.class.name.underscore}";
var noteable_id = "#{votable.id}";
var aliases = #{AwardEmoji.aliases.to_json};
window.awards_handler = new AwardsHandler(
+ get_emojis_url,
post_emoji_url,
noteable_type,
noteable_id,
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 55cb6af232e..ccefd0f71a0 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -5,6 +5,9 @@ class ProjectCacheWorker
def perform(project_id)
project = Project.find(project_id)
+
+ return unless project.repository.exists?
+
project.update_repository_size
project.update_commit_count
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
index d06e4480292..b51c6a266c9 100644
--- a/app/workers/project_destroy_worker.rb
+++ b/app/workers/project_destroy_worker.rb
@@ -5,7 +5,7 @@ class ProjectDestroyWorker
def perform(project_id, user_id, params)
begin
- project = Project.find(project_id)
+ project = Project.unscoped.find(project_id)
rescue ActiveRecord::RecordNotFound
return
end