Commit 06e473e2 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Merge branch 'master' into assign-to-issuable-opener

parents 9e5a11f4 97747233
......@@ -7,21 +7,44 @@ exclude:
- 'app/assets/stylesheets/pages/emojis.scss'
linters:
# Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags.
BangFormat:
enabled: false
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
# Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes).
ChainedClasses:
enabled: false
# Prefer hexadecimal color codes over color keywords.
# (e.g. `color: green` is a color keyword)
ColorKeyword:
enabled: false
# Prefer color literals (keywords or hexadecimal codes) to be used only in
# variable declarations. They should be referred to via variables everywhere
# else.
ColorVariable:
enabled: false
# Which form of comments to prefer in CSS.
Comment:
enabled: false
# Reports @debug statements (which you probably left behind accidentally).
DebugStatement:
enabled: false
# Rule sets should be ordered as follows:
# - @extend declarations
# - @include declarations without inner @content
# - properties, @include declarations with inner @content
# - nested rule sets.
DeclarationOrder:
enabled: false
......@@ -32,15 +55,25 @@ linters:
DisableLinterReason:
enabled: true
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
enabled: false
# Reports when you have an empty rule set.
EmptyRule:
enabled: false
# Reports when you have an @extend directive.
ExtendDirective:
enabled: false
# Files should always have a final newline. This results in better diffs
# when adding lines to the file, since SCM systems such as git won't
# think that you touched the last line.
FinalNewline:
enabled: false
......@@ -53,12 +86,17 @@ linters:
HexNotation:
enabled: true
# Avoid using ID selectors.
IdSelector:
enabled: false
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath:
enabled: false
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule:
enabled: false
......@@ -67,33 +105,51 @@ linters:
enabled: true
width: 2
# Don't write leading zeros for numeric values with a decimal point.
LeadingZero:
enabled: false
# Reports when you define the same selector twice in a single sheet.
MergeableSelector:
enabled: false
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
NameFormat:
enabled: false
# Avoid nesting selectors too deeply.
NestingDepth:
enabled: false
# Always use placeholder selectors in @extend.
PlaceholderInExtend:
enabled: false
# Sort properties in a strict order.
PropertySortOrder:
enabled: false
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling:
enabled: false
# Configure which units are allowed for property values.
PropertyUnits:
enabled: false
# Pseudo-elements, like ::before, and ::first-letter, should be declared
# with two colons. Pseudo-classes, like :hover and :first-child, should
# be declared with one colon.
PseudoElement:
enabled: false
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement:
enabled: false
# Don't write selectors with a depth of applicability greater than 3.
SelectorDepth:
enabled: false
......@@ -113,9 +169,12 @@ linters:
enabled: true
allow_single_line_rule_sets: true
# Split selectors onto separate lines after each comma, and have each
# individual selector occupy a single line.
SingleLinePerSelector:
enabled: false
# Commas in lists should be followed by a space.
SpaceAfterComma:
enabled: false
......@@ -128,28 +187,75 @@ linters:
# colon.
SpaceAfterPropertyName:
enabled: true
# Variables should be formatted with a single space separating the colon
# from the variable's value.
SpaceAfterVariableColon:
enabled: false
# Variables should be formatted with no space between the name and the
# colon.
SpaceAfterVariableName:
enabled: false
# Operators should be formatted with a single space on both sides of an
# infix operator.
SpaceAroundOperator:
enabled: false
# Opening braces should be preceded by a single space.
SpaceBeforeBrace:
enabled: true
# Parentheses should not be padded with spaces.
SpaceBetweenParens:
enabled: false
# Enforces that string literals should be written with a consistent form
# of quotes (single or double).
StringQuotes:
enabled: false
# Property values, @extend, @include, and @import directives, and variable
# declarations should always end with a semicolon.
TrailingSemicolon:
enabled: false
# Reports lines containing trailing whitespace.
TrailingWhitespace:
enabled: false
# Don't write trailing zeros for numeric values with a decimal point.
TrailingZero:
enabled: false
# Don't use the `all` keyword to specify transition properties.
TransitionAll:
enabled: false
# Numeric values should not contain unnecessary fractional portions.
UnnecessaryMantissa:
enabled: false
# Do not use parent selector references (&) when they would otherwise
# be unnecessary.
UnnecessaryParentReference:
enabled: false
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
enabled: false
# URLs should always be enclosed within quotes.
UrlQuotes:
enabled: false
# Properties, like color and font, are easier to read and maintain
# when defined using variables rather than literals.
VariableForProperty:
enabled: false
# Avoid vendor prefixes. Or rather: don't write them yourself.
VendorPrefix:
enabled: false
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- 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)
- Allow back dating on issues when created through the API
- 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.)
- Allow issues and merge requests to be assigned to the author
......@@ -14,6 +20,49 @@ v 8.7.0 (unreleased)
v 8.6.2 (unreleased)
- Comments on confidential issues don't show up in activity feed to non-members
- Fix NoMethodError when visiting CI root path at `/ci`
- 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)
- Improved UX of the navigation sidebar
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
......@@ -86,6 +135,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
......
......@@ -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)
......
class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
$(".js-add-award").on "click", (event) =>
event.stopPropagation()
event.preventDefault()
......@@ -34,7 +34,7 @@ class @AwardsHandler
$("#emoji_search").focus()
else
$('.js-add-award').addClass "is-loading"
$.get "/emojis", (response) =>
$.get @get_emojis_url, (response) =>
$('.js-add-award').removeClass "is-loading"
$(".js-award-holder").append response
setTimeout =>
......
......@@ -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,27 +97,48 @@ 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
# Remote data
@remote = new GitLabDropdownRemote @options.data, {
dataType: @options.dataType,
beforeSend: @toggleLoading.bind(@)
success: (data) =>
@fullData = data
# If data is an array
if _.isArray @options.data
@fullData = @options.data
@parseData @options.data
else
# Remote data
@remote = new GitLabDropdownRemote @options.data, {
dataType: @options.dataType,
beforeSend: @toggleLoading.bind(@)
success: (data) =>
@fullData = data
@parseData @fullData
}
@parseData @fullData
}
# Init filiterable
# Init filterable
if @options.filterable
@input = @dropdown.find('.dropdown-input .dropdown-input-field')
@filter = new GitLabDropdownFilter @input,
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
......@@ -123,7 +148,8 @@ class GitLabDropdown
@parseData data
@highlightRow 1
enterCallback: =>
@selectFirstRow()
if @enterCallback
@selectFirstRow()
# Event listeners
......@@ -145,11 +171,14 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) ->
e.preventDefault()
self.rowClicked $(@)
selected = self.rowClicked $(@)
if self.options.clicked
self.options.clicked.call(@,e)
self.options.clicked(selected)
# Finds an element inside wrapper element
getElement: (selector) ->
@dropdown.find selector
toggleLoading: ->
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
......@@ -194,7 +223,9 @@ class GitLabDropdown
@remote.execute()
if @options.filterable
@dropdown.find(".dropdown-input-field").focus()
@filterInput.focus()
@dropdown.trigger('shown.gl.dropdown')
hidden: (e) =>
if @options.filterable
......@@ -210,6 +241,8 @@ class GitLabDropdown
if @options.hidden
@options.hidden.call(@,e)
@dropdown.trigger('hidden.gl.dropdown')
# Render the full menu
renderMenu: (html) ->
......@@ -234,13 +267,19 @@ class GitLabDropdown
renderItem: (data) ->
html = ""
# Divider
return "<li class='divider'></li>" if data is "divider"
# Separator is a full-width divider
return "<li class='separator'></li>" if data is "separator"
# Header
return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
if @options.renderRow
# Call the render function
html = @options.renderRow(data)
else
selected = if @options.isSelected then @options.isSelected(data) else false
if not selected
value = if @options.id then @options.id(data) else data.id
fieldName = @options.fieldName
......@@ -248,13 +287,26 @@ class GitLabDropdown
if field.length
selected = true
url = if @options.url then @options.url(data) else "#"
text = if @options.text then @options.text(data) else ""
# Set URL
if @options.url?
url = @options.url(data)
else
url = if data.url? then data.url else '#'
# Set Text
if @options.text?
text = @options.text(data)
else
text = if data.text? then data.text else ''
cssClass = "";
if selected
cssClass = "is-active"
if @highlight
text = @highlightTextMatches(text, @filterInput.val())
html = "<li>"
html += "<a href='#{url}' class='#{cssClass}'>"
html += text
......@@ -263,20 +315,26 @@ 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
......@@ -285,17 +343,15 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
else
fieldName = @options.fieldName
selectedIndex = el.parent().index()
if @renderedData
selectedObject = @renderedData[selectedIndex]
selectedObject.selected = true
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
else
if !value?
field.remove()
......@@ -304,7 +360,7 @@ class GitLabDropdown
@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
......@@ -313,10 +369,14 @@ class GitLabDropdown
if !field.length
# Create hidden input for form
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
if @options.inputId?
if @options.inputId?
input = $(input)
.attr('id', @options.inputId)
@dropdown.before input
else
field.val value
return selectedObject
selectFirstRow: ->
selector = '.dropdown-content li:first-child a'
......@@ -328,4 +388,6 @@ class GitLabDropdown
$.fn.glDropdown = (opts) ->
return @.each ->
new GitLabDropdown @, opts
if (!$.data @, 'glDropdown')
$.data(@, 'glDropdown', new GitLabDropdown @, opts)
......@@ -9,7 +9,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
$(document).on "click",".edit-link", (e) ->
$(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
$block = $(@).parents('.block')
$selectbox = $block.find('.selectbox')
if $selectbox.is(':visible')
......
@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)
......
......@@ -16,6 +16,7 @@ class @LabelsSelect
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut()
......@@ -142,6 +143,7 @@ class @LabelsSelect
if not selected.length
data[abilityName].label_ids = ['']
$loading.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
url: issueUpdateURL
......@@ -149,15 +151,20 @@ class @LabelsSelect
data: data
).done (data) ->
$loading.fadeOut()
$dropdown.trigger('loaded.gl.dropdown')
$selectbox.hide()
data.issueURLSplit = issueURLSplit
if not data.labels.length
template = labelNoneHTMLTemplate()
else
labelCount = 0
if data.labels.length
template = labelHTMLTemplate(data)
href = $value
.show()
.html(template)
labelCount = data.labels.length
else
template = labelNoneHTMLTemplate()
$value
.removeAttr('style')
.html(template)
$sidebarCollapsedValue.text(labelCount)
$value
.find('a')
.each((i) ->
......@@ -226,18 +233,20 @@ class @LabelsSelect
hidden: ->
$selectbox.hide()
$value.show()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect'
saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: ->
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()
......
((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
ciLabelForStatus: (status) ->
if status == 'success'
'passed'
else
status
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
this.showCiCoverage data.coverage
, 'json'
@showCICoverage data.coverage
if showNotification
message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status))
message = message.replace('{{sha}}', data.sha)
message = message.replace('{{title}}', data.title)
notify(
"Build #{@ciLabelForStatus(data.status)}",
message,
@opts.gitlab_icon,
->
@close()
Turbolinks.visit _this.opts.builds_path
)
@opts.ci_status = data.status
showCiState: (state) ->
showCIStatus: (state) ->
$('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states
......@@ -52,7 +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)
......
......@@ -11,12 +11,14 @@ class @MilestoneSelect
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()
......@@ -32,22 +34,32 @@ class @MilestoneSelect
$.ajax(
url: milestonesUrl
).done (data) ->
if $dropdown.hasClass "js-extra-options"
if showNo
data.unshift(
id: '0'
title: 'No Milestone'
)
extraOptions = []
if showAny
extraOptions.push(
id: 0
name: ''
title: 'Any Milestone'
)
if showAny
data.unshift(
isAny: true
title: 'Any Milestone'
)
if showNo
extraOptions.push(
id: -1
name: 'No Milestone'
title: 'No Milestone'
)
if data.length > 2
data.splice 2, 0, 'divider'
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']
......@@ -62,23 +74,26 @@ 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
milestone.name is selectedMilestone
hidden: ->
$selectbox.hide()
$value.show()
clicked: (e) ->
# 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'
$dropdown.parents('form').submit()
if $dropdown.hasClass('js-filter-submit')
if selected.name?
selectedMilestone = selected.name
else
selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
else
selected = $selectbox
.find('input[type="hidden"]')
......@@ -88,20 +103,22 @@ class @MilestoneSelect
data[abilityName].milestone_id = selected
$loading
.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
url: issueUpdateURL
data: data
).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
$milestoneLink = $value
.show()
.find('a')
$value.removeAttr('style')
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
$value.html(milestoneLinkTemplate(data.milestone))
$sidebarCollapsedValue.find('span').text(data.milestone.title)
else
$value.html(milestoneLinkNoneTemplate)
)
\ No newline at end of file
$sidebarCollapsedValue.find('span').text('No')
)
class @Sidebar
constructor: (currentUser) ->
@addEventListeners()
addEventListeners: ->
$('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
$('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
sidebarDropdownLoading: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
img = $sidebarCollapsedIcon.find('img')
i = $sidebarCollapsedIcon.find('i')
$loading = $('<i class="fa fa-spinner fa-spin"></i>')
if img.length
img.before($loading)
img.hide()
else if i.length
i.before($loading)
i.hide()
sidebarDropdownLoaded: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
img = $sidebarCollapsedIcon.find('img')
$sidebarCollapsedIcon.find('i.fa-spin').remove()
i = $sidebarCollapsedIcon.find('i')
if img.length
img.show()
else
i.show()
sidebarCollapseClicked: (e) ->
e.preventDefault()
$block = $(@).closest('.block')
$('aside')
.find('.gutter-toggle')
.trigger('click')
$editLink = $block.find('.edit-link')
if $editLink.length
$editLink.trigger('click')
$block.addClass('collapse-after-update')
$('.page-with-sidebar').addClass('with-overlay')
sidebarDropdownHidden: (e) ->
$block = $(@).closest('.block')
if $block.hasClass('collapse-after-update')
$block.removeClass('collapse-after-update')
$('.page-with-sidebar').removeClass('with-overlay')
$('aside')
.find('.gutter-toggle')
.trigger('click')
\ No newline at end of file
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'))
......@@ -19,6 +19,7 @@ class @UsersSelect
$block = $selectbox.closest('.block')
abilityName = $dropdown.data('ability-name')
$value = $block.find('.value')
$collapsedSidebar = $block.find('.sidebar-collapsed-user')
$loading = $block.find('.block-loading').fadeOut()
$block.on('click', '.js-assign-yourself', (e) =>
......@@ -32,12 +33,14 @@ class @UsersSelect
data[abilityName].assignee_id = selected
$loading
.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
dataType: 'json'
url: issueURL
data: data
).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
......@@ -51,11 +54,22 @@ class @UsersSelect
name: 'Unassigned'
username: ''
avatar: ''
$value.html(assigneeTemplate(user))
$collapsedSidebar.html(collapsedAssigneeTemplate(user))
$value.html(noAssigneeTemplate(user))
$value.find('a').attr('href')
noAssigneeTemplate = _.template(
collapsedAssigneeTemplate = _.template(
'<% if( avatar ) { %>
<a class="author_link" href="/u/<%= username %>">
<img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>">
<span class="author">Toni Boehm</span>
</a>
<% } else { %>
<i class="fa fa-user"></i>
<% } %>'
)
assigneeTemplate = _.template(
'<% if (username) { %>
<a class="author_link " href="/u/<%= username %>">
<% if( avatar ) { %>
......@@ -131,9 +145,10 @@ class @UsersSelect
hidden: (e) ->
$selectbox.hide()
$value.show()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
clicked: ->
clicked: (user) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
......@@ -141,6 +156,7 @@ class @UsersSelect
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()
......
......@@ -13,10 +13,10 @@
// Toggle between two states.
.js-toggler-container {
.turn-on { display: block; }
.turn-on { display: block; }
.turn-off { display: none; }
&.on {
.turn-on { display: none; }
.turn-on { display: none; }
.turn-off { display: block; }
}
}
.calender-block {
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
overflow-x: scroll;
}
}
.user-calendar-activities {
.calendar_onclick_hr {
padding: 0;
......
......@@ -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;
......@@ -141,6 +149,17 @@
line-height: 16px;
}
}
.dropdown-header {
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
padding: 0 10px 10px;
}
.separator + .dropdown-header {
padding-top: 2px;
}
}
.dropdown-menu-paging {
......@@ -158,6 +177,10 @@
.dropdown-menu-back {
display: block;
}
.dropdown-content {
padding: 0 10px;
}
}
}
......@@ -193,7 +216,7 @@
}
.dropdown-select {
width: 300px;
width: $dropdown-width;
}
.dropdown-menu-align-right {
......@@ -222,20 +245,11 @@
}
}
.dropdown-header {
padding-left: 5px;
padding-right: 5px;
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
}
.dropdown-title {
position: relative;
margin-bottom: 10px;
padding-left: 30px;
padding-right: 30px;
padding-bottom: 10px;
padding: 0 0 15px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
text-align: center;
......@@ -261,21 +275,26 @@
}
.dropdown-menu-close {
right: 0;
right: 7px;
width: 20px;
height: 20px;
top: -1px;
}
.dropdown-menu-back {
left: 0;
left: 7px;
top: 2px;
}
.dropdown-input {
position: relative;
margin-bottom: 10px;
padding: 0 10px;
.fa {
position: absolute;
top: 10px;
right: 10px;
right: 20px;
color: #c7c7c7;
font-size: 12px;
pointer-events: none;
......@@ -285,6 +304,9 @@
display: none;
cursor: pointer;
pointer-events: all;
right: 22px;
top: 9px;
font-size: 14px;
}
&.has-value {
......
......@@ -3,7 +3,7 @@
vertical-align: top;
}
@media (min-width: $screen-sm-min) {
@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
.dropdown-menu-toggle {
......
......@@ -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,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');
}
&.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;
}
......
......@@ -56,6 +56,17 @@
}
}
.nav-search {
display: inline-block;
width: 50%;
padding: 11px 0;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-links {
display: inline-block;
width: 50%;
......@@ -100,6 +111,7 @@
> form {
display: inline-block;
margin-top: -1px;
}
.icon-label {
......@@ -110,7 +122,7 @@
height: 34px;
display: inline-block;
position: relative;
top: 1px;
top: 2px;
margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
......
......@@ -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;
}
}
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;
......
......@@ -138,6 +138,12 @@
}
}
a.no-attachment-icon {
&:before {
display: none;
}
}
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
......
......@@ -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,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");
background-repeat: no-repeat;
background-position: right 5px center;
......
......@@ -42,7 +42,7 @@
}
}
.loading{
.loading {
font-size: 20px;
}
......
.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;
}
......
.commits-compare-switch{
.commits-compare-switch {
@include btn-default;
@include btn-white;
background: image-url("switch_icon.png") no-repeat center center;
......
.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 {
......
.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;
......
......@@ -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 {
float: right;
margin-left: 10px;
.discussion-actions,
.note-actions {
float: right;
margin-left: 10px;
color: $notes-action-color;
}
a {
margin-left: 5px;
color: $gl-gray;
.note-action-button,
.discussion-action-button {
display: inline-block;
margin-left: 10px;
line-height: 24px;
i.fa {
font-size: 16px;
line-height: 16px;
}
.fa {
position: relative;
top: 1px;
font-size: 17px;
}
&:hover {
@extend .cgray;
&.danger { @extend .cred; }
}
}
.fa-trash-o {
top: 0;
font-size: 16px;
}
}
.discussion-toggle-button {
line-height: 20px;
font-size: 13px;
.fa {
margin-right: 3px;
font-size: 10px;
line-height: 18px;
vertical-align: top;
}
}
.note-role {
position: relative;
top: -2px;
display: inline-block;
padding-left: 4px;
padding-right: 4px;
color: $notes-role-color;
font-size: 12px;
line-height: 20px;
border: 1px solid $notes-role-border-color;
border-radius: $border-radius-base;
}
.diff-file .note .note-actions {
right: 0;
top: 0;
......
......@@ -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,
......
......@@ -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
......
......@@ -57,8 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json { render json: @merge_request }
format.diff { render text: @merge_request.to_diff(current_user) }
format.patch { render text: @merge_request.to_patch(current_user) }
format.diff { render text: @merge_request.to_diff }
format.patch { render text: @merge_request.to_patch }
end
end
......@@ -154,7 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.target_project, @merge_request])
end
format.json do
render json: @merge_request.to_json(include: [:milestone, :labels, :assignee])
render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
end
end
else
......@@ -224,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch)
ci_commit = @merge_request.ci_commit
if ci_commit
status = ci_commit.status
coverage = ci_commit.try(:coverage)
else
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
end
end
response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
status: status,
coverage: coverage
}
......
......@@ -24,7 +24,7 @@ class Projects::MilestonesController < Projects::ApplicationController
@milestones = @milestones.page(params[:page])
end
format.json do
render json: @milestones
render json: @milestones.to_json(methods: :name)
end
end
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
......
......@@ -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
......@@ -47,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,8 +5,10 @@ module NotesHelper
end
def note_target_fields(note)
hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
hidden_field_tag(:target_id, note.noteable.id)
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)
......
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
......
......@@ -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') }
......@@ -469,7 +471,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
......@@ -590,7 +592,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
......@@ -929,16 +931,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
......@@ -362,6 +364,11 @@ class Repository
expire_tag_count_cache
end
def before_import
expire_emptiness_caches
expire_exists_cache
end
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
......@@ -612,10 +619,14 @@ class Repository
refs_contains_sha('tag', sha)
end
def branches
@branches ||= raw_repository.branches
def local_branches
@local_branches ||= rugged.branches.each(:local).map do |branch|
Gitlab::Git::Branch.new(branch.name, branch.target)
end
end
alias_method :branches, :local_branches
def tags
@tags ||= raw_repository.tags
end
......@@ -818,7 +829,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
......@@ -46,6 +46,8 @@ module Projects
def import_data
return unless has_importer?
project.repository.before_import
unless importer.execute
raise Error, 'The remote data could not be imported.'
end
......
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,
user_username: model.user.username,
user_name: model.user.name,
user_email: model.user.email,
user_id: model.user.id,
access_level: model.human_access,
project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
project_name: project.name,
project_path: project.path,
project_path_with_namespace: project.path_with_namespace,
project_id: project.id,
user_username: model.user.username,
user_name: model.user.name,
user_email: model.user.email,
user_id: model.user.id,
access_level: model.human_access,
project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase
}
end
......
......@@ -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))
......
# 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
......
......@@ -15,7 +15,7 @@
%td
- if project
= link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace"
= link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
......
.admin-dashboard
.admin-dashboard.prepend-top-default
.row
.col-md-4
%h4 Statistics
......
- page_title "Deploy Keys"
.panel.panel-default
.panel.panel-default.prepend-top-default
.panel-heading
Public deploy keys (#{@deploy_keys.count})
.controls
......
- css_class = '' unless local_assigns[:css_class]
- css_class += ' no-description' if group.description.blank?
%li.group-row{ class: css_class }
.controls.hidden-xs
= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm'
= link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove'
.stats
%span
= icon('bookmark')
= number_with_delimiter(group.projects.count)
%span
= icon('users')
= number_with_delimiter(group.users.count)
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
= image_tag group_icon(group), class: 'avatar s40 hidden-xs'
.title
= link_to [:admin, group], class: 'group-name' do
= group.name
- if group.description.present?
.description
= markdown(group.description, pipeline: :description)
- page_title "Groups"
%h3.page-title
Groups (#{number_with_delimiter(@groups.total_count)})
= link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%p.light
Group allows you to keep projects organized.
Use groups for uniting related projects.
%hr
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
= hidden_field_tag :sort, @sort
.form-group
= text_field_tag :name, params[:name], class: "form-control"
= button_tag "Search", class: "btn submit btn-primary"
.top-area
.nav-search
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
= hidden_field_tag :sort, @sort
= text_field_tag :name, params[:name], class: "form-control"
= button_tag "Search", class: "btn submit btn-primary"
.pull-right
.nav-controls
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light
......@@ -33,34 +32,10 @@
= sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
%hr
%ul.bordered-list
%ul.content-list
- @groups.each do |group|
%li
.clearfix
.pull-right.prepend-top-10
= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-sm"
= link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-sm btn-remove"
%h4
= link_to [:admin, group] do
%span{ class: visibility_level_color(group.visibility_level) }
= visibility_level_icon(group.visibility_level)
%i.fa.fa-folder
= group.name
&rarr;
%span.monospace
%strong #{group.path}/
.clearfix
%p
= truncate group.description, length: 150
.clearfix
%p.light
#{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
= render 'group', group: group
= paginate @groups, theme: "gitlab"
- page_title "Labels"
= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
New label
%h3.page-title
Labels
%div
= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
New label
%h3.page-title
Labels
%hr
.labels
......@@ -13,4 +15,4 @@
- else
.light-well
.nothing-here-block There are no labels yet
%p.lead
%p.lead.prepend-top-default
%span
To register a new runner you should enter the following registration token.
With this token the runner will request a unique runner token and use that for future communication.
......
%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} }
.todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
......@@ -10,7 +10,10 @@
(removed)
%span.todo-label
= todo_action_name(todo)
= todo_target_link(todo)
- if todo.target
= todo_target_link(todo)
- else
(removed)
&middot; #{time_ago_with_tooltip(todo.created_at)}
......
- 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"}
- 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"
.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
= render "layouts/nav/#{sidebar}"
- elsif current_user
= render 'layouts/nav/dashboard'
- 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}"
- 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'
......
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1"
- if controller.controller_path =~ /^groups/
- 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)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
- if @project && @project.persisted?
- if current_controller?(:issues)
= hidden_field_tag :scope, 'issues'
- elsif current_controller?(:merge_requests)
......@@ -21,10 +44,3 @@
= hidden_field_tag :repository_ref, @ref
= button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
:javascript
$('.search-input').on('keyup', function(e) {
if (e.keyCode == 27) {
$('.search-input').blur();
}
});
......@@ -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')
......
%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
......
%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')
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment