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: ...@@ -7,21 +7,44 @@ exclude:
- 'app/assets/stylesheets/pages/emojis.scss' - 'app/assets/stylesheets/pages/emojis.scss'
linters: linters:
# Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags.
BangFormat: BangFormat:
enabled: false enabled: false
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero: BorderZero:
enabled: false 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: ColorKeyword:
enabled: false 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: ColorVariable:
enabled: false enabled: false
# Which form of comments to prefer in CSS.
Comment: Comment:
enabled: false 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: DeclarationOrder:
enabled: false enabled: false
...@@ -32,15 +55,25 @@ linters: ...@@ -32,15 +55,25 @@ linters:
DisableLinterReason: DisableLinterReason:
enabled: true enabled: true
# Reports when you define the same property twice in a single rule set.
DuplicateProperty: DuplicateProperty:
enabled: false enabled: false
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks: EmptyLineBetweenBlocks:
enabled: false enabled: false
# Reports when you have an empty rule set.
EmptyRule: EmptyRule:
enabled: false 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: FinalNewline:
enabled: false enabled: false
...@@ -53,12 +86,17 @@ linters: ...@@ -53,12 +86,17 @@ linters:
HexNotation: HexNotation:
enabled: true enabled: true
# Avoid using ID selectors.
IdSelector: IdSelector:
enabled: false enabled: false
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath: ImportPath:
enabled: false enabled: false
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule: ImportantRule:
enabled: false enabled: false
...@@ -67,33 +105,51 @@ linters: ...@@ -67,33 +105,51 @@ linters:
enabled: true enabled: true
width: 2 width: 2
# Don't write leading zeros for numeric values with a decimal point.
LeadingZero: LeadingZero:
enabled: false enabled: false
# Reports when you define the same selector twice in a single sheet.
MergeableSelector: MergeableSelector:
enabled: false enabled: false
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
NameFormat: NameFormat:
enabled: false enabled: false
# Avoid nesting selectors too deeply.
NestingDepth: NestingDepth:
enabled: false enabled: false
# Always use placeholder selectors in @extend.
PlaceholderInExtend: PlaceholderInExtend:
enabled: false enabled: false
# Sort properties in a strict order.
PropertySortOrder: PropertySortOrder:
enabled: false enabled: false
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling: PropertySpelling:
enabled: false 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: PseudoElement:
enabled: false enabled: false
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement: QualifyingElement:
enabled: false enabled: false
# Don't write selectors with a depth of applicability greater than 3.
SelectorDepth: SelectorDepth:
enabled: false enabled: false
...@@ -113,9 +169,12 @@ linters: ...@@ -113,9 +169,12 @@ linters:
enabled: true enabled: true
allow_single_line_rule_sets: 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: SingleLinePerSelector:
enabled: false enabled: false
# Commas in lists should be followed by a space.
SpaceAfterComma: SpaceAfterComma:
enabled: false enabled: false
...@@ -129,27 +188,74 @@ linters: ...@@ -129,27 +188,74 @@ linters:
SpaceAfterPropertyName: SpaceAfterPropertyName:
enabled: true 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: SpaceAroundOperator:
enabled: false enabled: false
# Opening braces should be preceded by a single space.
SpaceBeforeBrace: SpaceBeforeBrace:
enabled: true
# Parentheses should not be padded with spaces.
SpaceBetweenParens:
enabled: false enabled: false
# Enforces that string literals should be written with a consistent form
# of quotes (single or double).
StringQuotes: StringQuotes:
enabled: false enabled: false
# Property values, @extend, @include, and @import directives, and variable
# declarations should always end with a semicolon.
TrailingSemicolon: TrailingSemicolon:
enabled: false enabled: false
# Reports lines containing trailing whitespace.
TrailingWhitespace: TrailingWhitespace:
enabled: false 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: UnnecessaryMantissa:
enabled: false enabled: false
# Do not use parent selector references (&) when they would otherwise
# be unnecessary.
UnnecessaryParentReference: UnnecessaryParentReference:
enabled: false 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: VendorPrefix:
enabled: false enabled: false
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased) 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) - 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 - Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu) - Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API - Allow back dating on issues when created through the API
- Fix avatar stretching by providing a cropping feature - 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 - 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 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Implement 'TODOs 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 - Allow issues and merge requests to be assigned to the author
...@@ -14,6 +20,49 @@ v 8.7.0 (unreleased) ...@@ -14,6 +20,49 @@ v 8.7.0 (unreleased)
v 8.6.2 (unreleased) v 8.6.2 (unreleased)
- Comments on confidential issues don't show up in activity feed to non-members - Comments on confidential issues don't show up in activity feed to non-members
- Fix NoMethodError when visiting CI root path at `/ci` - 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 v 8.6.1
- Add option to reload the schema before restoring a database backup. !2807 - Add option to reload the schema before restoring a database backup. !2807
...@@ -86,6 +135,7 @@ v 8.6.0 ...@@ -86,6 +135,7 @@ v 8.6.0
- Add main language of a project in the list of projects (Tiago Botelho) - Add main language of a project in the list of projects (Tiago Botelho)
- Add #upcoming filter to Milestone filter (Tiago Botelho) - Add #upcoming filter to Milestone filter (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages - 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 - Move group activity to separate page
- Create external users which are excluded of internal and private projects unless access was explicitly granted - 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 - Continue parameters are checked to ensure redirection goes to the same instance
......
...@@ -214,7 +214,7 @@ gem 'jquery-rails', '~> 4.0.0' ...@@ -214,7 +214,7 @@ gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3' gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0' gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2' 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 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1' gem 'net-ssh', '~> 3.0.1'
......
...@@ -126,9 +126,9 @@ GEM ...@@ -126,9 +126,9 @@ GEM
coderay (1.1.0) coderay (1.1.0)
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
coffee-rails (4.1.0) coffee-rails (4.1.1)
coffee-script (>= 2.2.0) 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 (2.4.1)
coffee-script-source coffee-script-source
execjs execjs
...@@ -652,7 +652,7 @@ GEM ...@@ -652,7 +652,7 @@ GEM
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-store (1.1.7) redis-store (1.1.7)
redis (>= 2.2) redis (>= 2.2)
request_store (1.2.1) request_store (1.3.0)
rerun (0.11.0) rerun (0.11.0)
listen (~> 3.0) listen (~> 3.0)
responders (2.1.1) responders (2.1.1)
...@@ -1011,7 +1011,7 @@ DEPENDENCIES ...@@ -1011,7 +1011,7 @@ DEPENDENCIES
redcarpet (~> 3.3.3) redcarpet (~> 3.3.3)
redis-namespace redis-namespace
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.2.0) request_store (~> 1.3.0)
rerun (~> 0.11.0) rerun (~> 0.11.0)
responders (~> 2.0) responders (~> 2.0)
rouge (~> 1.10.1) rouge (~> 1.10.1)
......
class @AwardsHandler 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) => $(".js-add-award").on "click", (event) =>
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()
...@@ -34,7 +34,7 @@ class @AwardsHandler ...@@ -34,7 +34,7 @@ class @AwardsHandler
$("#emoji_search").focus() $("#emoji_search").focus()
else else
$('.js-add-award').addClass "is-loading" $('.js-add-award').addClass "is-loading"
$.get "/emojis", (response) => $.get @get_emojis_url, (response) =>
$('.js-add-award').removeClass "is-loading" $('.js-add-award').removeClass "is-loading"
$(".js-award-holder").append response $(".js-award-holder").append response
setTimeout => setTimeout =>
......
...@@ -146,15 +146,11 @@ class Dispatcher ...@@ -146,15 +146,11 @@ class Dispatcher
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
# If we haven't installed a custom shortcut handler, install the default one # If we haven't installed a custom shortcut handler, install the default one
if not shortcut_handler if not shortcut_handler
new Shortcuts() new Shortcuts()
initSearch: -> 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 ...@@ -3,6 +3,10 @@ class GitLabDropdownFilter
HAS_VALUE_CLASS = "has-value" HAS_VALUE_CLASS = "has-value"
constructor: (@input, @options) -> constructor: (@input, @options) ->
{
@filterInputBlur = true
} = @options
$inputContainer = @input.parent() $inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear') $clearButton = $inputContainer.find('.js-dropdown-input-clear')
...@@ -33,7 +37,7 @@ class GitLabDropdownFilter ...@@ -33,7 +37,7 @@ class GitLabDropdownFilter
blur_field = @shouldBlur e.keyCode blur_field = @shouldBlur e.keyCode
search_text = @input.val() search_text = @input.val()
if blur_field if blur_field and @filterInputBlur
@input.blur() @input.blur()
if @options.remote if @options.remote
...@@ -93,12 +97,34 @@ class GitLabDropdown ...@@ -93,12 +97,34 @@ class GitLabDropdown
PAGE_TWO_CLASS = "is-page-two" PAGE_TWO_CLASS = "is-page-two"
ACTIVE_CLASS = "is-active" ACTIVE_CLASS = "is-active"
FILTER_INPUT = '.dropdown-input .dropdown-input-field'
constructor: (@el, @options) -> constructor: (@el, @options) ->
self = @
@dropdown = $(@el).parent() @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 []; search_fields = if @options.search then @options.search.fields else [];
if @options.data if @options.data
# If data is an array
if _.isArray @options.data
@fullData = @options.data
@parseData @options.data
else
# Remote data # Remote data
@remote = new GitLabDropdownRemote @options.data, { @remote = new GitLabDropdownRemote @options.data, {
dataType: @options.dataType, dataType: @options.dataType,
...@@ -109,11 +135,10 @@ class GitLabDropdown ...@@ -109,11 +135,10 @@ class GitLabDropdown
@parseData @fullData @parseData @fullData
} }
# Init filiterable # Init filterable
if @options.filterable if @options.filterable
@input = @dropdown.find('.dropdown-input .dropdown-input-field') @filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
@filter = new GitLabDropdownFilter @input,
remote: @options.filterRemote remote: @options.filterRemote
query: @options.data query: @options.data
keys: @options.search.fields keys: @options.search.fields
...@@ -123,6 +148,7 @@ class GitLabDropdown ...@@ -123,6 +148,7 @@ class GitLabDropdown
@parseData data @parseData data
@highlightRow 1 @highlightRow 1
enterCallback: => enterCallback: =>
if @enterCallback
@selectFirstRow() @selectFirstRow()
# Event listeners # Event listeners
...@@ -145,11 +171,14 @@ class GitLabDropdown ...@@ -145,11 +171,14 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a" selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) -> @dropdown.on "click", selector, (e) ->
e.preventDefault() selected = self.rowClicked $(@)
self.rowClicked $(@)
if self.options.clicked 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: -> toggleLoading: ->
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
...@@ -194,7 +223,9 @@ class GitLabDropdown ...@@ -194,7 +223,9 @@ class GitLabDropdown
@remote.execute() @remote.execute()
if @options.filterable if @options.filterable
@dropdown.find(".dropdown-input-field").focus() @filterInput.focus()
@dropdown.trigger('shown.gl.dropdown')
hidden: (e) => hidden: (e) =>
if @options.filterable if @options.filterable
...@@ -210,6 +241,8 @@ class GitLabDropdown ...@@ -210,6 +241,8 @@ class GitLabDropdown
if @options.hidden if @options.hidden
@options.hidden.call(@,e) @options.hidden.call(@,e)
@dropdown.trigger('hidden.gl.dropdown')
# Render the full menu # Render the full menu
renderMenu: (html) -> renderMenu: (html) ->
...@@ -234,13 +267,19 @@ class GitLabDropdown ...@@ -234,13 +267,19 @@ class GitLabDropdown
renderItem: (data) -> renderItem: (data) ->
html = "" html = ""
# Divider
return "<li class='divider'></li>" if data is "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 if @options.renderRow
# Call the render function # Call the render function
html = @options.renderRow(data) html = @options.renderRow(data)
else else
selected = if @options.isSelected then @options.isSelected(data) else false
if not selected if not selected
value = if @options.id then @options.id(data) else data.id value = if @options.id then @options.id(data) else data.id
fieldName = @options.fieldName fieldName = @options.fieldName
...@@ -248,13 +287,26 @@ class GitLabDropdown ...@@ -248,13 +287,26 @@ class GitLabDropdown
if field.length if field.length
selected = true selected = true
url = if @options.url then @options.url(data) else "#" # Set URL
text = if @options.text then @options.text(data) else "" 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 = ""; cssClass = "";
if selected if selected
cssClass = "is-active" cssClass = "is-active"
if @highlight
text = @highlightTextMatches(text, @filterInput.val())
html = "<li>" html = "<li>"
html += "<a href='#{url}' class='#{cssClass}'>" html += "<a href='#{url}' class='#{cssClass}'>"
html += text html += text
...@@ -263,20 +315,26 @@ class GitLabDropdown ...@@ -263,20 +315,26 @@ class GitLabDropdown
return html 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: -> noResults: ->
html = "<li>" 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 += "No matching results."
html += "</a>" html += "</a>"
html += "</li>" html += "</li>"
highlightRow: (index) -> highlightRow: (index) ->
if @input.val() isnt "" if @filterInput.val() isnt ""
selector = '.dropdown-content li:first-child a' selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a" selector = ".dropdown-page-one .dropdown-content li:first-child a"
$(selector).addClass 'is-focused' @getElement(selector).addClass 'is-focused'
rowClicked: (el) -> rowClicked: (el) ->
fieldName = @options.fieldName fieldName = @options.fieldName
...@@ -285,17 +343,15 @@ class GitLabDropdown ...@@ -285,17 +343,15 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex] selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS) if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS) el.removeClass(ACTIVE_CLASS)
field.remove() 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? if !value?
field.remove() field.remove()
...@@ -304,7 +360,7 @@ class GitLabDropdown ...@@ -304,7 +360,7 @@ class GitLabDropdown
@dropdown.parent().find("input[name='#{fieldName}']").remove() @dropdown.parent().find("input[name='#{fieldName}']").remove()
# Toggle active class for the tick mark # Toggle active class for the tick mark
el.toggleClass "is-active" el.addClass ACTIVE_CLASS
# Toggle the dropdown label # Toggle the dropdown label
if @options.toggleLabel if @options.toggleLabel
...@@ -317,6 +373,10 @@ class GitLabDropdown ...@@ -317,6 +373,10 @@ class GitLabDropdown
input = $(input) input = $(input)
.attr('id', @options.inputId) .attr('id', @options.inputId)
@dropdown.before input @dropdown.before input
else
field.val value
return selectedObject
selectFirstRow: -> selectFirstRow: ->
selector = '.dropdown-content li:first-child a' selector = '.dropdown-content li:first-child a'
...@@ -328,4 +388,6 @@ class GitLabDropdown ...@@ -328,4 +388,6 @@ class GitLabDropdown
$.fn.glDropdown = (opts) -> $.fn.glDropdown = (opts) ->
return @.each -> return @.each ->
new GitLabDropdown @, opts if (!$.data @, 'glDropdown')
$.data(@, 'glDropdown', new GitLabDropdown @, opts)
...@@ -9,7 +9,7 @@ class @IssuableContext ...@@ -9,7 +9,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit() $(this).submit()
$(document).on "click",".edit-link", (e) -> $(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
$block = $(@).parents('.block') $block = $(@).parents('.block')
$selectbox = $block.find('.selectbox') $selectbox = $block.find('.selectbox')
if $selectbox.is(':visible') if $selectbox.is(':visible')
......
@Issues = @Issues =
init: -> init: ->
Issues.initSearch() Issues.initSearch()
Issues.initSelects()
Issues.initChecks() Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", -> $("body").on "ajax:success", ".close_issue, .reopen_issue", ->
...@@ -17,18 +16,9 @@ ...@@ -17,18 +16,9 @@
$(this).html totalIssues - 1 $(this).html totalIssues - 1
reload: -> reload: ->
Issues.initSelects()
Issues.initChecks() Issues.initChecks()
$('#filter_issue_search').val($('#issue_search').val()) $('#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: -> initChecks: ->
$(".check_all_issues").click -> $(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked) $(".selected_issue").prop("checked", @checked)
......
...@@ -16,6 +16,7 @@ class @LabelsSelect ...@@ -16,6 +16,7 @@ class @LabelsSelect
abilityName = $dropdown.data('ability-name') abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox') $selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block') $block = $selectbox.closest('.block')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value') $value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut() $loading = $block.find('.block-loading').fadeOut()
...@@ -142,6 +143,7 @@ class @LabelsSelect ...@@ -142,6 +143,7 @@ class @LabelsSelect
if not selected.length if not selected.length
data[abilityName].label_ids = [''] data[abilityName].label_ids = ['']
$loading.fadeIn() $loading.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax( $.ajax(
type: 'PUT' type: 'PUT'
url: issueUpdateURL url: issueUpdateURL
...@@ -149,15 +151,20 @@ class @LabelsSelect ...@@ -149,15 +151,20 @@ class @LabelsSelect
data: data data: data
).done (data) -> ).done (data) ->
$loading.fadeOut() $loading.fadeOut()
$dropdown.trigger('loaded.gl.dropdown')
$selectbox.hide() $selectbox.hide()
data.issueURLSplit = issueURLSplit data.issueURLSplit = issueURLSplit
if not data.labels.length labelCount = 0
template = labelNoneHTMLTemplate() if data.labels.length
else
template = labelHTMLTemplate(data) template = labelHTMLTemplate(data)
href = $value labelCount = data.labels.length
.show() else
template = labelNoneHTMLTemplate()
$value
.removeAttr('style')
.html(template) .html(template)
$sidebarCollapsedValue.text(labelCount)
$value $value
.find('a') .find('a')
.each((i) -> .each((i) ->
...@@ -226,18 +233,20 @@ class @LabelsSelect ...@@ -226,18 +233,20 @@ class @LabelsSelect
hidden: -> hidden: ->
$selectbox.hide() $selectbox.hide()
$value.show() # display:block overrides the hide-collapse rule
$value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect' if $dropdown.hasClass 'js-multiselect'
saveLabelData() saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect' multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
clicked: ->
page = $('body').data 'page' page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index' isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index' isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedLabel = label.title
Issues.filterResults $dropdown.closest('form') Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit' else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit() $dropdown.closest('form').submit()
......
((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 ...@@ -2,13 +2,18 @@ class @MergeRequestWidget
# Initialize MergeRequestWidget behavior # Initialize MergeRequestWidget behavior
# #
# check_enable - Boolean, whether to check automerge status # check_enable - Boolean, whether to check automerge status
# url_to_automerge_check - String, URL to use to check automerge status # merge_check_url - String, URL to use to check automerge status
# current_status - String, current automerge status # ci_status_url - String, URL to use to check CI status
# ci_enable - Boolean, whether a CI service is enabled
# url_to_ci_check - String, URL to use to check CI status
# #
constructor: (@opts) -> 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)-> mergeInProgress: (deleteSourceBranch = false)->
$.ajax $.ajax
...@@ -27,18 +32,57 @@ class @MergeRequestWidget ...@@ -27,18 +32,57 @@ class @MergeRequestWidget
dataType: 'json' dataType: 'json'
getMergeStatus: -> getMergeStatus: ->
$.get @opts.url_to_automerge_check, (data) -> $.get @opts.merge_check_url, (data) ->
$('.mr-state-widget').replaceWith(data) $('.mr-state-widget').replaceWith(data)
getCiStatus: -> ciLabelForStatus: (status) ->
if @opts.ci_enable if status == 'success'
$.get @opts.url_to_ci_check, (data) => 'passed'
this.showCiState data.status 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 if data.coverage
this.showCiCoverage data.coverage @showCICoverage data.coverage
, 'json'
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() $('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"] allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states if state in allowed_states
...@@ -52,7 +96,7 @@ class @MergeRequestWidget ...@@ -52,7 +96,7 @@ class @MergeRequestWidget
$('.ci_widget.ci-error').show() $('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger') @setMergeButtonClass('btn-danger')
showCiCoverage: (coverage) -> showCICoverage: (coverage) ->
text = 'Coverage ' + coverage + '%' text = 'Coverage ' + coverage + '%'
$('.ci_widget:visible .ci-coverage').text(text) $('.ci_widget:visible .ci-coverage').text(text)
......
...@@ -11,12 +11,14 @@ class @MilestoneSelect ...@@ -11,12 +11,14 @@ class @MilestoneSelect
selectedMilestone = $dropdown.data('selected') selectedMilestone = $dropdown.data('selected')
showNo = $dropdown.data('show-no') showNo = $dropdown.data('show-no')
showAny = $dropdown.data('show-any') showAny = $dropdown.data('show-any')
showUpcoming = $dropdown.data('show-upcoming')
useId = $dropdown.data('use-id') useId = $dropdown.data('use-id')
defaultLabel = $dropdown.data('default-label') defaultLabel = $dropdown.data('default-label')
issuableId = $dropdown.data('issuable-id') issuableId = $dropdown.data('issuable-id')
abilityName = $dropdown.data('ability-name') abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox') $selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block') $block = $selectbox.closest('.block')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon')
$value = $block.find('.value') $value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut() $loading = $block.find('.block-loading').fadeOut()
...@@ -32,22 +34,32 @@ class @MilestoneSelect ...@@ -32,22 +34,32 @@ class @MilestoneSelect
$.ajax( $.ajax(
url: milestonesUrl url: milestonesUrl
).done (data) -> ).done (data) ->
if $dropdown.hasClass "js-extra-options" extraOptions = []
if showAny
extraOptions.push(
id: 0
name: ''
title: 'Any Milestone'
)
if showNo if showNo
data.unshift( extraOptions.push(
id: '0' id: -1
name: 'No Milestone'
title: 'No Milestone' title: 'No Milestone'
) )
if showAny if showUpcoming
data.unshift( extraOptions.push(
isAny: true id: -2
title: 'Any Milestone' name: '#upcoming'
title: 'Upcoming'
) )
if data.length > 2 if extraOptions.length > 2
data.splice 2, 0, 'divider' extraOptions.push 'divider'
callback(data)
callback(extraOptions.concat(data))
filterable: true filterable: true
search: search:
fields: ['title'] fields: ['title']
...@@ -62,23 +74,26 @@ class @MilestoneSelect ...@@ -62,23 +74,26 @@ class @MilestoneSelect
milestone.title milestone.title
id: (milestone) -> id: (milestone) ->
if !useId if !useId
if !milestone.isAny? milestone.name
milestone.title
else
''
else else
milestone.id milestone.id
isSelected: (milestone) -> isSelected: (milestone) ->
milestone.title is selectedMilestone milestone.name is selectedMilestone
hidden: -> hidden: ->
$selectbox.hide() $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' if $dropdown.hasClass 'js-filter-bulk-update'
return return
if $dropdown.hasClass 'js-filter-submit' if $dropdown.hasClass('js-filter-submit')
$dropdown.parents('form').submit() if selected.name?
selectedMilestone = selected.name
else
selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
else else
selected = $selectbox selected = $selectbox
.find('input[type="hidden"]') .find('input[type="hidden"]')
...@@ -88,20 +103,22 @@ class @MilestoneSelect ...@@ -88,20 +103,22 @@ class @MilestoneSelect
data[abilityName].milestone_id = selected data[abilityName].milestone_id = selected
$loading $loading
.fadeIn() .fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax( $.ajax(
type: 'PUT' type: 'PUT'
url: issueUpdateURL url: issueUpdateURL
data: data data: data
).done (data) -> ).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut() $loading.fadeOut()
$selectbox.hide() $selectbox.hide()
$milestoneLink = $value $value.removeAttr('style')
.show()
.find('a')
if data.milestone? if data.milestone?
data.milestone.namespace = _this.currentProject.namespace data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path data.milestone.path = _this.currentProject.path
$value.html(milestoneLinkTemplate(data.milestone)) $value.html(milestoneLinkTemplate(data.milestone))
$sidebarCollapsedValue.find('span').text(data.milestone.title)
else else
$value.html(milestoneLinkNoneTemplate) $value.html(milestoneLinkNoneTemplate)
$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 class @SearchAutocomplete
constructor: (search_autocomplete_path, project_id, project_ref) ->
project_id = '' unless project_id KEYCODE =
project_ref = '' unless project_ref ESCAPE: 27
query = "?project_id=" + project_id + "&project_ref=" + project_ref BACKSPACE: 8
ENTER: 13
$("#search").autocomplete
source: search_autocomplete_path + query constructor: (opts = {}) ->
minLength: 1 {
select: (event, ui) -> @wrap = $('.search')
location.href = ui.item.url
@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' ...@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = -> toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-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: '/' }) $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( -> setTimeout ( ->
......
...@@ -6,10 +6,12 @@ class @Todos ...@@ -6,10 +6,12 @@ class @Todos
clearListeners: -> clearListeners: ->
$('.done-todo').off('click') $('.done-todo').off('click')
$('.js-todos-mark-all').off('click') $('.js-todos-mark-all').off('click')
$('.todo').off('click')
initBtnListeners: -> initBtnListeners: ->
$('.done-todo').on('click', @doneClicked) $('.done-todo').on('click', @doneClicked)
$('.js-todos-mark-all').on('click', @allDoneClicked) $('.js-todos-mark-all').on('click', @allDoneClicked)
$('.todo').on('click', @goToTodoUrl)
doneClicked: (e) => doneClicked: (e) =>
e.preventDefault() e.preventDefault()
...@@ -54,3 +56,6 @@ class @Todos ...@@ -54,3 +56,6 @@ class @Todos
updateBadges: (data) -> updateBadges: (data) ->
$('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count $('.todos-done .badge').text data.done_count
goToTodoUrl: ->
Turbolinks.visit($(this).data('url'))
...@@ -19,6 +19,7 @@ class @UsersSelect ...@@ -19,6 +19,7 @@ class @UsersSelect
$block = $selectbox.closest('.block') $block = $selectbox.closest('.block')
abilityName = $dropdown.data('ability-name') abilityName = $dropdown.data('ability-name')
$value = $block.find('.value') $value = $block.find('.value')
$collapsedSidebar = $block.find('.sidebar-collapsed-user')
$loading = $block.find('.block-loading').fadeOut() $loading = $block.find('.block-loading').fadeOut()
$block.on('click', '.js-assign-yourself', (e) => $block.on('click', '.js-assign-yourself', (e) =>
...@@ -32,12 +33,14 @@ class @UsersSelect ...@@ -32,12 +33,14 @@ class @UsersSelect
data[abilityName].assignee_id = selected data[abilityName].assignee_id = selected
$loading $loading
.fadeIn() .fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax( $.ajax(
type: 'PUT' type: 'PUT'
dataType: 'json' dataType: 'json'
url: issueURL url: issueURL
data: data data: data
).done (data) -> ).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut() $loading.fadeOut()
$selectbox.hide() $selectbox.hide()
...@@ -51,11 +54,22 @@ class @UsersSelect ...@@ -51,11 +54,22 @@ class @UsersSelect
name: 'Unassigned' name: 'Unassigned'
username: '' username: ''
avatar: '' 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) { %> '<% if (username) { %>
<a class="author_link " href="/u/<%= username %>"> <a class="author_link " href="/u/<%= username %>">
<% if( avatar ) { %> <% if( avatar ) { %>
...@@ -131,9 +145,10 @@ class @UsersSelect ...@@ -131,9 +145,10 @@ class @UsersSelect
hidden: (e) -> hidden: (e) ->
$selectbox.hide() $selectbox.hide()
$value.show() # display:block overrides the hide-collapse rule
$value.removeAttr('style')
clicked: -> clicked: (user) ->
page = $('body').data 'page' page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index' isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index' isMRIndex = page is page is 'projects:merge_requests:index'
...@@ -141,6 +156,7 @@ class @UsersSelect ...@@ -141,6 +156,7 @@ class @UsersSelect
return return
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedId = user.id
Issues.filterResults $dropdown.closest('form') Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit' else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit() $dropdown.closest('form').submit()
......
.calender-block {
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
overflow-x: scroll;
}
}
.user-calendar-activities { .user-calendar-activities {
.calendar_onclick_hr { .calendar_onclick_hr {
padding: 0; padding: 0;
......
...@@ -121,7 +121,7 @@ p.time { ...@@ -121,7 +121,7 @@ p.time {
text-shadow: none; text-shadow: none;
} }
.thin_area{ .thin_area {
height: 150px; height: 150px;
} }
...@@ -148,7 +148,7 @@ li.note { ...@@ -148,7 +148,7 @@ li.note {
} }
} }
.wiki_content code, .readme code{ .wiki_content code, .readme code {
background-color: inherit; background-color: inherit;
} }
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
font-size: 15px; font-size: 15px;
text-align: left; text-align: left;
border: 1px solid $dropdown-toggle-border-color; border: 1px solid $dropdown-toggle-border-color;
border-radius: 2px; border-radius: $dropdown-border-radius;
outline: 0; outline: 0;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
...@@ -75,12 +75,12 @@ ...@@ -75,12 +75,12 @@
width: 240px; width: 240px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
padding: 10px; font-size: 15px;
font-size: 14px;
font-weight: normal; font-weight: normal;
padding: 10px 0;
background-color: $dropdown-bg; background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color; border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base; border-radius: $dropdown-border-radius;
box-shadow: 0 2px 4px $dropdown-shadow-color; box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading { &.is-loading {
...@@ -101,9 +101,17 @@ ...@@ -101,9 +101,17 @@
li { li {
text-align: left; text-align: left;
list-style: none; list-style: none;
padding: 0 10px;
} }
.divider { .divider {
height: 1px;
margin: 8px 10px;
padding: 0;
background-color: $dropdown-divider-color;
}
.separator {
width: 100%; width: 100%;
height: 1px; height: 1px;
margin-top: 8px; margin-top: 8px;
...@@ -141,6 +149,17 @@ ...@@ -141,6 +149,17 @@
line-height: 16px; 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 { .dropdown-menu-paging {
...@@ -158,6 +177,10 @@ ...@@ -158,6 +177,10 @@
.dropdown-menu-back { .dropdown-menu-back {
display: block; display: block;
} }
.dropdown-content {
padding: 0 10px;
}
} }
} }
...@@ -193,7 +216,7 @@ ...@@ -193,7 +216,7 @@
} }
.dropdown-select { .dropdown-select {
width: 300px; width: $dropdown-width;
} }
.dropdown-menu-align-right { .dropdown-menu-align-right {
...@@ -222,20 +245,11 @@ ...@@ -222,20 +245,11 @@
} }
} }
.dropdown-header {
padding-left: 5px;
padding-right: 5px;
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
}
.dropdown-title { .dropdown-title {
position: relative; position: relative;
margin-bottom: 10px; padding: 0 0 15px;
padding-left: 30px; margin: 0 10px 10px;
padding-right: 30px;
padding-bottom: 10px;
font-weight: 600; font-weight: 600;
line-height: 1; line-height: 1;
text-align: center; text-align: center;
...@@ -261,21 +275,26 @@ ...@@ -261,21 +275,26 @@
} }
.dropdown-menu-close { .dropdown-menu-close {
right: 0; right: 7px;
width: 20px;
height: 20px;
top: -1px;
} }
.dropdown-menu-back { .dropdown-menu-back {
left: 0; left: 7px;
top: 2px;
} }
.dropdown-input { .dropdown-input {
position: relative; position: relative;
margin-bottom: 10px; margin-bottom: 10px;
padding: 0 10px;
.fa { .fa {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 20px;
color: #c7c7c7; color: #c7c7c7;
font-size: 12px; font-size: 12px;
pointer-events: none; pointer-events: none;
...@@ -285,6 +304,9 @@ ...@@ -285,6 +304,9 @@
display: none; display: none;
cursor: pointer; cursor: pointer;
pointer-events: all; pointer-events: all;
right: 22px;
top: 9px;
font-size: 14px;
} }
&.has-value { &.has-value {
......
...@@ -6,40 +6,6 @@ input { ...@@ -6,40 +6,6 @@ input {
border-radius: $border-radius-base; 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 { input[type='text'].danger {
background: #f2dede!important; background: #f2dede!important;
border-color: #d66; border-color: #d66;
...@@ -125,7 +91,7 @@ label { ...@@ -125,7 +91,7 @@ label {
} }
.form-control::-webkit-input-placeholder { .form-control::-webkit-input-placeholder {
color: #7f8fa4; color: $gl-placeholder-color;
} }
.input-group { .input-group {
......
...@@ -33,10 +33,15 @@ ...@@ -33,10 +33,15 @@
background: $color; background: $color;
} }
.complex-sidebar .nav-primary {
border-right: 1px solid lighten($color, 3%);
}
.sidebar-wrapper { .sidebar-wrapper {
background: $color-darker; background: $color-darker;
.sidebar-user { .sidebar-user {
border-top: 1px solid lighten($color, 3%);
background: $color-darker; background: $color-darker;
color: $color-light; color: $color-light;
...@@ -62,7 +67,6 @@ ...@@ -62,7 +67,6 @@
.count { .count {
color: $color-light; color: $color-light;
background: $color-dark;
} }
} }
......
...@@ -36,7 +36,7 @@ header { ...@@ -36,7 +36,7 @@ header {
padding: 0; padding: 0;
.nav > li > a { .nav > li > a {
color: #7f8fa4; color: $gl-icon-color;
font-size: 18px; font-size: 18px;
padding: 0; padding: 0;
margin: ($header-height - 28) / 2 0; margin: ($header-height - 28) / 2 0;
...@@ -62,7 +62,7 @@ header { ...@@ -62,7 +62,7 @@ header {
background-color: #eee; background-color: #eee;
} }
&.active { &.active {
color: #7f8fa4; color: $gl-icon-color;
} }
} }
} }
...@@ -81,14 +81,14 @@ header { ...@@ -81,14 +81,14 @@ header {
font-size: 19px; font-size: 19px;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
color: #4c4e54; color: $gl-text-color;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: top; vertical-align: top;
white-space: nowrap; white-space: nowrap;
a { a {
color: #4c4e54; color: $gl-text-color;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
} }
...@@ -117,37 +117,17 @@ header { ...@@ -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 { .impersonation i {
color: $red-normal; color: $red-normal;
} }
} }
@mixin collapsed-header { @mixin collapsed-header {
margin-left: $sidebar_collapsed_width; margin-left: 40px;
} }
.header-collapsed { .header-collapsed {
margin-left: $sidebar_collapsed_width; margin-left: 40px;
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
@include collapsed-header; @include collapsed-header;
......
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
} }
.page-title { .page-title {
.note_created_ago, .new-issue-link { .note-created-ago, .new-issue-link {
display: none; display: none;
} }
} }
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
display: none; display: none;
} }
aside:not(.right-sidebar){ aside:not(.right-sidebar) {
display: none; display: none;
} }
......
...@@ -56,6 +56,17 @@ ...@@ -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 { .nav-links {
display: inline-block; display: inline-block;
width: 50%; width: 50%;
...@@ -100,6 +111,7 @@ ...@@ -100,6 +111,7 @@
> form { > form {
display: inline-block; display: inline-block;
margin-top: -1px;
} }
.icon-label { .icon-label {
...@@ -110,7 +122,7 @@ ...@@ -110,7 +122,7 @@
height: 34px; height: 34px;
display: inline-block; display: inline-block;
position: relative; position: relative;
top: 1px; top: 2px;
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */ /* Medium devices (desktops, 992px and up) */
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
padding: 10px 15px; padding: 10px 15px;
} }
.select2-drop{ .select2-drop {
color: #7f8fa4; color: #7f8fa4;
} }
......
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
} }
a { a {
padding: 7px 15px; padding: 7px 12px;
font-size: $gl-font-size; font-size: $gl-font-size;
line-height: 24px; line-height: 24px;
color: $gray; color: $gray;
...@@ -169,10 +169,12 @@ ...@@ -169,10 +169,12 @@
} }
.count { .count {
float: right; &:before {
background: #eee; content: '(';
padding: 0 8px; }
@include border-radius(6px); &:after {
content: ')';
}
} }
&.back-link i { &.back-link i {
...@@ -191,6 +193,27 @@ ...@@ -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 { .collapse-nav a {
width: $sidebar_width; width: $sidebar_width;
position: fixed; position: fixed;
...@@ -210,55 +233,12 @@ ...@@ -210,55 +233,12 @@
} }
.page-sidebar-collapsed { .page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width;
.sidebar-wrapper { .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 { .page-sidebar-expanded {
padding-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
padding-left: $sidebar_width; padding-left: $sidebar_width;
} }
...@@ -288,6 +268,10 @@ ...@@ -288,6 +268,10 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width; padding-right: $sidebar_collapsed_width;
} }
.sidebar-collapsed-icon {
cursor: pointer;
}
} }
.right-sidebar-expanded { .right-sidebar-expanded {
...@@ -300,4 +284,53 @@ ...@@ -300,4 +284,53 @@
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
padding-right: $gutter_width; 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; ...@@ -56,8 +56,8 @@ $component-active-bg: $brand-info;
//## //##
$input-color: $text-color; $input-color: $text-color;
$input-border: #e7e9ed; $input-border: $border-color;
$input-border-focus: #7f8fa4; $input-border-focus: $focus-border-color;
$legend-color: $text-color; $legend-color: $text-color;
......
...@@ -138,6 +138,12 @@ ...@@ -138,6 +138,12 @@
} }
} }
a.no-attachment-icon {
&:before {
display: none;
}
}
/* Link to current header. */ /* Link to current header. */
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
position: relative; position: relative;
......
...@@ -11,6 +11,7 @@ $gutter_inner_width: 258px; ...@@ -11,6 +11,7 @@ $gutter_inner_width: 258px;
* UI elements * UI elements
*/ */
$border-color: #efeff1; $border-color: #efeff1;
$focus-border-color: #3aabf0;
$table-border-color: #eef0f2; $table-border-color: #eef0f2;
$background-color: #faf9f9; $background-color: #faf9f9;
...@@ -26,6 +27,7 @@ $gl-text-orange: #d90; ...@@ -26,6 +27,7 @@ $gl-text-orange: #d90;
$gl-link-color: #3084bb; $gl-link-color: #3084bb;
$gl-dark-link-color: #333; $gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f; $gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color;
$gl-gray: $gl-text-color; $gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color; $gl-header-color: $gl-title-color;
...@@ -66,7 +68,7 @@ $header-height: 58px; ...@@ -66,7 +68,7 @@ $header-height: 58px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$gl-avatar-size: 40px; $gl-avatar-size: 40px;
$error-exclamation-point: #e62958; $error-exclamation-point: #e62958;
$border-radius-default: 3px; $border-radius-default: 2px;
$btn-transparent-color: #8f8f8f; $btn-transparent-color: #8f8f8f;
$ssh-key-icon-color: #8f8f8f; $ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px; $ssh-key-icon-size: 18px;
...@@ -166,6 +168,8 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif ...@@ -166,6 +168,8 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
/* /*
* Dropdowns * Dropdowns
*/ */
$dropdown-border-radius: 2px;
$dropdown-width: 300px;
$dropdown-bg: #fff; $dropdown-bg: #fff;
$dropdown-link-color: #555; $dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover; $dropdown-link-hover-bg: $row-hover;
...@@ -176,8 +180,8 @@ $dropdown-divider-color: rgba(#000, .1); ...@@ -176,8 +180,8 @@ $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494; $dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf; $dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555; $dropdown-input-color: #555;
$dropdown-input-focus-border: rgb(58, 171, 240); $dropdown-input-focus-border: $focus-border-color;
$dropdown-input-focus-shadow: rgba(#000, .2); $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, .6);
$dropdown-toggle-bg: #fff; $dropdown-toggle-bg: #fff;
...@@ -193,3 +197,23 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; ...@@ -193,3 +197,23 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
$award-emoji-menu-bg: #fff; $award-emoji-menu-bg: #fff;
$award-emoji-menu-border: #f1f2f4; $award-emoji-menu-border: #f1f2f4;
$award-emoji-new-btn-icon-color: #dcdcdc; $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 @@ ...@@ -37,7 +37,7 @@
height: 300px; height: 300px;
overflow-y: scroll; overflow-y: scroll;
input.emoji-search{ input.emoji-search {
background-image: url("data:image/png;base64,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"); background-image: url("data:image/png;base64,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");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 5px center; background-position: right 5px center;
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
} }
} }
.loading{ .loading {
font-size: 20px; font-size: 20px;
} }
......
.commit-title{ .commit-title {
display: block; display: block;
} }
.commit-author, .commit-committer{ .commit-author, .commit-committer {
display: block; display: block;
color: #999; color: #999;
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }
.commit-author strong, .commit-committer strong{ .commit-author strong, .commit-committer strong {
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
color: $gl-text-red; color: $gl-text-red;
} }
} }
.edit-file{ .edit-file {
a { a {
color: $gl-text-color; color: $gl-text-color;
} }
......
.commits-compare-switch{ .commits-compare-switch {
@include btn-default; @include btn-default;
@include btn-white; @include btn-white;
background: image-url("switch_icon.png") no-repeat center center; background: image-url("switch_icon.png") no-repeat center center;
......
.file-editor { .file-editor {
#editor{ #editor {
border: none; border: none;
@include border-radius(0); @include border-radius(0);
height: 500px; height: 500px;
......
...@@ -43,10 +43,6 @@ ...@@ -43,10 +43,6 @@
.md { .md {
color: #7f8fa4; color: #7f8fa4;
font-size: $gl-font-size; font-size: $gl-font-size;
iframe.twitter-share-button {
vertical-align: bottom;
}
} }
pre { pre {
......
.ci-body { .ci-body {
.incorrect-syntax{ .incorrect-syntax {
font-size: 19px; font-size: 19px;
color: red; color: red;
} }
.correct-syntax{ .correct-syntax {
font-size: 19px; font-size: 19px;
color: #47a447; color: #47a447;
} }
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
} }
} }
.login-box{ .login-box {
background: #fafafa; background: #fafafa;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 0 2px #ccc; box-shadow: 0 0 2px #ccc;
......
...@@ -22,7 +22,7 @@ ul.notes { ...@@ -22,7 +22,7 @@ ul.notes {
margin-left: 55px; margin-left: 55px;
} }
.note_created_ago, .note-updated-at { .note-created-ago, .note-updated-at {
white-space: nowrap; white-space: nowrap;
} }
...@@ -39,53 +39,6 @@ ul.notes { ...@@ -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 { .discussion-body {
padding-top: 15px; padding-top: 15px;
} }
...@@ -198,40 +151,88 @@ ul.notes { ...@@ -198,40 +151,88 @@ ul.notes {
border-width: 1px 0; border-width: 1px 0;
padding-top: 0; padding-top: 0;
vertical-align: top; vertical-align: top;
&.parallel{ &.parallel {
border-width: 1px; 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 * Actions for Discussions/Notes
*/ */
.discussion, .discussion-actions,
.note { .note-actions {
.discussion-actions,
.note-actions {
float: right; float: right;
margin-left: 10px; margin-left: 10px;
color: $notes-action-color;
}
a { .note-action-button,
margin-left: 5px; .discussion-action-button {
color: $gl-gray; display: inline-block;
margin-left: 10px;
line-height: 24px;
i.fa { .fa {
font-size: 16px; position: relative;
line-height: 16px; top: 1px;
font-size: 17px;
} }
&:hover { .fa-trash-o {
@extend .cgray; top: 0;
&.danger { @extend .cred; } 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 { .diff-file .note .note-actions {
right: 0; right: 0;
top: 0; top: 0;
......
...@@ -21,3 +21,145 @@ ...@@ -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 @@ ...@@ -6,13 +6,19 @@
.navbar-nav { .navbar-nav {
li { li {
.badge.todos-pending-count { .badge.todos-pending-count {
background-color: #7f8fa4; background-color: $gl-icon-color;
margin-top: -5px; margin-top: -5px;
font-weight: normal; font-weight: normal;
} }
} }
} }
.todo {
&:hover {
cursor: pointer;
}
}
.todo-item { .todo-item {
.todo-title { .todo-title {
@include str-truncated(calc(100% - 174px)); @include str-truncated(calc(100% - 174px));
......
...@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication, :require_two_factor_authentication,
:two_factor_grace_period, :two_factor_grace_period,
:gravatar_enabled, :gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text, :sign_in_text,
:help_page_text, :help_page_text,
:home_page_url, :home_page_url,
......
...@@ -2,11 +2,12 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -2,11 +2,12 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers before_action :no_cache_headers
def build def build
badge = Gitlab::Badge::Build.new(project, params[:ref])
respond_to do |format| respond_to do |format|
format.html { render_404 } format.html { render_404 }
format.svg do format.svg do
image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref]) send_data(badge.data, type: badge.type, disposition: 'inline')
send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
end end
end end
end end
......
...@@ -57,8 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -57,8 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: @merge_request } format.json { render json: @merge_request }
format.diff { render text: @merge_request.to_diff(current_user) } format.diff { render text: @merge_request.to_diff }
format.patch { render text: @merge_request.to_patch(current_user) } format.patch { render text: @merge_request.to_patch }
end end
end end
...@@ -154,7 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -154,7 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.target_project, @merge_request]) @merge_request.target_project, @merge_request])
end end
format.json do 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
end end
else else
...@@ -224,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -224,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def ci_status def ci_status
ci_commit = @merge_request.ci_commit
if ci_commit
status = ci_commit.status
coverage = ci_commit.try(:coverage)
else
ci_service = @merge_request.source_project.ci_service ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage) if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch) coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
end end
end
response = { response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
status: status, status: status,
coverage: coverage coverage: coverage
} }
......
...@@ -24,7 +24,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::MilestonesController < Projects::ApplicationController
@milestones = @milestones.page(params[:page]) @milestones = @milestones.page(params[:page])
end end
format.json do format.json do
render json: @milestones render json: @milestones.to_json(methods: :name)
end end
end end
end end
......
...@@ -71,7 +71,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -71,7 +71,7 @@ class ProjectsController < Projects::ApplicationController
def remove_fork def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project) 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.' flash[:notice] = 'The fork relationship has been removed.'
end end
end end
...@@ -138,7 +138,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -138,7 +138,7 @@ class ProjectsController < Projects::ApplicationController
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id) participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = { @suggestions = {
emojis: autocomplete_emojis, emojis: AwardEmoji.urls,
issues: autocomplete.issues, issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests, mergerequests: autocomplete.merge_requests,
members: participants members: participants
...@@ -235,17 +235,6 @@ class ProjectsController < Projects::ApplicationController ...@@ -235,17 +235,6 @@ class ProjectsController < Projects::ApplicationController
) )
end 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? def repo_exists?
project.repository_exists? && !project.empty_repo? project.repository_exists? && !project.empty_repo?
end end
......
...@@ -243,7 +243,7 @@ class IssuableFinder ...@@ -243,7 +243,7 @@ class IssuableFinder
end end
def filter_by_upcoming_milestone? def filter_by_upcoming_milestone?
params[:milestone_title] == '#upcoming' params[:milestone_title] == Milestone::Upcoming.name
end end
def by_milestone(items) def by_milestone(items)
...@@ -252,7 +252,7 @@ class IssuableFinder ...@@ -252,7 +252,7 @@ class IssuableFinder
items = items.where(milestone_id: [-1, nil]) items = items.where(milestone_id: [-1, nil])
elsif filter_by_upcoming_milestone? elsif filter_by_upcoming_milestone?
upcoming = Milestone.where(project_id: projects).upcoming 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 else
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] }) items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
......
...@@ -3,10 +3,6 @@ module ApplicationSettingsHelper ...@@ -3,10 +3,6 @@ module ApplicationSettingsHelper
current_application_settings.gravatar_enabled? current_application_settings.gravatar_enabled?
end end
def twitter_sharing_enabled?
current_application_settings.twitter_sharing_enabled?
end
def signup_enabled? def signup_enabled?
current_application_settings.signup_enabled? current_application_settings.signup_enabled?
end end
......
...@@ -214,4 +214,12 @@ module EventsHelper ...@@ -214,4 +214,12 @@ module EventsHelper
end end
end end
end end
def event_row_class(event)
if event.body?
"event-block"
else
"event-inline"
end
end
end end
...@@ -47,6 +47,14 @@ module IssuablesHelper ...@@ -47,6 +47,14 @@ module IssuablesHelper
end end
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 private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -5,9 +5,11 @@ module NotesHelper ...@@ -5,9 +5,11 @@ module NotesHelper
end end
def note_target_fields(note) def note_target_fields(note)
if note.noteable
hidden_field_tag(:target_type, note.noteable.class.name.underscore) + hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
hidden_field_tag(:target_id, note.noteable.id) hidden_field_tag(:target_id, note.noteable.id)
end end
end
def note_editable?(note) def note_editable?(note)
note.editable? && can?(current_user, :admin_note, note) note.editable? && can?(current_user, :admin_note, note)
......
module SearchHelper module SearchHelper
def search_autocomplete_opts(term) def search_autocomplete_opts(term)
return unless current_user return unless current_user
...@@ -23,45 +24,44 @@ module SearchHelper ...@@ -23,45 +24,44 @@ module SearchHelper
# Autocomplete results for various settings pages # Autocomplete results for various settings pages
def default_autocomplete def default_autocomplete
[ [
{ label: "Profile settings", url: profile_path }, { category: "Settings", label: "Profile settings", url: profile_path },
{ label: "SSH Keys", url: profile_keys_path }, { category: "Settings", label: "SSH Keys", url: profile_keys_path },
{ label: "Dashboard", url: root_path }, { category: "Settings", label: "Dashboard", url: root_path },
{ label: "Admin Section", url: admin_root_path }, { category: "Settings", label: "Admin Section", url: admin_root_path },
] ]
end end
# Autocomplete results for internal help pages # Autocomplete results for internal help pages
def help_autocomplete def help_autocomplete
[ [
{ label: "help: API Help", url: help_page_path("api", "README") }, { category: "Help", label: "API Help", url: help_page_path("api", "README") },
{ label: "help: Markdown Help", url: help_page_path("markdown", "markdown") }, { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") },
{ label: "help: Permissions Help", url: help_page_path("permissions", "permissions") }, { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") },
{ label: "help: Public Access Help", url: help_page_path("public_access", "public_access") }, { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") },
{ label: "help: Rake Tasks Help", url: help_page_path("raketasks", "README") }, { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") },
{ label: "help: SSH Keys Help", url: help_page_path("ssh", "README") }, { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") },
{ label: "help: System Hooks Help", url: help_page_path("system_hooks", "system_hooks") }, { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
{ label: "help: Webhooks Help", url: help_page_path("web_hooks", "web_hooks") }, { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
{ label: "help: Workflow Help", url: help_page_path("workflow", "README") }, { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") },
] ]
end end
# Autocomplete results for the current project, if it's defined # Autocomplete results for the current project, if it's defined
def project_autocomplete def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref if @project && @project.repository.exists? && @project.repository.root_ref
prefix = search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref ref = @ref || @project.repository.root_ref
[ [
{ label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) }, { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
{ label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
{ label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
{ label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
{ label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) }, { category: "Current Project", label: "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: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
] ]
else else
[] []
...@@ -72,7 +72,9 @@ module SearchHelper ...@@ -72,7 +72,9 @@ module SearchHelper
def groups_autocomplete(term, limit = 5) def groups_autocomplete(term, limit = 5)
current_user.authorized_groups.search(term).limit(limit).map do |group| 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) url: group_path(group)
} }
end end
...@@ -83,7 +85,10 @@ module SearchHelper ...@@ -83,7 +85,10 @@ module SearchHelper
current_user.authorized_projects.search_by_title(term). current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p| 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) url: namespace_project_path(p.namespace, p)
} }
end end
......
...@@ -110,6 +110,10 @@ class Notify < BaseMailer ...@@ -110,6 +110,10 @@ class Notify < BaseMailer
headers['Reply-To'] = address 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 @reply_by_email = true
end end
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
# updated_at :datetime # updated_at :datetime
# home_page_url :string(255) # home_page_url :string(255)
# default_branch_protection :integer default(2) # default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text # restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE) # version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null # max_attachment_size :integer default(10), not null
...@@ -140,7 +139,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -140,7 +139,6 @@ class ApplicationSetting < ActiveRecord::Base
default_branch_protection: Settings.gitlab['default_branch_protection'], default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'], signin_enabled: Settings.gitlab['signin_enabled'],
twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'], gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'], sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
......
...@@ -74,14 +74,14 @@ class Commit ...@@ -74,14 +74,14 @@ class Commit
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})? (?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit>\h{7,40}) (?<commit>\h{7,40})
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("commit", /(?<commit>\h{7,40})/) @link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -43,14 +43,14 @@ class CommitRange ...@@ -43,14 +43,14 @@ class CommitRange
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})? (?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{STRICT_PATTERN}) (?<commit_range>#{STRICT_PATTERN})
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("compare", /(?<commit_range>#{PATTERN})/) @link_reference_pattern ||= super("compare", /(?<commit_range>#{PATTERN})/)
end end
# Initialize a CommitRange # Initialize a CommitRange
......
...@@ -19,6 +19,7 @@ module Issuable ...@@ -19,6 +19,7 @@ module Issuable
has_many :notes, as: :noteable, dependent: :destroy has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links has_many :labels, through: :label_links
has_many :todos, as: :target, dependent: :destroy
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
...@@ -41,7 +42,7 @@ module Issuable ...@@ -41,7 +42,7 @@ module Issuable
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(: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, delegate :name,
:email, :email,
......
...@@ -31,7 +31,7 @@ class ExternalIssue ...@@ -31,7 +31,7 @@ class ExternalIssue
# Pattern used to extract `JIRA-123` issue references from text # Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern 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 end
def to_reference(_from_project = nil) def to_reference(_from_project = nil)
......
...@@ -14,6 +14,7 @@ class GlobalMilestone ...@@ -14,6 +14,7 @@ class GlobalMilestone
def initialize(title, milestones) def initialize(title, milestones)
@title = title @title = title
@name = title
@milestones = milestones @milestones = milestones
end end
......
...@@ -73,14 +73,14 @@ class Issue < ActiveRecord::Base ...@@ -73,14 +73,14 @@ class Issue < ActiveRecord::Base
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<issue>\d+) #{Regexp.escape(reference_prefix)}(?<issue>\d+)
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("issues", /(?<issue>\d+)/) @link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -56,7 +56,7 @@ class Label < ActiveRecord::Base ...@@ -56,7 +56,7 @@ class Label < ActiveRecord::Base
# This pattern supports cross-project references. # This pattern supports cross-project references.
# #
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)} #{Regexp.escape(reference_prefix)}
(?: (?:
......
...@@ -135,6 +135,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -135,6 +135,7 @@ class MergeRequest < ActiveRecord::Base
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :of_projects, ->(ids) { where(target_project_id: ids) } 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 :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
...@@ -149,14 +150,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -149,14 +150,14 @@ class MergeRequest < ActiveRecord::Base
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<merge_request>\d+) #{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("merge_requests", /(?<merge_request>\d+)/) @link_reference_pattern ||= super("merge_requests", /(?<merge_request>\d+)/)
end end
# Returns all the merge requests from an ActiveRecord:Relation. # Returns all the merge requests from an ActiveRecord:Relation.
...@@ -279,7 +280,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -279,7 +280,7 @@ class MergeRequest < ActiveRecord::Base
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def work_in_progress? def work_in_progress?
title =~ WIP_REGEX !!(title =~ WIP_REGEX)
end end
def wipless_title def wipless_title
...@@ -331,15 +332,15 @@ class MergeRequest < ActiveRecord::Base ...@@ -331,15 +332,15 @@ class MergeRequest < ActiveRecord::Base
# Returns the raw diff for this merge request # Returns the raw diff for this merge request
# #
# see "git diff" # see "git diff"
def to_diff(current_user) def to_diff
target_project.repository.diff_text(target_branch, source_sha) target_project.repository.diff_text(diff_base_commit.sha, source_sha)
end end
# Returns the commit as a series of email patches. # Returns the commit as a series of email patches.
# #
# see "git format-patch" # see "git format-patch"
def to_patch(current_user) def to_patch
target_project.repository.format_patch(target_branch, source_sha) target_project.repository.format_patch(diff_base_commit.sha, source_sha)
end end
def hook_attrs def hook_attrs
......
...@@ -79,7 +79,7 @@ class Milestone < ActiveRecord::Base ...@@ -79,7 +79,7 @@ class Milestone < ActiveRecord::Base
end end
def self.link_reference_pattern def self.link_reference_pattern
super("milestones", /(?<milestone>\d+)/) @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end end
def self.upcoming def self.upcoming
...@@ -89,7 +89,7 @@ class Milestone < ActiveRecord::Base ...@@ -89,7 +89,7 @@ class Milestone < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
escaped_title = self.title.gsub("]", "\\]") 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) url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
"[#{escaped_title}](#{url})" "[#{escaped_title}](#{url})"
......
...@@ -311,7 +311,7 @@ class Note < ActiveRecord::Base ...@@ -311,7 +311,7 @@ class Note < ActiveRecord::Base
for_merge_request? && for_diff_line? for_merge_request? && for_diff_line?
end end
def for_project_snippet? def for_snippet?
noteable_type == "Snippet" noteable_type == "Snippet"
end end
......
...@@ -206,6 +206,8 @@ class Project < ActiveRecord::Base ...@@ -206,6 +206,8 @@ class Project < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
# Scopes # Scopes
default_scope { where(pending_delete: false) }
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') } scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
...@@ -469,7 +471,7 @@ class Project < ActiveRecord::Base ...@@ -469,7 +471,7 @@ class Project < ActiveRecord::Base
end end
def web_url 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 end
def web_url_without_protocol def web_url_without_protocol
...@@ -590,7 +592,7 @@ class Project < ActiveRecord::Base ...@@ -590,7 +592,7 @@ class Project < ActiveRecord::Base
if avatar.present? if avatar.present?
[gitlab_config.url, avatar.url].join [gitlab_config.url, avatar.url].join
elsif avatar_in_git 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
end end
...@@ -929,16 +931,6 @@ class Project < ActiveRecord::Base ...@@ -929,16 +931,6 @@ class Project < ActiveRecord::Base
self.builds_enabled = true self.builds_enabled = true
end 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) def any_runners?(&block)
if runners.active.any?(&block) if runners.active.any?(&block)
return true return true
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
# #
class GitlabIssueTrackerService < IssueTrackerService 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 prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
class JiraService < IssueTrackerService class JiraService < IssueTrackerService
include HTTParty include HTTParty
include Gitlab::Application.routes.url_helpers include Gitlab::Routing.url_helpers
DEFAULT_API_VERSION = 2 DEFAULT_API_VERSION = 2
......
...@@ -22,7 +22,7 @@ class SlackService ...@@ -22,7 +22,7 @@ class SlackService
@issue_url = obj_attr[:url] @issue_url = obj_attr[:url]
@action = obj_attr[:action] @action = obj_attr[:action]
@state = obj_attr[:state] @state = obj_attr[:state]
@description = obj_attr[:description] @description = obj_attr[:description] || ''
end end
def attachments def attachments
......
...@@ -72,7 +72,7 @@ class Repository ...@@ -72,7 +72,7 @@ class Repository
return @has_visible_content unless @has_visible_content.nil? return @has_visible_content unless @has_visible_content.nil?
@has_visible_content = cache.fetch(:has_visible_content?) do @has_visible_content = cache.fetch(:has_visible_content?) do
raw_repository.branch_count > 0 branch_count > 0
end end
end end
...@@ -173,7 +173,7 @@ class Repository ...@@ -173,7 +173,7 @@ class Repository
end end
def branch_names def branch_names
cache.fetch(:branch_names) { raw_repository.branch_names } cache.fetch(:branch_names) { branches.map(&:name) }
end end
def tag_names def tag_names
...@@ -191,7 +191,7 @@ class Repository ...@@ -191,7 +191,7 @@ class Repository
end end
def branch_count def branch_count
@branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count } @branch_count ||= cache.fetch(:branch_count) { branches.size }
end end
def tag_count def tag_count
...@@ -239,7 +239,7 @@ class Repository ...@@ -239,7 +239,7 @@ class Repository
def expire_branches_cache def expire_branches_cache
cache.expire(:branch_names) cache.expire(:branch_names)
@branches = nil @local_branches = nil
end end
def expire_cache(branch_name = nil, revision = nil) def expire_cache(branch_name = nil, revision = nil)
...@@ -335,6 +335,8 @@ class Repository ...@@ -335,6 +335,8 @@ class Repository
# Runs code just before a repository is deleted. # Runs code just before a repository is deleted.
def before_delete def before_delete
expire_exists_cache
expire_cache if exists? expire_cache if exists?
expire_root_ref_cache expire_root_ref_cache
...@@ -362,6 +364,11 @@ class Repository ...@@ -362,6 +364,11 @@ class Repository
expire_tag_count_cache expire_tag_count_cache
end end
def before_import
expire_emptiness_caches
expire_exists_cache
end
# Runs code after a repository has been forked/imported. # Runs code after a repository has been forked/imported.
def after_import def after_import
expire_emptiness_caches expire_emptiness_caches
...@@ -612,9 +619,13 @@ class Repository ...@@ -612,9 +619,13 @@ class Repository
refs_contains_sha('tag', sha) refs_contains_sha('tag', sha)
end end
def branches def local_branches
@branches ||= raw_repository.branches @local_branches ||= rugged.branches.each(:local).map do |branch|
Gitlab::Git::Branch.new(branch.name, branch.target)
end end
end
alias_method :branches, :local_branches
def tags def tags
@tags ||= raw_repository.tags @tags ||= raw_repository.tags
...@@ -818,7 +829,7 @@ class Repository ...@@ -818,7 +829,7 @@ class Repository
end end
def fetch_ref(source_path, source_ref, target_ref) 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) Gitlab::Popen.popen(args, path_to_repo)
end end
......
...@@ -56,14 +56,14 @@ class Snippet < ActiveRecord::Base ...@@ -56,14 +56,14 @@ class Snippet < ActiveRecord::Base
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<snippet>\d+) #{Regexp.escape(reference_prefix)}(?<snippet>\d+)
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("snippets", /(?<snippet>\d+)/) @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -408,6 +408,8 @@ class User < ActiveRecord::Base ...@@ -408,6 +408,8 @@ class User < ActiveRecord::Base
end end
def owns_notification_email 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) self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
end end
......
...@@ -43,7 +43,7 @@ module Issues ...@@ -43,7 +43,7 @@ module Issues
def create_new_issue def create_new_issue
new_params = { id: nil, iid: nil, label_ids: [], milestone: nil, new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
project: @new_project, author: @old_issue.author, 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) new_params = @old_issue.serializable_hash.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute CreateService.new(@new_project, @current_user, new_params).execute
...@@ -53,7 +53,7 @@ module Issues ...@@ -53,7 +53,7 @@ module Issues
@old_issue.notes.find_each do |note| @old_issue.notes.find_each do |note|
new_note = note.dup new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue, 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, created_at: note.created_at,
updated_at: note.updated_at } updated_at: note.updated_at }
...@@ -61,6 +61,18 @@ module Issues ...@@ -61,6 +61,18 @@ module Issues
end end
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 def close_issue
close_service = CloseService.new(@old_project, @current_user) close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false) close_service.execute(@old_issue, notifications: false, system_note: false)
...@@ -78,20 +90,12 @@ module Issues ...@@ -78,20 +90,12 @@ module Issues
direction: :to) direction: :to)
end end
def unfold_references(content) def mark_as_moved
return unless content @old_issue.update(moved_to: @new_issue)
rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
@current_user)
rewriter.rewrite(@new_project)
end end
def notify_participants def notify_participants
notification_service.issue_moved(@old_issue, @new_issue, @current_user) notification_service.issue_moved(@old_issue, @new_issue, @current_user)
end end
def mark_as_moved
@old_issue.update(moved_to: @new_issue)
end
end end
end end
...@@ -46,6 +46,8 @@ module Projects ...@@ -46,6 +46,8 @@ module Projects
def import_data def import_data
return unless has_importer? return unless has_importer?
project.repository.before_import
unless importer.execute unless importer.execute
raise Error, 'The remote data could not be imported.' raise Error, 'The remote data could not be imported.'
end 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 ...@@ -95,17 +95,19 @@ class SystemHooksService
end end
def project_member_data(model) def project_member_data(model)
project = model.project || Project.unscoped.find(model.source_id)
{ {
project_name: model.project.name, project_name: project.name,
project_path: model.project.path, project_path: project.path,
project_path_with_namespace: model.project.path_with_namespace, project_path_with_namespace: project.path_with_namespace,
project_id: model.project.id, project_id: project.id,
user_username: model.user.username, user_username: model.user.username,
user_name: model.user.name, user_name: model.user.name,
user_email: model.user.email, user_email: model.user.email,
user_id: model.user.id, user_id: model.user.id,
access_level: model.human_access, access_level: model.human_access,
project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase
} }
end end
......
...@@ -224,7 +224,7 @@ class SystemNoteService ...@@ -224,7 +224,7 @@ class SystemNoteService
# #
# "Started branch `issue-branch-button-201`" # "Started branch `issue-branch-button-201`"
def self.new_issue_branch(issue, project, author, branch) 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) link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "Started branch [`#{branch}`](#{link})" body = "Started branch [`#{branch}`](#{link})"
......
...@@ -123,7 +123,7 @@ class TodoService ...@@ -123,7 +123,7 @@ class TodoService
def handle_note(note, author) def handle_note(note, author)
# Skip system notes, and notes on project snippet # 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 project = note.project
target = note.noteable target = note.noteable
...@@ -170,14 +170,30 @@ class TodoService ...@@ -170,14 +170,30 @@ class TodoService
end end
def filter_mentioned_users(project, target, author) def filter_mentioned_users(project, target, author)
mentioned_users = target.mentioned_users.select do |user| mentioned_users = target.mentioned_users
user.can?(:read_project, project) mentioned_users = reject_users_without_access(mentioned_users, project, target)
end
mentioned_users.delete(author) mentioned_users.delete(author)
mentioned_users.uniq mentioned_users.uniq
end 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 = {}) def pending_todos(user, criteria = {})
valid_keys = [:project_id, :target_id, :target_type, :commit_id] valid_keys = [:project_id, :target_id, :target_type, :commit_id]
user.todos.pending.where(criteria.slice(*valid_keys)) user.todos.pending.where(criteria.slice(*valid_keys))
......
# encoding: utf-8 # encoding: utf-8
class FileUploader < CarrierWave::Uploader::Base class FileUploader < CarrierWave::Uploader::Base
include UploaderHelper include UploaderHelper
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
storage :file storage :file
attr_accessor :project, :secret attr_accessor :project, :secret
def initialize(project, secret = self.class.generate_secret) def initialize(project, secret = nil)
@project = project @project = project
@secret = secret @secret = secret || self.class.generate_secret
end end
def base_dir def base_dir
...@@ -23,14 +24,14 @@ class FileUploader < CarrierWave::Uploader::Base ...@@ -23,14 +24,14 @@ class FileUploader < CarrierWave::Uploader::Base
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret) File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end end
def self.generate_secret
SecureRandom.hex
end
def secure_url def secure_url
File.join("/uploads", @secret, file.filename) File.join("/uploads", @secret, file.filename)
end end
def to_markdown
to_h[:markdown]
end
def to_h def to_h
filename = image? ? self.file.basename : self.file.filename filename = image? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]") escaped_filename = filename.gsub("]", "\\]")
...@@ -45,4 +46,8 @@ class FileUploader < CarrierWave::Uploader::Base ...@@ -45,4 +46,8 @@ class FileUploader < CarrierWave::Uploader::Base
markdown: markdown markdown: markdown
} }
end end
def self.generate_secret
SecureRandom.hex
end
end end
...@@ -76,13 +76,6 @@ ...@@ -76,13 +76,6 @@
= f.label :gravatar_enabled do = f.label :gravatar_enabled do
= f.check_box :gravatar_enabled = f.check_box :gravatar_enabled
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 .form-group
= f.label :default_projects_limit, class: 'control-label col-sm-2' = f.label :default_projects_limit, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
%td %td
- if project - 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 %td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" = 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 .row
.col-md-4 .col-md-4
%h4 Statistics %h4 Statistics
......
- page_title "Deploy Keys" - page_title "Deploy Keys"
.panel.panel-default .panel.panel-default.prepend-top-default
.panel-heading .panel-heading
Public deploy keys (#{@deploy_keys.count}) Public deploy keys (#{@deploy_keys.count})
.controls .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" - page_title "Groups"
%h3.page-title %h3.page-title
Groups (#{number_with_delimiter(@groups.total_count)}) Groups (#{number_with_delimiter(@groups.total_count)})
= link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%p.light %p.light
Group allows you to keep projects organized. Group allows you to keep projects organized.
Use groups for uniting related projects. Use groups for uniting related projects.
%hr .top-area
= form_tag admin_groups_path, method: :get, class: 'form-inline' do .nav-search
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
= hidden_field_tag :sort, @sort = hidden_field_tag :sort, @sort
.form-group
= text_field_tag :name, params[:name], class: "form-control" = text_field_tag :name, params[:name], class: "form-control"
= button_tag "Search", class: "btn submit btn-primary" = button_tag "Search", class: "btn submit btn-primary"
.pull-right .nav-controls
.dropdown.inline .dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light %span.light
...@@ -33,34 +32,10 @@ ...@@ -33,34 +32,10 @@
= sort_title_recently_updated = sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated) do = link_to admin_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated = sort_title_oldest_updated
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
%hr %ul.content-list
%ul.bordered-list
- @groups.each do |group| - @groups.each do |group|
%li = render 'group', group: group
.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')}
= paginate @groups, theme: "gitlab" = paginate @groups, theme: "gitlab"
- page_title "Labels" - page_title "Labels"
= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
%div
= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
New label New label
%h3.page-title %h3.page-title
Labels Labels
%hr %hr
......
%p.lead %p.lead.prepend-top-default
%span %span
To register a new runner you should enter the following registration token. 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. 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 .todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:'' = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
(removed) (removed)
%span.todo-label %span.todo-label
= todo_action_name(todo) = todo_action_name(todo)
- if todo.target
= todo_target_link(todo) = todo_target_link(todo)
- else
(removed)
&middot; #{time_ago_with_tooltip(todo.created_at)} &middot; #{time_ago_with_tooltip(todo.created_at)}
......
- if event.visible_to_user?(current_user) - 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 .event-item-timestamp
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
......
...@@ -7,21 +7,3 @@ ...@@ -7,21 +7,3 @@
= link_to_project event.project = link_to_project event.project
- else - else
= event.project_name = 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}" } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast" = render "layouts/broadcast"
.expand-nav
= link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo .header-logo
%a#logo %a#logo
...@@ -8,15 +10,19 @@ ...@@ -8,15 +10,19 @@
.gitlab-text-container .gitlab-text-container
%h3 GitLab %h3 GitLab
- if defined?(sidebar) && sidebar - primary_sidebar = current_user ? 'dashboard' : 'explore'
- if defined?(sidebar) && sidebar && sidebar != primary_sidebar
.complex-sidebar
.nav-primary
= render "layouts/nav/#{primary_sidebar}"
.nav-secondary
= render "layouts/nav/#{sidebar}" = render "layouts/nav/#{sidebar}"
- elsif current_user
= render 'layouts/nav/dashboard'
- else - else
= render 'layouts/nav/explore' = render "layouts/nav/#{primary_sidebar}"
.collapse-nav .collapse-nav
= render partial: 'layouts/collapse_button' = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar"
- if current_user - if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile" do = link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
......
.search - if controller.controller_path =~ /^groups/
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| - label = 'This group'
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1" - if controller.controller_path =~ /^projects/
- label = 'This project'
.search.search-form{class: "#{'has-location-badge' if label.present?}"}
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
.search-location-badge
- if label.present?
%span.location-badge
%i.location-text
= label
.search-input-wrap
.dropdown{ data: {url: search_autocomplete_path } }
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
.dropdown-menu.dropdown-select
= dropdown_content do
%ul
%li
%a.is-focused.dropdown-menu-empty-link
Loading...
= dropdown_loading
%i.search-icon
%i.clear-icon.js-clear-input
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
= hidden_field_tag :project_id, @project.id
- if @project && @project.persisted?
- if current_controller?(:issues) - if current_controller?(:issues)
= hidden_field_tag :scope, 'issues' = hidden_field_tag :scope, 'issues'
- elsif current_controller?(:merge_requests) - elsif current_controller?(:merge_requests)
...@@ -21,10 +44,3 @@ ...@@ -21,10 +44,3 @@
= hidden_field_tag :repository_ref, @ref = hidden_field_tag :repository_ref, @ref
= button_tag 'Go' if ENV['RAILS_ENV'] == 'test' = 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 } .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 @@ ...@@ -95,7 +95,7 @@
Spam Logs Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all)) %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 = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw') = icon('cogs fw')
%span %span
......
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
= icon('dashboard fw') = icon('dashboard fw')
%span %span
Activity Activity
= nav_link(controller: :groups) do = nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw') = icon('group fw')
%span %span
Groups Groups
= nav_link(controller: :milestones) do = nav_link(path: 'dashboard#milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw') = icon('clock-o fw')
%span %span
...@@ -48,7 +48,6 @@ ...@@ -48,7 +48,6 @@
%span %span
Help Help
%li.separate-item
= nav_link(controller: :profile) do = nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw') = icon('user fw')
......
%ul.nav.nav-sidebar %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 = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
= icon('group fw') = icon('group fw')
...@@ -42,7 +34,7 @@ ...@@ -42,7 +34,7 @@
%span %span
Members Members
- if can?(current_user, :admin_group, @group) - 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 = link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw') = icon ('cogs fw')
%span %span
......
%ul.nav.nav-sidebar %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 = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
= icon('user fw') = 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