Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/project-import_url
# Conflicts: # db/schema.rb
This commit is contained in:
commit
7f7769172e
258 changed files with 13163 additions and 1656 deletions
|
@ -132,8 +132,9 @@ linters:
|
|||
SpaceAroundOperator:
|
||||
enabled: false
|
||||
|
||||
# Opening braces should be preceded by a single space.
|
||||
SpaceBeforeBrace:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
StringQuotes:
|
||||
enabled: false
|
||||
|
|
50
CHANGELOG
50
CHANGELOG
|
@ -1,16 +1,61 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.7.0 (unreleased)
|
||||
- Improved Markdown rendering performance !3389 (Yorick Peterse)
|
||||
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu)
|
||||
- Preserve time notes/comments have been updated at when moving issue
|
||||
- Make HTTP(s) label consistent on clone bar (Stan Hu)
|
||||
- Expose label description in API (Mariusz Jachimowicz)
|
||||
- Allow back dating on issues when created through the API
|
||||
- Fix avatar stretching by providing a cropping feature
|
||||
- Add endpoints to archive or unarchive a project !3372
|
||||
- Add links to CI setup documentation from project settings and builds pages
|
||||
- Handle nil descriptions in Slack issue messages (Stan Hu)
|
||||
- Add default scope to projects to exclude projects pending deletion
|
||||
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
|
||||
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
|
||||
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
|
||||
- Fix creation of merge requests for orphaned branches (Stan Hu)
|
||||
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
|
||||
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
|
||||
|
||||
v 8.6.2 (unreleased)
|
||||
- Comments on confidential issues don't show up in activity feed to non-members
|
||||
v 8.6.4
|
||||
- Don't attempt to fetch any tags from a forked repo (Stan Hu)
|
||||
|
||||
v 8.6.3
|
||||
- Mentions on confidential issues doesn't create todos for non-members. !3374
|
||||
- Destroy related todos when an Issue/MR is deleted. !3376
|
||||
- Fix error 500 when target is nil on todo list. !3376
|
||||
- Fix copying uploads when moving issue to another project. !3382
|
||||
- Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432
|
||||
- Fix raw/rendered diff producing different results on merge requests. !3450
|
||||
- Fix commit comment alignment (Stan Hu). !3466
|
||||
- Fix Error 500 when searching for a comment in a project snippet. !3468
|
||||
- Allow temporary email as notification email. !3477
|
||||
- Fix issue with dropdowns not selecting values. !3478
|
||||
- Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280
|
||||
|
||||
v 8.6.2
|
||||
- Fix dropdown alignment. !3298
|
||||
- Fix issuable sidebar overlaps on tablet. !3299
|
||||
- Make dropdowns pixel perfect. !3337
|
||||
- Fix order of steps to prevent PostgreSQL errors when running migration. !3355
|
||||
- Fix bold text in issuable sidebar. !3358
|
||||
- Fix error with anonymous token in applications settings. !3362
|
||||
- Fix the milestone 'upcoming' filter. !3364 + !3368
|
||||
- Fix comments on confidential issues showing up in activity feed to non-members. !3375
|
||||
- Fix `NoMethodError` when visiting CI root path at `/ci`. !3377
|
||||
- Add a tooltip to new branch button in issue page. !3380
|
||||
- Fix an issue hiding the password form when signed-in with a linked account. !3381
|
||||
- Add links to CI setup documentation from project settings and builds pages. !3384
|
||||
- Fix an issue with width of project select dropdown. !3386
|
||||
- Remove redundant `require`s from Banzai files. !3391
|
||||
- Fix error 500 with cancel button on issuable edit form. !3392 + !3417
|
||||
- Fix background when editing a highlighted note. !3423
|
||||
- Remove tabstop from the WIP toggle links. !3426
|
||||
- Ensure private project snippets are not viewable by unauthorized people.
|
||||
- Gracefully handle notes on deleted commits in merge requests (Stan Hu). !3402
|
||||
- Fixed issue with notification settings not saving. !3452
|
||||
|
||||
v 8.6.1
|
||||
- Add option to reload the schema before restoring a database backup. !2807
|
||||
|
@ -83,6 +128,7 @@ v 8.6.0
|
|||
- Add main language of a project in the list of projects (Tiago Botelho)
|
||||
- Add #upcoming filter to Milestone filter (Tiago Botelho)
|
||||
- Add ability to show archived projects on dashboard, explore and group pages
|
||||
- Remove fork link closes all merge requests opened on source project (Florent Baldino)
|
||||
- Move group activity to separate page
|
||||
- Create external users which are excluded of internal and private projects unless access was explicitly granted
|
||||
- Continue parameters are checked to ensure redirection goes to the same instance
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.6.11
|
||||
2.7.0
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -214,7 +214,7 @@ gem 'jquery-rails', '~> 4.0.0'
|
|||
gem 'jquery-scrollto-rails', '~> 1.4.3'
|
||||
gem 'jquery-ui-rails', '~> 5.0.0'
|
||||
gem 'raphael-rails', '~> 2.1.2'
|
||||
gem 'request_store', '~> 1.2.0'
|
||||
gem 'request_store', '~> 1.3.0'
|
||||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
|
|
|
@ -126,9 +126,9 @@ GEM
|
|||
coderay (1.1.0)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
coffee-rails (4.1.0)
|
||||
coffee-rails (4.1.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
railties (>= 4.0.0, < 5.1.x)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
|
@ -652,7 +652,7 @@ GEM
|
|||
redis-store (~> 1.1.0)
|
||||
redis-store (1.1.7)
|
||||
redis (>= 2.2)
|
||||
request_store (1.2.1)
|
||||
request_store (1.3.0)
|
||||
rerun (0.11.0)
|
||||
listen (~> 3.0)
|
||||
responders (2.1.1)
|
||||
|
@ -1011,7 +1011,7 @@ DEPENDENCIES
|
|||
redcarpet (~> 3.3.3)
|
||||
redis-namespace
|
||||
redis-rails (~> 4.0.0)
|
||||
request_store (~> 1.2.0)
|
||||
request_store (~> 1.3.0)
|
||||
rerun (~> 0.11.0)
|
||||
responders (~> 2.0)
|
||||
rouge (~> 1.10.1)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,10 @@ class GitLabDropdownFilter
|
|||
HAS_VALUE_CLASS = "has-value"
|
||||
|
||||
constructor: (@input, @options) ->
|
||||
{
|
||||
@filterInputBlur = true
|
||||
} = @options
|
||||
|
||||
$inputContainer = @input.parent()
|
||||
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
|
||||
|
||||
|
@ -33,7 +37,7 @@ class GitLabDropdownFilter
|
|||
blur_field = @shouldBlur e.keyCode
|
||||
search_text = @input.val()
|
||||
|
||||
if blur_field
|
||||
if blur_field and @filterInputBlur
|
||||
@input.blur()
|
||||
|
||||
if @options.remote
|
||||
|
@ -93,12 +97,34 @@ class GitLabDropdown
|
|||
PAGE_TWO_CLASS = "is-page-two"
|
||||
ACTIVE_CLASS = "is-active"
|
||||
|
||||
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
|
||||
# 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,
|
||||
|
@ -109,11 +135,10 @@ class GitLabDropdown
|
|||
@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
|
||||
|
@ -123,11 +148,14 @@ class GitLabDropdown
|
|||
@parseData data
|
||||
@highlightRow 1
|
||||
enterCallback: =>
|
||||
if @enterCallback
|
||||
@selectFirstRow()
|
||||
|
||||
# Event listeners
|
||||
|
||||
@dropdown.on "shown.bs.dropdown", @opened
|
||||
@dropdown.on "hidden.bs.dropdown", @hidden
|
||||
@dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
|
||||
|
||||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
|
||||
|
@ -143,10 +171,14 @@ class GitLabDropdown
|
|||
selector = ".dropdown-page-one .dropdown-content a"
|
||||
|
||||
@dropdown.on "click", selector, (e) ->
|
||||
self.rowClicked $(@)
|
||||
selected = self.rowClicked $(@)
|
||||
|
||||
if self.options.clicked
|
||||
self.options.clicked()
|
||||
self.options.clicked(selected)
|
||||
|
||||
# Finds an element inside wrapper element
|
||||
getElement: (selector) ->
|
||||
@dropdown.find selector
|
||||
|
||||
toggleLoading: ->
|
||||
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
|
||||
|
@ -176,15 +208,26 @@ class GitLabDropdown
|
|||
|
||||
@appendMenu(full_html)
|
||||
|
||||
shouldPropagate: (e) =>
|
||||
if @options.multiSelect
|
||||
$target = $(e.target)
|
||||
if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
|
||||
e.stopPropagation()
|
||||
return false
|
||||
else
|
||||
return true
|
||||
|
||||
opened: =>
|
||||
contentHtml = $('.dropdown-content', @dropdown).html()
|
||||
if @remote && contentHtml is ""
|
||||
@remote.execute()
|
||||
|
||||
if @options.filterable
|
||||
@dropdown.find(".dropdown-input-field").focus()
|
||||
@filterInput.focus()
|
||||
|
||||
hidden: =>
|
||||
@dropdown.trigger('shown.gl.dropdown')
|
||||
|
||||
hidden: (e) =>
|
||||
if @options.filterable
|
||||
@dropdown
|
||||
.find(".dropdown-input-field")
|
||||
|
@ -195,6 +238,11 @@ class GitLabDropdown
|
|||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
|
||||
|
||||
if @options.hidden
|
||||
@options.hidden.call(@,e)
|
||||
|
||||
@dropdown.trigger('hidden.gl.dropdown')
|
||||
|
||||
|
||||
# Render the full menu
|
||||
renderMenu: (html) ->
|
||||
|
@ -219,20 +267,46 @@ 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
|
||||
url = if @options.url then @options.url(data) else "#"
|
||||
text = if @options.text then @options.text(data) else ""
|
||||
if not selected
|
||||
value = if @options.id then @options.id(data) else data.id
|
||||
fieldName = @options.fieldName
|
||||
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
|
||||
if field.length
|
||||
selected = true
|
||||
|
||||
# Set URL
|
||||
if @options.url?
|
||||
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"
|
||||
|
||||
if @highlight
|
||||
text = @highlightTextMatches(text, @filterInput.val())
|
||||
|
||||
html = "<li>"
|
||||
html += "<a href='#{url}' class='#{cssClass}'>"
|
||||
html += text
|
||||
|
@ -241,58 +315,68 @@ class GitLabDropdown
|
|||
|
||||
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 += "<a class='dropdown-menu-empty-link is-focused'>"
|
||||
html += "No matching results."
|
||||
html += "</a>"
|
||||
html += "</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
|
||||
field = @dropdown.parent().find("input[name='#{fieldName}']")
|
||||
|
||||
if el.hasClass(ACTIVE_CLASS)
|
||||
field.remove()
|
||||
else
|
||||
fieldName = @options.fieldName
|
||||
selectedIndex = el.parent().index()
|
||||
if @renderedData
|
||||
selectedObject = @renderedData[selectedIndex]
|
||||
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
|
||||
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
|
||||
|
||||
if el.hasClass(ACTIVE_CLASS)
|
||||
el.removeClass(ACTIVE_CLASS)
|
||||
field.remove()
|
||||
|
||||
# Toggle the dropdown label
|
||||
if @options.toggleLabel
|
||||
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
|
||||
else
|
||||
if !value?
|
||||
field.remove()
|
||||
|
||||
if @options.multiSelect
|
||||
oldValue = field.val()
|
||||
if oldValue
|
||||
value = "#{oldValue},#{value}"
|
||||
else
|
||||
if not @options.multiSelect
|
||||
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
|
||||
@dropdown.parent().find("input[name='#{fieldName}']").remove()
|
||||
|
||||
# Toggle active class for the tick mark
|
||||
el.toggleClass "is-active"
|
||||
el.addClass ACTIVE_CLASS
|
||||
|
||||
# Toggle the dropdown label
|
||||
if @options.toggleLabel
|
||||
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
|
||||
|
||||
if value?
|
||||
if !field.length
|
||||
# Create hidden input for form
|
||||
input = "<input type='hidden' name='#{fieldName}' />"
|
||||
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
|
||||
if @options.inputId?
|
||||
input = $(input)
|
||||
.attr('id', @options.inputId)
|
||||
@dropdown.before input
|
||||
else
|
||||
field.val value
|
||||
|
||||
@dropdown.parent().find("input[name='#{fieldName}']").val value
|
||||
return selectedObject
|
||||
|
||||
selectFirstRow: ->
|
||||
selector = '.dropdown-content li:first-child a'
|
||||
|
@ -304,4 +388,6 @@ class GitLabDropdown
|
|||
|
||||
$.fn.glDropdown = (opts) ->
|
||||
return @.each ->
|
||||
new GitLabDropdown @, opts
|
||||
if (!$.data @, 'glDropdown')
|
||||
$.data(@, 'glDropdown', new GitLabDropdown @, opts)
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
class @IssuableContext
|
||||
constructor: ->
|
||||
constructor: (currentUser) ->
|
||||
@initParticipants()
|
||||
|
||||
new UsersSelect()
|
||||
new UsersSelect(currentUser)
|
||||
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
|
||||
|
||||
$(".issuable-sidebar .inline-update").on "change", "select", ->
|
||||
|
@ -10,11 +9,21 @@ class @IssuableContext
|
|||
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
|
||||
$(this).submit()
|
||||
|
||||
$(document).on "click",".edit-link", (e) ->
|
||||
block = $(@).parents('.block')
|
||||
block.find('.selectbox').show()
|
||||
block.find('.value').hide()
|
||||
block.find('.js-select2').select2("open")
|
||||
$(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
|
||||
$block = $(@).parents('.block')
|
||||
$selectbox = $block.find('.selectbox')
|
||||
if $selectbox.is(':visible')
|
||||
$selectbox.hide()
|
||||
$block.find('.value').show()
|
||||
else
|
||||
$selectbox.show()
|
||||
$block.find('.value').hide()
|
||||
|
||||
if $selectbox.is(':visible')
|
||||
setTimeout (->
|
||||
$block.find('.dropdown-menu-toggle').trigger 'click'
|
||||
), 0
|
||||
|
||||
|
||||
$(".right-sidebar").niceScroll()
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
@Issues =
|
||||
init: ->
|
||||
Issues.initSearch()
|
||||
Issues.initSelects()
|
||||
Issues.initChecks()
|
||||
|
||||
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
|
||||
|
@ -17,18 +16,9 @@
|
|||
$(this).html totalIssues - 1
|
||||
|
||||
reload: ->
|
||||
Issues.initSelects()
|
||||
Issues.initChecks()
|
||||
$('#filter_issue_search').val($('#issue_search').val())
|
||||
|
||||
initSelects: ->
|
||||
$("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("#milestone_id, #assignee_id, #label_name").on "change", ->
|
||||
$(this).closest("form").submit()
|
||||
|
||||
initChecks: ->
|
||||
$(".check_all_issues").click ->
|
||||
$(".selected_issue").prop("checked", @checked)
|
||||
|
|
|
@ -4,14 +4,21 @@ class @LabelsSelect
|
|||
$dropdown = $(dropdown)
|
||||
projectId = $dropdown.data('project-id')
|
||||
labelUrl = $dropdown.data('labels')
|
||||
issueUpdateURL = $dropdown.data('issueUpdate')
|
||||
selectedLabel = $dropdown.data('selected')
|
||||
if selectedLabel
|
||||
selectedLabel = selectedLabel.toString().split(',')
|
||||
if selectedLabel?
|
||||
selectedLabel = selectedLabel.split(',')
|
||||
newLabelField = $('#new_label_name')
|
||||
newColorField = $('#new_label_color')
|
||||
showNo = $dropdown.data('show-no')
|
||||
showAny = $dropdown.data('show-any')
|
||||
defaultLabel = $dropdown.data('default-label')
|
||||
abilityName = $dropdown.data('ability-name')
|
||||
$selectbox = $dropdown.closest('.selectbox')
|
||||
$block = $selectbox.closest('.block')
|
||||
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
|
||||
$value = $block.find('.value')
|
||||
$loading = $block.find('.block-loading').fadeOut()
|
||||
|
||||
if newLabelField.length
|
||||
$newLabelCreateButton = $('.js-new-label-btn')
|
||||
|
@ -21,6 +28,22 @@ class @LabelsSelect
|
|||
|
||||
# Suggested colors in the dropdown to chose from pre-chosen colors
|
||||
$('.suggest-colors-dropdown a').on 'click', (e) ->
|
||||
|
||||
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
|
||||
if issueUpdateURL
|
||||
labelHTMLTemplate = _.template(
|
||||
'<% _.each(labels, function(label){ %>
|
||||
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
|
||||
<span class="label color-label" style="background-color: <%= label.color %>;">
|
||||
<%= label.title %>
|
||||
</span>
|
||||
</a>
|
||||
<% }); %>'
|
||||
);
|
||||
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
|
||||
|
||||
if newLabelField.length and $dropdown.hasClass 'js-extra-options'
|
||||
$('.suggest-colors-dropdown a').on "click", (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
newColorField
|
||||
|
@ -57,6 +80,23 @@ class @LabelsSelect
|
|||
# This allows us to enable the button when ready
|
||||
enableLabelCreateButton = ->
|
||||
if newLabelField.val() isnt '' and newColorField.val() isnt ''
|
||||
$newLabelError.hide()
|
||||
$('.js-new-label-btn').disable()
|
||||
|
||||
# Create new label with API
|
||||
Api.newLabel projectId, {
|
||||
name: newLabelField.val()
|
||||
color: newColorField.val()
|
||||
}, (label) ->
|
||||
$('.js-new-label-btn').enable()
|
||||
|
||||
if label.message?
|
||||
$newLabelError
|
||||
.text label.message
|
||||
.show()
|
||||
else
|
||||
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
|
||||
|
||||
$newLabelCreateButton.enable()
|
||||
else
|
||||
$newLabelCreateButton.disable()
|
||||
|
@ -90,11 +130,57 @@ class @LabelsSelect
|
|||
else
|
||||
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
|
||||
|
||||
saveLabelData = ->
|
||||
selected = $dropdown
|
||||
.closest('.selectbox')
|
||||
.find("input[name='#{$dropdown.data('field-name')}']")
|
||||
.map(->
|
||||
@value
|
||||
).get()
|
||||
data = {}
|
||||
data[abilityName] = {}
|
||||
data[abilityName].label_ids = selected
|
||||
if not selected.length
|
||||
data[abilityName].label_ids = ['']
|
||||
$loading.fadeIn()
|
||||
$dropdown.trigger('loading.gl.dropdown')
|
||||
$.ajax(
|
||||
type: 'PUT'
|
||||
url: issueUpdateURL
|
||||
dataType: 'JSON'
|
||||
data: data
|
||||
).done (data) ->
|
||||
$loading.fadeOut()
|
||||
$dropdown.trigger('loaded.gl.dropdown')
|
||||
$selectbox.hide()
|
||||
data.issueURLSplit = issueURLSplit
|
||||
labelCount = 0
|
||||
if data.labels.length
|
||||
template = labelHTMLTemplate(data)
|
||||
labelCount = data.labels.length
|
||||
else
|
||||
template = labelNoneHTMLTemplate()
|
||||
$value
|
||||
.removeAttr('style')
|
||||
.html(template)
|
||||
$sidebarCollapsedValue.text(labelCount)
|
||||
|
||||
$value
|
||||
.find('a')
|
||||
.each((i) ->
|
||||
setTimeout(=>
|
||||
glAnimate($(@), 'pulse')
|
||||
,200 * i
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
$dropdown.glDropdown(
|
||||
data: (term, callback) ->
|
||||
$.ajax(
|
||||
url: labelUrl
|
||||
).done (data) ->
|
||||
if $dropdown.hasClass 'js-extra-options'
|
||||
if showNo
|
||||
data.unshift(
|
||||
id: 0
|
||||
|
@ -109,22 +195,19 @@ class @LabelsSelect
|
|||
|
||||
if data.length > 2
|
||||
data.splice 2, 0, 'divider'
|
||||
|
||||
callback data
|
||||
|
||||
renderRow: (label) ->
|
||||
if $.isArray(selectedLabel)
|
||||
selected = ''
|
||||
$.each selectedLabel, (i, selectedLbl) ->
|
||||
selectedLbl = selectedLbl.trim()
|
||||
if selected is '' and label.title is selectedLbl
|
||||
selected = 'is-active'
|
||||
else
|
||||
selected = if label.title is selectedLabel then 'is-active' else ''
|
||||
selectedClass = ''
|
||||
if $selectbox.find("input[type='hidden']\
|
||||
[name='#{$dropdown.data('field-name')}']\
|
||||
[value='#{label.id}']").length
|
||||
selectedClass = 'is-active'
|
||||
|
||||
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
|
||||
|
||||
"<li>
|
||||
<a href='#' class='#{selected}'>
|
||||
<a href='#' class='#{selectedClass}'>
|
||||
#{color}
|
||||
#{label.title}
|
||||
</a>
|
||||
|
@ -133,6 +216,7 @@ class @LabelsSelect
|
|||
search:
|
||||
fields: ['title']
|
||||
selectable: true
|
||||
|
||||
toggleLabel: (selected) ->
|
||||
if selected and selected.title isnt 'Any Label'
|
||||
selected.title
|
||||
|
@ -142,15 +226,33 @@ class @LabelsSelect
|
|||
id: (label) ->
|
||||
if label.isAny?
|
||||
''
|
||||
else
|
||||
else if $dropdown.hasClass "js-filter-submit"
|
||||
label.title
|
||||
clicked: ->
|
||||
else
|
||||
label.id
|
||||
|
||||
hidden: ->
|
||||
$selectbox.hide()
|
||||
# display:block overrides the hide-collapse rule
|
||||
$value.removeAttr('style')
|
||||
if $dropdown.hasClass 'js-multiselect'
|
||||
saveLabelData()
|
||||
|
||||
multiSelect: $dropdown.hasClass 'js-multiselect'
|
||||
clicked: (label) ->
|
||||
page = $('body').data 'page'
|
||||
isIssueIndex = page is 'projects:issues:index'
|
||||
isMRIndex = page is page is 'projects:merge_requests:index'
|
||||
|
||||
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
||||
selectedLabel = label.title
|
||||
|
||||
Issues.filterResults $dropdown.closest('form')
|
||||
else if $dropdown.hasClass 'js-filter-submit'
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
if $dropdown.hasClass 'js-multiselect'
|
||||
return
|
||||
else
|
||||
saveLabelData()
|
||||
)
|
||||
|
|
13
app/assets/javascripts/lib/animate.js.coffee
Normal file
13
app/assets/javascripts/lib/animate.js.coffee
Normal file
|
@ -0,0 +1,13 @@
|
|||
((w) ->
|
||||
|
||||
w.glAnimate = ($el, animation, done) ->
|
||||
$el
|
||||
.removeClass()
|
||||
.addClass(animation + ' animated')
|
||||
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
|
||||
$(this).removeClass()
|
||||
return
|
||||
return
|
||||
return
|
||||
|
||||
) window
|
30
app/assets/javascripts/lib/notify.js.coffee
Normal file
30
app/assets/javascripts/lib/notify.js.coffee
Normal file
|
@ -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
|
|
@ -2,13 +2,18 @@ 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()
|
||||
|
||||
mergeInProgress: (deleteSourceBranch = false)->
|
||||
$.ajax
|
||||
|
@ -27,18 +32,57 @@ 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
|
||||
if data.coverage
|
||||
this.showCiCoverage data.coverage
|
||||
, 'json'
|
||||
ciLabelForStatus: (status) ->
|
||||
if status == 'success'
|
||||
'passed'
|
||||
else
|
||||
status
|
||||
|
||||
showCiState: (state) ->
|
||||
pollCIStatus: ->
|
||||
@fetchBuildStatusInterval = setInterval ( =>
|
||||
return if not @readyForCICheck
|
||||
|
||||
@getCIStatus(true)
|
||||
|
||||
@readyForCICheck = false
|
||||
), 5000
|
||||
|
||||
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 data.status isnt @opts.ci_status
|
||||
@showCIStatus data.status
|
||||
if data.coverage
|
||||
@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
|
||||
|
||||
showCIStatus: (state) ->
|
||||
$('.ci_widget').hide()
|
||||
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
|
||||
if state in allowed_states
|
||||
|
@ -52,7 +96,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)
|
||||
|
||||
|
|
|
@ -1,36 +1,65 @@
|
|||
class @MilestoneSelect
|
||||
constructor: ->
|
||||
constructor: (currentProject) ->
|
||||
if currentProject?
|
||||
_this = @
|
||||
@currentProject = JSON.parse(currentProject)
|
||||
$('.js-milestone-select').each (i, dropdown) ->
|
||||
$dropdown = $(dropdown)
|
||||
projectId = $dropdown.data('project-id')
|
||||
milestonesUrl = $dropdown.data('milestones')
|
||||
issueUpdateURL = $dropdown.data('issueUpdate')
|
||||
selectedMilestone = $dropdown.data('selected')
|
||||
showNo = $dropdown.data('show-no')
|
||||
showAny = $dropdown.data('show-any')
|
||||
showUpcoming = $dropdown.data('show-upcoming')
|
||||
useId = $dropdown.data('use-id')
|
||||
defaultLabel = $dropdown.data('default-label')
|
||||
issuableId = $dropdown.data('issuable-id')
|
||||
abilityName = $dropdown.data('ability-name')
|
||||
$selectbox = $dropdown.closest('.selectbox')
|
||||
$block = $selectbox.closest('.block')
|
||||
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon')
|
||||
$value = $block.find('.value')
|
||||
$loading = $block.find('.block-loading').fadeOut()
|
||||
|
||||
if issueUpdateURL
|
||||
milestoneLinkTemplate = _.template(
|
||||
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
|
||||
)
|
||||
|
||||
milestoneLinkNoneTemplate = '<div class="light">None</div>'
|
||||
|
||||
$dropdown.glDropdown(
|
||||
data: (term, callback) ->
|
||||
$.ajax(
|
||||
url: milestonesUrl
|
||||
).done (data) ->
|
||||
if showNo
|
||||
data.unshift(
|
||||
id: '0'
|
||||
title: 'No Milestone'
|
||||
)
|
||||
|
||||
extraOptions = []
|
||||
if showAny
|
||||
data.unshift(
|
||||
isAny: true
|
||||
extraOptions.push(
|
||||
id: 0
|
||||
name: ''
|
||||
title: 'Any Milestone'
|
||||
)
|
||||
|
||||
if data.length > 2
|
||||
data.splice 2, 0, 'divider'
|
||||
if showNo
|
||||
extraOptions.push(
|
||||
id: -1
|
||||
name: 'No Milestone'
|
||||
title: 'No Milestone'
|
||||
)
|
||||
|
||||
callback(data)
|
||||
if showUpcoming
|
||||
extraOptions.push(
|
||||
id: -2
|
||||
name: '#upcoming'
|
||||
title: 'Upcoming'
|
||||
)
|
||||
|
||||
if extraOptions.length > 2
|
||||
extraOptions.push 'divider'
|
||||
|
||||
callback(extraOptions.concat(data))
|
||||
filterable: true
|
||||
search:
|
||||
fields: ['title']
|
||||
|
@ -45,21 +74,51 @@ class @MilestoneSelect
|
|||
milestone.title
|
||||
id: (milestone) ->
|
||||
if !useId
|
||||
if !milestone.isAny?
|
||||
milestone.title
|
||||
else
|
||||
''
|
||||
milestone.name
|
||||
else
|
||||
milestone.id
|
||||
isSelected: (milestone) ->
|
||||
milestone.title is selectedMilestone
|
||||
clicked: ->
|
||||
page = $('body').data 'page'
|
||||
isIssueIndex = page is 'projects:issues:index'
|
||||
isMRIndex = page is page is 'projects:merge_requests:index'
|
||||
milestone.name is selectedMilestone
|
||||
hidden: ->
|
||||
$selectbox.hide()
|
||||
|
||||
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
||||
# display:block overrides the hide-collapse rule
|
||||
$value.removeAttr('style')
|
||||
clicked: (selected) ->
|
||||
if $dropdown.hasClass 'js-filter-bulk-update'
|
||||
return
|
||||
|
||||
if $dropdown.hasClass('js-filter-submit')
|
||||
if selected.name?
|
||||
selectedMilestone = selected.name
|
||||
else
|
||||
selectedMilestone = ''
|
||||
Issues.filterResults $dropdown.closest('form')
|
||||
else if $dropdown.hasClass 'js-filter-submit'
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
selected = $selectbox
|
||||
.find('input[type="hidden"]')
|
||||
.val()
|
||||
data = {}
|
||||
data[abilityName] = {}
|
||||
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()
|
||||
$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')
|
||||
)
|
||||
|
|
55
app/assets/javascripts/right_sidebar.js.coffee
Normal file
55
app/assets/javascripts/right_sidebar.js.coffee
Normal file
|
@ -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')
|
|
@ -1,11 +1,270 @@
|
|||
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(@)
|
||||
|
||||
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
|
||||
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: ->
|
||||
@searchInput.on 'keydown', @onSearchInputKeyDown
|
||||
@searchInput.on 'keyup', @onSearchInputKeyUp
|
||||
@searchInput.on 'click', @onSearchInputClick
|
||||
@searchInput.on 'focus', @onSearchInputFocus
|
||||
@searchInput.on 'blur', @onSearchInputBlur
|
||||
@clearInput.on 'click', @onRemoveLocationClick
|
||||
|
||||
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()
|
||||
|
||||
# Avoid falsy value to be returned
|
||||
return
|
||||
|
||||
onSearchInputClick: (e) =>
|
||||
# Prevents closing the dropdown menu
|
||||
e.stopImmediatePropagation()
|
||||
|
||||
onSearchInputFocus: =>
|
||||
@wrap.addClass('search-active')
|
||||
|
||||
onRemoveLocationClick: (e) =>
|
||||
e.preventDefault()
|
||||
@removeLocationBadge()
|
||||
@searchInput.val('').focus()
|
||||
@skipBlurEvent = true
|
||||
|
||||
onSearchInputBlur: (e) =>
|
||||
@skipBlurEvent = false
|
||||
|
||||
# We should wait to make sure we are not clearing the input instead
|
||||
setTimeout( =>
|
||||
return if @skipBlurEvent
|
||||
|
||||
@wrap.removeClass('search-active')
|
||||
|
||||
# If input is blank then restore state
|
||||
if @searchInput.val() is ''
|
||||
@restoreOriginalState()
|
||||
, 150)
|
||||
|
||||
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)
|
||||
|
|
|
@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
|
|||
toggleSidebar = ->
|
||||
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
|
||||
$('header').toggleClass("header-collapsed header-expanded")
|
||||
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
|
||||
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
|
||||
|
||||
setTimeout ( ->
|
||||
|
|
|
@ -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,6 @@ class @Todos
|
|||
updateBadges: (data) ->
|
||||
$('.todos-pending .badge, .todos-pending-count').text data.count
|
||||
$('.todos-done .badge').text data.done_count
|
||||
|
||||
goToTodoUrl: ->
|
||||
Turbolinks.visit($(this).data('url'))
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
class @UsersSelect
|
||||
constructor: ->
|
||||
constructor: (currentUser) ->
|
||||
@usersPath = "/autocomplete/users.json"
|
||||
@userPath = "/autocomplete/users/:id.json"
|
||||
if currentUser?
|
||||
@currentUser = JSON.parse(currentUser)
|
||||
|
||||
$('.js-user-search').each (i, dropdown) =>
|
||||
$dropdown = $(dropdown)
|
||||
|
@ -12,6 +14,81 @@ class @UsersSelect
|
|||
firstUser = $dropdown.data('first-user')
|
||||
selectedId = $dropdown.data('selected')
|
||||
defaultLabel = $dropdown.data('default-label')
|
||||
issueURL = $dropdown.data('issueUpdate')
|
||||
$selectbox = $dropdown.closest('.selectbox')
|
||||
$block = $selectbox.closest('.block')
|
||||
abilityName = $dropdown.data('ability-name')
|
||||
$value = $block.find('.value')
|
||||
$collapsedSidebar = $block.find('.sidebar-collapsed-user')
|
||||
$loading = $block.find('.block-loading').fadeOut()
|
||||
|
||||
$block.on('click', '.js-assign-yourself', (e) =>
|
||||
e.preventDefault()
|
||||
assignTo(@currentUser.id)
|
||||
)
|
||||
|
||||
assignTo = (selected) ->
|
||||
data = {}
|
||||
data[abilityName] = {}
|
||||
data[abilityName].assignee_id = selected
|
||||
$loading
|
||||
.fadeIn()
|
||||
$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()
|
||||
|
||||
if data.assignee
|
||||
user =
|
||||
name: data.assignee.name
|
||||
username: data.assignee.username
|
||||
avatar: data.assignee.avatar_url
|
||||
else
|
||||
user =
|
||||
name: 'Unassigned'
|
||||
username: ''
|
||||
avatar: ''
|
||||
$value.html(assigneeTemplate(user))
|
||||
$collapsedSidebar.html(collapsedAssigneeTemplate(user))
|
||||
|
||||
|
||||
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 ) { %>
|
||||
<img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
|
||||
<% } %>
|
||||
<span class="author"><%= name %></span>
|
||||
<span class="username">
|
||||
@<%= username %>
|
||||
</span>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<span class="assign-yourself">
|
||||
No assignee -
|
||||
<a href="#" class="js-assign-yourself">
|
||||
assign yourself
|
||||
</a>
|
||||
</span>
|
||||
<% } %>'
|
||||
)
|
||||
|
||||
$dropdown.glDropdown(
|
||||
data: (term, callback) =>
|
||||
|
@ -57,20 +134,38 @@ class @UsersSelect
|
|||
fields: ['name', 'username']
|
||||
selectable: true
|
||||
fieldName: $dropdown.data('field-name')
|
||||
|
||||
toggleLabel: (selected) ->
|
||||
if selected && 'id' of selected
|
||||
selected.name
|
||||
else
|
||||
defaultLabel
|
||||
clicked: ->
|
||||
|
||||
inputId: 'issue_assignee_id'
|
||||
|
||||
hidden: (e) ->
|
||||
$selectbox.hide()
|
||||
# display:block overrides the hide-collapse rule
|
||||
$value.removeAttr('style')
|
||||
|
||||
clicked: (user) ->
|
||||
page = $('body').data 'page'
|
||||
isIssueIndex = page is 'projects:issues:index'
|
||||
isMRIndex = page is page is 'projects:merge_requests:index'
|
||||
if $dropdown.hasClass('js-filter-bulk-update')
|
||||
return
|
||||
|
||||
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
||||
selectedId = user.id
|
||||
Issues.filterResults $dropdown.closest('form')
|
||||
else if $dropdown.hasClass 'js-filter-submit'
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
selected = $dropdown
|
||||
.closest('.selectbox')
|
||||
.find("input[name='#{$dropdown.data('field-name')}']").val()
|
||||
assignTo(selected)
|
||||
|
||||
renderRow: (user) ->
|
||||
username = if user.username then "@#{user.username}" else ""
|
||||
avatar = if user.avatar_url then user.avatar_url else false
|
||||
|
@ -87,17 +182,25 @@ class @UsersSelect
|
|||
if avatar
|
||||
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
|
||||
|
||||
"<li>
|
||||
# split into three parts so we can remove the username section if nessesary
|
||||
listWithName = "<li>
|
||||
<a href='#' class='dropdown-menu-user-link #{selected}'>
|
||||
#{img}
|
||||
<strong class='dropdown-menu-user-full-name'>
|
||||
#{user.name}
|
||||
</strong>
|
||||
<span class='dropdown-menu-user-username'>
|
||||
</strong>"
|
||||
|
||||
listWithUserName = "<span class='dropdown-menu-user-username'>
|
||||
#{username}
|
||||
</span>
|
||||
</a>
|
||||
</span>"
|
||||
listClosingTags = "</a>
|
||||
</li>"
|
||||
|
||||
|
||||
if username is ''
|
||||
listWithUserName = ''
|
||||
|
||||
listWithName + listWithUserName + listClosingTags
|
||||
)
|
||||
|
||||
$('.ajax-users-select').each (i, select) =>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
*= require dropzone/basic
|
||||
*= require cal-heatmap
|
||||
*= require cropper.css
|
||||
*= require animate
|
||||
*/
|
||||
|
||||
/*
|
||||
|
|
|
@ -121,7 +121,7 @@ p.time {
|
|||
text-shadow: none;
|
||||
}
|
||||
|
||||
.thin_area{
|
||||
.thin_area {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ li.note {
|
|||
}
|
||||
}
|
||||
|
||||
.wiki_content code, .readme code{
|
||||
.wiki_content code, .readme code {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -136,6 +144,21 @@
|
|||
background-color: $dropdown-empty-row-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.dropdown-menu-user-link {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +177,10 @@
|
|||
.dropdown-menu-back {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,13 +194,13 @@
|
|||
}
|
||||
|
||||
.dropdown-menu-user-link {
|
||||
padding-top: 7px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.dropdown-menu-user-full-name {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
@ -189,7 +216,7 @@
|
|||
}
|
||||
|
||||
.dropdown-select {
|
||||
width: 300px;
|
||||
width: $dropdown-width;
|
||||
}
|
||||
|
||||
.dropdown-menu-align-right {
|
||||
|
@ -218,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;
|
||||
|
@ -257,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;
|
||||
|
@ -281,6 +304,9 @@
|
|||
display: none;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
right: 22px;
|
||||
top: 9px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&.has-value {
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
*
|
||||
*/
|
||||
.file-holder {
|
||||
border: none;
|
||||
border: 1px solid $border-color;
|
||||
|
||||
&.readme-holder {
|
||||
margin-top: 10px;
|
||||
border-bottom: 0;
|
||||
margin: $gl-padding-top 0;
|
||||
}
|
||||
|
||||
table {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -33,10 +33,15 @@
|
|||
background: $color;
|
||||
}
|
||||
|
||||
.complex-sidebar .nav-primary {
|
||||
border-right: 1px solid lighten($color, 3%);
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
background: $color-darker;
|
||||
|
||||
.sidebar-user {
|
||||
border-top: 1px solid lighten($color, 3%);
|
||||
background: $color-darker;
|
||||
color: $color-light;
|
||||
|
||||
|
@ -62,7 +67,6 @@
|
|||
|
||||
.count {
|
||||
color: $color-light;
|
||||
background: $color-dark;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,37 +117,17 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin collapsed-header {
|
||||
margin-left: $sidebar_collapsed_width;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.header-collapsed {
|
||||
margin-left: $sidebar_collapsed_width;
|
||||
margin-left: 40px;
|
||||
|
||||
@media (min-width: $screen-md-min) {
|
||||
@include collapsed-header;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
|
||||
> form {
|
||||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
|
@ -110,7 +111,7 @@
|
|||
height: 34px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
top: 2px;
|
||||
margin-right: $gl-padding-top;
|
||||
|
||||
/* Medium devices (desktops, 992px and up) */
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.select2-drop{
|
||||
.select2-drop {
|
||||
color: #7f8fa4;
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@
|
|||
}
|
||||
|
||||
a {
|
||||
padding: 7px 15px;
|
||||
padding: 7px 12px;
|
||||
font-size: $gl-font-size;
|
||||
line-height: 24px;
|
||||
color: $gray;
|
||||
|
@ -169,10 +169,12 @@
|
|||
}
|
||||
|
||||
.count {
|
||||
float: right;
|
||||
background: #eee;
|
||||
padding: 0 8px;
|
||||
@include border-radius(6px);
|
||||
&:before {
|
||||
content: '(';
|
||||
}
|
||||
&:after {
|
||||
content: ')';
|
||||
}
|
||||
}
|
||||
|
||||
&.back-link i {
|
||||
|
@ -191,6 +193,27 @@
|
|||
}
|
||||
}
|
||||
|
||||
.expand-nav a {
|
||||
color: $gl-icon-color;
|
||||
width: 60px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 20px;
|
||||
background: #fff;
|
||||
height: 59px;
|
||||
text-align: center;
|
||||
line-height: 59px;
|
||||
border-bottom: 1px solid #eee;
|
||||
transition-duration: .3s;
|
||||
outline: none;
|
||||
z-index: 100;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
width: $sidebar_width;
|
||||
position: fixed;
|
||||
|
@ -210,55 +233,12 @@
|
|||
}
|
||||
|
||||
.page-sidebar-collapsed {
|
||||
padding-left: $sidebar_collapsed_width;
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: $sidebar_collapsed_width;
|
||||
|
||||
.header-logo {
|
||||
width: $sidebar_collapsed_width;
|
||||
|
||||
a {
|
||||
padding-left: ($sidebar_collapsed_width - 36) / 2;
|
||||
|
||||
.gitlab-text-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sidebar {
|
||||
width: $sidebar_collapsed_width;
|
||||
|
||||
li {
|
||||
width: auto;
|
||||
|
||||
a {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
width: $sidebar_collapsed_width;
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
padding-left: ($sidebar_collapsed_width - 36) / 2;
|
||||
width: $sidebar_collapsed_width;
|
||||
|
||||
.username {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-sidebar-expanded {
|
||||
padding-left: $sidebar_collapsed_width;
|
||||
|
||||
@media (min-width: $screen-md-min) {
|
||||
padding-left: $sidebar_width;
|
||||
}
|
||||
|
@ -288,6 +268,10 @@
|
|||
@media (min-width: $screen-sm-min) {
|
||||
padding-right: $sidebar_collapsed_width;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.right-sidebar-expanded {
|
||||
|
@ -300,4 +284,53 @@
|
|||
@media (min-width: $screen-md-min) {
|
||||
padding-right: $gutter_width;
|
||||
}
|
||||
|
||||
&.with-overlay {
|
||||
padding-right: $sidebar_collapsed_width;
|
||||
}
|
||||
}
|
||||
|
||||
.complex-sidebar {
|
||||
display: inline-block;
|
||||
|
||||
.nav-primary {
|
||||
width: 61px;
|
||||
float: left;
|
||||
height: 100vh;
|
||||
|
||||
.nav-sidebar {
|
||||
width: 60px;
|
||||
|
||||
li a {
|
||||
width: 60px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-secondary {
|
||||
$nav-secondary-width: 168px;
|
||||
|
||||
float: left;
|
||||
width: $nav-secondary-width;
|
||||
|
||||
.nav-sidebar {
|
||||
width: $nav-secondary-width;
|
||||
|
||||
li {
|
||||
width: $nav-secondary-width;
|
||||
|
||||
a {
|
||||
width: $nav-secondary-width;
|
||||
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -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,23 @@ $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: $dropdown-input-focus-border;
|
||||
$search-input-focus-shadow-color: $dropdown-input-focus-shadow;
|
||||
$search-input-width: $dropdown-width;
|
||||
$location-badge-color: #aaa;
|
||||
$location-badge-bg: $gray-normal;
|
||||
$location-icon-color: #e7e9ed;
|
||||
$location-active-color: $gl-text-color;
|
||||
$location-active-bg: $search-input-border-color;
|
||||
|
||||
/*
|
||||
* Notes
|
||||
*/
|
||||
$notes-light-color: #8e8e8e;
|
||||
$notes-action-color: #c3c3c3;
|
||||
$notes-role-color: #8e8e8e;
|
||||
$notes-role-border-color: #e4e4e4;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.loading{
|
||||
.loading {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -74,7 +74,7 @@
|
|||
color: $gl-text-red;
|
||||
}
|
||||
}
|
||||
.edit-file{
|
||||
.edit-file {
|
||||
a {
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -93,7 +93,6 @@ li.commit {
|
|||
.commit-row-info {
|
||||
color: $gl-gray;
|
||||
line-height: 24px;
|
||||
font-size: 13px;
|
||||
|
||||
a {
|
||||
color: $gl-gray;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.file-editor {
|
||||
#editor{
|
||||
#editor {
|
||||
border: none;
|
||||
@include border-radius(0);
|
||||
height: 500px;
|
||||
|
|
|
@ -43,10 +43,6 @@
|
|||
.md {
|
||||
color: #7f8fa4;
|
||||
font-size: $gl-font-size;
|
||||
|
||||
iframe.twitter-share-button {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
}
|
||||
|
||||
.issuable-sidebar {
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.block {
|
||||
@include clearfix;
|
||||
padding: $gl-padding 0;
|
||||
|
@ -89,7 +93,7 @@
|
|||
}
|
||||
|
||||
.cross-project-reference {
|
||||
color: $gl-link-color;
|
||||
color: inherit;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
|
@ -133,6 +137,12 @@
|
|||
|
||||
.value {
|
||||
line-height: 1;
|
||||
|
||||
.assign-yourself {
|
||||
margin-top: 10px;
|
||||
font-weight: normal;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.bold {
|
||||
|
@ -252,6 +262,15 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-toggle {
|
||||
width: 100%;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.open .dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default.gutter-toggle {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.ci-body {
|
||||
.incorrect-syntax{
|
||||
.incorrect-syntax {
|
||||
font-size: 19px;
|
||||
color: red;
|
||||
}
|
||||
.correct-syntax{
|
||||
.correct-syntax {
|
||||
font-size: 19px;
|
||||
color: #47a447;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.login-box{
|
||||
.login-box {
|
||||
background: #fafafa;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 2px #ccc;
|
||||
|
|
|
@ -230,3 +230,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.builds {
|
||||
.table-holder {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,8 +71,6 @@
|
|||
}
|
||||
|
||||
.note-form-actions {
|
||||
background: #fff;
|
||||
|
||||
.note-form-option {
|
||||
margin-top: 8px;
|
||||
margin-left: 30px;
|
||||
|
|
|
@ -22,7 +22,7 @@ ul.notes {
|
|||
margin-left: 55px;
|
||||
}
|
||||
|
||||
.note_created_ago, .note-updated-at {
|
||||
.note-created-ago, .note-updated-at {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
@ -39,53 +39,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;
|
||||
}
|
||||
|
@ -198,40 +151,88 @@ ul.notes {
|
|||
border-width: 1px 0;
|
||||
padding-top: 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 {
|
||||
.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 {
|
||||
.fa {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.fa-trash-o {
|
||||
top: 0;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@extend .cgray;
|
||||
&.danger { @extend .cred; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
margin-right: 12px;
|
||||
|
||||
a {
|
||||
margin: -1px !important;
|
||||
margin: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,7 @@
|
|||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
line-height: 42px;
|
||||
line-height: 36px;
|
||||
margin: 0;
|
||||
|
||||
> li + li:before {
|
||||
|
|
|
@ -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-active-bg;
|
||||
color: $white-light;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
i {
|
||||
color: $location-active-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-location-badge {
|
||||
.search-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-location-badge {
|
||||
.search-input-wrap {
|
||||
width: 78%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
module Ci
|
||||
class ProjectsController < Ci::ApplicationController
|
||||
before_action :project
|
||||
before_action :authorize_read_project!, except: [:badge]
|
||||
before_action :no_cache, only: [:badge]
|
||||
before_action :authorize_read_project!, except: [:badge, :index]
|
||||
skip_before_action :authenticate_user!, only: [:badge]
|
||||
protect_from_forgery
|
||||
|
||||
def index
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
def show
|
||||
# Temporary compatibility with CI badges pointing to CI project page
|
||||
redirect_to namespace_project_path(project.namespace, project)
|
||||
|
@ -35,5 +39,9 @@ module Ci
|
|||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
|
||||
end
|
||||
|
||||
def authorize_read_project!
|
||||
return access_denied! unless can?(current_user, :read_project, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,12 @@ class Projects::BadgesController < Projects::ApplicationController
|
|||
before_action :no_cache_headers
|
||||
|
||||
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
|
||||
|
|
|
@ -68,7 +68,13 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
@merge_requests = @issue.referenced_merge_requests(current_user)
|
||||
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
|
||||
|
||||
respond_with(@issue)
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: @issue.to_json(include: [:milestone, :labels])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -107,10 +113,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
format.json do
|
||||
render json: {
|
||||
saved: @issue.valid?,
|
||||
assignee_avatar_url: @issue.assignee.try(:avatar_url)
|
||||
}
|
||||
render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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,10 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@merge_request.target_project, @merge_request])
|
||||
end
|
||||
format.json do
|
||||
render json: {
|
||||
saved: @merge_request.valid?,
|
||||
assignee_avatar_url: @merge_request.assignee.try(:avatar_url)
|
||||
}
|
||||
render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -227,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def ci_status
|
||||
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)
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
response = {
|
||||
title: merge_request.title,
|
||||
sha: merge_request.last_commit_short_sha,
|
||||
status: status,
|
||||
coverage: coverage
|
||||
}
|
||||
|
|
|
@ -19,13 +19,12 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
@milestones = @milestones.includes(:project)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@milestones = @milestones.page(params[:page])
|
||||
end
|
||||
format.json do
|
||||
render json: @milestones
|
||||
render json: @milestones.to_json(methods: :name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController
|
|||
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
|
||||
|
||||
# Allow read any snippet
|
||||
before_action :authorize_read_project_snippet!
|
||||
before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
|
||||
|
||||
# Allow write(create) snippet
|
||||
before_action :authorize_create_project_snippet!, only: [:new, :create]
|
||||
|
@ -81,6 +81,10 @@ class Projects::SnippetsController < Projects::ApplicationController
|
|||
@snippet ||= @project.snippets.find(params[:id])
|
||||
end
|
||||
|
||||
def authorize_read_project_snippet!
|
||||
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
|
||||
end
|
||||
|
||||
def authorize_update_project_snippet!
|
||||
return render_404 unless can?(current_user, :update_project_snippet, @snippet)
|
||||
end
|
||||
|
|
|
@ -71,7 +71,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
|
||||
|
@ -138,7 +138,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
|
||||
|
@ -235,17 +235,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
|
||||
|
|
|
@ -243,7 +243,7 @@ class IssuableFinder
|
|||
end
|
||||
|
||||
def filter_by_upcoming_milestone?
|
||||
params[:milestone_title] == '#upcoming'
|
||||
params[:milestone_title] == Milestone::Upcoming.name
|
||||
end
|
||||
|
||||
def by_milestone(items)
|
||||
|
@ -252,7 +252,7 @@ class IssuableFinder
|
|||
items = items.where(milestone_id: [-1, nil])
|
||||
elsif filter_by_upcoming_milestone?
|
||||
upcoming = Milestone.where(project_id: projects).upcoming
|
||||
items = items.joins(:milestone).where(milestones: { title: upcoming.title })
|
||||
items = items.joins(:milestone).where(milestones: { title: upcoming.try(:title) })
|
||||
else
|
||||
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -60,7 +60,7 @@ module DropdownsHelper
|
|||
title_output << content_tag(:span, title)
|
||||
|
||||
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
|
||||
icon('times')
|
||||
icon('times', class: 'dropdown-menu-close-icon')
|
||||
end
|
||||
|
||||
title_output.html_safe
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,6 +16,16 @@ module IssuablesHelper
|
|||
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
|
||||
end
|
||||
|
||||
def issuable_json_path(issuable)
|
||||
project = issuable.project
|
||||
|
||||
if issuable.kind_of?(MergeRequest)
|
||||
namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
|
||||
else
|
||||
namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_issuable_for(issuable)
|
||||
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
|
||||
end
|
||||
|
@ -37,6 +47,14 @@ module IssuablesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def milestone_dropdown_label(milestone_title, default_label = "Milestone")
|
||||
if milestone_title == Milestone::Upcoming.name
|
||||
milestone_title = Milestone::Upcoming.title
|
||||
end
|
||||
|
||||
h(milestone_title.presence || default_label)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sidebar_gutter_collapsed?
|
||||
|
|
|
@ -5,9 +5,11 @@ module NotesHelper
|
|||
end
|
||||
|
||||
def note_target_fields(note)
|
||||
if note.noteable
|
||||
hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
|
||||
hidden_field_tag(:target_id, note.noteable.id)
|
||||
end
|
||||
end
|
||||
|
||||
def note_editable?(note)
|
||||
note.editable? && can?(current_user, :admin_note, note)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ class Ability
|
|||
case true
|
||||
when subject.is_a?(PersonalSnippet)
|
||||
anonymous_personal_snippet_abilities(subject)
|
||||
when subject.is_a?(ProjectSnippet)
|
||||
anonymous_project_snippet_abilities(subject)
|
||||
when subject.is_a?(CommitStatus)
|
||||
anonymous_commit_status_abilities(subject)
|
||||
when subject.is_a?(Project) || subject.respond_to?(:project)
|
||||
|
@ -100,6 +102,14 @@ class Ability
|
|||
end
|
||||
end
|
||||
|
||||
def anonymous_project_snippet_abilities(snippet)
|
||||
if snippet.public?
|
||||
[:read_project_snippet]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def global_abilities(user)
|
||||
rules = []
|
||||
rules << :create_group if user.can_create_group
|
||||
|
@ -338,25 +348,23 @@ class Ability
|
|||
end
|
||||
end
|
||||
|
||||
[:note, :project_snippet].each do |name|
|
||||
define_method "#{name}_abilities" do |user, subject|
|
||||
def note_abilities(user, note)
|
||||
rules = []
|
||||
|
||||
if subject.author == user
|
||||
if note.author == user
|
||||
rules += [
|
||||
:"read_#{name}",
|
||||
:"update_#{name}",
|
||||
:"admin_#{name}"
|
||||
:read_note,
|
||||
:update_note,
|
||||
:admin_note
|
||||
]
|
||||
end
|
||||
|
||||
if subject.respond_to?(:project) && subject.project
|
||||
rules += project_abilities(user, subject.project)
|
||||
if note.respond_to?(:project) && note.project
|
||||
rules += project_abilities(user, note.project)
|
||||
end
|
||||
|
||||
rules
|
||||
end
|
||||
end
|
||||
|
||||
def personal_snippet_abilities(user, snippet)
|
||||
rules = []
|
||||
|
@ -376,6 +384,24 @@ class Ability
|
|||
rules
|
||||
end
|
||||
|
||||
def project_snippet_abilities(user, snippet)
|
||||
rules = []
|
||||
|
||||
if snippet.author == user || user.admin?
|
||||
rules += [
|
||||
:read_project_snippet,
|
||||
:update_project_snippet,
|
||||
:admin_project_snippet
|
||||
]
|
||||
end
|
||||
|
||||
if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user))
|
||||
rules << :read_project_snippet
|
||||
end
|
||||
|
||||
rules
|
||||
end
|
||||
|
||||
def group_member_abilities(user, subject)
|
||||
rules = []
|
||||
target_user = subject.user
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -14,6 +14,7 @@ class GlobalMilestone
|
|||
|
||||
def initialize(title, milestones)
|
||||
@title = title
|
||||
@name = title
|
||||
@milestones = milestones
|
||||
end
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)}
|
||||
(?:
|
||||
|
|
|
@ -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.
|
||||
|
@ -279,7 +280,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
|
||||
|
||||
def work_in_progress?
|
||||
title =~ WIP_REGEX
|
||||
!!(title =~ WIP_REGEX)
|
||||
end
|
||||
|
||||
def wipless_title
|
||||
|
@ -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
|
||||
|
|
|
@ -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})"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -206,6 +206,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') }
|
||||
|
@ -491,7 +493,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
|
||||
|
@ -612,7 +614,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
|
||||
|
||||
|
@ -951,16 +953,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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
class JiraService < IssueTrackerService
|
||||
include HTTParty
|
||||
include Gitlab::Application.routes.url_helpers
|
||||
include Gitlab::Routing.url_helpers
|
||||
|
||||
DEFAULT_API_VERSION = 2
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class SlackService
|
|||
@issue_url = obj_attr[:url]
|
||||
@action = obj_attr[:action]
|
||||
@state = obj_attr[:state]
|
||||
@description = obj_attr[:description]
|
||||
@description = obj_attr[:description] || ''
|
||||
end
|
||||
|
||||
def attachments
|
||||
|
|
|
@ -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)
|
||||
|
@ -335,6 +335,8 @@ class Repository
|
|||
|
||||
# Runs code just before a repository is deleted.
|
||||
def before_delete
|
||||
expire_exists_cache
|
||||
|
||||
expire_cache if exists?
|
||||
|
||||
expire_root_ref_cache
|
||||
|
@ -612,9 +614,13 @@ 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
|
||||
|
@ -818,7 +824,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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -408,6 +408,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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
19
app/services/projects/unlink_fork_service.rb
Normal file
19
app/services/projects/unlink_fork_service.rb
Normal file
|
@ -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
|
|
@ -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,
|
||||
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(model.project.visibility_level_field).downcase
|
||||
project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -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})"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -76,13 +76,6 @@
|
|||
= f.label :gravatar_enabled do
|
||||
= 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
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
.wiki
|
||||
%h1
|
||||
GitLab CI is now integrated in GitLab UI
|
||||
%h2 For existing projects
|
||||
|
||||
%p
|
||||
Check the following pages to find the CI status you're looking for:
|
||||
|
||||
%ul
|
||||
%li Projects page - shows CI status for each project.
|
||||
%li Project commits page - show CI status for each commit.
|
||||
|
||||
|
||||
|
||||
%h2 For new projects
|
||||
|
||||
%p
|
||||
If you want to enable CI for a new project it is easy as adding
|
||||
= link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
|
||||
file to your repository
|
|
@ -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)
|
||||
- if todo.target
|
||||
= todo_target_link(todo)
|
||||
- else
|
||||
(removed)
|
||||
|
||||
· #{time_ago_with_tooltip(todo.created_at)}
|
||||
|
||||
|
|
|
@ -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)}
|
||||
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
- if nav_menu_collapsed?
|
||||
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
|
||||
- else
|
||||
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
|
|
@ -1,5 +1,7 @@
|
|||
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
|
||||
= render "layouts/broadcast"
|
||||
.expand-nav
|
||||
= link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar"
|
||||
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
|
||||
.header-logo
|
||||
%a#logo
|
||||
|
@ -8,15 +10,19 @@
|
|||
.gitlab-text-container
|
||||
%h3 GitLab
|
||||
|
||||
- if defined?(sidebar) && sidebar
|
||||
- primary_sidebar = current_user ? 'dashboard' : 'explore'
|
||||
|
||||
- if defined?(sidebar) && sidebar && sidebar != primary_sidebar
|
||||
.complex-sidebar
|
||||
.nav-primary
|
||||
= render "layouts/nav/#{primary_sidebar}"
|
||||
.nav-secondary
|
||||
= render "layouts/nav/#{sidebar}"
|
||||
- elsif current_user
|
||||
= render 'layouts/nav/dashboard'
|
||||
- else
|
||||
= render 'layouts/nav/explore'
|
||||
= render "layouts/nav/#{primary_sidebar}"
|
||||
|
||||
.collapse-nav
|
||||
= render partial: 'layouts/collapse_button'
|
||||
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar"
|
||||
- if current_user
|
||||
= link_to current_user, class: 'sidebar-user', title: "Profile" do
|
||||
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
|
||||
|
|
|
@ -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"
|
||||
= hidden_field_tag :group_id, @group.try(:id)
|
||||
- if @project && @project.persisted?
|
||||
= hidden_field_tag :project_id, @project.id
|
||||
- if controller.controller_path =~ /^groups/
|
||||
- label = 'This group'
|
||||
- if controller.controller_path =~ /^projects/
|
||||
- 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)
|
||||
= 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();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
Spam Logs
|
||||
%span.count= number_with_delimiter(SpamLog.count(:all))
|
||||
|
||||
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
|
||||
= nav_link(controller: :application_settings) do
|
||||
= link_to admin_application_settings_path, title: 'Settings' do
|
||||
= icon('cogs fw')
|
||||
%span
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
= icon('dashboard fw')
|
||||
%span
|
||||
Activity
|
||||
= nav_link(controller: :groups) do
|
||||
= nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do
|
||||
= link_to dashboard_groups_path, title: 'Groups' do
|
||||
= icon('group fw')
|
||||
%span
|
||||
Groups
|
||||
= nav_link(controller: :milestones) do
|
||||
= nav_link(path: 'dashboard#milestones') do
|
||||
= link_to dashboard_milestones_path, title: 'Milestones' do
|
||||
= icon('clock-o fw')
|
||||
%span
|
||||
|
@ -48,7 +48,6 @@
|
|||
%span
|
||||
Help
|
||||
|
||||
%li.separate-item
|
||||
= nav_link(controller: :profile) do
|
||||
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
|
||||
= icon('user fw')
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
%ul.nav.nav-sidebar
|
||||
= nav_link do
|
||||
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
|
||||
= icon('caret-square-o-left fw')
|
||||
%span
|
||||
Go to dashboard
|
||||
|
||||
%li.separate-item
|
||||
|
||||
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
|
||||
= link_to group_path(@group), title: 'Home' do
|
||||
= icon('group fw')
|
||||
|
@ -42,7 +34,7 @@
|
|||
%span
|
||||
Members
|
||||
- if can?(current_user, :admin_group, @group)
|
||||
= nav_link(html_options: { class: "separate-item" }) do
|
||||
= nav_link do
|
||||
= link_to edit_group_path(@group), title: 'Settings' do
|
||||
= icon ('cogs fw')
|
||||
%span
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
%ul.nav.nav-sidebar
|
||||
= nav_link do
|
||||
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
|
||||
= icon('caret-square-o-left fw')
|
||||
%span
|
||||
Go to dashboard
|
||||
|
||||
%li.separate-item
|
||||
|
||||
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
|
||||
= link_to profile_path, title: 'Profile Settings' do
|
||||
= icon('user fw')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue