diff options
author | James Lopez <james@jameslopez.es> | 2016-05-03 14:14:31 +0300 |
---|---|---|
committer | James Lopez <james@jameslopez.es> | 2016-05-03 14:14:31 +0300 |
commit | 3929972d900ee2b0eca806c36074c93ac83af2fd (patch) | |
tree | 0a6d347b5c32facc0a5d38645e24918b2cff7c98 /app | |
parent | 5a4f576359a71a57c70544568d9291cba53a2d39 (diff) | |
parent | e172028a3615d1a7f1520ce65cddcdf707a16e5f (diff) |
Merge branch 'feature/project-import' of gitlab.com:gitlab-org/gitlab-ce into feature/project-export-ui-experimental
# Conflicts:
# app/controllers/import/gitlab_project_controller.rb
Diffstat (limited to 'app')
117 files changed, 1419 insertions, 874 deletions
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index af4462ece38..bf95e06b4e5 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,77 +1,77 @@ class @AwardsHandler - constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) -> - $(".js-add-award").on "click", (event) => + constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) -> + $('.js-add-award').on 'click', (event) => event.stopPropagation() event.preventDefault() @showEmojiMenu() - $("html").on 'click', (event) -> - if !$(event.target).closest(".emoji-menu").length - if $(".emoji-menu").is(":visible") - $(".emoji-menu").removeClass "is-visible" + $('html').on 'click', (event) -> + if !$(event.target).closest('.emoji-menu').length + if $('.emoji-menu').is(':visible') + $('.emoji-menu').removeClass 'is-visible' - $(".awards") - .off "click" - .on "click", ".js-emoji-btn", @handleClick + $('.awards') + .off 'click' + .on 'click', '.js-emoji-btn', @handleClick @renderFrequentlyUsedBlock() handleClick: (e) -> e.preventDefault() emoji = $(this) - .find(".icon") - .data "emoji" + .find('.icon') + .data 'emoji' - if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown" - awards_handler.addAward "thumbsdown" + if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown' + awardsHandler.addAward 'thumbsdown' - else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup" - awards_handler.addAward "thumbsup" + else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup' + awardsHandler.addAward 'thumbsup' - awards_handler.addAward emoji + awardsHandler.addAward emoji + + $(this).trigger 'blur' didUserClickEmoji: (that, emoji) -> - if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title") - $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1 + if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title') + $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1 showEmojiMenu: -> - if $(".emoji-menu").length - if $(".emoji-menu").is ".is-visible" - $(".emoji-menu").removeClass "is-visible" - $("#emoji_search").blur() + if $('.emoji-menu').length + if $('.emoji-menu').is '.is-visible' + $('.emoji-menu').removeClass 'is-visible' + $('#emoji_search').blur() else - $(".emoji-menu").addClass "is-visible" - $("#emoji_search").focus() + $('.emoji-menu').addClass 'is-visible' + $('#emoji_search').focus() else - $('.js-add-award').addClass "is-loading" - $.get @get_emojis_url, (response) => - $('.js-add-award').removeClass "is-loading" - $(".js-award-holder").append response + $('.js-add-award').addClass 'is-loading' + $.get @getEmojisUrl, (response) => + $('.js-add-award').removeClass 'is-loading' + $('.js-award-holder').append response setTimeout => - $(".emoji-menu").addClass "is-visible" - $("#emoji_search").focus() + $('.emoji-menu').addClass 'is-visible' + $('#emoji_search').focus() @setupSearch() , 200 addAward: (emoji) -> - emoji = @normilizeEmojiName(emoji) @postEmoji emoji, => @addAwardToEmojiBar(emoji) - $(".emoji-menu").removeClass "is-visible" + $('.emoji-menu').removeClass 'is-visible' addAwardToEmojiBar: (emoji) -> @addEmojiToFrequentlyUsedList(emoji) - emoji = @normilizeEmojiName(emoji) if @exist(emoji) if @isActive(emoji) @decrementCounter(emoji) else - counter = @findEmojiIcon(emoji).siblings(".js-counter") + counter = @findEmojiIcon(emoji).siblings('.js-counter') counter.text(parseInt(counter.text()) + 1) - counter.parent().addClass("active") + counter.parent().addClass('active') @addMeToAuthorList(emoji) else @createEmoji(emoji) @@ -80,47 +80,47 @@ class @AwardsHandler @findEmojiIcon(emoji).length > 0 isActive: (emoji) -> - @findEmojiIcon(emoji).parent().hasClass("active") + @findEmojiIcon(emoji).parent().hasClass('active') decrementCounter: (emoji) -> - counter = @findEmojiIcon(emoji).siblings(".js-counter") + counter = @findEmojiIcon(emoji).siblings('.js-counter') emojiIcon = counter.parent() if parseInt(counter.text()) > 1 counter.text(parseInt(counter.text()) - 1) - emojiIcon.removeClass("active") + emojiIcon.removeClass('active') @removeMeFromAuthorList(emoji) - else if emoji == "thumbsup" || emoji == "thumbsdown" - emojiIcon.tooltip("destroy") + else if emoji == 'thumbsup' || emoji == 'thumbsdown' + emojiIcon.tooltip('destroy') counter.text(0) - emojiIcon.removeClass("active") + emojiIcon.removeClass('active') @removeMeFromAuthorList(emoji) else - emojiIcon.tooltip("destroy") + emojiIcon.tooltip('destroy') emojiIcon.remove() removeMeFromAuthorList: (emoji) -> - award_block = @findEmojiIcon(emoji).parent() - authors = award_block - .attr("data-original-title") - .split(", ") - authors.splice(authors.indexOf("me"),1) - award_block - .closest(".js-emoji-btn") - .attr("data-original-title", authors.join(", ")) - @resetTooltip(award_block) + awardBlock = @findEmojiIcon(emoji).parent() + authors = awardBlock + .attr('data-original-title') + .split(', ') + authors.splice(authors.indexOf('me'),1) + awardBlock + .closest('.js-emoji-btn') + .attr('data-original-title', authors.join(', ')) + @resetTooltip(awardBlock) addMeToAuthorList: (emoji) -> - award_block = @findEmojiIcon(emoji).parent() - origTitle = award_block.attr("data-original-title").trim() + awardBlock = @findEmojiIcon(emoji).parent() + origTitle = awardBlock.attr('data-original-title').trim() authors = [] if origTitle authors = origTitle.split(', ') - authors.push("me") - award_block.attr("data-original-title", authors.join(", ")) - @resetTooltip(award_block) + authors.push('me') + awardBlock.attr('data-original-title', authors.join(', ')) + @resetTooltip(awardBlock) resetTooltip: (award) -> - award.tooltip("destroy") + award.tooltip('destroy') # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout. setTimeout (-> @@ -139,28 +139,28 @@ class @AwardsHandler "</button>" ) - emoji_node = $(nodes.join("\n")) - .insertBefore(".js-award-holder") - .find(".emoji-icon") - .data("emoji", emoji) + $(nodes.join("\n")) + .insertBefore('.js-award-holder') + .find('.emoji-icon') + .data('emoji', emoji) $('.award-control').tooltip() resolveNameToCssClass: (emoji) -> - emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") + emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']") - if emoji_icon.length > 0 - unicodeName = emoji_icon.data("unicode-name") + if emojiIcon.length > 0 + unicodeName = emojiIcon.data('unicode-name') else # Find by alias - unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name") + unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name') "emoji-#{unicodeName}" postEmoji: (emoji, callback) -> - $.post @post_emoji_url, { note: { + $.post @postEmojiUrl, { note: { note: ":#{emoji}:" - noteable_type: @noteable_type - noteable_id: @noteable_id + noteable_type: @noteableType + noteable_id: @noteableId }},(data) -> if data.ok callback.call() @@ -173,46 +173,43 @@ class @AwardsHandler scrollTop: $('.awards').offset().top - 80 }, 200) - normilizeEmojiName: (emoji) -> - @aliases[emoji] || emoji - addEmojiToFrequentlyUsedList: (emoji) -> - frequently_used_emojis = @getFrequentlyUsedEmojis() - frequently_used_emojis.push(emoji) - $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 }) + frequentlyUsedEmojis = @getFrequentlyUsedEmojis() + frequentlyUsedEmojis.push(emoji) + $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }) getFrequentlyUsedEmojis: -> - frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") - _.compact(_.uniq(frequently_used_emojis)) + frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',') + _.compact(_.uniq(frequentlyUsedEmojis)) renderFrequentlyUsedBlock: -> if $.cookie('frequently_used_emojis') - frequently_used_emojis = @getFrequentlyUsedEmojis() + frequentlyUsedEmojis = @getFrequentlyUsedEmojis() - ul = $("<ul>") + ul = $('<ul>') - for emoji in frequently_used_emojis + for emoji in frequentlyUsedEmojis do (emoji) -> - $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul) + $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul) - $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used")) + $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used')) setupSearch: -> - $("input.emoji-search").keyup (ev) => + $('input.emoji-search').keyup (ev) => term = $(ev.target).val() # Clean previous search results - $("ul.emoji-menu-search, h5.emoji-search").remove() + $('ul.emoji-menu-search, h5.emoji-search').remove() if term # Generate a search result block - h5 = $("<h5>").text("Search results").addClass("emoji-search") - found_emojis = @searchEmojis(term).show() - ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis) - $(".emoji-menu-content ul, .emoji-menu-content h5").hide() - $(".emoji-menu-content").append(h5).append(ul) + h5 = $('<h5>').text('Search results').addClass('emoji-search') + foundEmojis = @searchEmojis(term).show() + ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis) + $('.emoji-menu-content ul, .emoji-menu-content h5').hide() + $('.emoji-menu-content').append(h5).append(ul) else - $(".emoji-menu-content").children().show() + $('.emoji-menu-content').children().show() searchEmojis: (term)-> $(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone() diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 2fdb7562515..f91aa3c5ad7 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -108,6 +108,8 @@ class Dispatcher new BuildArtifacts() when 'projects:group_links:index' new GroupsSelect() + when 'search:show' + new Search() switch path.first() when 'admin' diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 29466e9f2ed..1d1bfeb2e77 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -184,6 +184,9 @@ class GitLabDropdown @dropdown.on "shown.bs.dropdown", @opened @dropdown.on "hidden.bs.dropdown", @hidden @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate + @dropdown.on 'keyup', (e) => + if e.which is 27 # Escape key + $('.dropdown-menu-close', @dropdown).trigger 'click' if @dropdown.find(".dropdown-toggle-page").length @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee index 3a439b94c59..3c491ebfc4c 100644 --- a/app/assets/javascripts/issuable_context.js.coffee +++ b/app/assets/javascripts/issuable_context.js.coffee @@ -10,8 +10,8 @@ class @IssuableContext $(this).submit() $(document) - .off 'click', '.dropdown-content a' - .on 'click', '.dropdown-content a', (e) -> + .off 'click', '.issuable-sidebar .dropdown-content a' + .on 'click', '.issuable-sidebar .dropdown-content a', (e) -> e.preventDefault() $(document) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index c7d74a12f99..157361404e0 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -12,6 +12,7 @@ class @Issue @initMergeRequests() @initRelatedBranches() + @initCanCreateBranch() initTaskList: -> $('.detail-page-description .js-task-list-container').taskList('enable') @@ -92,3 +93,25 @@ class @Issue .success (data) -> if 'html' of data $container.html(data.html) + + initCanCreateBranch: -> + $container = $('div#new-branch') + + # If the user doesn't have the required permissions the container isn't + # rendered at all. + return unless $container + + $.getJSON($container.data('path')) + .error -> + $container.find('.checking').hide() + $container.find('.unavailable').show() + + new Flash('Failed to check if a new branch can be created.', 'alert') + .success (data) -> + if data.can_create_branch + $container.find('.checking').hide() + $container.find('.available').show() + $container.find('a').attr('disabled', false) + else + $container.find('.checking').hide() + $container.find('.unavailable').show() diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 85517b18c5a..995fd768603 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -30,7 +30,7 @@ class @LabelsSelect if issueUpdateURL labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> - <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= _.escape(label.title) %>"> + <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%= _.escape(label.title) %>"> <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;"> <%= _.escape(label.title) %> </span> @@ -163,6 +163,21 @@ class @LabelsSelect $.ajax( url: labelUrl ).done (data) -> + data = _.chain data + .groupBy (label) -> + label.title + .map (label) -> + color = _.map label, (dup) -> + dup.color + + return { + id: label[0].id + title: label[0].title + color: color + duplicate: color.length > 1 + } + .value() + if $dropdown.hasClass 'js-extra-options' if showNo data.unshift( @@ -178,6 +193,7 @@ class @LabelsSelect if data.length > 2 data.splice 2, 0, 'divider' + callback data renderRow: (label) -> @@ -192,11 +208,31 @@ class @LabelsSelect if $dropdown.hasClass('js-multiselect') and removesAll selectedClass.push 'dropdown-clear-active' - color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else "" + if label.duplicate + spacing = 100 / label.color.length + + # Reduce the colors to 4 + label.color = label.color.filter (color, i) -> + i < 4 + + color = _.map(label.color, (color, i) -> + percentFirst = Math.floor(spacing * i) + percentSecond = Math.floor(spacing * (i + 1)) + "#{color} #{percentFirst}%,#{color} #{percentSecond}% " + ).join(',') + color = "linear-gradient(#{color})" + else + if label.color? + color = label.color[0] + + if color + colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>" + else + colorEl = '' "<li> <a href='#' class='#{selectedClass.join(' ')}'> - #{color} + #{colorEl} #{_.escape(label.title)} </a> </li>" diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 82e210fed7d..efb3e8e2198 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -167,8 +167,8 @@ class @Notes return if note.award - awards_handler.addAwardToEmojiBar(note.note) - awards_handler.scrollToAwards() + awardsHandler.addAwardToEmojiBar(note.note) + awardsHandler.scrollToAwards() # render note if it not present in loaded list # or skip if rendered @@ -373,11 +373,11 @@ class @Notes new GLForm form if scrollTo? and myLastNote? - # scroll to the bottom + # scroll to the bottom # so the open of the last element doesn't make a jump $('html, body').scrollTop($(document).height()); $('html, body').animate({ - scrollTop: myLastNote.offset().top - 150 + scrollTop: myLastNote.offset().top - 150 }, 500, -> $noteText = form.find(".js-note-text") $noteText.focus() diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee index 67403554340..2d084b76cfe 100644 --- a/app/assets/javascripts/right_sidebar.js.coffee +++ b/app/assets/javascripts/right_sidebar.js.coffee @@ -1,10 +1,12 @@ class @Sidebar constructor: (currentUser) -> + @sidebar = $('aside') + @addEventListeners() addEventListeners: -> - $('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked) - $('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden) + @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked) + $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden) $('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading) $('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded) @@ -30,26 +32,56 @@ class @Sidebar else i.show() - sidebarCollapseClicked: (e) -> + sidebar = e.data e.preventDefault() $block = $(@).closest('.block') + sidebar.openDropdown($block); - $('aside') - .find('.gutter-toggle') - .trigger('click') - $editLink = $block.find('.edit-link') + openDropdown: (blockOrName) -> + $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName + + $block.find('.edit-link').trigger('click') - if $editLink.length - $editLink.trigger('click') - $block.addClass('collapse-after-update') - $('.page-with-sidebar').addClass('with-overlay') + if not @isOpen() + @setCollapseAfterUpdate($block) + @toggleSidebar('open') - sidebarDropdownHidden: (e) -> + setCollapseAfterUpdate: ($block) -> + $block.addClass('collapse-after-update') + $('.page-with-sidebar').addClass('with-overlay') + + onSidebarDropdownHidden: (e) -> + sidebar = e.data + e.preventDefault() $block = $(@).closest('.block') + sidebar.sidebarDropdownHidden($block) + + sidebarDropdownHidden: ($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 + @toggleSidebar('hide') + + triggerOpenSidebar: -> + @sidebar + .find('.js-sidebar-toggle') + .trigger('click') + + toggleSidebar: (action = 'toggle') -> + if action is 'toggle' + @triggerOpenSidebar() + + if action is 'open' + @triggerOpenSidebar() if not @isOpen() + + if action is 'hide' + @triggerOpenSidebar() is @isOpen() + + isOpen: -> + @sidebar.is('.right-sidebar-expanded') + + getBlock: (name) -> + @sidebar.find(".block.#{name}") + + diff --git a/app/assets/javascripts/search.js.coffee b/app/assets/javascripts/search.js.coffee new file mode 100644 index 00000000000..661e1195f60 --- /dev/null +++ b/app/assets/javascripts/search.js.coffee @@ -0,0 +1,75 @@ +class @Search + constructor: -> + $groupDropdown = $('.js-search-group-dropdown') + $projectDropdown = $('.js-search-project-dropdown') + @eventListeners() + + $groupDropdown.glDropdown( + selectable: true + filterable: true + fieldName: 'group_id' + data: (term, callback) -> + Api.groups term, null, (data) -> + data.unshift( + name: 'Any' + ) + data.splice 1, 0, 'divider' + + callback(data) + id: (obj) -> + obj.id + text: (obj) -> + obj.name + toggleLabel: (obj) -> + "#{$groupDropdown.data('default-label')} #{obj.name}" + clicked: => + @submitSearch() + ) + + $projectDropdown.glDropdown( + selectable: true + filterable: true + fieldName: 'project_id' + data: (term, callback) -> + Api.projects term, 'id', (data) -> + data.unshift( + name_with_namespace: 'Any' + ) + data.splice 1, 0, 'divider' + + callback(data) + id: (obj) -> + obj.id + text: (obj) -> + obj.name_with_namespace + toggleLabel: (obj) -> + "#{$projectDropdown.data('default-label')} #{obj.name_with_namespace}" + clicked: => + @submitSearch() + ) + + eventListeners: -> + $(document) + .off 'keyup', '.js-search-input' + .on 'keyup', '.js-search-input', @searchKeyUp + + $(document) + .off 'click', '.js-search-clear' + .on 'click', '.js-search-clear', @clearSearchField + + submitSearch: -> + $('.js-search-form').submit() + + searchKeyUp: -> + $input = $(@) + + if $input.val() is '' + $('.js-search-clear').addClass 'hidden' + else + $('.js-search-clear').removeClass 'hidden' + + clearSearchField: -> + $('.js-search-input') + .val '' + .trigger 'keyup' + .focus() diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee index 100e3aac535..f3d66004138 100644 --- a/app/assets/javascripts/shortcuts.js.coffee +++ b/app/assets/javascripts/shortcuts.js.coffee @@ -2,34 +2,35 @@ class @Shortcuts constructor: -> @enabledHelp = [] Mousetrap.reset() - Mousetrap.bind('?', @selectiveHelp) + Mousetrap.bind('?', @onToggleHelp) Mousetrap.bind('s', Shortcuts.focusSearch) Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview) Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL? - selectiveHelp: (e) => - Shortcuts.showHelp(e, @enabledHelp) + onToggleHelp: (e) => + e.preventDefault() + @toggleHelp(@enabledHelp) toggleMarkdownPreview: (e) => $(document).triggerHandler('markdown-preview:toggle', [e]) - @showHelp: (e, location) -> - if $('#modal-shortcuts').length > 0 - $('#modal-shortcuts').modal('show') - else - url = '/help/shortcuts' - url = gon.relative_url_root + url if gon.relative_url_root? - $.ajax( - url: url, - dataType: 'script', - success: (e) -> - if location and location.length > 0 - $(l).show() for l in location - else - $('.hidden-shortcut').show() - $('.js-more-help-button').remove() - ) - e.preventDefault() + toggleHelp: (location) -> + $modal = $('#modal-shortcuts') + + if $modal.length + $modal.modal('toggle') + return + + $.ajax( + url: gon.shortcuts_path, + dataType: 'script', + success: (e) -> + if location and location.length > 0 + $(l).show() for l in location + else + $('.hidden-shortcut').show() + $('.js-more-help-button').remove() + ) @focusSearch: (e) -> $('#search').focus() diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee index bbf02f1db24..ad9b3c1c6bf 100644 --- a/app/assets/javascripts/shortcuts_issuable.coffee +++ b/app/assets/javascripts/shortcuts_issuable.coffee @@ -4,18 +4,8 @@ class @ShortcutsIssuable extends ShortcutsNavigation constructor: (isMergeRequest) -> super() - Mousetrap.bind('a', -> - $('.block.assignee .edit-link').trigger('click') - return false - ) - Mousetrap.bind('m', -> - $('.block.milestone .edit-link').trigger('click') - return false - ) - Mousetrap.bind('r', => - @replyWithSelectedText() - return false - ) + Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee')) + Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone')) Mousetrap.bind('j', => @prevIssue() return false @@ -28,7 +18,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation @editIssue() return false ) - + Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels')) if isMergeRequest @enabledHelp.push('.hidden-shortcut.merge_requests') @@ -71,3 +61,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation editIssue: -> $editBtn = $('.issuable-edit') Turbolinks.visit($editBtn.attr('href')) + + openSidebarDropdown: (name) -> + sidebar.openDropdown(name) + return false diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee index 8decaedd87b..f39504e0645 100644 --- a/app/assets/javascripts/shortcuts_navigation.coffee +++ b/app/assets/javascripts/shortcuts_navigation.coffee @@ -14,6 +14,7 @@ class @ShortcutsNavigation extends Shortcuts Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests')) Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki')) Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets')) + Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue')) @enabledHelp.push('.hidden-shortcut.project') @findAndFollowLink: (selector) -> diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee index 09b7eec9104..c2aeffe2381 100644 --- a/app/assets/javascripts/user_tabs.js.coffee +++ b/app/assets/javascripts/user_tabs.js.coffee @@ -92,7 +92,7 @@ class @UserTabs @setCurrentAction(action) activateTab: (action) -> - @parentEl.find(".nav-links .#{action}-tab a").tab('show') + @parentEl.find(".nav-links .js-#{action}-tab a").tab('show') setTab: (source, action) -> return if @loaded[action] is true diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c85ab9148d0..560de9fc0bd 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -25,6 +25,7 @@ @import "framework/lists.scss"; @import "framework/markdown_area.scss"; @import "framework/mobile.scss"; +@import "framework/modal.scss"; @import "framework/nav.scss"; @import "framework/pagination.scss"; @import "framework/progress.scss"; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 62b2af0dbf7..e72e4aa47ef 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -1,5 +1,5 @@ .light-well { - background-color: #f8fafc; + background-color: $background-color; padding: 15px; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 18a74fe21a0..062da397b6b 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -139,6 +139,10 @@ pointer-events: auto !important; } + &[disabled] { + pointer-events: none !important; + } + .caret { margin-left: 5px; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 2ade341c9dd..3386523dbf7 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -11,6 +11,7 @@ .prepend-top-10 { margin-top: 10px } .prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-20 { margin-top: 20px } +.prepend-left-5 { margin-left: 5px } .prepend-left-10 { margin-left: 10px } .prepend-left-default { margin-left: $gl-padding; } .prepend-left-20 { margin-left: 20px } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 239eaf15cc1..4bf3a050403 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -42,7 +42,7 @@ font-size: 15px; text-align: left; border: 1px solid $dropdown-toggle-border-color; - border-radius: $dropdown-border-radius; + border-radius: $border-radius-base; outline: 0; text-overflow: ellipsis; white-space: nowrap; @@ -80,7 +80,7 @@ padding: 10px 0; background-color: $dropdown-bg; border: 1px solid $dropdown-border-color; - border-radius: $dropdown-border-radius; + border-radius: $border-radius-base; box-shadow: 0 2px 4px $dropdown-shadow-color; &.is-loading { diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 54cb5461113..558b133f593 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -78,6 +78,24 @@ label { border-radius: 3px; } +.select-wrapper { + position: relative; + + .caret { + position: absolute; + right: 10px; + top: $gl-padding; + color: $gray-darkest; + pointer-events: none; + } +} + +.select-control { + padding-left: 10px; + padding-right: 10px; + -webkit-appearance: none; +} + .form-control-inline { display: inline; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index c303380764b..5fa10d29a87 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -26,9 +26,9 @@ header { z-index: 100; margin-bottom: 0; min-height: $header-height; - background-color: #fff; + background-color: $background-color; border: none; - border-bottom: 1px solid #eee; + border-bottom: 1px solid $border-color; .container-fluid { width: 100% !important; @@ -47,7 +47,7 @@ header { text-align: center; &:hover, &:focus, &:active { - background-color: #fff; + background-color: $background-color; } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 704fa1ff800..fd885b38680 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -95,7 +95,7 @@ &.md-preview-holder { code { white-space: pre-wrap; - word-break: break-all; + word-break: keep-all; } } } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss new file mode 100644 index 00000000000..26ad2870aa0 --- /dev/null +++ b/app/assets/stylesheets/framework/modal.scss @@ -0,0 +1,22 @@ +.modal-body { + position: relative; + overflow-y: auto; + padding: 15px; + + .form-actions { + margin: -$gl-padding+1; + margin-top: 15px; + } + + .text-danger { + font-weight: bold; + } +} + +body.modal-open { + overflow: hidden; +} + +.modal .modal-dialog { + width: 860px; +} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 192d53b048a..5fe687dcec3 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -185,3 +185,22 @@ } } } + +.layout-nav { + background: $background-color; + border-bottom: 1px solid $border-color; + + .controls { + float: right; + position: relative; + top: 10px; + + .dropdown { + margin-left: 7px; + } + } + + .nav-links { + border-bottom: none; + } +} diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index eae5f062dda..6efc6ec1e4b 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -7,13 +7,11 @@ .select2-choice { background: #fff; border-color: $input-border; - border-color: $border-white-light; height: 35px; padding: $gl-vert-padding $gl-btn-padding; font-size: $gl-font-size; line-height: 1.42857143; - - @include border-radius($border-radius-default); + border-radius: $border-radius-base; .select2-arrow { background-image: none; @@ -199,6 +197,14 @@ } } +.select2-highlighted { + .group-result { + .group-path { + color: #fff; + } + } +} + .group-result { .group-image { float: left; diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index f0ec250de2b..29501069d27 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -11,7 +11,7 @@ border-bottom: 1px solid $border-white-light; &:target { - background: $row-hover; + background: $line-target-blue; } .avatar { diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index c72af5dad0a..371c1bf17e1 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -153,8 +153,8 @@ $nav-link-padding: 13px $gl-padding; //== Code // //## -$pre-bg: #f8fafc !default; +$pre-bg: $background-color !default; $pre-color: $gl-gray !default; -$pre-border-color: #e7e9ed; +$pre-border-color: $border-color; $table-bg-accent: $background-color; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 0a5b4b8834c..b2535ddf4bd 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -205,6 +205,10 @@ h1, h2, h3, h4, h5, h6 { font-weight: 600; } +.light-header { + font-weight: 600; +} + /** CODE **/ pre { font-family: $monospace_font; @@ -259,3 +263,9 @@ h1, h2, h3, h4 { color: $gl-gray; } } + +.text-right-lg { + @media (min-width: $screen-lg-min) { + text-align: right; + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 30ca27ab104..b8ed7e8a74c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -71,8 +71,7 @@ $gl-avatar-size: 40px; $error-exclamation-point: #e62958; $border-radius-default: 2px; $btn-transparent-color: #8f8f8f; -$ssh-key-icon-color: #8f8f8f; -$ssh-key-icon-size: 18px; +$settings-icon-size: 18px; $provider-btn-group-border: #e5e5e5; $provider-btn-not-active-color: #4688f1; @@ -168,8 +167,12 @@ $line-removed: #fbe9eb; $line-removed-dark: #fac5cd; $line-number-old: #f9d7dc; $line-number-new: #ddfbe6; +$line-number-select: #fbf2da; $match-line: #fafafa; $table-border-gray: #f0f0f0; +$line-target-blue: #eaf3fc; +$line-select-yellow: #fcf8e7; +$line-select-yellow-dark: #f0e2bd; /* * Fonts */ @@ -179,7 +182,6 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif /* * Dropdowns */ -$dropdown-border-radius: 2px; $dropdown-width: 300px; $dropdown-bg: #fff; $dropdown-link-color: #555; diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 1ff6ad75e07..31a4e3deaac 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -21,11 +21,6 @@ // Diff line .line_holder { - td.diff-line-num.hll:not(.empty-cell), - td.line_content.hll:not(.empty-cell) { - background-color: #f8eec7; - border-color: darken(#f8eec7, 15%); - } .diff-line-num { &.old { @@ -37,11 +32,16 @@ background-color: $line-number-new; border-color: $line-added-dark; } + + &.hll:not(.empty-cell) { + background-color: $line-number-select; + border-color: $line-select-yellow-dark; + } } .line_content { &.old { - background: $line-removed; + background-color: $line-removed; span.idiff { background-color: $line-removed-dark; @@ -58,7 +58,11 @@ &.match { color: $black-transparent; - background: $match-line; + background-color: $match-line; + } + + &.hll:not(.empty-cell) { + background-color: $line-select-yellow; } } } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 751a5ab4d92..3438dbe4958 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -40,6 +40,7 @@ .wiki { code { white-space: pre-wrap; + word-break: keep-all; } } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index e7c8198ba45..1a7d5f9666e 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -98,7 +98,11 @@ } td.line_content.parallel { - width: 50%; + width: 46%; + } + + .add-diff-note { + margin-left: -65px; } } @@ -127,8 +131,13 @@ margin: 0; padding: 0 0.5em; border: none; + &.parallel { display: table-cell; + + span { + word-break: break-all; + } } } diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index ee95bdf488e..4a95b7b852e 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -55,25 +55,6 @@ } } -.modal-body { - position: relative; - overflow-y: auto; - padding: 15px; - - .form-actions { - margin: -$gl-padding+1; - margin-top: 15px; - } -} - -body.modal-open { - overflow: hidden; -} - -.modal .modal-dialog { - width: 860px; -} - .documentation { padding: 7px; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index d54abe9bc02..9619d65db85 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -109,6 +109,10 @@ ul.notes { border-color: darken(#f5f5f5, 8%); margin: 10px 0; } + + code { + word-break: keep-all; + } } a { @@ -211,7 +215,7 @@ ul.notes { } .discussion-actions { - @media (max-width: $screen-sm-max) { + @media (max-width: $screen-md-max) { float: none; margin-left: 0; diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index a9656e5cae7..01f98479623 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -18,7 +18,8 @@ } .account-btn-link, -.profile-settings-sidebar a { +.profile-settings-sidebar a, +.settings-sidebar a { color: $md-link-color; } @@ -123,12 +124,6 @@ } } -.key-icon { - color: $ssh-key-icon-color; - font-size: $ssh-key-icon-size; - line-height: 42px; -} - .key-created-at { line-height: 42px; } @@ -180,14 +175,6 @@ } } -.profile-settings-message { - line-height: 32px; - color: $warning-message-color; - background-color: $warning-message-bg; - border: 1px solid $warning-message-border; - border-radius: $border-radius-base; -} - .oauth-applications { form { display: inline-block; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index fcca9d4faf5..99108e9bfc4 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -202,8 +202,31 @@ min-width: 200px; } -.deploy-project-label { - margin: 1px; +.deploy-key-content { + @media (min-width: $screen-sm-min) { + float: left; + + &:last-child { + float: right; + } + } +} + +.deploy-key-projects { + @media (min-width: $screen-sm-min) { + line-height: 42px; + } +} + +a.deploy-project-label { + padding: 5px; + margin-right: 5px; + color: $gl-gray; + background-color: $row-hover; + + &:hover { + color: $gl-link-color; + } } .vs-public { @@ -256,12 +279,6 @@ } } -table.table.protected-branches-list tr.no-border { - th, td { - border: 0; - } -} - .project-import .btn { float: left; margin-right: 10px; @@ -474,3 +491,14 @@ pre.light-well { color: #fff; } } + +.protected-branches-list { + a { + color: $gl-gray; + font-weight: 600; + + &:hover { + color: $gl-link-color; + } + } +} diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index f0f3744c6fa..2bff70c8c64 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -10,17 +10,6 @@ } } -.search-holder { - max-width: 600px; - margin: 0 auto; - margin-bottom: 20px; - - input { - border-color: #bbb; - font-weight: bold; - } -} - .search { margin-right: 10px; margin-left: 10px; @@ -159,7 +148,85 @@ &.has-location-badge { .search-input-wrap { - width: 78%; + width: 68%; } } } + +.search-holder { + @media (min-width: $screen-sm-min) { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + } + + .search-field-holder { + -webkit-flex: 1 0 auto; + -ms-flex: 1 0 auto; + flex: 1 0 auto; + position: relative; + margin-right: 0; + + @media (min-width: $screen-sm-min) { + margin-right: 5px; + } + } + + .search-icon { + position: absolute; + left: 10px; + top: 10px; + color: $gray-darkest; + pointer-events: none; + } + + .search-text-input { + padding-left: $gl-padding + 15px; + padding-right: $gl-padding + 15px; + } + + .btn-search { + width: 100%; + margin-top: 5px; + + @media (min-width: $screen-sm-min) { + width: auto; + margin-top: 0; + margin-left: 5px; + } + } + + .dropdown { + @media (min-width: $screen-sm-min) { + margin-left: 5px; + margin-right: 5px; + } + } + + .dropdown-menu-toggle { + width: 100%; + margin-top: 5px; + + @media (min-width: $screen-sm-min) { + width: 160px; + margin-top: 0; + } + } +} + +.search-clear { + position: absolute; + right: 10px; + top: 10px; + padding: 0; + color: $gray-darkest; + line-height: 0; + background: none; + border: 0; + + &:hover, + &:focus { + color: $gl-link-color; + outline: none; + } +} diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss new file mode 100644 index 00000000000..3fb70085713 --- /dev/null +++ b/app/assets/stylesheets/pages/settings.scss @@ -0,0 +1,14 @@ +.settings-list-icon { + color: $gl-placeholder-color; + font-size: $settings-icon-size; + line-height: 42px; +} + +.settings-message { + padding: 5px; + line-height: 1.3; + color: $warning-message-color; + background-color: $warning-message-bg; + border: 1px solid $warning-message-border; + border-radius: $border-radius-base; +} diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index 9083bfb41cf..cf795d977ce 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController layout 'admin' def authenticate_admin! - return render_404 unless current_user.is_admin? - end - - def authorize_impersonator! - if session[:impersonator_id] - User.find_by!(username: session[:impersonator_id]).admin? - end + render_404 unless current_user.is_admin? end end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 93c4894ea0f..4e85b6b4cf2 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -39,6 +39,12 @@ class Admin::HooksController < Admin::ApplicationController end def hook_params - params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events) + params.require(:hook).permit( + :enable_ssl_verification, + :push_events, + :tag_push_events, + :token, + :url + ) end end diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb deleted file mode 100644 index bf98af78615..00000000000 --- a/app/controllers/admin/impersonation_controller.rb +++ /dev/null @@ -1,38 +0,0 @@ -class Admin::ImpersonationController < Admin::ApplicationController - skip_before_action :authenticate_admin!, only: :destroy - - before_action :user - before_action :authorize_impersonator! - - def create - if @user.blocked? - flash[:alert] = "You cannot impersonate a blocked user" - - redirect_to admin_user_path(@user) - else - session[:impersonator_id] = current_user.username - session[:impersonator_return_to] = admin_user_path(@user) - - warden.set_user(user, scope: 'user') - - flash[:alert] = "You are impersonating #{user.username}." - - redirect_to root_path - end - end - - def destroy - redirect = session[:impersonator_return_to] - - warden.set_user(user, scope: 'user') - - session[:impersonator_return_to] = nil - session[:impersonator_id] = nil - - redirect_to redirect || root_path - end - - def user - @user ||= User.find_by!(username: params[:id] || session[:impersonator_id]) - end -end diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb new file mode 100644 index 00000000000..2db824c87ef --- /dev/null +++ b/app/controllers/admin/impersonations_controller.rb @@ -0,0 +1,24 @@ +class Admin::ImpersonationsController < Admin::ApplicationController + skip_before_action :authenticate_admin! + before_action :authenticate_impersonator! + + def destroy + original_user = current_user + + warden.set_user(impersonator, scope: :user) + + session[:impersonator_id] = nil + + redirect_to admin_user_path(original_user) + end + + private + + def impersonator + @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id] + end + + def authenticate_impersonator! + render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked? + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9abf08d0e19..b8976fa09a9 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -31,6 +31,22 @@ class Admin::UsersController < Admin::ApplicationController user end + def impersonate + if user.blocked? + flash[:alert] = "You cannot impersonate a blocked user" + + redirect_to admin_user_path(user) + else + session[:impersonator_id] = current_user.id + + warden.set_user(user, scope: :user) + + flash[:alert] = "You are now impersonating #{user.username}" + + redirect_to root_path + end + end + def block if user.block redirect_back_or_admin_user(notice: "Successfully blocked") diff --git a/app/controllers/import/gitlab_project_controller.rb b/app/controllers/import/gitlab_project_controller.rb new file mode 100644 index 00000000000..ab0da196ac1 --- /dev/null +++ b/app/controllers/import/gitlab_project_controller.rb @@ -0,0 +1,45 @@ +class Import::GitlabProjectController < Import::BaseController + before_action :verify_gitlab_project_import_enabled + before_action :gitlab_project_auth, except: :callback + + rescue_from OAuth::Error, with: :gitlab_project_unauthorized + + #TODO permissions stuff + + def callback + + redirect_to status_import_gitlab_project_url + end + + def status + @repos = client.projects + @incompatible_repos = client.incompatible_projects + + @already_added_projects = current_user.created_projects.where(import_type: "gitlab_project") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.to_a.reject!{ |repo| already_added_projects_names.include? "#{repo["owner"]}/#{repo["slug"]}" } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "gitlab_project").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @file = params[:file] + + repo_owner = current_user.username + @target_namespace = params[:new_namespace].presence || repo_owner + + # namespace = get_or_create_namespace || (render and return) + + @project = Gitlab::ImportExport::ImportService.execute(archive_file: file, owner: repo_owner) + end + + private + + def verify_gitlab_project_import_enabled + render_404 unless gitlab_project_import_enabled? + end +end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 7d09288bc80..83d5ced9be8 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -7,31 +7,24 @@ class Projects::DeployKeysController < Projects::ApplicationController layout "project_settings" def index - @enabled_keys = @project.deploy_keys - - @available_keys = accessible_keys - @enabled_keys - @available_project_keys = current_user.project_deploy_keys - @enabled_keys - @available_public_keys = DeployKey.are_public - @enabled_keys - - # Public keys that are already used by another accessible project are already - # in @available_project_keys. - @available_public_keys -= @available_project_keys + @key = DeployKey.new + set_index_vars end def new - @key = @project.deploy_keys.new - - respond_with(@key) + redirect_to namespace_project_deploy_keys_path(@project.namespace, + @project) end def create @key = DeployKey.new(deploy_key_params) + set_index_vars if @key.valid? && @project.deploy_keys << @key redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) else - render "new" + render "index" end end @@ -51,6 +44,18 @@ class Projects::DeployKeysController < Projects::ApplicationController protected + def set_index_vars + @enabled_keys ||= @project.deploy_keys + + @available_keys ||= accessible_keys - @enabled_keys + @available_project_keys ||= current_user.project_deploy_keys - @enabled_keys + @available_public_keys ||= DeployKey.are_public - @enabled_keys + + # Public keys that are already used by another accessible project are already + # in @available_project_keys. + @available_public_keys -= @available_project_keys + end + def accessible_keys @accessible_keys ||= current_user.accessible_deploy_keys end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 5fd4f855dec..dfa9bd259e8 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -52,8 +52,16 @@ class Projects::HooksController < Projects::ApplicationController end def hook_params - params.require(:hook).permit(:url, :push_events, :issues_events, - :merge_requests_events, :tag_push_events, :note_events, - :build_events, :enable_ssl_verification) + params.require(:hook).permit( + :build_events, + :enable_ssl_verification, + :issues_events, + :merge_requests_events, + :note_events, + :push_events, + :tag_push_events, + :token, + :url + ) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 7d4fc361ce2..016f5dd0005 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -3,8 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController include IssuableActions before_action :module_enabled - before_action :issue, - only: [:edit, :update, :show, :referenced_merge_requests, :related_branches] + before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, + :related_branches, :can_create_branch] # Allow read any issue before_action :authorize_read_issue!, only: [:show] @@ -96,6 +96,8 @@ class Projects::IssuesController < Projects::ApplicationController if params[:move_to_project_id].to_i > 0 new_project = Project.find(params[:move_to_project_id]) + return render_404 unless issue.can_move?(current_user, new_project) + move_service = Issues::MoveService.new(project, current_user) @issue = move_service.execute(@issue, new_project) end @@ -139,6 +141,18 @@ class Projects::IssuesController < Projects::ApplicationController end end + def can_create_branch + can_create = current_user && + can?(current_user, :push_code, @project) && + @issue.can_be_worked_on?(current_user) + + respond_to do |format| + format.json do + render json: { can_create_branch: can_create } + end + end + end + def bulk_update result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" }) diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index c02bc28acef..0d6c32fabd2 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -40,10 +40,10 @@ class Projects::WikisController < Projects::ApplicationController end def update - @page = @project_wiki.find_page(params[:id]) - return render('empty') unless can?(current_user, :create_wiki, @project) + @page = @project_wiki.find_page(params[:id]) + if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) redirect_to( namespace_project_wiki_path(@project.namespace, @project, @page), diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index e42d2d73947..69c92d2bed2 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -8,8 +8,6 @@ class SearchController < ApplicationController def show return if params[:search].nil? || params[:search].blank? - @search_term = params[:search] - if params[:project_id].present? @project = Project.find_by(id: params[:project_id]) @project = nil unless can?(current_user, :download_code, @project) @@ -20,6 +18,8 @@ class SearchController < ApplicationController @group = nil unless can?(current_user, :read_group, @group) end + @search_term = params[:search] + @scope = params[:scope] @show_snippets = params[:snippets].eql? 'true' @@ -44,7 +44,7 @@ class SearchController < ApplicationController Search::GlobalService.new(current_user, params).execute end - @objects = @search_results.objects(@scope, params[:page]) + @search_objects = @search_results.objects(@scope, params[:page]) end def autocomplete diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index a41172816b8..01cbf91c658 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -51,7 +51,7 @@ class SnippetsFinder snippets = project.snippets.fresh if current_user - if project.team.member?(current_user.id) + if project.team.member?(current_user.id) || current_user.admin? snippets else snippets.public_and_internal diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index a4d7c425d0f..474c6f27374 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -3,8 +3,8 @@ module BlobHelper Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap) end - def highlight(blob_name, blob_content, nowrap: false) - Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap) + def highlight(blob_name, blob_content, nowrap: false, plain: false) + Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain) end def no_highlight_files diff --git a/app/helpers/ci_badge_helper.rb b/app/helpers/ci_badge_helper.rb deleted file mode 100644 index 27386133e36..00000000000 --- a/app/helpers/ci_badge_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -module CiBadgeHelper - def markdown_badge_code(project, ref) - url = status_ci_project_url(project, ref: ref, format: 'png') - link = namespace_project_commits_path(project.namespace, project, ref) - "[![build status](#{url})](#{link})" - end - - def html_badge_code(project, ref) - url = status_ci_project_url(project, ref: ref, format: 'png') - link = namespace_project_commits_path(project.namespace, project, ref) - "<a href='#{link}'><img src='#{url}' /></a>" - end -end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 97466d532f4..9f73edb4553 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -23,7 +23,7 @@ module DiffHelper end def diff_options - options = { ignore_whitespace_change: params[:w] == '1' } + options = { ignore_whitespace_change: hide_whitespace? } if diff_hard_limit_enabled? options.merge!(Commit.max_diff_options) end @@ -128,4 +128,31 @@ module DiffHelper title end end + + def commit_diff_whitespace_link(project, commit, options) + url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace) + toggle_whitespace_link(url, options) + end + + def diff_merge_request_whitespace_link(project, merge_request, options) + url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace) + toggle_whitespace_link(url, options) + end + + private + + def hide_whitespace? + params[:w] == '1' + end + + def params_with_whitespace + hide_whitespace? ? request.query_parameters.except(:w) : request.query_parameters.merge(w: 1) + end + + def toggle_whitespace_link(url, options) + options[:class] ||= '' + options[:class] << ' btn btn-default' + + link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class] + end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index afe1e11a0da..198d39455d7 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -16,31 +16,49 @@ module IssuesHelper def url_for_project_issues(project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.project_path - else - project.issues_tracker.project_url - end + url = + if options[:only_path] + project.issues_tracker.project_path + else + project.issues_tracker.project_url + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def url_for_new_issue(project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.new_issue_path - else - project.issues_tracker.new_issue_url - end + url = + if options[:only_path] + project.issues_tracker.new_issue_path + else + project.issues_tracker.new_issue_url + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def url_for_issue(issue_iid, project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.issue_path(issue_iid) - else - project.issues_tracker.issue_url(issue_iid) - end + url = + if options[:only_path] + project.issues_tracker.issue_path(issue_iid) + else + project.issues_tracker.issue_url(issue_iid) + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def bulk_update_milestone_options diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 3dded7c2f23..c99b137cdaa 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -37,7 +37,7 @@ module LabelsHelper link = send("namespace_project_#{type.to_s.pluralize}_path", project.namespace, project, - label_name: label.name) + label_name: [label.name]) if block_given? link_to link, &block diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8a97a74ad73..24c4c098c65 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -19,6 +19,16 @@ module SearchHelper end end + def search_entries_info(collection, scope, term) + return unless collection.count > 0 + + from = collection.offset_value + 1 + to = collection.offset_value + collection.length + count = collection.total_count + + "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" + end + private # Autocomplete results for various settings pages diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index cdc40b81ee1..96116e916dd 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -28,6 +28,14 @@ module Emails mail_answer_thread(@merge_request, note_thread_options(recipient_id)) end + def note_snippet_email(recipient_id, note_id) + setup_note_mail(note_id, recipient_id) + + @snippet = @note.noteable + @target_url = namespace_project_snippet_url(*note_target_url_options) + mail_answer_thread(@snippet, note_thread_options(recipient_id)) + end + private def note_target_url_options diff --git a/app/models/blob.rb b/app/models/blob.rb index 72e6c5fa3fd..0fea6b7f576 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -19,6 +19,14 @@ class Blob < SimpleDelegator new(blob) end + def no_highlighting? + size && size > 1.megabyte + end + + def only_display_raw? + size && size > 5.megabytes + end + def svg? text? && language && language.name == 'SVG' end diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index 5b8e3f654ea..7bcc78247ba 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -8,7 +8,7 @@ module Milestoneish end def complete?(user = nil) - total_items_count(user) == closed_items_count(user) + total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user) end def percent_complete(user = nil) diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index 8a293b7b76e..3ef91caad47 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -18,7 +18,7 @@ module Statuseable WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' WHEN (#{builds})=(#{pending}) THEN 'pending' - WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled' WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{running})+(#{pending})>0 THEN 'running' ELSE 'failed' diff --git a/app/models/event.rb b/app/models/event.rb index 12183524b79..897518aadc7 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -345,7 +345,7 @@ class Event < ActiveRecord::Base end def reset_project_activity - if project + if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain project.update_column(:last_activity_at, self.created_at) end end diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index bc6e0f98c3c..d149511b868 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -16,6 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# token :string # class ProjectHook < WebHook diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb index 80962264ba2..f45145eeb3a 100644 --- a/app/models/hooks/service_hook.rb +++ b/app/models/hooks/service_hook.rb @@ -16,6 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# token :string # class ServiceHook < WebHook diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index 15dddcc2447..012cc8ec005 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -16,6 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# token :string # class SystemHook < WebHook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 3a2e4f546f7..1e3b4815596 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -16,6 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# token :string # class WebHook < ActiveRecord::Base @@ -43,23 +44,17 @@ class WebHook < ActiveRecord::Base if parsed_url.userinfo.blank? response = WebHook.post(url, body: data.to_json, - headers: { - "Content-Type" => "application/json", - "X-Gitlab-Event" => hook_name.singularize.titleize - }, + headers: build_headers(hook_name), verify: enable_ssl_verification) else - post_url = url.gsub("#{parsed_url.userinfo}@", "") + post_url = url.gsub("#{parsed_url.userinfo}@", '') auth = { username: CGI.unescape(parsed_url.user), password: CGI.unescape(parsed_url.password), } response = WebHook.post(post_url, body: data.to_json, - headers: { - "Content-Type" => "application/json", - "X-Gitlab-Event" => hook_name.singularize.titleize - }, + headers: build_headers(hook_name), verify: enable_ssl_verification, basic_auth: auth) end @@ -73,4 +68,15 @@ class WebHook < ActiveRecord::Base def async_execute(data, hook_name) Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name) end + + private + + def build_headers(hook_name) + headers = { + 'Content-Type' => 'application/json', + 'X-Gitlab-Event' => hook_name.singularize.titleize + } + headers['X-Gitlab-Token'] = token if token.present? + headers + end end diff --git a/app/models/project.rb b/app/models/project.rb index 70921e02759..3e782be637e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -745,19 +745,17 @@ class Project < ActiveRecord::Base end def open_branches - all_branches = repository.branches + # We're using a Set here as checking values in a large Set is faster than + # checking values in a large Array. + protected_set = Set.new(protected_branch_names) - if protected_branches.present? - all_branches.reject! do |branch| - protected_branches_names.include?(branch.name) - end + repository.branches.reject do |branch| + protected_set.include?(branch.name) end - - all_branches end - def protected_branches_names - @protected_branches_names ||= protected_branches.map(&:name) + def protected_branch_names + @protected_branch_names ||= protected_branches.pluck(:name) end def root_ref?(branch) @@ -774,7 +772,7 @@ class Project < ActiveRecord::Base # Check if current branch name is marked as protected in the system def protected_branch?(branch_name) - protected_branches_names.include?(branch_name) + protected_branches.where(name: branch_name).any? end def developers_can_push_to_protected_branch?(branch_name) @@ -911,6 +909,7 @@ class Project < ActiveRecord::Base repository.rugged.references.create('HEAD', "refs/heads/#{branch}", force: true) + repository.copy_gitattributes(branch) reload_default_branch end diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 3efbfd2eec3..861cc974ec4 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -26,7 +26,7 @@ class BuildkiteService < CiService prop_accessor :project_url, :token, :enable_ssl_verification - validates :project_url, presence: true, if: :activated? + validates :project_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? after_save :compose_service_hook, if: :activated? @@ -91,7 +91,7 @@ class BuildkiteService < CiService { type: 'text', name: 'project_url', placeholder: "#{ENDPOINT}/example/project" }, - + { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 25045224ce5..c5501e06411 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -21,7 +21,7 @@ class IssueTrackerService < Service - validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? + validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? default_value_for :category, 'issue_tracker' diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 1ed42c4f3e7..b4418ba9284 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -28,6 +28,8 @@ class JiraService < IssueTrackerService prop_accessor :username, :password, :api_url, :jira_issue_transition_id, :title, :description, :project_url, :issues_url, :new_issue_url + validates :api_url, presence: true, url: true, if: :activated? + before_validation :set_api_url, :set_jira_issue_transition_id before_update :reset_password diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index fd65027f084..7092b757549 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -22,7 +22,7 @@ class SlackService < Service prop_accessor :webhook, :username, :channel boolean_accessor :notify_only_broken_builds - validates :webhook, presence: true, if: :activated? + validates :webhook, presence: true, url: true, if: :activated? def initialize_properties if properties.nil? diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 1f7d85a5f3d..d48f0546159 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -22,4 +22,6 @@ class ProjectSnippet < Snippet # Scopes scope :fresh, -> { order("created_at DESC") } + + participant :author, :notes end diff --git a/app/models/repository.rb b/app/models/repository.rb index 61c8dce6060..b4319297e43 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -457,7 +457,7 @@ class Repository def changelog cache.fetch(:changelog) do tree(:head).blobs.find do |file| - file.name =~ /\A(changelog|history)/i + file.name =~ /\A(changelog|history|changes|news)/i end end end @@ -938,6 +938,16 @@ class Repository raw_repository.ls_files(actual_ref) end + def copy_gitattributes(ref) + actual_ref = ref || root_ref + begin + raw_repository.copy_gitattributes(actual_ref) + true + rescue Gitlab::Git::Repository::InvalidRef + false + end + end + def main_language return if empty? || rugged.head_unborn? diff --git a/app/models/snippet.rb b/app/models/snippet.rb index b96e3937281..0fd08061925 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -112,6 +112,10 @@ class Snippet < ActiveRecord::Base visibility_level end + def no_highlighting? + content.lines.count > 1000 + end + class << self # Searches for snippets with a matching title or file name. # diff --git a/app/models/user.rb b/app/models/user.rb index ab48f8f1960..b6f405c6981 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -91,7 +91,7 @@ class User < ActiveRecord::Base devise :two_factor_backupable, otp_number_of_backup_codes: 10 serialize :otp_backup_codes, JSON - devise :lockable, :async, :recoverable, :rememberable, :trackable, + devise :lockable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable attr_accessor :force_random_password diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 1e1be8cd04b..b7af80055bf 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -42,7 +42,12 @@ class GitPushService < BaseService # Collect data for this git push @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev]) process_commit_messages + + # Update the bare repositories info/attributes file using the contents of the default branches + # .gitattributes file + update_gitattributes if is_default_branch? end + # Update merge requests that may be affected by this push. A new branch # could cause the last commit of a merge request to change. update_merge_requests @@ -54,6 +59,10 @@ class GitPushService < BaseService perform_housekeeping end + def update_gitattributes + @project.repository.copy_gitattributes(params[:ref]) + end + def update_main_language # Performance can be bad so for now only check main_language once # See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937 diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index fa34753c4fd..3544752d47a 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -7,6 +7,9 @@ module MergeRequests merge_request.can_be_created = false merge_request.compare_commits = [] merge_request.source_project = project unless merge_request.source_project + + merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project) + merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_branch ||= merge_request.target_project.default_branch diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 2bb312bb252..01586994813 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -5,6 +5,8 @@ module Notes note.author = current_user note.system = false + return unless valid_project?(note) + if note.save # Finish the harder work in the background NewNoteWorker.perform_in(2.seconds, note.id, params) @@ -13,5 +15,14 @@ module Notes note end + + private + + def valid_project?(note) + return false unless project + return true if note.for_commit? + + note.noteable.try(:project) == project + end end end diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb index 988c663b9d0..24a817c06c9 100644 --- a/app/services/wiki_pages/create_service.rb +++ b/app/services/wiki_pages/create_service.rb @@ -1,7 +1,8 @@ module WikiPages class CreateService < WikiPages::BaseService def execute - page = WikiPage.new(@project.wiki) + project_wiki = ProjectWiki.new(@project, current_user) + page = WikiPage.new(project_wiki) if page.create(@params) execute_hooks(page, 'create') diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 67d23c80233..7b388cf7862 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -13,9 +13,15 @@ = form_errors(@hook) .form-group - = f.label :url, "URL:", class: 'control-label' + = f.label :url, 'URL', class: 'control-label' .col-sm-10 - = f.text_field :url, class: "form-control" + = f.text_field :url, class: 'form-control' + .form-group + = f.label :token, 'Secret Token', class: 'control-label' + .col-sm-10 + = f.text_field :token, class: 'form-control' + %p.help-block + Use this token to validate received payloads .form-group = f.label :url, "Trigger", class: 'control-label' .col-sm-10.prepend-top-10 diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 79df17ba612..3998e66f40d 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -44,7 +44,7 @@ = icon('pencil') = render 'delete_form', application: application, small: true - else - .profile-settings-message.text-center + .settings-message.text-center You don't have any applications .oauth-authorized-applications.prepend-top-20.append-bottom-default - if user_oauth_applications? @@ -78,5 +78,5 @@ %td= token.scopes %td= render 'doorkeeper/authorized_applications/delete_form', token: token - else - .profile-settings-message.text-center + .settings-message.text-center You don't have any authorized applications diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 4d20dd5830e..5d622582088 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -4,7 +4,12 @@ #{time_ago_with_tooltip(event.created_at)} = cache [event, current_application_settings, "v2.2"] do - = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:'' + - if event.author + = link_to user_path(event.author.username) do + = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:'' + - else + = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:'' + - if event.created_project? = render "events/event/created_project", event: event - elsif event.push? diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index da3c3711cdd..70e88da7aae 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -21,7 +21,7 @@ %tr %td.shortcut .key ? - %td Show this dialog + %td Show/hide this dialog %tr %td.shortcut - if browser.mac? @@ -169,6 +169,10 @@ %td.shortcut .key t %td Go to finding file + %tr + %td.shortcut + .key i + %td New issue .col-lg-4 %table.shortcut-mappings %tbody{ class: 'hidden-shortcut network', style: 'display:none' } @@ -241,6 +245,10 @@ %td.shortcut .key e %td Edit issue + %tr + %td.shortcut + .key l + %td Change Label %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' } %tr %th @@ -261,3 +269,7 @@ %td.shortcut .key e %td Edit merge request + %tr + %td.shortcut + .key l + %td Change Label diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index ca9c2a0bf2e..ad8a2e1e6c7 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -22,13 +22,13 @@ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' .username = current_user.username + - if defined?(nav) && nav + .layout-nav + .container-fluid + = render "layouts/nav/#{nav}" .content-wrapper = render "layouts/flash" = yield :flash_message - - if defined?(nav) && nav - .layout-nav - %div{ class: container_class } - = render "layouts/nav/#{nav}" %div{ class: (container_class unless @no_container) } .content .clearfix diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 3beb8ff7c0d..cde9e1b918b 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -15,7 +15,7 @@ - if current_user - if session[:impersonator_id] %li.impersonation - = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('user-secret fw') - if current_user.is_admin? %li diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index a15b7758c4b..479bde33719 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -124,3 +124,8 @@ %li.hidden = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do Network + + -# Shortcut to create a new issue + %li.hidden + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do + Create a new issue diff --git a/app/views/notify/note_snippet_email.html.haml b/app/views/notify/note_snippet_email.html.haml new file mode 100644 index 00000000000..2fa2f784661 --- /dev/null +++ b/app/views/notify/note_snippet_email.html.haml @@ -0,0 +1 @@ += render 'note_message' diff --git a/app/views/notify/note_snippet_email.text.erb b/app/views/notify/note_snippet_email.text.erb new file mode 100644 index 00000000000..4d5a406f4b0 --- /dev/null +++ b/app/views/notify/note_snippet_email.text.erb @@ -0,0 +1,8 @@ +New comment for Snippet <%= @snippet.id %> + +<%= url_for(namespace_project_snippet_url(@snippet.project.namespace, @snippet.project, @snippet, anchor: "note_#{@note.id}")) %> + + +Author: <%= @note.author_name %> + +<%= @note.note %> diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 57527361eb6..6f7fefdb46d 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -45,4 +45,4 @@ %span.label.label-info Public Email - if email.email === current_user.notification_email %span.label.label-info Notification Email - = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right' + = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10' diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index 4dbaa662b66..3276db6692c 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -1,6 +1,6 @@ %li.key-list-item .pull-left.append-right-10 - = icon 'key', class: "key-icon hidden-xs" + = icon 'key', class: "settings-list-icon hidden-xs" .key-list-item-info = link_to path_to_key(key, is_admin), class: "title" do = key.title diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml index 296cafa6e31..e78763bdcb2 100644 --- a/app/views/profiles/keys/_key_table.html.haml +++ b/app/views/profiles/keys/_key_table.html.haml @@ -4,7 +4,7 @@ %ul.well-list = render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin } - else - %p.profile-settings-message.text-center + %p.settings-message.text-center - if is_admin There are no SSH keys associated with this account. - else diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index d09cd73558c..b1769759dce 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -1,10 +1,19 @@ -- blob.load_all_data!(@repository) -- if markup?(blob.name) - .file-content.wiki - = render_markup(blob.name, blob.data) +- if blob.only_display_raw? + .file-content.code + .nothing-here-block + File too large, you can + = succeed '.' do + = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank' + - else - - unless blob.empty? - = render 'shared/file_highlight', blob: blob + - blob.load_all_data!(@repository) + + - if markup?(blob.name) + .file-content.wiki + = render_markup(blob.name, blob.data) - else - .file-content.code - .nothing-here-block Empty file + - if blob.empty? + .file-content.code + .nothing-here-block Empty file + - else + = render 'shared/file_highlight', blob: blob diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 25714e6cb47..d3acd33116c 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -16,7 +16,7 @@ - if defined?(link_to_commit) && link_to_commit for commit = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - - if ci_commit.duration > 0 + - if ci_commit.duration in = time_interval_in_words ci_commit.duration diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 8d66bae8cdf..450aaeb367c 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -1,32 +1,27 @@ %li - .pull-right + .pull-left.append-right-10.hidden-xs + = icon "key", class: "key-icon" + .deploy-key-content.key-list-item-info + %strong.title + = deploy_key.title + .description + = deploy_key.fingerprint + .deploy-key-content.prepend-left-default.deploy-key-projects + - deploy_key.projects.each do |project| + - if can?(current_user, :read_project, project) + = link_to namespace_project_path(project.namespace, project), class: "label deploy-project-label" do + = project.name_with_namespace + .deploy-key-content + %span.key-created-at + created #{time_ago_with_tooltip(deploy_key.created_at)} + .visible-xs-block.visible-sm-block - if @available_keys.include?(deploy_key) - = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do - = icon('plus') + = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-sm prepend-left-10", method: :put do Enable - else - if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned? - = link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right" + = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: "You are going to remove deploy key. Are you sure?" }, method: :put, class: "btn btn-warning btn-sm prepend-left-10" do + Remove - else - = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do - = icon('power-off') + = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-warning btn-sm prepend-left-10", method: :put do Disable - - = icon('key') - %strong= deploy_key.title - %br - %code.key-fingerprint= deploy_key.fingerprint - - %p.light.prepend-top-10 - - if deploy_key.public? - %span.label.label-info.deploy-project-label - Public deploy key - - - deploy_key.projects.each do |project| - - if can?(current_user, :read_project, project) - %span.label.label-gray.deploy-project-label - = link_to namespace_project_path(project.namespace, project) do - = project.name_with_namespace - - %small.pull-right - Created #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index f6565f85836..894c36a96df 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,18 +1,13 @@ -%div - = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f| - = form_errors(@key) - - .form-group - = f.label :title, class: "control-label" - .col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true - .form-group - = f.label :key, class: "control-label" - .col-sm-10 - %p.light - Paste a machine public key here. Read more about how to generate it - = link_to "here", help_page_path("ssh", "README") - = f.text_area :key, class: "form-control thin_area", rows: 5, required: true - - .form-actions - = f.submit 'Create Deploy Key', class: "btn-create btn" - = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel" += form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f| + = form_errors(@key) + .form-group + = f.label :title, class: "label-light" + = f.text_field :title, class: 'form-control', autofocus: true, required: true + .form-group + = f.label :key, class: "label-light" + = f.text_area :key, class: "form-control", rows: 5, required: true + .form-group + %p.light.append-bottom-0 + Paste a machine public key here. Read more about how to generate it + = link_to "here", help_page_path("ssh", "README") + = f.submit "Add key", class: "btn-create btn" diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index 8e24c778b7c..e230834e8ba 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -1,43 +1,36 @@ - page_title "Deploy Keys" -%h3.page-title - Deploy keys allow read-only access to the repository - - = link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do - %i.fa.fa-plus - New Deploy Key - -%p.light - Deploy keys can be used for CI, staging or production servers. - You can create a deploy key or add an existing one - -%hr.clearfix - -.row - .col-md-6.enabled-keys - %h5 - %strong.cgreen Enabled deploy keys - for this project - %ul.bordered-list - = render @enabled_keys - - if @enabled_keys.blank? - .light-well - .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one - .col-md-6.available-keys - - # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown. - - if @available_project_keys.any? || @available_public_keys.blank? - %h5 - %strong Deploy keys - from projects you have access to - %ul.bordered-list +.row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + = page_title + %p + Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. + .col-lg-9 + %h5.prepend-top-0 + Create a new deploy key for this project + = render "form" + .col-lg-9.col-lg-offset-3 + %hr + .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys + %h5.prepend-top-0 + Enabled deploy keys for this project (#{@enabled_keys.size}) + - if @enabled_keys.any? + %ul.well-list + = render @enabled_keys + - else + .profile-settings-message.text-center + No deploy keys found. Create one with the form above or add existing one below. + %h5.prepend-top-default + Deploy keys from projects you have access to (#{@available_project_keys.size}) + - if @available_project_keys.any? + %ul.well-list = render @available_project_keys - - if @available_project_keys.blank? - .light-well - .nothing-here-block Deploy keys from projects you have access to will be displayed here - + - else + .profile-settings-message.text-center + No deploy keys from your projects could be found. Create one with the form above or add existing one below. - if @available_public_keys.any? - %h5 - %strong Public deploy keys - available to any project - %ul.bordered-list + %h5.prepend-top-default + Public deploy keys available to any project (#{@available_public_keys.size}) + %ul.well-list = render @available_public_keys diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index eaab99973a4..d9c4b410d32 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,3 +1,4 @@ +- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true) - if diff_view == 'parallel' - fluid_layout true @@ -5,6 +6,11 @@ .content-block.oneline-block.files-changed .inline-parallel-buttons + - if show_whitespace_toggle + - if current_controller?(:commit) + = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs') + - elsif current_controller?(:merge_requests) + = diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs') .btn-group = inline_diff_btn = parallel_diff_btn diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 83a8d7ae9bf..0f04fc5d33c 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -40,19 +40,19 @@ = view_file_btn(diff_commit.id, diff_file, project) .diff-content.diff-wrap-lines - -# Skipp all non non-supported blobs + - # Skip all non non-supported blobs - return unless blob.respond_to?('text?') - if diff_file.too_large? - .nothing-here-block - This diff could not be displayed because it is too large. - - else - - if blob_text_viewable?(blob) - - if diff_view == 'parallel' - = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - - else - = render "projects/diffs/text_file", diff_file: diff_file, index: i - - elsif blob.image? - - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file) - = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs + .nothing-here-block This diff could not be displayed because it is too large. + - elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob) + .nothing-here-block This diff was suppressed by a .gitattributes entry. + - elsif blob_text_viewable?(blob) + - if diff_view == 'parallel' + = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - else - .nothing-here-block No preview for this file type + = render "projects/diffs/text_file", diff_file: diff_file, index: i + - elsif blob.image? + - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file) + = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs + - else + .nothing-here-block No preview for this file type diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml index 13f5fc141fa..2b904544f28 100644 --- a/app/views/projects/group_links/index.html.haml +++ b/app/views/projects/group_links/index.html.haml @@ -1,41 +1,44 @@ - page_title "Groups" -%h3.page_title Share project with other groups -%p.light - Projects can be stored in only one group at once. However you can share a project with other groups here. -%hr -- if @group_links.present? - .enabled-groups.panel.panel-default - .panel-heading - Already shared with - %ul.well-list - - @group_links.each do |group_link| - - group = group_link.group - %li - .pull-right - = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: 'btn btn-sm' do - %i.icon-remove - disable sharing - = link_to group do - %strong - %i.icon-folder-open - = group.name - %br - .light up to #{group_link.human_access} - - -.available-groups - %h4 - Can be shared with - %div - = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post, class: 'form-horizontal' do +.row.prepend-top-default + .col-lg-3.settings-sidebar + %h4.prepend-top-0 + Share project with other groups + %p + Projects can be stored in only one group at once. However you can share a project with other groups here. + .col-lg-9 + %h5.prepend-top-0 + Set a group to share + = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post do .form-group - = label_tag :link_group_id, 'Group', class: 'control-label' - .col-sm-10 - = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path)) + = label_tag :link_group_id, "Group", class: "label-light" + = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path)) .form-group - = label_tag :link_group_access, 'Max access level', class: 'control-label' - .col-sm-10 - = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control" - .form-actions - = submit_tag "Share", class: "btn btn-create" - + = label_tag :link_group_access, "Max access level", class: "label-light" + .select-wrapper + = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" + %span.caret + = submit_tag "Share", class: "btn btn-create" + .col-lg-9.col-lg-offset-3 + %hr + .col-lg-9.col-lg-offset-3.append-bottom-default.enabled-groups + %h5.prepend-top-0 + Groups you share with (#{@group_links.size}) + - if @group_links.present? + %ul.well-list + - @group_links.each do |group_link| + - group = group_link.group + %li + .pull-left.append-right-10.hidden-xs + = icon("folder-open-o", class: "settings-list-icon") + .pull-left + = link_to group do + = group.name + %br + up to #{group_link.human_access} + .pull-right + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do + %span.sr-only disable sharing + = icon("trash") + - else + .settings-message.text-center + There are no groups with access to your project, add one in the form above diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/hooks/_project_hook.html.haml new file mode 100644 index 00000000000..62eba5888a4 --- /dev/null +++ b/app/views/projects/hooks/_project_hook.html.haml @@ -0,0 +1,15 @@ +%li + .row + .col-md-8.col-lg-7 + %strong.light-header= hook.url + %div + - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger| + - if hook.send(trigger) + %span.label.label-gray.deploy-project-label= trigger.titleize + .col-md-4.col-lg-5.text-right-lg.prepend-top-5 + %span.append-right-10.inline + SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} + = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm" + = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do + %span.sr-only Remove + = icon('trash') diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index aae3abcad4b..36c1d69f060 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -1,88 +1,84 @@ - page_title "Webhooks" -%h3.page-title - Webhooks +.row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + = page_title + %p + #{link_to "Webhooks", help_page_path("web_hooks", "web_hooks")} can be + used for binding events when something is happening within the project. + .col-lg-9.append-bottom-default + %h5.prepend-top-0 + Add new webhook + = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project) do |f| + = form_errors(@hook) -%p.light - #{link_to "Webhooks ", help_page_path("web_hooks", "web_hooks"), class: "vlink"} can be - used for binding events when something is happening within the project. - -%hr.clearfix - -= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f| - = form_errors(@hook) - - .form-group - = f.label :url, "URL", class: 'control-label' - .col-sm-10 - = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json' - .form-group - = f.label :url, "Trigger", class: 'control-label' - .col-sm-10.prepend-top-10 - %div - = f.check_box :push_events, class: 'pull-left' - .prepend-left-20 - = f.label :push_events, class: 'list-label' do - %strong Push events - %p.light - This url will be triggered by a push to the repository - %div - = f.check_box :tag_push_events, class: 'pull-left' - .prepend-left-20 - = f.label :tag_push_events, class: 'list-label' do - %strong Tag push events - %p.light - This url will be triggered when a new tag is pushed to the repository - %div - = f.check_box :note_events, class: 'pull-left' - .prepend-left-20 - = f.label :note_events, class: 'list-label' do - %strong Comments - %p.light - This url will be triggered when someone adds a comment - %div - = f.check_box :issues_events, class: 'pull-left' - .prepend-left-20 - = f.label :issues_events, class: 'list-label' do - %strong Issues events - %p.light - This url will be triggered when an issue is created/updated/merged - %div - = f.check_box :merge_requests_events, class: 'pull-left' - .prepend-left-20 - = f.label :merge_requests_events, class: 'list-label' do - %strong Merge Request events - %p.light - This url will be triggered when a merge request is created/updated/merged - %div - = f.check_box :build_events, class: 'pull-left' - .prepend-left-20 - = f.label :build_events, class: 'list-label' do - %strong Build events - %p.light - This url will be triggered when the build status changes - .form-group - = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox' - .col-sm-10 - .checkbox - = f.label :enable_ssl_verification do - = f.check_box :enable_ssl_verification - %strong Enable SSL verification - .form-actions - = f.submit "Add Webhook", class: "btn btn-create" - --if @hooks.any? - .panel.panel-default - .panel-heading + .form-group + = f.label :url, "URL", class: "label-light" + = f.text_field :url, class: "form-control", placeholder: "http://example.com/trigger-ci.json" + .form-group + = f.label :token, "Secret Token", class: 'label-light' + = f.text_field :token, class: "form-control", placeholder: '' + %p.help-block + Use this token to validate received payloads + .form-group + = f.label :url, "Trigger", class: "label-light" + %div + = f.check_box :push_events, class: "pull-left" + .prepend-left-20 + = f.label :push_events, class: "label-light append-bottom-0" do + Push events + %p.light + This url will be triggered by a push to the repository + %div + = f.check_box :tag_push_events, class: "pull-left" + .prepend-left-20 + = f.label :tag_push_events, class: "label-light append-bottom-0" do + Tag push events + %p.light + This url will be triggered when a new tag is pushed to the repository + %div + = f.check_box :note_events, class: "pull-left" + .prepend-left-20 + = f.label :note_events, class: "label-light append-bottom-0" do + Comments + %p.light + This url will be triggered when someone adds a comment + %div + = f.check_box :issues_events, class: "pull-left" + .prepend-left-20 + = f.label :issues_events, class: "label-light append-bottom-0" do + Issues events + %p.light + This url will be triggered when an issue is created/updated/merged + %div + = f.check_box :merge_requests_events, class: "pull-left" + .prepend-left-20 + = f.label :merge_requests_events, class: "label-light append-bottom-0" do + Merge Request events + %p.light + This url will be triggered when a merge request is created/updated/merged + %div + = f.check_box :build_events, class: "pull-left" + .prepend-left-20 + = f.label :build_events, class: "label-light append-bottom-0" do + Build events + %p.light + This url will be triggered when the build status changes + .form-group + = f.label :enable_ssl_verification, "SSL verification", class: "label-light" + %div + = f.check_box :enable_ssl_verification, class: "pull-left" + .prepend-left-20 + = f.label :enable_ssl_verification, class: "label-light append-bottom-0" do + Enable SSL verification + = f.submit "Add Webhook", class: "btn btn-create" + %hr + %h5.prepend-top-default Webhooks (#{@hooks.count}) - %ul.content-list - - @hooks.each do |hook| - %li - .controls - = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped" - = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" - .monospace= hook.url - %div - - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger| - - if hook.send(trigger) - %span.label.label-gray= trigger.titleize - %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} + - if @hooks.any? + %ul.well-list + - @hooks.each do |hook| + = render "project_hook", hook: hook + - else + %p.profile-settings-message.text-center.append-bottom-0 + No webhooks found, add one in the form above. diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 6da8e4f33a9..469429ccf3c 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,5 +1,13 @@ -- if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) +- if can?(current_user, :push_code, @project) .pull-right - = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name do - = icon('code-fork') - New Branch + #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)} + = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do + .checking + %i.fa.fa-spinner.fa-spin + Checking branches + .available(style="display: none") + %i.fa.fa-code-fork + New branch + .unavailable(style="display: none") + %i.fa.fa-exclamation-triangle + New branch unavailable diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 2f14a91e64f..18b3f9e1549 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -42,7 +42,7 @@ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. %p To preserve performance the line changes are not shown. - else - = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs + = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false - if @ci_commit #builds.builds.tab-pane = render "projects/merge_requests/show/builds" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index be63875ab34..56543ccd062 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -42,9 +42,12 @@ = preserve do = markdown @milestone.description -- if @milestone.complete?(current_user) && @milestone.active? +- if @milestone.total_items_count(current_user).zero? .alert.alert-success.prepend-top-default - %span All issues for this milestone are closed. You may close milestone now. + %span Assign some issues to this milestone. +- elsif @milestone.complete?(current_user) && @milestone.active? + .alert.alert-success.prepend-top-default + %span All issues for this milestone are closed. You may close this milestone now. = render 'shared/milestones/summary', milestone: @milestone, project: @project = render 'shared/milestones/tabs', milestone: @milestone diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index f68449b1863..b9e9dd8aaea 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -1,35 +1,41 @@ -- unless @branches.empty? - %br - %h4 Already Protected: - .table-holder +%h5.prepend-top-0 + Already Protected (#{@branches.size}) +- if @branches.empty? + %p.profile-settings-message.text-center + No branches are protected, protect a branch with the form above. +- else + - can_admin_project = can?(current_user, :admin_project, @project) + .table-responsive %table.table.protected-branches-list + %colgroup + %col{ width: "30%" } + %col{ width: "30%" } + %col{ width: "25%" } + - if can_admin_project + %col %thead - %tr.no-border + %tr %th Branch - %th Developers can push %th Last commit - %th - + %th Developers can push + - if can_admin_project + %th %tbody - @branches.each do |branch| - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch) %tr %td - = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do - %strong= branch.name - - if @project.root_ref?(branch.name) - %span.label.label-info default - %td - = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url - %td - - if commit = branch.commit - = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do - = commit.short_id - · - #{time_ago_with_tooltip(commit.committed_date)} - - else - (branch was removed from repository) + = link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name)) + - if @project.root_ref?(branch.name) + %span.label.label-info.prepend-left-5 default + %td + - if commit = branch.commit + = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id') + #{time_ago_with_tooltip(commit.committed_date)} + - else + (branch was removed from repository) + %td + = check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url }) + - if can_admin_project %td - .pull-right - - if can? current_user, :admin_project, @project - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm" + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm" diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 653b02da4db..c7d317dbaee 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -1,31 +1,33 @@ - page_title "Protected branches" -%h3.page-title Protected branches -%p.light Keep stable branches secure and force developers to use Merge Requests -%hr -.well - %p Protected branches are designed to - %ul - %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} - %li prevent anyone from force pushing to the branch - %li prevent anyone from deleting the branch - %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"} +.row.prepend-top-default.append-bottom-default + .col-lg-3 + %h4.prepend-top-0 + = page_title + %p Keep stable branches secure and force developers to use Merge Requests + .col-lg-9 + %h5.prepend-top-0 + Protect a branch + .account-well.append-bottom-default + %p.light-header.append-bottom-0 Protected branches are designed to + %ul + %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} + %li prevent anyone from force pushing to the branch + %li prevent anyone from deleting the branch + %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"} + - if can? current_user, :admin_project, @project + = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f| + = form_errors(@protected_branch) -- if can? current_user, :admin_project, @project - = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f| - = form_errors(@protected_branch) - - .form-group - = f.label :name, "Branch", class: 'control-label' - .col-sm-10 - = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}}) - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.label :developers_can_push do - = f.check_box :developers_can_push - %strong Developers can push - .help-block Allow developers to push to this branch - .form-actions - = f.submit 'Protect', class: "btn-create btn" -= render 'branches_list' + .form-group + = f.label :name, "Branch", class: "label-light" + = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}}) + .form-group + = f.check_box :developers_can_push, class: "pull-left" + .prepend-left-20 + = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0" + %p.light.append-bottom-0 + Allow developers to push to this branch + = f.submit "Protect", class: "btn-create btn" + %hr + = render "branches_list" diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml index 48b3b5c9920..112b51712ef 100644 --- a/app/views/projects/triggers/_trigger.html.haml +++ b/app/views/projects/triggers/_trigger.html.haml @@ -1,7 +1,6 @@ %tr %td - .clearfix - %span.monospace= trigger.token + %span.monospace= trigger.token %td - if trigger.last_trigger_request @@ -9,6 +8,5 @@ - else Never - %td - .pull-right - = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped" + %td.text-right + = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-warning btn-sm" diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml index bd346c4b8e6..f91885b216d 100644 --- a/app/views/projects/triggers/index.html.haml +++ b/app/views/projects/triggers/index.html.haml @@ -1,71 +1,70 @@ - page_title "Triggers" -%h3.page-title - Triggers -%p.light - Triggers can be used to force a rebuild of a specific branch or tag with an API call. +.row.prepend-top-default.append-bottom-default + .col-lg-3 + %h4.prepend-top-0 + = page_title + %p + Triggers can be used to force a rebuild of a specific branch or tag with an API call. + .col-lg-9 + %h5.prepend-top-0 + Your triggers + - if @triggers.any? + .table-responsive + %table.table + %thead + %th Token + %th Last used + %th + = render partial: 'trigger', collection: @triggers, as: :trigger + - else + %p.profile-settings-message.text-center.append-bottom-default + There are no triggers to use, add one by the button below. -%hr.clearfix + = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f| + = f.submit "Add Trigger", class: 'btn btn-success' --if @triggers.any? - .table-holder - %table.table - %thead - %th Token - %th Last used - %th - = render partial: 'trigger', collection: @triggers, as: :trigger -- else - %h4 No triggers + %h5.prepend-top-default + Use CURL -= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create'), html: { class: 'form-horizontal' } do |f| - .clearfix - = f.submit "Add Trigger", class: 'btn btn-success pull-right' + %p.light + Copy the token above and set your branch or tag name. This is the reference that will be rebuild. -%hr.clearfix --if @triggers.any? - %h3 - Use CURL + %pre + :plain + curl -X POST \ + -F token=TOKEN \ + -F ref=REF_NAME \ + #{builds_trigger_url(@project.id)} + %h5.prepend-top-default + Use .gitlab-ci.yml - %p.light - Copy the token above and set your branch or tag name. This is the reference that will be rebuild. + %p.light + Copy the snippet to + %i .gitlab-ci.yml + of dependent project. + At the end of your build it will trigger this project to rebuilt. + %pre + :plain + trigger: + type: deploy + script: + - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" + %h5.prepend-top-default + Pass build variables - %pre - :plain - curl -X POST \ - -F token=TOKEN \ - -F ref=REF_NAME \ - #{builds_trigger_url(@project.id)} - %h3 - Use .gitlab-ci.yml + %p.light + Add + %strong variables[VARIABLE]=VALUE + to API request. + The value of variable could then be used to distinguish triggered build from normal one. - %p.light - Copy the snippet to - %i .gitlab-ci.yml - of dependent project. - At the end of your build it will trigger this project to rebuilt. - - %pre - :plain - trigger: - type: deploy - script: - - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" - %h3 - Pass build variables - - %p.light - Add - %strong variables[VARIABLE]=VALUE - to API request. - The value of variable could then be used to distinguish triggered build from normal one. - - %pre - :plain - curl -X POST \ - -F token=TOKEN \ - -F "ref=REF_NAME" \ - -F "variables[RUN_NIGHTLY_BUILD]=true" \ - #{builds_trigger_url(@project.id)} + %pre.append-bottom-0 + :plain + curl -X POST \ + -F token=TOKEN \ + -F "ref=REF_NAME" \ + -F "variables[RUN_NIGHTLY_BUILD]=true" \ + #{builds_trigger_url(@project.id)} diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml index 2c3fca439f3..2c378231237 100644 --- a/app/views/search/_category.html.haml +++ b/app/views/search/_category.html.haml @@ -2,97 +2,70 @@ - if @project %li{class: ("active" if @scope == 'blobs')} = link_to search_filter_path(scope: 'blobs') do - = icon('code fw') - %span - Code - %span.badge - = @search_results.blobs_count + Code + %span.badge + = @search_results.blobs_count %li{class: ("active" if @scope == 'issues')} = link_to search_filter_path(scope: 'issues') do - = icon('exclamation-circle fw') - %span - Issues - %span.badge - = @search_results.issues_count + Issues + %span.badge + = @search_results.issues_count %li{class: ("active" if @scope == 'merge_requests')} = link_to search_filter_path(scope: 'merge_requests') do - = icon('tasks fw') - %span - Merge requests - %span.badge - = @search_results.merge_requests_count + Merge requests + %span.badge + = @search_results.merge_requests_count %li{class: ("active" if @scope == 'milestones')} = link_to search_filter_path(scope: 'milestones') do - = icon('clock-o fw') - %span - Milestones - %span.badge - = @search_results.milestones_count + Milestones + %span.badge + = @search_results.milestones_count %li{class: ("active" if @scope == 'notes')} = link_to search_filter_path(scope: 'notes') do - = icon('comments fw') - %span - Comments - %span.badge - = @search_results.notes_count + Comments + %span.badge + = @search_results.notes_count %li{class: ("active" if @scope == 'wiki_blobs')} = link_to search_filter_path(scope: 'wiki_blobs') do - = icon('book fw') - %span - Wiki - %span.badge - = @search_results.wiki_blobs_count + Wiki + %span.badge + = @search_results.wiki_blobs_count %li{class: ("active" if @scope == 'commits')} = link_to search_filter_path(scope: 'commits') do - = icon('history fw') - %span - Commits - %span.badge - = @search_results.commits_count + Commits + %span.badge + = @search_results.commits_count - elsif @show_snippets %li{class: ("active" if @scope == 'snippet_blobs')} = link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do - = icon('code fw') - %span - Snippet Contents - %span.badge - = @search_results.snippet_blobs_count + Snippet Contents + %span.badge + = @search_results.snippet_blobs_count %li{class: ("active" if @scope == 'snippet_titles')} = link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do - = icon('book fw') - %span - Titles and Filenames - %span.badge - = @search_results.snippet_titles_count + Titles and Filenames + %span.badge + = @search_results.snippet_titles_count - else %li{class: ("active" if @scope == 'projects')} = link_to search_filter_path(scope: 'projects') do - = icon('bookmark fw') - %span - Projects - %span.badge - = @search_results.projects_count + Projects + %span.badge + = @search_results.projects_count %li{class: ("active" if @scope == 'issues')} = link_to search_filter_path(scope: 'issues') do - = icon('exclamation-circle fw') - %span - Issues - %span.badge - = @search_results.issues_count + Issues + %span.badge + = @search_results.issues_count %li{class: ("active" if @scope == 'merge_requests')} = link_to search_filter_path(scope: 'merge_requests') do - = icon('tasks fw') - %span - Merge requests - %span.badge - = @search_results.merge_requests_count + Merge requests + %span.badge + = @search_results.merge_requests_count %li{class: ("active" if @scope == 'milestones')} = link_to search_filter_path(scope: 'milestones') do - = icon('clock-o fw') - %span - Milestones - %span.badge - = @search_results.milestones_count - + Milestones + %span.badge + = @search_results.milestones_count diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index 4ef544136a8..ef1c0296d49 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -1,47 +1,33 @@ -.dropdown.inline - %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} - %span.light Group: - - if @group.present? - %strong= @group.name - - else - Any - %b.caret - .dropdown-menu.dropdown-select.dropdown-menu-selectable - .dropdown-title - %span Filter results by group - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} - = icon('times') - .dropdown-content - %ul - %li - = link_to search_filter_path(group_id: nil), class: ("is-active" if !params[:group_id].present?) do - Any - %li.divider - - current_user.authorized_groups.sort_by(&:name).each do |group| - %li - = link_to search_filter_path(group_id: group.id, project_id: nil), class: ("is-active" if params[:group_id] == group.id.to_s) do - = group.name +- if params[:group_id].present? + = hidden_field_tag :group_id, params[:group_id] +- if params[:project_id].present? + = hidden_field_tag :project_id, params[:project_id] +.dropdown + %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } } + %span.dropdown-toggle-text + Group: + - if @group.present? + = @group.name + - else + Any + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right + = dropdown_title("Filter results by group") + = dropdown_filter("Search groups") + = dropdown_content + = dropdown_loading -.dropdown.inline.prepend-left-10.project-filter - %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} - %span.light Project: - - if @project.present? - %strong= @project.name_with_namespace - - else - Any - %b.caret - .dropdown-menu.dropdown-select.dropdown-menu-selectable - .dropdown-title - %span Filter results by project - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} - = icon('times') - .dropdown-content - %ul - %li - = link_to search_filter_path(project_id: nil), class: ("is-active" if !params[:project_id].present?) do - Any - %li.divider - - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project| - %li - = link_to search_filter_path(project_id: project.id, group_id: nil), class: ("is-active" if params[:project_id] == project.id.to_s) do - = project.name_with_namespace +.dropdown.project-filter + %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } } + %span.dropdown-toggle-text + Project: + - if @project.present? + = @project.name_with_namespace + - else + Any + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right + = dropdown_title("Filter results by project") + = dropdown_filter("Search projects") + = dropdown_content + = dropdown_loading diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml index a9dbc84da29..3139be1cd37 100644 --- a/app/views/search/_form.html.haml +++ b/app/views/search/_form.html.haml @@ -1,14 +1,15 @@ -= form_tag search_path, method: :get do |f| - = hidden_field_tag :project_id, params[:project_id] - = hidden_field_tag :group_id, params[:group_id] += form_tag search_path, method: :get, class: 'js-search-form' do |f| = hidden_field_tag :snippets, params[:snippets] = hidden_field_tag :scope, params[:scope] - .search-holder.clearfix - .input-group - = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true, spellcheck: false - %span.input-group-btn - = button_tag 'Search', class: "btn btn-primary" + .search-holder + .search-field-holder + = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false + = icon("search", class: "search-icon") + %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" } + = icon("times-circle") + %span.sr-only + Clear search - unless params[:snippets].eql? 'true' - %br = render 'filter' if current_user + = button_tag "Search", class: "btn btn-success btn-search" diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 60df348891c..711337f308e 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -1,10 +1,8 @@ -- if @search_results.empty? +- if @search_objects.empty? = render partial: "search/results/empty" - else .gray-content-block - Search results for - %code - = @search_term + = search_entries_info(@search_objects, @scope, @search_term) - unless @show_snippets - if @project in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]} @@ -15,12 +13,9 @@ .search-results - if @scope == 'projects' .term - = render 'shared/projects/list', projects: @objects + = render 'shared/projects/list', projects: @search_objects - else - = render partial: "search/results/#{@scope.singularize}", collection: @objects + = render partial: "search/results/#{@scope.singularize}", collection: @search_objects - if @scope != 'projects' - = paginate @objects, theme: 'gitlab' - -:javascript - $(".search-results .term").highlight("#{escape_javascript(params[:search])}"); + = paginate(@search_objects, theme: 'gitlab') diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index 710f5613c81..640890fbe92 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -7,7 +7,7 @@ - if issue.description.present? .description.term = preserve do - = search_md_sanitize(markdown(issue.description, { project: issue.project })) + = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project })) %span.light #{issue.project.name_with_namespace} - if issue.closed? diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml index 34241cd8aad..b0fc60573f7 100644 --- a/app/views/shared/_confirm_modal.html.haml +++ b/app/views/shared/_confirm_modal.html.haml @@ -7,7 +7,7 @@ Confirmation required .modal-body - %p.cred.lead.js-confirm-text + %p.text-danger.js-confirm-text %p This action can lead to data loss. diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index 57856031d6e..37dcf39c062 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,12 +1,13 @@ .file-content.code.js-syntax-highlight .line-numbers - if blob.data.present? + - link_icon = icon('link') - blob.data.each_line.each_with_index do |_, index| - offset = defined?(first_line_number) ? first_line_number : 1 - i = index + offset -# We're not using `link_to` because it is too slow once we get to thousands of lines. %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} - %i.fa.fa-link + = link_icon = i .blob-content{data: {blob_id: blob.id}} - = highlight(blob.name, blob.data) + = highlight(blob.name, blob.data, plain: blob.no_highlighting?) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 04b834ba5d1..ed1b8a8da2a 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -166,5 +166,5 @@ new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); new Subscription('.subscription') - new Sidebar(); new DueDateSelect(); + sidebar = new Sidebar(); diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 3028491e5b6..0dff27f9654 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -69,13 +69,13 @@ = @user.location %ul.nav-links.center.user-profile-nav - %li.activity-tab + %li.js-activity-tab = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do Activity - %li.groups-tab + %li.js-groups-tab = link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do Groups - %li.contributed-tab + %li.js-contributed-tab = link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do Contributed projects %li.projects-tab diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index dc249155b92..4beb8746444 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -15,16 +15,16 @@ - if current_user :javascript - var get_emojis_url = "#{emojis_path}"; - var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"; - var noteable_type = "#{votable.class.name.underscore}"; - var noteable_id = "#{votable.id}"; - var aliases = #{AwardEmoji.aliases.to_json}; + var getEmojisUrl = "#{emojis_path}"; + var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"; + var noteableType = "#{votable.class.name.underscore}"; + var noteableId = "#{votable.id}"; + var unicodes = #{AwardEmoji.unicode.to_json}; - window.awards_handler = new AwardsHandler( - get_emojis_url, - post_emoji_url, - noteable_type, - noteable_id, - aliases + window.awardsHandler = new AwardsHandler( + getEmojisUrl, + postEmojiUrl, + noteableType, + noteableId, + unicodes ); diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb index 44b3145d50f..a3e16fa5212 100644 --- a/app/workers/repository_check/batch_worker.rb +++ b/app/workers/repository_check/batch_worker.rb @@ -33,8 +33,8 @@ module RepositoryCheck # has to sit and wait for this query to finish. def project_ids limit = 10_000 - never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit). - pluck(:id) + never_checked_projects = Project.where('last_repository_check_at IS NULL AND created_at < ?', 24.hours.ago). + limit(limit).pluck(:id) old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago). reorder('last_repository_check_at ASC').limit(limit).pluck(:id) never_checked_projects + old_check_projects |