diff options
author | James Lopez <james@jameslopez.es> | 2016-04-14 18:12:10 +0200 |
---|---|---|
committer | James Lopez <james@jameslopez.es> | 2016-04-14 18:12:10 +0200 |
commit | e5f7a545308922bf790f1ef8becb6e8dcd573f95 (patch) | |
tree | 891091eefc62c616e29ee4547a46ccc640974771 /app | |
parent | 46d1cf43a43a4d7a25f25be97d1fee79cefdc773 (diff) | |
parent | dd9ced0af9514c0cf511c8f0f10d19c014fa4d19 (diff) | |
download | gitlab-ce-e5f7a545308922bf790f1ef8becb6e8dcd573f95.tar.gz |
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/project-import_url
Diffstat (limited to 'app')
184 files changed, 1638 insertions, 1071 deletions
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index f01c67e9474..6f435e4c542 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -22,7 +22,17 @@ #= require cal-heatmap #= require turbolinks #= require autosave -#= require bootstrap +#= require bootstrap/affix +#= require bootstrap/alert +#= require bootstrap/button +#= require bootstrap/collapse +#= require bootstrap/dropdown +#= require bootstrap/modal +#= require bootstrap/scrollspy +#= require bootstrap/tab +#= require bootstrap/transition +#= require bootstrap/tooltip +#= require bootstrap/popover #= require select2 #= require raphael #= require g.raphael @@ -41,6 +51,7 @@ #= require shortcuts_issuable #= require shortcuts_network #= require jquery.nicescroll +#= require date.format #= require_tree . #= require fuzzaldrin-plus #= require cropper @@ -163,7 +174,7 @@ $ -> $('.trigger-submit').on 'change', -> $(@).parents('form').submit() - $('abbr.timeago, .js-timeago').timeago() + gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false) # Flash if (flash = $(".flash-container")).length > 0 diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 6a670d5e887..af4462ece38 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -22,8 +22,19 @@ class @AwardsHandler emoji = $(this) .find(".icon") .data "emoji" + + if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown" + awards_handler.addAward "thumbsdown" + + else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup" + awards_handler.addAward "thumbsup" + awards_handler.addAward emoji + 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 + showEmojiMenu: -> if $(".emoji-menu").length if $(".emoji-menu").is ".is-visible" @@ -105,7 +116,7 @@ class @AwardsHandler if origTitle authors = origTitle.split(', ') authors.push("me") - award_block.attr("title", authors.join(", ")) + award_block.attr("data-original-title", authors.join(", ")) @resetTooltip(award_block) resetTooltip: (award) -> @@ -122,7 +133,7 @@ class @AwardsHandler nodes = [] nodes.push( - "<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>", + "<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>", "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>", "<span class='award-control-text js-counter'>1</span>", "</button>" diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee index 6e29d374267..3cb96bacaa7 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js.coffee +++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee @@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> e.preventDefault() $form = $(e.target).closest('form') - $form.find('input[type=submit], button[type=submit]').disable() + $submit_button = $form.find('input[type=submit], button[type=submit]') + + return if $submit_button.attr('disabled') + + $submit_button.disable() $form.submit() # If the user tabs to a submit button on a `js-quick-submit` form, display a diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee index 79d750d1847..0faa570ce13 100644 --- a/app/assets/javascripts/behaviors/requires_input.js.coffee +++ b/app/assets/javascripts/behaviors/requires_input.js.coffee @@ -35,4 +35,18 @@ $.fn.requiresInput = -> $form.on 'change input', fieldSelector, requireInput $ -> - $('form.js-requires-input').requiresInput() + $form = $('form.js-requires-input') + $form.requiresInput() + + # Hide or Show the help block when creating a new project + # based on the option selected + hideOrShowHelpBlock = (form) -> + selected = $('.js-select-namespace option:selected') + if selected.length and selected.data('options-parent') is 'groups' + return form.find('.help-block').hide() + else if selected.length + form.find('.help-block').show() + + hideOrShowHelpBlock($form) + + $('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form) diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee new file mode 100644 index 00000000000..f20992ead3e --- /dev/null +++ b/app/assets/javascripts/compare.js.coffee @@ -0,0 +1,67 @@ +class @Compare + constructor: (@opts) -> + @source_loading = $ ".js-source-loading" + @target_loading = $ ".js-target-loading" + + $('.js-compare-dropdown').each (i, dropdown) => + $dropdown = $(dropdown) + + $dropdown.glDropdown( + selectable: true + fieldName: $dropdown.data 'field-name' + filterable: true + id: (obj, $el) -> + $el.data 'id' + toggleLabel: (obj, $el) -> + $el.text().trim() + clicked: (e, el) => + if $dropdown.is '.js-target-branch' + @getTargetHtml() + else if $dropdown.is '.js-source-branch' + @getSourceHtml() + else if $dropdown.is '.js-target-project' + @getTargetProject() + ) + + @initialState() + + initialState: -> + @getSourceHtml() + @getTargetHtml() + + getTargetProject: -> + $.ajax( + url: @opts.targetProjectUrl + data: + target_project_id: $("input[name='merge_request[target_project_id]']").val() + beforeSend: -> + $('.mr_target_commit').empty() + success: (html) -> + $('.js-target-branch-dropdown .dropdown-content').html html + ) + + getSourceHtml: -> + @sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit', + ref: $("input[name='merge_request[source_branch]']").val() + ) + + getTargetHtml: -> + @sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit', + target_project_id: $("input[name='merge_request[target_project_id]']").val() + ref: $("input[name='merge_request[target_branch]']").val() + ) + + sendAjax: (url, loading, target, data) -> + $target = $(target) + + $.ajax( + url: url + data: data + beforeSend: -> + loading.show() + $target.empty() + success: (html) -> + loading.hide() + $target.html html + $('.js-timeago', $target).timeago() + ) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 70fd6f50e9c..0b9110d35fa 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -28,26 +28,26 @@ class Dispatcher new Todos() when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() - new DropzoneInput($('.milestone-form')) + new GLForm($('.milestone-form')) when 'groups:milestones:new' new ZenMode() when 'projects:compare:show' new Diff() when 'projects:issues:new','projects:issues:edit' shortcut_handler = new ShortcutsNavigation() - new DropzoneInput($('.issue-form')) + new GLForm($('.issue-form')) new IssuableForm($('.issue-form')) when 'projects:merge_requests:new', 'projects:merge_requests:edit' new Diff() shortcut_handler = new ShortcutsNavigation() - new DropzoneInput($('.merge-request-form')) + new GLForm($('.merge-request-form')) new IssuableForm($('.merge-request-form')) when 'projects:tags:new' new ZenMode() - new DropzoneInput($('.tag-form')) + new GLForm($('.tag-form')) when 'projects:releases:edit' new ZenMode() - new DropzoneInput($('.release-form')) + new GLForm($('.release-form')) when 'projects:merge_requests:show' new Diff() shortcut_handler = new ShortcutsIssuable(true) @@ -137,7 +137,7 @@ class Dispatcher new Wikis() shortcut_handler = new ShortcutsNavigation() new ZenMode() - new DropzoneInput($('.wiki-form')) + new GLForm($('.wiki-form')) when 'snippets' shortcut_handler = new ShortcutsNavigation() new ZenMode() if path[2] == 'show' diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index b502131a99d..6eb8d27ee2b 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -15,11 +15,13 @@ class @DropzoneInput project_uploads_path = window.project_uploads_path or null max_file_size = gon.max_file_size or 10 - form_textarea = $(form).find("textarea.markdown-area") + form_textarea = $(form).find(".js-gfm-input") form_textarea.wrap "<div class=\"div-dropzone\"></div>" form_textarea.on 'paste', (event) => handlePaste(event) + $mdArea = $(form_textarea).closest('.md-area') + $(form).setupMarkdownPreview() form_dropzone = $(form).find('.div-dropzone') @@ -49,17 +51,16 @@ class @DropzoneInput $(".div-dropzone-alert").alert "close" dragover: -> - form_textarea.addClass "div-dropzone-focus" + $mdArea.addClass 'is-dropzone-hover' form.find(".div-dropzone-hover").css "opacity", 0.7 return dragleave: -> - form_textarea.removeClass "div-dropzone-focus" + $mdArea.removeClass 'is-dropzone-hover' form.find(".div-dropzone-hover").css "opacity", 0 return drop: -> - form_textarea.removeClass "div-dropzone-focus" form.find(".div-dropzone-hover").css "opacity", 0 form_textarea.focus() return diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index e8d25591f63..2dc37257e22 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -57,14 +57,30 @@ class GitLabDropdownFilter filter: (search_text) -> data = @options.data() - results = data - if search_text isnt "" - results = fuzzaldrinPlus.filter(data, search_text, - key: @options.keys - ) + if data? + results = data - @options.callback results + if search_text isnt '' + results = fuzzaldrinPlus.filter(data, search_text, + key: @options.keys + ) + + @options.callback results + else + elements = @options.elements() + + if search_text + elements.each -> + $el = $(@) + matches = fuzzaldrinPlus.match($el.text().trim(), search_text) + + if matches.length + $el.show() + else + $el.hide() + else + elements.show() class GitLabDropdownRemote constructor: (@dataEndpoint, @options) -> @@ -106,7 +122,9 @@ class GitLabDropdown FILTER_INPUT = '.dropdown-input .dropdown-input-field' constructor: (@el, @options) -> - @dropdown = $(@el).parent() + self = @ + selector = $(@el).data "target" + @dropdown = if selector? then $(selector) else $(@el).parent() # Set Defaults { @@ -123,7 +141,7 @@ class GitLabDropdown if _.isString(@filterInput) @filterInput = @getElement(@filterInput) - search_fields = if @options.search then @options.search.fields else []; + searchFields = if @options.search then @options.search.fields else []; if @options.data # If data is an array @@ -147,7 +165,14 @@ class GitLabDropdown filterInputBlur: @filterInputBlur remote: @options.filterRemote query: @options.data - keys: @options.search.fields + keys: searchFields + elements: => + selector = '.dropdown-content li:not(.divider)' + + if @dropdown.find('.dropdown-toggle-page').length + selector = ".dropdown-page-one #{selector}" + + return $(selector) data: => return @fullData callback: (data) => @@ -376,7 +401,7 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject) + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el) if value? if !field.length and fieldName # Create hidden input for form diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee new file mode 100644 index 00000000000..d540cc4dc46 --- /dev/null +++ b/app/assets/javascripts/gl_form.js.coffee @@ -0,0 +1,51 @@ +class @GLForm + constructor: (@form) -> + @textarea = @form.find('textarea.js-gfm-input') + + # Before we start, we should clean up any previous data for this form + @destroy() + + # Setup the form + @setupForm() + + @form.data 'gl-form', @ + + destroy: -> + # Clean form listeners + @clearEventListeners() + @form.data 'gl-form', null + + setupForm: -> + isNewForm = @form.is(':not(.gfm-form)') + + @form.removeClass 'js-new-note-form' + + if isNewForm + @form.find('.div-dropzone').remove() + @form.addClass('gfm-form') + disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button') + + # remove notify commit author checkbox for non-commit notes + GitLab.GfmAutoComplete.setup() + new DropzoneInput(@form) + + autosize(@textarea) + + # form and textarea event listeners + @addEventListeners() + + # hide discard button + @form.find('.js-note-discard').hide() + + @form.show() + + clearEventListeners: -> + @textarea.off 'focus' + @textarea.off 'blur' + + addEventListeners: -> + @textarea.on 'focus', -> + $(@).closest('.md-area').addClass 'is-focused' + + @textarea.on 'blur', -> + $(@).closest('.md-area').removeClass 'is-focused' diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 946d83b7bdd..c7d74a12f99 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -10,6 +10,9 @@ class @Issue @initTaskList() @initIssueBtnEventListeners() + @initMergeRequests() + @initRelatedBranches() + initTaskList: -> $('.detail-page-description .js-task-list-container').taskList('enable') $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList @@ -69,3 +72,23 @@ class @Issue type: 'PATCH' url: $('form.js-issuable-update').attr('action') data: patchData + + initMergeRequests: -> + $container = $('#merge-requests') + + $.getJSON($container.data('url')) + .error -> + new Flash('Failed to load referenced merge requests', 'alert') + .success (data) -> + if 'html' of data + $container.html(data.html) + + initRelatedBranches: -> + $container = $('#related-branches') + + $.getJSON($container.data('url')) + .error -> + new Flash('Failed to load related branches', 'alert') + .success (data) -> + if 'html' of data + $container.html(data.html) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index d1fe116397a..bc80980acb7 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -34,7 +34,7 @@ class @LabelsSelect labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>"> - <span class="label color-label" style="background-color: <%= label.color %>;"> + <span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;"> <%= label.title %> </span> </a> @@ -165,6 +165,8 @@ class @LabelsSelect .html(template) $sidebarCollapsedValue.text(labelCount) + $('.has-tooltip', $value).tooltip(container: 'body') + $value .find('a') .each((i) -> @@ -218,7 +220,7 @@ class @LabelsSelect selectable: true toggleLabel: (selected) -> - if selected and selected.title isnt 'Any Label' + if selected and selected.title? selected.title else defaultLabel diff --git a/app/assets/javascripts/lib/datetime_utility.js.coffee b/app/assets/javascripts/lib/datetime_utility.js.coffee new file mode 100644 index 00000000000..ad1d1c70481 --- /dev/null +++ b/app/assets/javascripts/lib/datetime_utility.js.coffee @@ -0,0 +1,17 @@ +((w) -> + + w.gl ?= {} + w.gl.utils ?= {} + + w.gl.utils.formatDate = (datetime) -> + dateFormat(datetime, 'mmm d, yyyy h:MMtt Z') + + w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) -> + $timeagoEls.each( -> + $el = $(@) + $el.attr('title', gl.utils.formatDate($el.attr('datetime'))) + ) + + $timeagoEls.timeago() if setTimeago + +) window diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/notify.js.coffee index 3f9ca39912c..9e28353ac34 100644 --- a/app/assets/javascripts/lib/notify.js.coffee +++ b/app/assets/javascripts/lib/notify.js.coffee @@ -2,6 +2,11 @@ notificationGranted = (message, opts, onclick) -> notification = new Notification(message, opts) + # Hide the notification after X amount of seconds + setTimeout -> + notification.close() + , 8000 + if onclick notification.onclick = onclick diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 839e6ec2c08..1ab6e5114bc 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -73,7 +73,8 @@ class @MergeRequestTabs @expandView() else if action == 'diffs' @loadDiff($target.attr('href')) - @shrinkView() + if bp? and bp.getBreakpointSize() isnt 'lg' + @shrinkView() else if action == 'builds' @loadBuilds($target.attr('href')) @expandView() @@ -84,8 +85,10 @@ class @MergeRequestTabs scrollToElement: (container) -> if window.location.hash - $el = $("div#{container} #{window.location.hash}") - $('body').scrollTo($el.offset().top) if $el.length + navBarHeight = $('.navbar-gitlab').outerHeight() + + $el = $("#{container} #{window.location.hash}") + $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length # Activate a tab based on the current action activateTab: (action) -> @@ -141,7 +144,7 @@ class @MergeRequestTabs url: "#{source}.json" success: (data) => document.querySelector("div#commits").innerHTML = data.html - $('.js-timeago').timeago() + gl.utils.localTimeAgo($('.js-timeago', 'div#commits')) @commitsLoaded = true @scrollToElement("#commits") @@ -151,12 +154,38 @@ class @MergeRequestTabs @_get url: "#{source}.json" + @_location.search success: (data) => - document.querySelector("div#diffs").innerHTML = data.html - $('.js-timeago').timeago() - $('div#diffs .js-syntax-highlight').syntaxHighlight() + $('#diffs').html data.html + gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')) + $('#diffs .js-syntax-highlight').syntaxHighlight() @expandViewContainer() if @diffViewType() is 'parallel' @diffsLoaded = true @scrollToElement("#diffs") + @highlighSelectedLine() + + $(document) + .off 'click', '.diff-line-num a' + .on 'click', '.diff-line-num a', (e) => + e.preventDefault() + window.location.hash = $(e.currentTarget).attr 'href' + @highlighSelectedLine() + @scrollToElement("#diffs") + + highlighSelectedLine: -> + $('.hll').removeClass 'hll' + locationHash = window.location.hash + + if locationHash isnt '' + hashClassString = ".#{locationHash.replace('#', '')}" + $diffLine = $(locationHash) + + if $diffLine.is ':not(tr)' + $diffLine = $("td#{locationHash}, td#{hashClassString}") + else + $diffLine = $('td', $diffLine) + + $diffLine.addClass 'hll' + diffLineTop = $diffLine.offset().top + navBarHeight = $('.navbar-gitlab').outerHeight() loadBuilds: (source) -> return if @buildsLoaded @@ -165,7 +194,7 @@ class @MergeRequestTabs url: "#{source}.json" success: (data) => document.querySelector("div#builds").innerHTML = data.html - $('.js-timeago').timeago() + gl.utils.localTimeAgo($('.js-timeago', 'div#builds')) @buildsLoaded = true @scrollToElement("#builds") diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 84a8887fbce..065626beeb8 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -12,10 +12,19 @@ class @MergeRequestWidget @readyForCICheck = true clearInterval @fetchBuildStatusInterval + @clearEventListeners() + @addEventListeners() @pollCIStatus() notifyPermissions() - setOpts: (@opts) -> + clearEventListeners: -> + $(document).off 'page:change.merge_request' + + addEventListeners: -> + $(document).on 'page:change.merge_request', => + if $('body').data('page') isnt 'projects:merge_requests:show' + clearInterval @fetchBuildStatusInterval + @clearEventListeners() mergeInProgress: (deleteSourceBranch = false)-> $.ajax @@ -38,7 +47,7 @@ class @MergeRequestWidget $('.mr-state-widget').replaceWith(data) ciLabelForStatus: (status) -> - if status == 'success' + if status is 'success' 'passed' else status @@ -67,18 +76,28 @@ class @MergeRequestWidget @opts.ci_status = data.status return - if data.status isnt @opts.ci_status + if data.status isnt @opts.ci_status and data.status? @showCIStatus data.status if data.coverage @showCICoverage data.coverage if showNotification - message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status)) + status = @ciLabelForStatus(data.status) + + if status is "preparing" + title = @opts.ci_title.preparing + status = status.charAt(0).toUpperCase() + status.slice(1); + message = @opts.ci_message.preparing.replace('{{status}}', status) + else + title = @opts.ci_title.normal + message = @opts.ci_message.normal.replace('{{status}}', status) + + title = title.replace('{{status}}', status) message = message.replace('{{sha}}', data.sha) message = message.replace('{{title}}', data.title) notify( - "Build #{@ciLabelForStatus(data.status)}", + title, message, @opts.gitlab_icon, -> @@ -98,6 +117,8 @@ class @MergeRequestWidget @setMergeButtonClass('btn-danger') when "running", "pending" @setMergeButtonClass('btn-warning') + when "success" + @setMergeButtonClass('btn-create') else $('.ci_widget.ci-error').show() @setMergeButtonClass('btn-danger') @@ -107,4 +128,6 @@ class @MergeRequestWidget $('.ci_widget:visible .ci-coverage').text(text) setMergeButtonClass: (css_class) -> - $('.accept_merge_request').removeClass("btn-create").addClass(css_class) + $('.accept_merge_request') + .removeClass('btn-danger btn-warning btn-create') + .addClass(css_class) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index f73127f49f0..6bd4e885a03 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -85,15 +85,21 @@ class @MilestoneSelect # display:block overrides the hide-collapse rule $value.removeAttr('style') clicked: (selected) -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + if $dropdown.hasClass 'js-filter-bulk-update' return - if $dropdown.hasClass('js-filter-submit') + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if selected.name? selectedMilestone = selected.name else selectedMilestone = '' Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass('js-filter-submit') + $dropdown.closest('form').submit() else selected = $selectbox .find('input[type="hidden"]') diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 86e3b860fcb..fa91baa07c0 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -163,9 +163,15 @@ class @Notes else if @isNewNote(note) @note_ids.push(note.id) - $('ul.main-notes-list') + $notesList = $('ul.main-notes-list') + + $notesList .append(note.html) .syntaxHighlight() + + # Update datetime format on the recent note + gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false) + @initTaskList() @updateNotesCount(1) @@ -217,6 +223,8 @@ class @Notes # append new note to all matching discussions discussionContainer.append note_html + gl.utils.localTimeAgo($('.js-timeago', note_html), false) + @updateNotesCount(1) ### @@ -275,32 +283,10 @@ class @Notes show the form ### setupNoteForm: (form) -> - disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") - form.removeClass "js-new-note-form" - form.find('.div-dropzone').remove() - - # hide discard button - form.find('.js-note-discard').hide() - - # setup preview buttons - previewButton = form.find(".js-md-preview-button") + new GLForm form textarea = form.find(".js-note-text") - textarea.on "input", -> - if $(this).val().trim() isnt "" - previewButton.removeClass("turn-off").addClass "turn-on" - else - previewButton.removeClass("turn-on").addClass "turn-off" - - textarea.on 'focus', -> - $(this).closest('.md-area').addClass 'is-focused' - - textarea.on 'blur', -> - $(this).closest('.md-area').removeClass 'is-focused' - - autosize(textarea) - new Autosave textarea, [ "Note" form.find("#note_commit_id").val() @@ -309,11 +295,6 @@ class @Notes form.find("#note_noteable_id").val() ] - # remove notify commit author checkbox for non-commit notes - GitLab.GfmAutoComplete.setup() - new DropzoneInput(form) - form.show() - ### Called in response to the new note form being submitted @@ -345,7 +326,9 @@ class @Notes updateNote: (_xhr, note, _status) => # Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html) - $('.js-timeago', $html).timeago() + + gl.utils.localTimeAgo($('.js-timeago', $html)) + $html.syntaxHighlight() $html.find('.js-task-list-container').taskList('enable') @@ -365,34 +348,15 @@ class @Notes note = $(this).closest(".note") note.addClass "is-editting" form = note.find(".note-edit-form") - isNewForm = form.is(':not(.gfm-form)') - if isNewForm - form.addClass('gfm-form') + form.addClass('current-note-edit-form') # Show the attachment delete link note.find(".js-note-attachment-delete").show() - # Setup markdown form - if isNewForm - GitLab.GfmAutoComplete.setup() - new DropzoneInput(form) - - textarea = form.find("textarea") - textarea.focus() + new GLForm form - if isNewForm - autosize(textarea) - - # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?). - # The textarea has the correct value, Chrome just won't show it unless we - # modify it, so let's clear it and re-set it! - value = textarea.val() - textarea.val "" - textarea.val value - - if isNewForm - disableButtonIfEmptyField textarea, form.find(".js-comment-button") + form.find(".js-note-text").focus() ### Called in response to clicking the edit note link @@ -549,6 +513,9 @@ class @Notes removeDiscussionNoteForm: (form)-> row = form.closest("tr") + glForm = form.data 'gl-form' + glForm.destroy() + form.find(".js-note-text").data("autosave").reset() # show the reply button (will only work for replies) @@ -560,7 +527,6 @@ class @Notes # only remove the form form.remove() - cancelDiscussionForm: (e) => e.preventDefault() form = $(e.target).closest(".js-discussion-note-form") diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index ae87c6c4e40..f4a2562885d 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -18,8 +18,11 @@ class @Profile $(this).find('.btn-save').enable() $(this).find('.loading-gif').hide() - $('.update-notifications').on 'ajax:complete', -> - $(this).find('.btn-save').enable() + $('.update-notifications').on 'ajax:success', (e, data) -> + if data.saved + new Flash("Notification settings saved", "notice") + else + new Flash("Failed to save new settings", "alert") @bindEvents() diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 87d313ed67c..07be85a32a5 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -37,19 +37,20 @@ class @Project $('.update-notification').on 'click', (e) -> e.preventDefault() notification_level = $(@).data 'notification-level' - $('#notification_level').val(notification_level) + label = $(@).data 'notification-title' + $('#notification_setting_level').val(notification_level) $('#notification-form').submit() - label = null - switch notification_level - when 0 then label = ' Disabled ' - when 1 then label = ' Participating ' - when 2 then label = ' Watching ' - when 3 then label = ' Global ' - when 4 then label = ' On Mention ' $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") $(@).parents('ul').find('li.active').removeClass 'active' $(@).parent().addClass 'active' + $('#notification-form').on 'ajax:success', (e, data) -> + if data.saved + new Flash("Notification settings saved", "notice") + else + new Flash("Failed to save new settings", "alert") + + @projectSelectDropdown() projectSelectDropdown: -> diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee index be8ab9b428d..704bd8dee53 100644 --- a/app/assets/javascripts/project_select.js.coffee +++ b/app/assets/javascripts/project_select.js.coffee @@ -1,5 +1,37 @@ class @ProjectSelect constructor: -> + $('.js-projects-dropdown-toggle').each (i, dropdown) -> + $dropdown = $(dropdown) + + $dropdown.glDropdown( + filterable: true + filterRemote: true + search: + fields: ['name_with_namespace'] + data: (term, callback) -> + finalCallback = (projects) -> + callback projects + + if @includeGroups + projectsCallback = (projects) -> + groupsCallback = (groups) -> + data = groups.concat(projects) + finalCallback(data) + + Api.groups term, false, groupsCallback + else + projectsCallback = finalCallback + + if @groupId + Api.groupProjects @groupId, term, projectsCallback + else + Api.projects term, @orderBy, projectsCallback + url: (project) -> + project.web_url + text: (project) -> + project.name_with_namespace + ) + $('.ajax-project-select').each (i, select) -> @groupId = $(select).data('group-id') @includeGroups = $(select).data('include-groups') diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index e1778511240..860d4f438d0 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -4,6 +4,7 @@ expanded = 'page-sidebar-expanded' toggleSidebar = -> $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('header').toggleClass("header-collapsed header-expanded") + $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left") $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) setTimeout ( -> diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee index 084f0e0dc65..e4b7a3172ec 100644 --- a/app/assets/javascripts/subscription.js.coffee +++ b/app/assets/javascripts/subscription.js.coffee @@ -10,10 +10,10 @@ class @Subscription btn = $(event.currentTarget) action = btn.find('span').text() current_status = @subscription_status.attr('data-status') - btn.prop('disabled', true) + btn.addClass('disabled') $.post @url, => - btn.prop('disabled', false) + btn.removeClass('disabled') status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed' @subscription_status.attr('data-status', status) action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe' diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index ec2df6c5b73..00d2b641723 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -57,5 +57,12 @@ class @Todos $('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-done .badge').text data.done_count - goToTodoUrl: -> - Turbolinks.visit($(this).data('url')) + goToTodoUrl: (e)-> + todoLink = $(this).data('url') + return unless todoLink + + if e.metaKey + e.preventDefault() + window.open(todoLink,'_blank') + else + Turbolinks.visit(todoLink) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 657c5f033c7..e8c0172680d 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -7,6 +7,7 @@ &:focus, &:active { outline: none; + background-color: $btn-active-gray; @include box-shadow($gl-btn-active-background); } } @@ -27,7 +28,8 @@ color: $color; } - &:active { + &:active, + &.active { @include box-shadow ($gl-btn-active-background); background-color: $dark; @@ -61,7 +63,7 @@ } @mixin btn-white { - @include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236); + @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active); } .btn { @@ -218,3 +220,26 @@ margin-right: 5px; } } + +.btn-text-field { + width: 100%; + text-align: left; + padding: 6px 16px; + border-color: $border-color; + color: $btn-placeholder-gray; + background-color: $background-color; + + &:hover, + &:active, + &:focus { + cursor: text; + box-shadow: none; + border-color: $border-color; + color: $btn-placeholder-gray; + background-color: $background-color; + } +} + +.btn-file-option { + background: linear-gradient(180deg, $white-light 25%, $gray-light 100%); +} diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 82dc1acbd01..ba6c7930cdc 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -248,7 +248,7 @@ .dropdown-title { position: relative; - padding: 0 0 15px; + padding: 0 25px 15px; margin: 0 10px 10px; font-weight: 600; line-height: 1; @@ -275,7 +275,7 @@ } .dropdown-menu-close { - right: 7px; + right: 5px; width: 20px; height: 20px; top: -1px; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index b15f4e7bd5e..789df42fb66 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -15,12 +15,13 @@ .file-title { position: relative; - background: $background-color; + background-color: $background-color; border-bottom: 1px solid $border-color; margin: 0; text-align: left; padding: 10px $gl-padding; word-wrap: break-word; + border-radius: 3px 3px 0 0; .file-actions { float: right; @@ -49,7 +50,7 @@ } } - a { + a:not(.btn) { color: $gl-dark-link-color; } diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss index 5ae0520fd7b..f4d35c4b4b1 100644 --- a/app/assets/stylesheets/framework/gfm.scss +++ b/app/assets/stylesheets/framework/gfm.scss @@ -1,24 +1,6 @@ /** * Styles that apply to all GFM related forms. */ -.issue-form, .merge-request-form, .wiki-form { - .description { - height: 16em; - border-top-left-radius: 0; - } -} - -.wiki-form { - .description { - height: 26em; - } -} - -.milestone-form { - .description { - height: 14em; - } -} .gfm-commit, .gfm-commit_range { font-family: $monospace_font; diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index fa9038ebaca..c83cf881596 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -33,15 +33,10 @@ background: $color; } - .complex-sidebar .nav-primary { - border-right: 1px solid lighten($color, 3%); - } - .sidebar-wrapper { background: $color-darker; .sidebar-user { - border-top: 1px solid lighten($color, 3%); background: $color-darker; color: $color-light; @@ -67,6 +62,7 @@ .count { color: $color-light; + background: $color-dark; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 724980b2208..3f015427d07 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -69,6 +69,7 @@ header { } .header-content { + position: relative; height: $header-height; padding-right: 20px; @@ -76,6 +77,10 @@ header { padding-right: 0; } + .dropdown-menu { + margin-top: -5px; + } + .title { margin: 0; font-size: 19px; @@ -123,11 +128,11 @@ header { } @mixin collapsed-header { - margin-left: 40px; + margin-left: $sidebar_collapsed_width; } .header-collapsed { - margin-left: 40px; + margin-left: $sidebar_collapsed_width; @media (min-width: $screen-md-min) { @include collapsed-header; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index c8f86d60e3b..0f32d36d59c 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -1,22 +1,15 @@ .div-dropzone-wrapper { .div-dropzone { position: relative; - margin-bottom: -5px; - - .div-dropzone-focus { - border-color: #66afe9 !important; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important; - outline: 0 !important; - } .div-dropzone-hover { position: absolute; top: 50%; left: 50%; - margin-top: -0.5em; - margin-left: -0.6em; + margin-top: -11.5px; + margin-left: -15px; opacity: 0; - font-size: 50px; + font-size: 30px; transition: opacity 200ms ease-in-out; pointer-events: none; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 94f5a12ff6a..192d53b048a 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -58,12 +58,12 @@ .nav-search { display: inline-block; - width: 50%; + width: 100%; padding: 11px 0; /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-sm-min) { - width: 100%; + @media (min-width: $screen-sm-min) { + width: 50%; } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 1d49249dd80..18189e985c4 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -144,7 +144,7 @@ } a { - padding: 7px 12px; + padding: 7px 15px; font-size: $gl-font-size; line-height: 24px; color: $gray; @@ -169,12 +169,10 @@ } .count { - &:before { - content: '('; - } - &:after { - content: ')'; - } + float: right; + background: #eee; + padding: 0 8px; + @include border-radius(6px); } &.back-link i { @@ -193,27 +191,6 @@ } } -.expand-nav a { - color: $gl-icon-color; - width: 60px; - position: fixed; - top: 0; - left: 0; - font-size: 20px; - background: #fff; - height: 59px; - text-align: center; - line-height: 59px; - border-bottom: 1px solid #eee; - transition-duration: .3s; - outline: none; - z-index: 100; - - &:hover { - text-decoration: none; - } -} - .collapse-nav a { width: $sidebar_width; position: fixed; @@ -233,12 +210,55 @@ } .page-sidebar-collapsed { + padding-left: $sidebar_collapsed_width; + .sidebar-wrapper { - display: none; + width: $sidebar_collapsed_width; + + .header-logo { + width: $sidebar_collapsed_width; + + a { + padding-left: ($sidebar_collapsed_width - 36) / 2; + + .gitlab-text-container { + display: none; + } + } + } + + .nav-sidebar { + width: $sidebar_collapsed_width; + + li { + width: auto; + + a { + span { + display: none; + } + } + } + } + + .collapse-nav a { + width: $sidebar_collapsed_width; + } + + .sidebar-user { + padding-left: ($sidebar_collapsed_width - 36) / 2; + width: $sidebar_collapsed_width; + + .username { + display: none; + } + } } } .page-sidebar-expanded { + padding-left: $sidebar_collapsed_width; + @media (min-width: $screen-md-min) { padding-left: $sidebar_width; } @@ -289,48 +309,3 @@ padding-right: $sidebar_collapsed_width; } } - -.complex-sidebar { - display: inline-block; - - .nav-primary { - width: 61px; - float: left; - height: 100vh; - - .nav-sidebar { - width: 60px; - - li a { - width: 60px; - - span { - display: none; - } - } - } - } - - .nav-secondary { - $nav-secondary-width: 168px; - - float: left; - width: $nav-secondary-width; - - .nav-sidebar { - width: $nav-secondary-width; - - li { - width: $nav-secondary-width; - - a { - width: $nav-secondary-width; - - i { - display: none; - } - } - } - } - } -} diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index aa244fe548d..b91f2f6f898 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -14,10 +14,6 @@ background: $row-hover; } - &:last-child { - border-bottom: none; - } - .avatar { margin-right: 15px; } diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index dd42db1840f..96bab7880c2 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -43,7 +43,6 @@ @import "bootstrap/modals"; @import "bootstrap/tooltip"; @import "bootstrap/popovers"; -@import "bootstrap/carousel"; // Utility classes .clearfix { diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 7b2aada5a0d..0a5b4b8834c 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -250,14 +250,6 @@ a > code { * Textareas intended for GFM * */ -.js-gfm-input { - font-family: $monospace_font; - color: $gl-text-color; -} - -.md-preview { -} - .strikethrough { text-decoration: line-through; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 8d3ad934a50..f910cf61817 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -10,10 +10,10 @@ $gutter_inner_width: 258px; /* * UI elements */ -$border-color: #efeff1; +$border-color: #e5e5e5; $focus-border-color: #3aabf0; $table-border-color: #eef0f2; -$background-color: #faf9f9; +$background-color: #fafafa; /* * Text @@ -28,6 +28,7 @@ $gl-link-color: #3084bb; $gl-dark-link-color: #333; $gl-placeholder-color: #8f8f8f; $gl-icon-color: $gl-placeholder-color; +$gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-header-color: $gl-title-color; @@ -81,7 +82,7 @@ $provider-btn-not-active-color: #4688f1; $white-light: #fff; $white-normal: #ededed; -$white-dark: #ededed; +$white-dark: #ececec; $gray-light: #faf9f9; $gray-normal: #f5f5f5; @@ -108,6 +109,8 @@ $red-light: #e52c5a; $red-normal: #d22852; $red-dark: darken($red-normal, 5%); +$black-transparent: rgba(0, 0, 0, 0.3); + $border-white-light: #f1f2f4; $border-white-normal: #d6dae2; $border-white-dark: #c6cacf; @@ -147,18 +150,26 @@ $light-grey-header: #faf9f9; */ $gl-primary: $blue-normal; $gl-success: $green-normal; +$gl-success-focus: rgba($gl-success, .4); $gl-info: $blue-normal; $gl-warning: $orange-normal; $gl-danger: $red-normal; -$gl-btn-active-background: rgba(0, 0, 0, 0.12); -$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background; +$gl-btn-active-background: rgba(0, 0, 0, 0.16); +$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; /* * Commit Diff Colors */ $added: #63c363; $deleted: #f77; - +$line-added: #ecfdf0; +$line-added-dark: #c7f0d2; +$line-removed: #fbe9eb; +$line-removed-dark: #fac5cd; +$line-number-old: #f9d7dc; +$line-number-new: #ddfbe6; +$match-line: #fafafa; +$table-border-gray: #f0f0f0; /* * Fonts */ @@ -192,6 +203,13 @@ $dropdown-toggle-icon-color: #c4c4c4; $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; /* +* Buttons +*/ +$btn-active-gray: #ececec; +$btn-placeholder-gray: #c7c7c7; +$btn-white-active: #848484; + +/* * Award emoji */ $award-emoji-menu-bg: #fff; diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 47673944896..77a73dc379b 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #557; + border-color: darken(#557, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080); } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 806401c21ae..28253d4ccb4 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #49483e; + border-color: darken(#49483e, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080); } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 6a809d4dfd2..c62bd021aef 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #174652; + border-color: darken(#174652, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46); } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index b90c95c62d1..524cfaf90c3 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -6,7 +6,7 @@ } .diff-line-num, .diff-line-num a { - color: rgba(0, 0, 0, 0.3); + color: $black-transparent; } // Code itself @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #ddd8c5; + border-color: darken(#ddd8c5, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4); } @@ -30,7 +36,7 @@ } .line_content.match { - color: rgba(0, 0, 0, 0.3); + color: $black-transparent; background: rgba(255, 255, 255, 0.4); } } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 8c1b0cd84ec..1ff6ad75e07 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -6,12 +6,12 @@ } .diff-line-num, .diff-line-num a { - color: rgba(0, 0, 0, 0.3); + color: $black-transparent; } // Code itself pre.code, .diff-line-num { - border-color: $border-color; + border-color: $table-border-gray; } &, pre.code, .line_holder .line_content { @@ -21,38 +21,44 @@ // 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 { - background: #fdd; - border-color: #f1c0c0; + background-color: $line-number-old; + border-color: $line-removed-dark; } &.new { - background: #dbffdb; - border-color: #c1e9c1; + background-color: $line-number-new; + border-color: $line-added-dark; } } .line_content { &.old { - background: #ffecec; + background: $line-removed; span.idiff { - background-color: #f8cbcb; + background-color: $line-removed-dark; } } &.new { - background: #eaffea; + background-color: $line-added; span.idiff { - background-color: #a6f3a6; + background-color: $line-added-dark; } } &.match { - color: rgba(0, 0, 0, 0.3); - background: #fafafa; + color: $black-transparent; + background: $match-line; } } } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 082911bd118..358d2f4ab9d 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -20,6 +20,8 @@ margin: 0; padding: 0; margin-top: 10px; + word-break: normal; + white-space: pre-wrap; } .commit-info-row { diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 8272615768d..6453c91d955 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -47,6 +47,7 @@ li.commit { .commit_short_id { min-width: 65px; + color: $gl-dark-link-color; font-family: $monospace_font; } @@ -88,6 +89,10 @@ li.commit { padding: 0; margin: 0; } + + a { + color: $gl-dark-link-color; + } } .commit-row-info { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 7a12aa96476..d0855f66911 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -2,6 +2,7 @@ .diff-file { border: 1px solid $border-color; margin-bottom: $gl-padding; + border-radius: 3px; .diff-header { position: relative; @@ -10,6 +11,7 @@ padding: 10px 16px; color: #555; z-index: 10; + border-radius: 3px 3px 0 0; .diff-title { font-family: $monospace_font; @@ -31,6 +33,7 @@ overflow-y: hidden; background: #fff; color: #333; + border-radius: 0 0 3px 3px; .unfold { cursor: pointer; @@ -63,10 +66,10 @@ .line_holder td { line-height: $code_line_height; font-size: $code_font_size; - } - td { - white-space: nowrap; + span { + white-space: pre; + } } } @@ -109,6 +112,10 @@ display: table-cell; } } + + .text-file.diff-wrap-lines table .line_holder td span { + white-space: pre-wrap; + } } .image { background: #ddd; @@ -321,6 +328,16 @@ float: right; } +.diffs { + .content-block { + border-bottom: none; + } +} + +.files-changed { + border-bottom: none; +} + // Mobile @media (max-width: 480px) { .diff-title { diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index c66efe978cd..6fe57c737b3 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -41,8 +41,17 @@ word-wrap: break-word; .md { - color: #7f8fa4; + color: $gl-grayish-blue; font-size: $gl-font-size; + + .label { + color: $gl-text-color; + font-size: inherit; + } + + iframe.twitter-share-button { + vertical-align: bottom; + } } pre { diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index bd224705f04..604f1700cf8 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -59,6 +59,9 @@ position: relative; overflow-y: auto; padding: 15px; + .form-actions { + margin: -$gl-padding+1; + } } body.modal-open { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 88c1b614c74..d9218e15095 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -263,6 +263,12 @@ } } + .dropdown-content { + a:hover { + color: inherit; + } + } + .dropdown-menu-toggle { width: 100%; padding-top: 6px; @@ -316,3 +322,9 @@ color: #8c8c8c; } } + +.issuable-form-padding-top { + @media (min-width: $screen-sm-min) { + padding-top: 7px; + } +} diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 4e02ec4e891..da20fa28802 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -49,6 +49,15 @@ } .label-row { + .label-name { + display: inline-block; + width: 200px; + + @media (max-width: $screen-xs-min) { + display: block; + } + } + .label { padding: 9px; font-size: 14px; @@ -69,3 +78,63 @@ background-color: $gl-danger; color: $white-light; } + +@mixin labels-mobile { + @media (max-width: $screen-xs-min) { + display: block; + width: 100%; + margin-left: 0; + padding: 10px 0; + } +} + + +.manage-labels-list { + + .prepend-left-10, .prepend-description-left { + display: inline-block; + width: 40%; + vertical-align: middle; + + @include labels-mobile; + } + + .prepend-description-left { + width: 57%; + + @include labels-mobile; + } + + .pull-info-right { + float: right; + + @media (max-width: $screen-xs-min) { + float: none; + } + + .action-buttons { + border-color: transparent; + padding: 6px; + color: $gl-text-color; + + &.subscribe-button { + padding-left: 0; + } + } + + i { + color: $gl-text-color; + } + + .append-right-20 { + a { + color: $gl-text-color; + } + + @media (max-width: $screen-xs-min) { + display: block; + margin-bottom: 10px; + } + } + } +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 1c6a4208974..4ef548ffbe7 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -123,6 +123,8 @@ .mr_source_commit, .mr_target_commit { + margin-bottom: 0; + .commit { margin: 0; padding: 2px 0; @@ -140,6 +142,7 @@ overflow: hidden; font-size: 90%; margin: 0 3px; + word-break: break-all; } .mr-list { @@ -174,10 +177,6 @@ display: none; } -.merge-request-form .select2-container { - width: 250px !important; -} - #modal_merge_info .modal-dialog { width: 600px; @@ -200,3 +199,76 @@ overflow-x: scroll; } } + +.panel-new-merge-request { + .panel-heading { + padding: 5px 10px; + font-weight: 600; + line-height: 25px; + } + + .panel-body { + padding: 10px 5px; + } + + .panel-footer { + padding: 5px 10px; + } + + .commit { + .commit-row-title { + margin-bottom: 4px; + } + + .avatar { + width: 20px; + height: 20px; + margin-right: 5px; + } + + .commit-row-info { + line-height: 20px; + } + } + + .btn-clipboard { + margin-right: 5px; + padding: 0; + background: transparent; + } + + .ci-status-link { + margin-right: 5px; + } +} + +.merge-request-select { + padding-left: 5px; + padding-right: 5px; + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + + @media (min-width: $screen-sm-min) { + float: left; + width: 50%; + margin-bottom: 0; + } + + .dropdown-menu-toggle { + width: 100%; + } + + .dropdown-menu { + left: 5px; + right: 5px; + width: auto; + } +} + +.issuable-form-select-holder { + display: inline-block; + width: 250px; +} diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index a909776b437..07c707e7b77 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -1,10 +1,10 @@ /** * Note Form */ -.reply-btn { - @extend .btn-primary; - margin: 10px $gl-padding; +.comment-btn { + @extend .btn-create; } + .diff-file .diff-content { tr.line_holder:hover > td .line_note_link { opacity: 1.0; @@ -40,6 +40,7 @@ } .note-textarea { + display: block; padding: 10px 0; font-family: $regular_font; border: 0; @@ -63,7 +64,7 @@ &.is-focused { border-color: $focus-border-color; - box-shadow: 0 0 2px rgba(#000, .2), + box-shadow: 0 0 2px $black-transparent, 0 0 4px rgba($focus-border-color, .4); .comment-toolbar, @@ -71,12 +72,35 @@ border-color: $focus-border-color; } } + + &.is-dropzone-hover { + border-color: $gl-success; + box-shadow: 0 0 2px $black-transparent, + 0 0 4px $gl-success-focus; + + .comment-toolbar, + .nav-links { + border-color: $gl-success; + } + } + + p { + code { + white-space: normal; + } + + pre { + code { + white-space: pre; + } + } + } } } .discussion-form { padding: $gl-padding-top $gl-padding; - background-color: #fff; + background-color: $white-light; } .note-edit-form { @@ -113,13 +137,12 @@ .discussion-body, .diff-file { .notes .note { - border-color: #ddd; padding: 10px 15px; } .discussion-reply-holder { - background: $background-color; - border-top: 1px solid $border-color; + background-color: $white-light; + padding: 10px 16px; } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index aca86457c70..e421a31549a 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -58,6 +58,7 @@ ul.notes { .note { display: block; position: relative; + border-bottom: 1px solid $table-border-gray; &.is-editting { .note-header, @@ -80,9 +81,15 @@ ul.notes { @include md-typography; // On diffs code should wrap nicely and not overflow - pre { + p { code { - white-space: pre; + white-space: normal; + } + + pre { + code { + white-space: pre; + } } } @@ -111,15 +118,16 @@ ul.notes { margin: 10px 0; } } + + a { + word-break: break-all; + } } .note-header { padding-bottom: 3px; } - &:last-child { - border-bottom: 1px solid $border-color; - } } } @@ -129,7 +137,7 @@ ul.notes { margin-right: 10px; } .line_content { - white-space: pre-wrap; + white-space: pre; } } @@ -137,29 +145,37 @@ ul.notes { font-family: $regular_font; td { - border: 1px solid #ddd; + border: 1px solid $table-border-gray; border-left: none; &.notes_line { vertical-align: middle; text-align: center; padding: 10px 0; - background: #fff; + background: $background-color; color: $text-color; } + &.notes_line2 { text-align: center; padding: 10px 0; border-left: 1px solid #ddd !important; } + &.notes_content { - background-color: #fff; + background-color: $background-color; border-width: 1px 0; padding: 0; vertical-align: top; + white-space: normal; + &.parallel { border-width: 1px; } + + .notes { + background-color: $white-light; + } } } } @@ -175,9 +191,6 @@ ul.notes { } } - .author_link { - font-weight: 600; - } } .note-headline-light, @@ -203,14 +216,26 @@ ul.notes { line-height: 24px; .fa { + color: $notes-action-color; position: relative; top: 1px; font-size: 17px; } - .fa-trash-o { - top: 0; - font-size: 16px; + &.js-note-delete { + i { + &:hover { + color: $gl-text-red; + } + } + } + + &.js-note-edit { + i { + &:hover { + color: $gl-link-color; + } + } } } diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index e83fa9e3d52..75f78569e3c 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -34,6 +34,11 @@ color: #7f8fa4; font-size: $gl-font-size; + .label { + color: $gl-text-color; + font-size: inherit; + } + p { color: #5c5d5e; } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c81cb85dc1b..ce5c84ee9bc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,6 +3,7 @@ require 'fogbugz' class ApplicationController < ActionController::Base include Gitlab::CurrentSettings + include Gitlab::GonHelper include GitlabRoutingHelper include PageLayoutHelper @@ -47,6 +48,16 @@ class ApplicationController < ActionController::Base email: current_user.email, username: current_user.username, ) + + Raven.tags_context(program: sentry_program_context) + end + end + + def sentry_program_context + if Sidekiq.server? + 'sidekiq' + else + 'rails' end end @@ -148,20 +159,6 @@ class ApplicationController < ActionController::Base end end - def add_gon_variables - gon.api_version = API::API.version - gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s - gon.default_issues_tracker = Project.new.default_issue_tracker.to_param - gon.max_file_size = current_application_settings.max_attachment_size - gon.relative_url_root = Gitlab.config.gitlab.relative_url_root - gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - - if current_user - gon.current_user_id = current_user.id - gon.api_token = current_user.private_token - end - end - def validate_user_service_ticket! return unless signed_in? && session[:service_tickets] diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index b23c3022fb5..9d5a28e8d4d 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -18,14 +18,14 @@ class Groups::MilestonesController < Groups::ApplicationController end def create - project_ids = params[:milestone][:project_ids] + project_ids = params[:milestone][:project_ids].reject(&:blank?) title = milestone_params[:title] - @projects.where(id: project_ids).each do |project| - Milestones::CreateService.new(project, current_user, milestone_params).execute + if create_milestones(project_ids) + redirect_to milestone_path(title) + else + render_new_with_error(project_ids.empty?) end - - redirect_to milestone_path(title) end def show @@ -41,6 +41,27 @@ class Groups::MilestonesController < Groups::ApplicationController private + def create_milestones(project_ids) + return false unless project_ids.present? + + ActiveRecord::Base.transaction do + @projects.where(id: project_ids).each do |project| + Milestones::CreateService.new(project, current_user, milestone_params).execute + end + end + + true + rescue ActiveRecord::ActiveRecordError => e + flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}" + false + end + + def render_new_with_error(empty_project_ids) + @milestone = Milestone.new(milestone_params) + @milestone.errors.add(:project_id, "Please select at least one project.") if empty_project_ids + render :new + end + def authorize_admin_milestones! return render_404 unless can?(current_user, :admin_milestones, group) end diff --git a/app/controllers/groups/notification_settings_controller.rb b/app/controllers/groups/notification_settings_controller.rb new file mode 100644 index 00000000000..de13b16ccf2 --- /dev/null +++ b/app/controllers/groups/notification_settings_controller.rb @@ -0,0 +1,16 @@ +class Groups::NotificationSettingsController < Groups::ApplicationController + before_action :authenticate_user! + + def update + notification_setting = current_user.notification_settings_for(group) + saved = notification_setting.update_attributes(notification_setting_params) + + render json: { saved: saved } + end + + private + + def notification_setting_params + params.require(:notification_setting).permit(:level) + end +end diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb index d1e4ac10f6c..c6bdd0602c1 100644 --- a/app/controllers/oauth/applications_controller.rb +++ b/app/controllers/oauth/applications_controller.rb @@ -1,9 +1,11 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController include Gitlab::CurrentSettings + include Gitlab::GonHelper include PageLayoutHelper before_action :verify_user_oauth_applications_enabled before_action :authenticate_user! + before_action :add_gon_variables layout 'profile' diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index d28e96c3f18..df98f56a1cd 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -60,6 +60,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController continue_login_process end + rescue Gitlab::OAuth::SignupDisabledError + handle_signup_error end def omniauth_error @@ -92,16 +94,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController continue_login_process end rescue Gitlab::OAuth::SignupDisabledError - label = Gitlab::OAuth::Provider.label_for(oauth['provider']) - message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." - - if current_application_settings.signup_enabled? - message << " Create a GitLab account first, and then connect it to your #{label} account." - end - - flash[:notice] = message - - redirect_to new_user_session_path + handle_signup_error end def handle_service_ticket provider, ticket @@ -122,6 +115,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController end end + def handle_signup_error + label = Gitlab::OAuth::Provider.label_for(oauth['provider']) + message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." + + if current_application_settings.signup_enabled? + message << " Create a GitLab account first, and then connect it to your #{label} account." + end + + flash[:notice] = message + + redirect_to new_user_session_path + end + def oauth @oauth ||= request.env['omniauth.auth'] end diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 1fd1d6882df..18ee55c839a 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -1,39 +1,18 @@ class Profiles::NotificationsController < Profiles::ApplicationController def show @user = current_user - @notification = current_user.notification - @project_members = current_user.project_members - @group_members = current_user.group_members + @group_notifications = current_user.notification_settings.for_groups + @project_notifications = current_user.notification_settings.for_projects end def update - type = params[:notification_type] - - @saved = if type == 'global' - current_user.update_attributes(user_params) - elsif type == 'group' - group_member = current_user.group_members.find(params[:notification_id]) - group_member.notification_level = params[:notification_level] - group_member.save - else - project_member = current_user.project_members.find(params[:notification_id]) - project_member.notification_level = params[:notification_level] - project_member.save - end - - respond_to do |format| - format.html do - if @saved - flash[:notice] = "Notification settings saved" - else - flash[:alert] = "Failed to save new settings" - end - - redirect_back_or_default(default: profile_notifications_path) - end - - format.js + if current_user.update_attributes(user_params) + flash[:notice] = "Notification settings saved" + else + flash[:alert] = "Failed to save new settings" end + + redirect_back_or_default(default: profile_notifications_path) end def user_params diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6d649e72f84..c26cfeccf1d 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController include IssuableActions before_action :module_enabled - before_action :issue, only: [:edit, :update, :show] + before_action :issue, + only: [:edit, :update, :show, :referenced_merge_requests, :related_branches] # Allow read any issue before_action :authorize_read_issue!, only: [:show] @@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController # Allow issues bulk update before_action :authorize_admin_issues!, only: [:bulk_update] - # Cross-reference merge requests - before_action :closed_by_merge_requests, only: [:show] - respond_to :html def index @@ -65,8 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController @note = @project.notes.new(noteable: @issue) @notes = @issue.notes.nonawards.with_associations.fresh @noteable = @issue - @merge_requests = @issue.referenced_merge_requests(current_user) - @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch) respond_to do |format| format.html @@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController end end + def referenced_merge_requests + @merge_requests = @issue.referenced_merge_requests(current_user) + @closed_by_merge_requests = @issue.closed_by_merge_requests(current_user) + + respond_to do |format| + format.json do + render json: { + html: view_to_html_string('projects/issues/_merge_requests') + } + end + end + end + + def related_branches + merge_requests = @issue.referenced_merge_requests(current_user) + + @related_branches = @issue.related_branches - + merge_requests.map(&:source_branch) + + respond_to do |format| + format.json do + render json: { + html: view_to_html_string('projects/issues/_related_branches') + } + 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" }) end - def closed_by_merge_requests - @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user) - end - protected def issue diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 49064f5d505..3e0cfc6aa65 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -207,20 +207,20 @@ class Projects::MergeRequestsController < Projects::ApplicationController #This is always source @source_project = @merge_request.nil? ? @project : @merge_request.source_project @commit = @repository.commit(params[:ref]) if params[:ref].present? + render layout: false end def branch_to @target_project = selected_target_project @commit = @target_project.commit(params[:ref]) if params[:ref].present? + render layout: false end def update_branches @target_project = selected_target_project @target_branches = @target_project.repository.branch_names - respond_to do |format| - format.js - end + render layout: false end def ci_status @@ -237,6 +237,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + status = "preparing" if status.nil? + response = { title: merge_request.title, sha: merge_request.last_commit_short_sha, diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 1b9dd568043..707a0d0e5c6 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -39,8 +39,7 @@ class Projects::NotesController < Projects::ApplicationController def destroy if note.editable? - note.destroy - note.reset_events_cache + Notes::DeleteService.new(project, current_user).execute(note) end respond_to do |format| diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb new file mode 100644 index 00000000000..7d81cc03c73 --- /dev/null +++ b/app/controllers/projects/notification_settings_controller.rb @@ -0,0 +1,16 @@ +class Projects::NotificationSettingsController < Projects::ApplicationController + before_action :authenticate_user! + + def update + notification_setting = current_user.notification_settings_for(project) + saved = notification_setting.update_attributes(notification_setting_params) + + render json: { saved: saved } + end + + private + + def notification_setting_params + params.require(:notification_setting).permit(:level) + end +end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 5c7614cfbaf..bb7a6b6a5ab 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -11,7 +11,6 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - RepositoryArchiveCacheWorker.perform_async headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format])) head :ok rescue => ex diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3cc37e59855..3768efe142a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -101,14 +101,18 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| format.html do + if current_user + @membership = @project.team.find_member(current_user.id) + + if @membership + @notification_setting = current_user.notification_settings_for(@project) + end + end + if @project.repository_exists? if @project.empty_repo? render 'projects/empty' else - if current_user - @membership = @project.team.find_member(current_user.id) - end - render :show end else diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e6ceb213532..16e5b8ac223 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -184,7 +184,7 @@ module ApplicationHelper element = content_tag :time, time.to_s, class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}", datetime: time.to_time.getutc.iso8601, - title: time.in_time_zone.to_s(:medium), + title: time.to_time.in_time_zone.to_s(:medium), data: { toggle: 'tooltip', placement: placement, container: 'body' } unless skip_js diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 820d69c230b..9e59a295fc4 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -27,9 +27,9 @@ module BlobHelper link_opts) if !on_top_of_branch?(project, ref) - button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } + button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } elsif can_edit_blob?(blob, project, ref) - link_to "Edit", edit_path, class: 'btn' + link_to "Edit", edit_path, class: 'btn btn-file-option' elsif can?(current_user, :fork_project, project) continue_params = { to: edit_path, @@ -38,7 +38,7 @@ module BlobHelper } fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) - link_to "Edit", fork_path, class: 'btn', method: :post + link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index bde0799f3de..35ba543cef1 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -28,7 +28,7 @@ module CommitsHelper def commit_to_html(commit, project, inline = true) template = inline ? "inline_commit" : "commit" - escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil? + render "projects/commits/#{template}", commit: commit, project: project unless commit.nil? end # Breadcrumb links for a Project and, if applicable, a tree path @@ -117,7 +117,7 @@ module CommitsHelper end end link_to( - "Browse Files »", + "Browse Files", namespace_project_tree_path(project.namespace, project, commit), class: "pull-right" ) @@ -197,7 +197,7 @@ module CommitsHelper link_to( namespace_project_blob_path(project.namespace, project, tree_join(commit_sha, diff.new_path)), - class: 'btn view-file js-view-file' + class: 'btn view-file js-view-file btn-file-option' ) do raw('View file @') + content_tag(:span, commit_sha[0..6], class: 'commit-short-id') diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb new file mode 100644 index 00000000000..6a43be2cf3e --- /dev/null +++ b/app/helpers/form_helper.rb @@ -0,0 +1,18 @@ +module FormHelper + def form_errors(model) + return unless model.errors.any? + + pluralized = 'error'.pluralize(model.errors.count) + headline = "The form contains the following #{pluralized}:" + + content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do + content_tag(:h4, headline) << + content_tag(:ul) do + model.errors.full_messages. + map { |msg| content_tag(:li, msg) }. + join. + html_safe + end + end + end +end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 2f760af02fd..3a45205563e 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -116,29 +116,6 @@ module GitlabMarkdownHelper end end - MARKDOWN_TIPS = [ - "End a line with two or more spaces for a line-break, or soft-return", - "Inline code can be denoted by `surrounding it with backticks`", - "Blocks of code can be denoted by three backticks ``` or four leading spaces", - "Emoji can be added by :emoji_name:, for example :thumbsup:", - "Notify other participants using @user_name", - "Notify a specific group using @group_name", - "Notify the entire team using @all", - "Reference an issue using a hash, for example issue #123", - "Reference a merge request using an exclamation point, for example MR !123", - "Italicize words or phrases using *asterisks* or _underscores_", - "Bold words or phrases using **double asterisks** or __double underscores__", - "Strikethrough words or phrases using ~~two tildes~~", - "Make a bulleted list using + pluses, - minuses, or * asterisks", - "Denote blockquotes using > at the beginning of a line", - "Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___" - ].freeze - - # Returns a random markdown tip for use as a textarea placeholder - def random_markdown_tip - MARKDOWN_TIPS.sample - end - private # Return +text+, truncated to +max_chars+ characters, excluding any HTML diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 24b90fef4fe..4cb8adcebad 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -52,6 +52,7 @@ module IssuesHelper def milestone_options(object) milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a + milestones.unshift(object.milestone) if object.milestone.present? && object.milestone.closed? milestones.unshift(Milestone::None) options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id) @@ -115,17 +116,32 @@ module IssuesHelper icon('eye-slash') if issue.confidential? end - def emoji_icon(name, unicode = nil, aliases = []) + def emoji_icon(name, unicode = nil, aliases = [], sprite: true) unicode ||= Emoji.emoji_filename(name) rescue "" - content_tag :div, "", - class: "icon emoji-icon emoji-#{unicode}", - title: name, - data: { - aliases: aliases.join(' '), - emoji: name, - unicode_name: unicode - } + data = { + aliases: aliases.join(" "), + emoji: name, + unicode_name: unicode + } + + if sprite + # Emoji icons for the emoji menu, these use a spritesheet. + content_tag :div, "", + class: "icon emoji-icon emoji-#{unicode}", + title: name, + data: data + else + # Emoji icons displayed separately, used for the awards already given + # to an issue or merge request. + content_tag :img, "", + class: "icon emoji", + title: name, + height: "20px", + width: "20px", + src: url_to_image("#{unicode}.png"), + data: data + end end def emoji_author_list(notes, current_user) diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index faba418c4db..94c6b548ecd 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -3,8 +3,16 @@ module NamespacesHelper groups = current_user.owned_groups + current_user.masters_groups users = [current_user.namespace] - group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ] - users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ] + data_attr_group = { 'data-options-parent' => 'groups' } + data_attr_users = { 'data-options-parent' => 'users' } + + group_opts = [ + "Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.path : g.human_name, g.id, data_attr_group] } + ] + + users_opts = [ + "Users", users.sort_by(&:human_name).map { |u| [display_path ? u.path : u.human_name, u.id, data_attr_users] } + ] options = [] options << group_opts diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 698f90cb27a..95072b5373f 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -69,10 +69,7 @@ module NotesHelper line_type: line_type } - button_tag class: 'btn btn-nr reply-btn js-discussion-reply-button', - data: data, title: 'Add a reply' do - link_text = icon('comment') - link_text << ' Reply' - end + button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', + data: data, title: 'Add a reply' end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 499c655d2bf..54ab9179efc 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -1,48 +1,48 @@ module NotificationsHelper include IconsHelper - def notification_icon(notification) - if notification.disabled? - icon('volume-off', class: 'ns-mute') - elsif notification.participating? - icon('volume-down', class: 'ns-part') - elsif notification.watch? - icon('volume-up', class: 'ns-watch') - else - icon('circle-o', class: 'ns-default') + def notification_icon_class(level) + case level.to_sym + when :disabled + 'microphone-slash' + when :participating + 'volume-up' + when :watch + 'eye' + when :mention + 'at' + when :global + 'globe' end end - def notification_list_item(notification_level, user_membership) - case notification_level - when Notification::N_DISABLED - update_notification_link(Notification::N_DISABLED, user_membership, 'Disabled', 'microphone-slash') - when Notification::N_PARTICIPATING - update_notification_link(Notification::N_PARTICIPATING, user_membership, 'Participate', 'volume-up') - when Notification::N_WATCH - update_notification_link(Notification::N_WATCH, user_membership, 'Watch', 'eye') - when Notification::N_MENTION - update_notification_link(Notification::N_MENTION, user_membership, 'On mention', 'at') - when Notification::N_GLOBAL - update_notification_link(Notification::N_GLOBAL, user_membership, 'Global', 'globe') - else - # do nothing - end + def notification_icon(level, text = nil) + icon("#{notification_icon_class(level)} fw", text: text) end - def update_notification_link(notification_level, user_membership, title, icon) - content_tag(:li, class: active_level_for(user_membership, notification_level)) do - link_to '#', class: 'update-notification', data: { notification_level: notification_level } do - icon("#{icon} fw", text: title) - end + def notification_title(level) + case level.to_sym + when :participating + 'Participate' + when :mention + 'On mention' + else + level.to_s.titlecase end end - def notification_label(user_membership) - Notification.new(user_membership).to_s - end + def notification_list_item(level, setting) + title = notification_title(level) + + data = { + notification_level: level, + notification_title: title + } - def active_level_for(user_membership, level) - 'active' if user_membership.notification_level == level + content_tag(:li, class: ('active' if setting.level == level)) do + link_to '#', class: 'update-notification', data: data do + notification_icon(level, title) + end + end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4e4c6e301d5..7e00aacceaa 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -65,21 +65,14 @@ module ProjectsHelper link_to(simple_sanitize(owner.name), user_path(owner)) end - project_link = link_to project_path(project), { class: "project-item-select-holder" } do - link_output = simple_sanitize(project.name) + project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } - if current_user - link_output += project_select_tag :project_path, - class: "project-item-select js-projects-dropdown", - data: { include_groups: false, order_by: 'last_activity_at' } - end - - link_output + if current_user + project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) end - project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user - full_title = namespace_link + ' / ' + project_link - full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name + full_title = "#{namespace_link} / #{project_link}".html_safe + full_title << ' · '.html_safe << link_to(simple_sanitize(name), url) if name full_title end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index edc5686cf08..2f066682180 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -20,6 +20,8 @@ module TodosHelper end def todo_target_path(todo) + return unless todo.target.present? + anchor = dom_id(todo.note) if todo.note.present? if todo.for_commit? diff --git a/app/models/commit.rb b/app/models/commit.rb index d09876a07d9..d1f07ccd55c 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -150,13 +150,11 @@ class Commit end def hook_attrs(with_changed_files: false) - path_with_namespace = project.path_with_namespace - data = { id: id, message: safe_message, timestamp: committed_date.xmlschema, - url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}", + url: Gitlab::UrlBuilder.build(self), author: { name: author_name, email: author_email diff --git a/app/models/concerns/notifiable.rb b/app/models/concerns/notifiable.rb deleted file mode 100644 index d7dcd97911d..00000000000 --- a/app/models/concerns/notifiable.rb +++ /dev/null @@ -1,15 +0,0 @@ -# == Notifiable concern -# -# Contains notification functionality -# -module Notifiable - extend ActiveSupport::Concern - - included do - validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true - end - - def notification - @notification ||= Notification.new(self) - end -end diff --git a/app/models/group.rb b/app/models/group.rb index b332601c59b..9a04ac70d35 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -27,6 +27,7 @@ class Group < Namespace has_many :users, through: :group_members has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: :project + has_many :notification_settings, dependent: :destroy, as: :source validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :visibility_level_allowed_by_projects diff --git a/app/models/issue.rb b/app/models/issue.rb index e064b0f8b95..3f188e04770 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -106,7 +106,7 @@ class Issue < ActiveRecord::Base def related_branches project.repository.branch_names.select do |branch| - branch.end_with?("-#{iid}") + branch =~ /\A#{iid}-(?!\d+-stable)/i end end @@ -151,7 +151,7 @@ class Issue < ActiveRecord::Base end def to_branch_name - "#{title.parameterize}-#{iid}" + "#{iid}-#{title.parameterize}" end def can_be_worked_on?(current_user) diff --git a/app/models/member.rb b/app/models/member.rb index ca08007b7eb..60efafef211 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -19,7 +19,6 @@ class Member < ActiveRecord::Base include Sortable - include Notifiable include Gitlab::Access attr_accessor :raw_invite_token @@ -56,12 +55,15 @@ class Member < ActiveRecord::Base before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } after_create :send_invite, if: :invite? + after_create :create_notification_setting, unless: :invite? after_create :post_create_hook, unless: :invite? after_update :post_update_hook, unless: :invite? after_destroy :post_destroy_hook, unless: :invite? delegate :name, :username, :email, to: :user, prefix: true + default_value_for :notification_level, NotificationSetting.levels[:global] + class << self def find_by_invite_token(invite_token) invite_token = Devise.token_generator.digest(self, :invite_token, invite_token) @@ -160,6 +162,14 @@ class Member < ActiveRecord::Base send_invite end + def create_notification_setting + user.notification_settings.find_or_create_for(source) + end + + def notification_setting + @notification_setting ||= user.notification_settings_for(source) + end + private def send_invite diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 65d2ea00570..9fb474a1a93 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -24,7 +24,6 @@ class GroupMember < Member # Make sure group member points only to group as it source default_value_for :source_type, SOURCE_TYPE - default_value_for :notification_level, Notification::N_GLOBAL validates_format_of :source_type, with: /\ANamespace\z/ default_scope { where(source_type: SOURCE_TYPE) } diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 560d1690e14..07ddb02ae9d 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -27,7 +27,6 @@ class ProjectMember < Member # Make sure project member points only to project as it source default_value_for :source_type, SOURCE_TYPE - default_value_for :notification_level, Notification::N_GLOBAL validates_format_of :source_type, with: /\AProject\z/ default_scope { where(source_type: SOURCE_TYPE) } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bf185cb5dd8..e410febdfff 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base validates :target_project, presence: true validates :target_branch, presence: true validates :merge_user, presence: true, if: :merge_when_build_succeeds? - validate :validate_branches + validate :validate_branches, unless: :allow_broken validate :validate_fork scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } @@ -218,7 +218,7 @@ class MergeRequest < ActiveRecord::Base end if opened? || reopened? - similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened + similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id if similar_mrs.any? errors.add :validate_branches, @@ -345,7 +345,7 @@ class MergeRequest < ActiveRecord::Base def hook_attrs attrs = { - source: source_project.hook_attrs, + source: source_project.try(:hook_attrs), target: target_project.hook_attrs, last_commit: nil, work_in_progress: work_in_progress? diff --git a/app/models/notification.rb b/app/models/notification.rb deleted file mode 100644 index 171b8df45c2..00000000000 --- a/app/models/notification.rb +++ /dev/null @@ -1,77 +0,0 @@ -class Notification - # - # Notification levels - # - N_DISABLED = 0 - N_PARTICIPATING = 1 - N_WATCH = 2 - N_GLOBAL = 3 - N_MENTION = 4 - - attr_accessor :target - - class << self - def notification_levels - [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH] - end - - def options_with_labels - { - disabled: N_DISABLED, - participating: N_PARTICIPATING, - watch: N_WATCH, - mention: N_MENTION, - global: N_GLOBAL - } - end - - def project_notification_levels - [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH, N_GLOBAL] - end - end - - def initialize(target) - @target = target - end - - def disabled? - target.notification_level == N_DISABLED - end - - def participating? - target.notification_level == N_PARTICIPATING - end - - def watch? - target.notification_level == N_WATCH - end - - def global? - target.notification_level == N_GLOBAL - end - - def mention? - target.notification_level == N_MENTION - end - - def level - target.notification_level - end - - def to_s - case level - when N_DISABLED - 'Disabled' - when N_PARTICIPATING - 'Participating' - when N_WATCH - 'Watching' - when N_MENTION - 'On mention' - when N_GLOBAL - 'Global' - else - # do nothing - end - end -end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb new file mode 100644 index 00000000000..5001738f411 --- /dev/null +++ b/app/models/notification_setting.rb @@ -0,0 +1,28 @@ +class NotificationSetting < ActiveRecord::Base + enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 } + + default_value_for :level, NotificationSetting.levels[:global] + + belongs_to :user + belongs_to :source, polymorphic: true + + validates :user, presence: true + validates :source, presence: true + validates :level, presence: true + validates :user_id, uniqueness: { scope: [:source_type, :source_id], + message: "already exists in source", + allow_nil: true } + + scope :for_groups, -> { where(source_type: 'Namespace') } + scope :for_projects, -> { where(source_type: 'Project') } + + def self.find_or_create_for(source) + setting = find_or_initialize_by(source: source) + + unless setting.persisted? + setting.save + end + + setting + end +end diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb new file mode 100644 index 00000000000..c78c7f4aa0e --- /dev/null +++ b/app/models/oauth_access_token.rb @@ -0,0 +1,19 @@ +# == Schema Information +# +# Table name: oauth_access_tokens +# +# id :integer not null, primary key +# resource_owner_id :integer +# application_id :integer +# token :string not null +# refresh_token :string +# expires_in :integer +# revoked_at :datetime +# created_at :datetime not null +# scopes :string +# + +class OauthAccessToken < ActiveRecord::Base + belongs_to :resource_owner, class_name: 'User' + belongs_to :application, class_name: 'Doorkeeper::Application' +end diff --git a/app/models/project.rb b/app/models/project.rb index 6304699386d..b82825906f3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -154,6 +154,7 @@ class Project < ActiveRecord::Base has_many :project_group_links, dependent: :destroy has_many :invited_groups, through: :project_group_links, source: :group has_many :todos, dependent: :destroy + has_many :notification_settings, dependent: :destroy, as: :source has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" @@ -388,9 +389,15 @@ class Project < ActiveRecord::Base def add_import_job if forked? - RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path) + job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path) else - RepositoryImportWorker.perform_async(self.id) + job_id = RepositoryImportWorker.perform_async(self.id) + end + + if job_id + Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}" + else + Rails.logger.error "Import job failed to start for #{path_with_namespace}" end end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 9e7f642180e..060062aaf7a 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -82,17 +82,17 @@ class BambooService < CiService end def build_info(sha) - url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}") + url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s if username.blank? && password.blank? - @response = HTTParty.get(parsed_url.to_s, verify: false) + @response = HTTParty.get(url, verify: false) else - get_url = "#{url}&os_authType=basic" + url << '&os_authType=basic' auth = { - username: username, - password: password, + username: username, + password: password } - @response = HTTParty.get(get_url, verify: false, basic_auth: auth) + @response = HTTParty.get(url, verify: false, basic_auth: auth) end end @@ -101,11 +101,11 @@ class BambooService < CiService if @response.code != 200 || @response['results']['results']['size'] == '0' # If actual build link can't be determined, send user to build summary page. - "#{bamboo_url}/browse/#{build_key}" + URI.join(bamboo_url, "/browse/#{build_key}").to_s else # If actual build link is available, go to build result page. result_key = @response['results']['results']['result']['planResultKey']['key'] - "#{bamboo_url}/browse/#{result_key}" + URI.join(bamboo_url, "/browse/#{result_key}").to_s end end @@ -134,7 +134,7 @@ class BambooService < CiService return unless supported_events.include?(data[:object_kind]) # Bamboo requires a GET and does not take any data. - self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", - verify: false) + url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s + self.class.get(url, verify: false) end end diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index f9f04838766..6ab6d7417b7 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -23,7 +23,7 @@ class BuildsEmailService < Service prop_accessor :recipients boolean_accessor :add_pusher boolean_accessor :notify_only_broken_builds - validates :recipients, presence: true, if: :activated? + validates :recipients, presence: true, if: ->(s) { s.activated? && !s.add_pusher? } def initialize_properties if properties.nil? @@ -87,10 +87,14 @@ class BuildsEmailService < Service end def all_recipients(data) - all_recipients = recipients.split(',').compact.reject(&:blank?) + all_recipients = [] + + unless recipients.blank? + all_recipients += recipients.split(',').compact.reject(&:blank?) + end if add_pusher? && data[:user][:email] - all_recipients << "#{data[:user][:email]}" + all_recipients << data[:user][:email] end all_recipients diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index b8e9416131a..8dceee5e2c5 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -85,13 +85,15 @@ class TeamcityService < CiService end def build_info(sha) - url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\ - "branch:unspecified:any,number:#{sha}") + url = URI.join( + teamcity_url, + "/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}" + ).to_s auth = { username: username, - password: password, + password: password } - @response = HTTParty.get("#{url}", verify: false, basic_auth: auth) + @response = HTTParty.get(url, verify: false, basic_auth: auth) end def build_page(sha, ref) @@ -100,12 +102,14 @@ class TeamcityService < CiService if @response.code != 200 # If actual build link can't be determined, # send user to build summary page. - "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}" + URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s else # If actual build link is available, go to build result page. built_id = @response['build']['id'] - "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\ - "&buildTypeId=#{build_type}" + URI.join( + teamcity_url, + "/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" + ).to_s end end @@ -140,12 +144,13 @@ class TeamcityService < CiService branch = Gitlab::Git.ref_name(data[:ref]) - self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", - body: "<build branchName=\"#{branch}\">"\ - "<buildType id=\"#{build_type}\"/>"\ - '</build>', - headers: { 'Content-type' => 'application/xml' }, - basic_auth: auth - ) + self.class.post( + URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s, + body: "<build branchName=\"#{branch}\">"\ + "<buildType id=\"#{build_type}\"/>"\ + '</build>', + headers: { 'Content-type' => 'application/xml' }, + basic_auth: auth + ) end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 8dead3a5884..0b2289cfa39 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -253,6 +253,8 @@ class Repository # This ensures this particular cache is flushed after the first commit to a # new repository. expire_emptiness_caches if empty? + expire_branch_count_cache + expire_tag_count_cache end def expire_branch_cache(branch_name = nil) @@ -896,9 +898,9 @@ class Repository end def main_language - unless empty? - Linguist::Repository.new(rugged, rugged.head.target_id).language - end + return if empty? || rugged.head_unborn? + + Linguist::Repository.new(rugged, rugged.head.target_id).language end def avatar diff --git a/app/models/user.rb b/app/models/user.rb index 2b0bee2099f..031315debd7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -143,6 +143,7 @@ class User < ActiveRecord::Base has_many :spam_logs, dependent: :destroy has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :todos, dependent: :destroy + has_many :notification_settings, dependent: :destroy # # Validations @@ -157,7 +158,7 @@ class User < ActiveRecord::Base presence: true, uniqueness: { case_sensitive: false } - validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true + validates :notification_level, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :unique_email, if: ->(user) { user.email_changed? } @@ -190,6 +191,13 @@ class User < ActiveRecord::Base # Note: When adding an option, it MUST go on the end of the array. enum project_view: [:readme, :activity, :files] + # Notification level + # Note: When adding an option, it MUST go on the end of the array. + # + # TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813 + # Because user.notification_disabled? is much better than user.disabled? + enum notification_level: [:disabled, :participating, :watch, :global, :mention] + alias_attribute :private_token, :authentication_token delegate :path, to: :namespace, allow_nil: true, prefix: true @@ -349,10 +357,6 @@ class User < ActiveRecord::Base "#{self.class.reference_prefix}#{username}" end - def notification - @notification ||= Notification.new(self) - end - def generate_password if self.force_random_password self.password = self.password_confirmation = Devise.friendly_token.first(8) @@ -827,6 +831,10 @@ class User < ActiveRecord::Base end end + def notification_settings_for(source) + notification_settings.find_or_initialize_by(source: source) + end + private def projects_union diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 770f32de944..772f5c5fffa 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -3,7 +3,7 @@ module Issues def hook_data(issue, action) issue_data = issue.to_hook_data(current_user) - issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) + issue_url = Gitlab::UrlBuilder.build(issue) issue_data[:object_attributes].merge!(url: issue_url, action: action) issue_data end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index ac5b58db862..e6837a18696 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -20,8 +20,7 @@ module MergeRequests def hook_data(merge_request, action) hook_data = merge_request.to_hook_data(current_user) - merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id) - hook_data[:object_attributes][:url] = merge_request_url + hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request) hook_data[:object_attributes][:action] = action hook_data end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 6e9152e444e..fa34753c4fd 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -51,7 +51,7 @@ module MergeRequests # be interpreted as the use wants to close that issue on this project # Pattern example: 112-fix-mep-mep # Will lead to appending `Closes #112` to the description - if match = merge_request.source_branch.match(/-(\d+)\z/) + if match = merge_request.source_branch.match(/\A(\d+)-/) iid = match[1] closes_issue = "Closes ##{iid}" diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb index b8e08c9f1eb..3b90399af64 100644 --- a/app/services/milestones/create_service.rb +++ b/app/services/milestones/create_service.rb @@ -3,7 +3,7 @@ module Milestones def execute milestone = project.milestones.new(params) - if milestone.save + if milestone.save! event_service.open_milestone(milestone, current_user) end diff --git a/app/services/notes/delete_service.rb b/app/services/notes/delete_service.rb new file mode 100644 index 00000000000..7f1b30ec84e --- /dev/null +++ b/app/services/notes/delete_service.rb @@ -0,0 +1,8 @@ +module Notes + class DeleteService < BaseService + def execute(note) + note.destroy + note.reset_events_cache + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index eff0d96f93d..42ec1ac9e1a 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -253,8 +253,8 @@ class NotificationService def project_watchers(project) project_members = project_member_notification(project) - users_with_project_level_global = project_member_notification(project, Notification::N_GLOBAL) - users_with_group_level_global = group_member_notification(project, Notification::N_GLOBAL) + users_with_project_level_global = project_member_notification(project, :global) + users_with_group_level_global = group_member_notification(project, :global) users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq) users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users) @@ -264,18 +264,16 @@ class NotificationService end def project_member_notification(project, notification_level=nil) - project_members = project.project_members - if notification_level - project_members.where(notification_level: notification_level).pluck(:user_id) + project.notification_settings.where(level: NotificationSetting.levels[notification_level]).pluck(:user_id) else - project_members.pluck(:user_id) + project.notification_settings.pluck(:user_id) end end def group_member_notification(project, notification_level) if project.group - project.group.group_members.where(notification_level: notification_level).pluck(:user_id) + project.group.notification_settings.where(level: NotificationSetting.levels[notification_level]).pluck(:user_id) else [] end @@ -284,13 +282,13 @@ class NotificationService def users_with_global_level_watch(ids) User.where( id: ids, - notification_level: Notification::N_WATCH + notification_level: NotificationSetting.levels[:watch] ).pluck(:id) end # Build a list of users based on project notifcation settings def select_project_member_setting(project, global_setting, users_global_level_watch) - users = project_member_notification(project, Notification::N_WATCH) + users = project_member_notification(project, :watch) # If project setting is global, add to watch list if global setting is watch global_setting.each do |user_id| @@ -304,7 +302,7 @@ class NotificationService # Build a list of users based on group notification settings def select_group_member_setting(project, project_members, global_setting, users_global_level_watch) - uids = group_member_notification(project, Notification::N_WATCH) + uids = group_member_notification(project, :watch) # Group setting is watch, add to users list if user is not project member users = [] @@ -331,40 +329,46 @@ class NotificationService # Remove users with disabled notifications from array # Also remove duplications and nil recipients def reject_muted_users(users, project = nil) - reject_users(users, :disabled?, project) + reject_users(users, :disabled, project) end # Remove users with notification level 'Mentioned' def reject_mention_users(users, project = nil) - reject_users(users, :mention?, project) + reject_users(users, :mention, project) end - # Reject users which method_name from notification object returns true. + # Reject users which has certain notification level # # Example: - # reject_users(users, :watch?, project) + # reject_users(users, :watch, project) # - def reject_users(users, method_name, project = nil) + def reject_users(users, level, project = nil) + level = level.to_s + + unless NotificationSetting.levels.keys.include?(level) + raise 'Invalid notification level' + end + users = users.to_a.compact.uniq users = users.reject(&:blocked?) users.reject do |user| - next user.notification.send(method_name) unless project + next user.notification_level == level unless project - member = project.project_members.find_by(user_id: user.id) + setting = user.notification_settings_for(project) - if !member && project.group - member = project.group.group_members.find_by(user_id: user.id) + if !setting && project.group + setting = user.notification_settings_for(project.group) end - # reject users who globally set mention notification and has no membership - next user.notification.send(method_name) unless member + # reject users who globally set mention notification and has no setting per project/group + next user.notification_level == level unless setting # reject users who set mention notification in project - next true if member.notification.send(method_name) + next true if setting.level == level - # reject users who have N_MENTION in project and disabled in global settings - member.notification.global? && user.notification.send(method_name) + # reject users who have mention level in project and disabled in global settings + setting.global? && user.notification_level == level end end diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index a0973c5d260..3b7c36f0908 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -26,7 +26,9 @@ module Projects GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace) ensure - @project.update_column(:pushes_since_gc, 0) + Gitlab::Metrics.measure(:reset_pushes_since_gc) do + @project.update_column(:pushes_since_gc, 0) + end end def needed? @@ -34,14 +36,18 @@ module Projects end def increment! - @project.increment!(:pushes_since_gc) + Gitlab::Metrics.measure(:increment_pushes_since_gc) do + @project.increment!(:pushes_since_gc) + end end private def try_obtain_lease - lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) - lease.try_obtain + Gitlab::Metrics.measure(:obtain_housekeeping_lease) do + lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) + lease.try_obtain + end end end end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 2e734654466..79a27f4af7e 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -34,8 +34,9 @@ module Projects raise TransferError.new("Project with same path in target namespace already exists") end - # Apply new namespace id + # Apply new namespace id and visibility level project.namespace = new_namespace + project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group? project.save! # Notifications @@ -56,7 +57,7 @@ module Projects Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) project.old_path_with_namespace = old_path - + SystemHooksService.new.execute_hooks_for(project, :transfer) true end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 658b086496f..82a0e2fd1f5 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -222,7 +222,7 @@ class SystemNoteService # Called when a branch is created from the 'new branch' button on a issue # Example note text: # - # "Started branch `issue-branch-button-201`" + # "Started branch `201-issue-branch-button`" def self.new_issue_branch(issue, project, author, branch) h = Gitlab::Routing.url_helpers link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml index 3bc1b24b5e2..06be1a53318 100644 --- a/app/views/abuse_reports/new.html.haml +++ b/app/views/abuse_reports/new.html.haml @@ -3,11 +3,9 @@ %p Please use this form to report users who create spam issues, comments or behave inappropriately. %hr = form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f| + = form_errors(@abuse_report) + = f.hidden_field :user_id - - if @abuse_report.errors.any? - .alert.alert-danger - - @abuse_report.errors.full_messages.each do |msg| - %p= msg .form-group = f.label :user_id, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 6f325914d14..d88f3ad314d 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -1,8 +1,5 @@ = form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| - - if @appearance.errors.any? - .alert.alert-danger - - @appearance.errors.full_messages.each do |msg| - %p= msg + = form_errors(@appearance) %fieldset.sign-in %legend diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index de86dacbb12..a8cca1a81cb 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -1,9 +1,5 @@ = form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| - - if @application_setting.errors.any? - #error_explanation - .alert.alert-danger - - @application_setting.errors.full_messages.each do |msg| - %p= msg + = form_errors(@application_setting) %fieldset %legend Visibility and Access Controls diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml index e18f7b499dd..4aacbb8cd77 100644 --- a/app/views/admin/applications/_form.html.haml +++ b/app/views/admin/applications/_form.html.haml @@ -1,9 +1,6 @@ = form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f| - - if application.errors.any? - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - - application.errors.full_messages.each do |msg| - %p= msg + = form_errors(application) + = content_tag :div, class: 'form-group' do = f.label :name, class: 'col-sm-2 control-label' .col-sm-10 diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index b748460a9f7..6b157abf842 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -4,10 +4,8 @@ = render_broadcast_message(@broadcast_message.message.presence || "Your message here") = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| - -if @broadcast_message.errors.any? - .alert.alert-danger - - @broadcast_message.errors.full_messages.each do |msg| - %p= msg + = form_errors(@broadcast_message) + .form-group = f.label :message, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index 5b46b3222a9..15aa059c93d 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -4,11 +4,7 @@ %div = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| - -if @deploy_key.errors.any? - .alert.alert-danger - %ul - - @deploy_key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@deploy_key) .form-group = f.label :title, class: "control-label" diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 7f2b1cd235d..0cc405401cf 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -1,8 +1,5 @@ = form_for [:admin, @group], html: { class: "form-horizontal" } do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first - + = form_errors(@group) = render 'shared/group_form', f: f .form-group.group-description-holder diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 53b3cd04c68..ad952052f25 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -10,10 +10,8 @@ = form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f| - -if @hook.errors.any? - .alert.alert-danger - - @hook.errors.full_messages.each do |msg| - %p= msg + = form_errors(@hook) + .form-group = f.label :url, "URL:", class: 'control-label' .col-sm-10 diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml index 3a788558226..112a201fafa 100644 --- a/app/views/admin/identities/_form.html.haml +++ b/app/views/admin/identities/_form.html.haml @@ -1,9 +1,5 @@ = form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f| - - if @identity.errors.any? - #error_explanation - .alert.alert-danger - - @identity.errors.full_messages.each do |msg| - %p= msg + = form_errors(@identity) .form-group = f.label :provider, class: 'control-label' diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml index 8c6b389bf15..448aa953548 100644 --- a/app/views/admin/labels/_form.html.haml +++ b/app/views/admin/labels/_form.html.haml @@ -1,11 +1,5 @@ = form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| - -if @label.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - @label.errors.full_messages.each do |msg| - %span= msg - %br + = form_errors(@label) .form-group = f.label :title, class: 'control-label' diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index d2527ede995..b05fdbd5552 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -1,10 +1,6 @@ .user_new = form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f| - -if @user.errors.any? - #error_explanation - .alert.alert-danger - - @user.errors.full_messages.each do |msg| - %p= msg + = form_errors(@user) %fieldset %legend Account diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml index 906b0676150..5c98265727a 100644 --- a/app/views/doorkeeper/applications/_form.html.haml +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -1,9 +1,5 @@ = form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f| - - if application.errors.any? - .alert.alert-danger - %ul - - application.errors.full_messages.each do |msg| - %li= msg + = form_errors(application) .form-group = f.label :name, class: 'label-light' diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 55f4a6f287d..0aff79749ef 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -68,7 +68,7 @@ %td= app.name %td= token.created_at %td= token.scopes - %td= render 'delete_form', application: app + %td= render 'doorkeeper/authorized_applications/delete_form', application: app - @authorized_anonymous_tokens.each do |token| %tr %td diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index ea5a0358392..a698cbbe9db 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -5,9 +5,7 @@ Group settings .panel-body = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first + = form_errors(@group) = render 'shared/group_form', f: f .form-group diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index a8e1ed77da9..7d9d27ae1fc 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -8,8 +8,16 @@ This will create milestone in every selected project %hr -= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f| += form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f| .row + - if @milestone.errors.any? + #error_explanation + .alert.alert-danger + %ul + - @milestone.errors.full_messages.each do |msg| + %li + = msg + .col-md-6 .form-group = f.label :title, "Title", class: "control-label" @@ -19,7 +27,7 @@ = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' .clearfix .error-alert .form-group diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 30ab8aeba13..2b8bc269e64 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -6,10 +6,7 @@ %hr = form_for @group, html: { class: 'group-form form-horizontal' } do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first - + = form_errors(@group) = render 'shared/group_form', f: f, autofocus: true .form-group.group-description-holder diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml new file mode 100644 index 00000000000..2ed51d87ca1 --- /dev/null +++ b/app/views/layouts/_collapse_button.html.haml @@ -0,0 +1,4 @@ +- if nav_menu_collapsed? + = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close" +- else + = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close" diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 9be36273c7d..c799e9c588d 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,7 +1,5 @@ .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } = render "layouts/broadcast" - .expand-nav - = link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar" .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .header-logo %a#logo @@ -10,19 +8,15 @@ .gitlab-text-container %h3 GitLab - - primary_sidebar = current_user ? 'dashboard' : 'explore' - - - if defined?(sidebar) && sidebar && sidebar != primary_sidebar - .complex-sidebar - .nav-primary - = render "layouts/nav/#{primary_sidebar}" - .nav-secondary - = render "layouts/nav/#{sidebar}" + - if defined?(sidebar) && sidebar + = render "layouts/nav/#{sidebar}" + - elsif current_user + = render 'layouts/nav/dashboard' - else - = render "layouts/nav/#{primary_sidebar}" + = render 'layouts/nav/explore' .collapse-nav - = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar" + = render partial: 'layouts/collapse_button' - if current_user = link_to current_user, class: 'sidebar-user', title: "Profile" do = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 0f3b8119379..3beb8ff7c0d 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -8,7 +8,7 @@ .navbar-collapse.collapse %ul.nav.navbar-nav %li.hidden-sm.hidden-xs - = render 'layouts/search' + = render 'layouts/search' unless current_controller?(:search) %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('search') @@ -45,6 +45,8 @@ %h1.title= title + = yield :header_content + = render 'shared/outdated_browser' - if @project && !@project.empty_repo? diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 22d1d4d8597..280a1b93729 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -95,7 +95,7 @@ Spam Logs %span.count= number_with_delimiter(SpamLog.count(:all)) - = nav_link(controller: :application_settings) do + = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = link_to admin_application_settings_path, title: 'Settings' do = icon('cogs fw') %span diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index d1a180e4299..5cef652da14 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -9,18 +9,18 @@ = icon('bell fw') %span Todos - %span.count= number_with_delimiter(todos_pending_count) + %span.count.todos-pending-count= number_with_delimiter(todos_pending_count) = nav_link(path: 'dashboard#activity') do = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do = icon('dashboard fw') %span Activity - = nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do + = nav_link(controller: :groups) do = link_to dashboard_groups_path, title: 'Groups' do = icon('group fw') %span Groups - = nav_link(path: 'dashboard#milestones') do + = nav_link(controller: :milestones) do = link_to dashboard_milestones_path, title: 'Milestones' do = icon('clock-o fw') %span @@ -48,6 +48,7 @@ %span Help + %li.separate-item = nav_link(controller: :profile) do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = icon('user fw') diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 0b7de9633ec..55940741dc0 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,4 +1,12 @@ %ul.nav.nav-sidebar + = nav_link do + = link_to root_path, title: 'Go to dashboard', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to dashboard + + %li.separate-item + = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do = icon('group fw') @@ -34,7 +42,7 @@ %span Members - if can?(current_user, :admin_group, @group) - = nav_link do + = nav_link(html_options: { class: "separate-item" }) do = link_to edit_group_path(@group), title: 'Settings' do = icon ('cogs fw') %span diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index cc119fd64e6..3b9d31a6fc5 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,4 +1,12 @@ %ul.nav.nav-sidebar + = nav_link do + = link_to root_path, title: 'Go to dashboard', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to dashboard + + %li.separate-item + = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do = icon('user fw') diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index d0f82b5f57f..86b46e8c75e 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,4 +1,19 @@ %ul.nav.nav-sidebar + - if @project.group + = nav_link do + = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to group + - else + = nav_link do + = link_to root_path, title: 'Go to dashboard', class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Go to dashboard + + %li.separate-item + = nav_link(path: 'projects#show', html_options: {class: 'home'}) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = icon('bookmark fw') @@ -98,7 +113,7 @@ Snippets - if project_nav_tab? :settings - = nav_link(html_options: {class: "#{project_tab_class}"}) do + = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do = link_to edit_project_path(@project), title: 'Settings' do = icon('cogs fw') %span diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index a7ef31acd3d..6dfe7fbdae8 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -17,4 +17,12 @@ - content_for :scripts_body do = render "layouts/init_auto_complete" if current_user +- content_for :header_content do + .js-dropdown-menu-projects + .dropdown-menu.dropdown-select.dropdown-menu-projects + = dropdown_title("Go to a project") + = dropdown_filter("Search your projects") + = dropdown_content + = dropdown_loading + = render template: "layouts/application" diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index 4d78215ed3c..b3ed59a1a4a 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -1,10 +1,6 @@ %div = form_for [:profile, @key], html: { class: 'js-requires-input' } do |f| - - if @key.errors.any? - .alert.alert-danger - %ul - - @key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@key) .form-group = f.label :key, class: 'label-light' diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml new file mode 100644 index 00000000000..89ae7ffda2b --- /dev/null +++ b/app/views/profiles/notifications/_group_settings.html.haml @@ -0,0 +1,13 @@ +%li.notification-list-item + %span.notification.fa.fa-holder.append-right-5 + - if setting.global? + = notification_icon(current_user.notification_level) + - else + = notification_icon(setting.level) + + %span.str-truncated + = link_to group.name, group_path(group) + + .pull-right + = form_for [group, setting], remote: true, html: { class: 'update-notifications' } do |f| + = f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit' diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml new file mode 100644 index 00000000000..17c097154da --- /dev/null +++ b/app/views/profiles/notifications/_project_settings.html.haml @@ -0,0 +1,13 @@ +%li.notification-list-item + %span.notification.fa.fa-holder.append-right-5 + - if setting.global? + = notification_icon(current_user.notification_level) + - else + = notification_icon(setting.level) + + %span.str-truncated + = link_to_project(project) + + .pull-right + = form_for [project.namespace.becomes(Namespace), project, setting], remote: true, html: { class: 'update-notifications' } do |f| + = f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit' diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml deleted file mode 100644 index d0d044136f6..00000000000 --- a/app/views/profiles/notifications/_settings.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -%li.notification-list-item - %span.notification.fa.fa-holder.append-right-5 - - if notification.global? - = notification_icon(@notification) - - else - = notification_icon(notification) - - %span.str-truncated - - if membership.kind_of? GroupMember - = link_to membership.group.name, membership.group - - else - = link_to_project(membership.project) - .pull-right - = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do - = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type') - = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id') - = select_tag :notification_level, options_for_select(Notification.options_with_labels, notification.level), class: 'form-control trigger-submit' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 3d15c0d932b..a2a505c082b 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,8 +1,8 @@ - page_title "Notifications" - header_title page_title, profile_notifications_path -= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| - -if @user.errors.any? +%div + - if @user.errors.any? %div.alert.alert-danger %ul - @user.errors.full_messages.each do |msg| @@ -20,56 +20,55 @@ .col-lg-9 %h5 Global notification settings - .form-group - = f.label :notification_email, class: "label-light" - = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" - .form-group - = f.label :notification_level, class: 'label-light' - .radio - = f.label :notification_level, value: Notification::N_DISABLED do - = f.radio_button :notification_level, Notification::N_DISABLED - .level-title - Disabled - %p You will not get any notifications via email - .radio - = f.label :notification_level, value: Notification::N_MENTION do - = f.radio_button :notification_level, Notification::N_MENTION - .level-title - On Mention - %p You will receive notifications only for comments in which you were @mentioned + = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| + .form-group + = f.label :notification_email, class: "label-light" + = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" + .form-group + = f.label :notification_level, class: 'label-light' + .radio + = f.label :notification_level, value: :disabled do + = f.radio_button :notification_level, :disabled + .level-title + Disabled + %p You will not get any notifications via email - .radio - = f.label :notification_level, value: Notification::N_PARTICIPATING do - = f.radio_button :notification_level, Notification::N_PARTICIPATING - .level-title - Participating - %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) + .radio + = f.label :notification_level, value: :mention do + = f.radio_button :notification_level, :mention + .level-title + On Mention + %p You will receive notifications only for comments in which you were @mentioned - .radio - = f.label :notification_level, value: Notification::N_WATCH do - = f.radio_button :notification_level, Notification::N_WATCH - .level-title - Watch - %p You will receive notifications for any activity + .radio + = f.label :notification_level, value: :participating do + = f.radio_button :notification_level, :participating + .level-title + Participating + %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) - .prepend-top-default - = f.submit 'Update settings', class: "btn btn-create" + .radio + = f.label :notification_level, value: :watch do + = f.radio_button :notification_level, :watch + .level-title + Watch + %p You will receive notifications for any activity + + .prepend-top-default + = f.submit 'Update settings', class: "btn btn-create" %hr -.col-lg-9.col-lg-push-3 - %h5 - Groups (#{@group_members.count}) - %div - %ul.bordered-list - - @group_members.each do |group_member| - - notification = Notification.new(group_member) - = render 'settings', type: 'group', membership: group_member, notification: notification - %h5 - Projects (#{@project_members.count}) - %p.account-well - To specify the notification level per project of a group you belong to, you need to be a member of the project itself, not only its group. - .append-bottom-default - %ul.bordered-list - - @project_members.each do |project_member| - - notification = Notification.new(project_member) - = render 'settings', type: 'project', membership: project_member, notification: notification + %h5 + Groups (#{@group_notifications.count}) + %div + %ul.bordered-list + - @group_notifications.each do |setting| + = render 'group_settings', setting: setting, group: setting.source + %h5 + Projects (#{@project_notifications.count}) + %p.account-well + To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there. + .append-bottom-default + %ul.bordered-list + - @project_notifications.each do |setting| + = render 'project_settings', setting: setting, project: setting.source diff --git a/app/views/profiles/notifications/update.js.haml b/app/views/profiles/notifications/update.js.haml deleted file mode 100644 index 84c6ab25599..00000000000 --- a/app/views/profiles/notifications/update.js.haml +++ /dev/null @@ -1,6 +0,0 @@ -- if @saved - :plain - new Flash("Notification settings saved", "notice") -- else - :plain - new Flash("Failed to save new settings", "alert") diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 44d758dceb3..5ac8a8b9d09 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -13,11 +13,8 @@ - unless @user.password_automatically_set? or recover your current one = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f| - -if @user.errors.any? - .alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) + - unless @user.password_automatically_set? .form-group = f.label :current_password, class: 'label-light' diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index d165f758c81..2eb9fac57c3 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -7,11 +7,8 @@ Please set a new password before proceeding. %br After a successful password update you will be redirected to login screen. - -if @user.errors.any? - .alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + + = form_errors(@user) - unless @user.password_automatically_set? .form-group diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index dcb3be9585d..f59d27f7ed0 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,9 +1,6 @@ = form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| - -if @user.errors.any? - %div.alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) + .row .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml index 7c8bb33ed7e..2dba22d3be6 100644 --- a/app/views/projects/_errors.html.haml +++ b/app/views/projects/_errors.html.haml @@ -1,4 +1 @@ -- if @project.errors.any? - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - = @project.errors.full_messages.first += form_errors(@project) diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 4920910fee1..7a78d61a611 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -2,13 +2,13 @@ .md-header %ul.nav-links %li.active - %a.js-md-write-button{ href: "#md-write-holder" } + %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 } Write %li - %a.js-md-preview-button{ href: "#md-preview-holder" } + %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } Preview %li.pull-right - %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button' } + %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 } Go full screen .md-write-holder diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index bddff5cdcbc..e1e35013968 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,8 +1,8 @@ .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' - if defined?(f) && f - = f.text_area attr, class: classes, placeholder: "Write a comment or drag your files here..." + = f.text_area attr, class: classes, placeholder: placeholder - else - = text_area_tag attr, nil, class: classes, placeholder: "Write a comment or drag your files here..." + = text_area_tag attr, nil, class: classes, placeholder: placeholder %a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" } = icon('compress') diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml index a3786c35a1f..c1e3e5b73a2 100644 --- a/app/views/projects/buttons/_notifications.html.haml +++ b/app/views/projects/buttons/_notifications.html.haml @@ -1,20 +1,11 @@ -- case @membership -- when ProjectMember - = form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do - = hidden_field_tag :notification_type, 'project' - = hidden_field_tag :notification_id, @membership.id - = hidden_field_tag :notification_level +- if @notification_setting + = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f| + = f.hidden_field :level %span.dropdown %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"} = icon('bell') - = notification_label(@membership) + = notification_title(@notification_setting.level) = icon('angle-down') %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - - Notification.project_notification_levels.each do |level| - = notification_list_item(level, @membership) - -- when GroupMember - .btn.disabled.notifications-btn.has-tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."} - = icon('bell') - = notification_label(@membership) - = icon('angle-down') + - NotificationSetting.levels.each do |level| + = notification_list_item(level.first, @notification_setting) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7f2903589a9..7da89231243 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -19,24 +19,17 @@ .pull-right - if ci_commit = render_ci_status(ci_commit) - = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" - .notes_count - - if note_count > 0 - %span.light - %i.fa.fa-comments - = note_count - - if commit.description? .commit-row-description.js-toggle-content %pre = preserve(markdown(escape_once(commit.description), pipeline: :single_line)) .commit-row-info + by = commit_author_link(commit, avatar: true, size: 24) - authored .committed_ago #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} = link_to_browse_code(project, commit) diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index 5e182af2669..f6565f85836 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,10 +1,6 @@ %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| - -if @key.errors.any? - .alert.alert-danger - %ul - - @key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@key) .form-group = f.label :title, class: "control-label" diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 2e1a37aa06d..eaab99973a4 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -3,7 +3,7 @@ - diff_files = safe_diff_files(diffs, diff_refs) -.content-block.oneline-block +.content-block.oneline-block.files-changed .inline-parallel-buttons .btn-group = inline_diff_btn diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 698ed02ea0e..83a8d7ae9bf 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -3,7 +3,7 @@ - if diff_file.diff.submodule? %span = icon('archive fw') - %strong + %span = submodule_link(blob, @commit.id, project.repository) - else = blob_icon blob.mode, blob.name @@ -11,13 +11,13 @@ = link_to "#diff-#{i}" do - if diff_file.renamed_file - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) - %strong.filename.old + .filename.old = old_path → - %strong.filename.new + .filename.new = new_path - else - %strong + %span = diff_file.new_path - if diff_file.deleted_file deleted @@ -28,8 +28,8 @@ .file-actions.hidden-xs - if blob_text_viewable?(blob) - = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do - = icon('comments') + = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do + = icon('comment') \ - if editable_diff?(diff_file) diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index d7c49068745..81948513e43 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -14,11 +14,11 @@ %td.new_line.diff-line-num %td.line_content.parallel.match= left[:text] - else - %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"} + %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"} = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code] - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(left[:line_code], 'old') - %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text]) + %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text]) - if right[:type] == 'new' - new_line_class = 'new' @@ -27,11 +27,11 @@ - new_line_class = nil - new_line_code = left[:line_code] - %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }} + %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }} = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(right[:line_code], 'new') - %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text]) + %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text]) - if @reply_allowed - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code]) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 6d872cd0b21..76a4f41193c 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -210,6 +210,7 @@ %li Be careful. Changing the project's namespace can have unintended side effects. %li You can only transfer the project to namespaces you manage. %li You will need to update your local repositories to point to the new location. + %li Project visibility level will be changed to match namespace rules when transfering to a group. .form-actions = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) } - else diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 67d016bd871..e39224d86c6 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -9,10 +9,8 @@ %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| - -if @hook.errors.any? - .alert.alert-danger - - @hook.errors.full_messages.each do |msg| - %p= msg + = form_errors(@hook) + .form-group = f.label :url, "URL", class: 'control-label' .col-sm-10 diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 33c48199ba5..7076f5db015 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-quick-submit js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form common-note-form js-quick-submit js-requires-input' } do |f| = render 'shared/issuable/form', f: f, issuable: @issue :javascript diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6fa059cbe68..5fe5ddc0819 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -64,9 +64,11 @@ = @issue.description = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') - .merge-requests - = render 'merge_requests' - = render 'related_branches' + #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)} + // This element is filled in using JavaScript. + + #related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)} + // This element is filled in using JavaScript. .content-block.content-block-small = render 'new_branch' diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index be7a0bb5628..aa143e54ffe 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,11 +1,5 @@ = form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| - -if @label.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - @label.errors.full_messages.each do |msg| - %span= msg - %br + = form_errors(@label) .form-group = f.label :title, class: 'control-label' diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 0612863296a..097a65969a6 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -1,24 +1,27 @@ %li{id: dom_id(label)} = render "shared/label_row", label: label - .pull-right - %strong.append-right-20 + .pull-info-right + %span.append-right-20 = link_to_label(label, type: :merge_request) do - = pluralize label.open_merge_requests_count, 'open merge request' + = pluralize label.open_merge_requests_count, 'merge request' - %strong.append-right-20 + %span.append-right-20 = link_to_label(label) do = pluralize label.open_issues_count(current_user), 'open issue' - if current_user .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .subscription-status{data: {status: label_subscription_status(label)}} - %button.btn.btn-sm.btn-info.subscribe-button + + %a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}} %span= label_subscription_toggle_button_text(label) - if can? current_user, :admin_label, @project - = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm' - = link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn action-buttons', data: {toggle: "tooltip"} do + %i.fa.fa-pencil-square-o + = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do + %i.fa.fa-trash-o - if current_user :javascript diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 3e4ab09c6d4..88525f4036a 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request :javascript diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 01dc7519bee..7d7c487e970 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -5,33 +5,74 @@ .hide.alert.alert-danger.mr-compare-errors .merge-request-branches.row .col-md-6 - .panel.panel-default + .panel.panel-default.panel-new-merge-request .panel-heading - %strong Source branch - .panel-body - = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true }) - - = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } }) + Source branch + .panel-body.clearfix + .merge-request-select.dropdown + = f.hidden_field :source_project_id + = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } + .dropdown-menu.dropdown-menu-selectable.dropdown-source-project + = dropdown_title("Select source project") + = dropdown_filter("Search projects") + = dropdown_content do + - is_active = f.object.source_project_id == @merge_request.source_project.id + %ul + %li + %a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } } + = @merge_request.source_project_path + .merge-request-select.dropdown + = f.hidden_field :source_branch + = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } + .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch + = dropdown_title("Select source branch") + = dropdown_filter("Search branches") + = dropdown_content do + %ul + - @merge_request.source_branches.each do |branch| + %li + %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } } + = branch .panel-footer - .mr_source_commit + = icon('spinner spin', class: 'js-source-loading') + %ul.list-unstyled.mr_source_commit .col-md-6 - .panel.panel-default + .panel.panel-default.panel-new-merge-request .panel-heading - %strong Target branch - .panel-body + Target branch + .panel-body.clearfix - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project] - = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true }) - - = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } }) + .merge-request-select.dropdown + = f.hidden_field :target_project_id + = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } + .dropdown-menu.dropdown-menu-selectable.dropdown-target-project + = dropdown_title("Select target project") + = dropdown_filter("Search projects") + = dropdown_content do + %ul + - projects.each do |project| + %li + %a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } } + = project.path_with_namespace + .merge-request-select.dropdown + = f.hidden_field :target_branch + = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } + .dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown + = dropdown_title("Select target branch") + = dropdown_filter("Search branches") + = dropdown_content do + %ul + - @merge_request.target_branches.each do |branch| + %li + %a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } } + = branch .panel-footer - .mr_target_commit + = icon('spinner spin', class: "js-target-loading") + %ul.list-unstyled.mr_target_commit - if @merge_request.errors.any? - .alert.alert-danger - - @merge_request.errors.full_messages.each do |msg| - %div= msg - + = form_errors(@merge_request) - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? .light-well.append-bottom-default .center @@ -45,40 +86,11 @@ and %span.label-branch #{@merge_request.target_branch} are the same. - - - .form-actions - = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" - -:javascript - var source_branch = $("#merge_request_source_branch") - , target_branch = $("#merge_request_target_branch") - , target_project = $("#merge_request_target_project_id"); - - $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() }); - $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() }); - - target_project.on("change", function() { - $.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() }); - }); - source_branch.on("change", function() { - $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() }); - $(".mr-compare-errors").fadeOut(); - $(".mr-compare-btn").enable(); - }); - target_branch.on("change", function() { - $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() }); - $(".mr-compare-errors").fadeOut(); - $(".mr-compare-btn").enable(); - }); - + = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" :javascript - $(".merge-request-form").on('submit', function () { - if ($("#merge_request_source_branch").val() === "" || $('#merge_request_target_branch').val() === "") { - $(".mr-compare-errors").html("You must select source and target branch to proceed"); - $(".mr-compare-errors").fadeIn(); - event.preventDefault(); - return; - } + new Compare({ + targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", + sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", + targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}" }); diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 9e59f7df71b..2f14a91e64f 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -10,7 +10,7 @@ %span.pull-right = link_to 'Change branches', mr_change_branches_path(@merge_request) %hr -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request = f.hidden_field :source_project_id = f.hidden_field :source_branch diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/branch_from.html.haml new file mode 100644 index 00000000000..4f90dde6fa8 --- /dev/null +++ b/app/views/projects/merge_requests/branch_from.html.haml @@ -0,0 +1 @@ += commit_to_html(@commit, @source_project, false) diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml deleted file mode 100644 index 9210798f39c..00000000000 --- a/app/views/projects/merge_requests/branch_from.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}"); - $('.js-timeago').timeago() diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/branch_to.html.haml new file mode 100644 index 00000000000..67a7a6bcec9 --- /dev/null +++ b/app/views/projects/merge_requests/branch_to.html.haml @@ -0,0 +1 @@ += commit_to_html(@commit, @target_project, false) diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml deleted file mode 100644 index 32fe2d535f3..00000000000 --- a/app/views/projects/merge_requests/branch_to.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}"); - $('.js-timeago').timeago() diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml new file mode 100644 index 00000000000..1b93188a10c --- /dev/null +++ b/app/views/projects/merge_requests/update_branches.html.haml @@ -0,0 +1,5 @@ +%ul + - @target_branches.each do |branch| + %li + %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } } + = branch diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml deleted file mode 100644 index ca21b3bc0de..00000000000 --- a/app/views/projects/merge_requests/update_branches.js.haml +++ /dev/null @@ -1,9 +0,0 @@ -:plain - $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}"); - - $('select.target_branch').select2({ - width: 'resolve', - dropdownAutoWidth: true - }); - - $(".mr_target_commit").html(""); diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 92d95358937..3c68d61c4b5 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -8,20 +8,22 @@ = render 'projects/merge_requests/widget/locked' :javascript - var merge_request_widget; var opts = { merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", check_enable: #{@merge_request.unchecked? ? "true" : "false"}, ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", ci_status: "", - ci_message: "Build {{status}} for \"{{title}}\"", + ci_message: { + normal: "Build {{status}} for \"{{title}}\"", + preparing: "{{status}} build for \"{{title}}\"" + }, ci_enable: #{@project.ci_service ? "true" : "false"}, + ci_title: { + preparing: "{{status}} build", + normal: "Build {{status}}" + }, builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}" }; - if(typeof merge_request_widget === 'undefined') { - merge_request_widget = new MergeRequestWidget(opts); - } else { - merge_request_widget.setOpts(opts); - } + merge_request_widget = new MergeRequestWidget(opts); diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 23f2bca7baf..687222fa92f 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,9 +1,5 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f| - -if @milestone.errors.any? - .alert.alert-danger - %ul - - @milestone.errors.full_messages.each do |msg| - %li= msg += form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input'} do |f| + = form_errors(@milestone) .row .col-md-6 .form-group @@ -14,7 +10,7 @@ = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' = render 'projects/notes/hints' .clearfix .error-alert diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 25233112132..a4c6094c69a 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -19,7 +19,7 @@ - if current_user.can_select_namespace? .input-group-addon = root_url - = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2', tabindex: 1} + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} .input-group-addon \/ - else diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml index 11f9859a90f..39be072855a 100644 --- a/app/views/projects/notes/_diff_notes_with_reply.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml @@ -3,9 +3,6 @@ - if !defined?(line) || line == note.diff_line %tr.notes_holder %td.notes_line{ colspan: 2 } - %span.discussion-notes-count - %i.fa.fa-comment - = notes.count %td.notes_content %ul.notes{ data: { discussion_id: note.discussion_id } } = render notes diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml index bb761ed2f94..f8aa5e2fa7d 100644 --- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -4,9 +4,6 @@ %tr.notes_holder - if note1 %td.notes_line.old - %span.btn.disabled - %i.fa.fa-comment - = notes_left.count %td.notes_content.parallel.old %ul.notes{ data: { discussion_id: note1.discussion_id } } = render notes_left @@ -19,9 +16,6 @@ - if note2 %td.notes_line.new - %span.btn.disabled - %i.fa.fa-comment - = notes_right.count %td.notes_content.parallel.new %ul.notes{ data: { discussion_id: note2.discussion_id } } = render notes_right diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index 23e4f93eab5..c87a3fadf72 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -2,7 +2,7 @@ = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f| = note_target_fields(note) = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do - = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field' + = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..." = render 'projects/notes/hints' .note-form-actions.clearfix diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index c446ecec2c3..d0ac380f216 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f| += form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form" }, authenticity_token: true do |f| = hidden_field_tag :view, diff_view = hidden_field_tag :line_type = note_target_fields(@note) @@ -8,7 +8,7 @@ = f.hidden_field :noteable_type = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text' + = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..." = render 'projects/notes/hints' .error-alert diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index a681d6dece4..03a44ca99c0 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -10,15 +10,15 @@ = "#{note.author.to_reference} commented" %a{ href: "##{dom_id(note)}" } = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') - - if note_editable?(note) - .note-actions - - access = note.project.team.human_max_access(note.author.id) - - if access - %span.note-role - = access + .note-actions + - access = note.project.team.human_max_access(note.author.id) + - if access + %span.note-role + = access + - if note_editable?(note) = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do - = icon('pencil-square-o') - = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do + = icon('pencil') + = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do = icon('trash-o') .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-text diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index cfd7e1534ca..653b02da4db 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -13,11 +13,7 @@ - if can? current_user, :admin_project, @project = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f| - -if @protected_branch.errors.any? - .alert.alert-danger - %ul - - @protected_branch.errors.full_messages.each do |msg| - %li= msg + = form_errors(@protected_branch) .form-group = f.label :name, "Branch", class: 'control-label' diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index c4a3f06ee06..6f0b32aa165 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -9,11 +9,11 @@ %strong #{@tag.name} .prepend-top-default - = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form js-quick-submit' }) do |f| + = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'projects/notes/hints' - .error-alert - .form-actions.prepend-top-default - = f.submit 'Save changes', class: 'btn btn-save' - = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" + .error-alert + .form-actions.prepend-top-default + = f.submit 'Save changes', class: 'btn btn-save' + = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 77c7c4d23de..b40a6e5cb2d 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -10,7 +10,7 @@ New Tag %hr -= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-quick-submit js-requires-input" do += form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal common-note-form tag-form js-quick-submit js-requires-input" do .form-group = label_tag :tag_name, nil, class: 'control-label' .col-sm-10 @@ -30,9 +30,9 @@ = label_tag :release_description, 'Release notes', class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', attr: :release_description, classes: 'description form-control' + = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'projects/notes/hints' - .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. + .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. .form-actions = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml index efe1e6f24c2..ca284b84d39 100644 --- a/app/views/projects/variables/show.html.haml +++ b/app/views/projects/variables/show.html.haml @@ -13,13 +13,7 @@ = nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f| - - if @project.errors.any? - #error_explanation - %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:" - .alert.alert-error - %ul - - @project.errors.full_messages.each do |msg| - %li= msg + = form_errors(@project) = f.fields_for :variables do |variable_form| .form-group diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index f0d1932e23c..797a1a59e9f 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,9 +1,5 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f| - -if @page.errors.any? - #error_explanation - .alert.alert-danger - - @page.errors.full_messages.each do |msg| - %p= msg += form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f| + = form_errors(@page) = f.hidden_field :title, value: @page.title .form-group @@ -15,7 +11,7 @@ = f.label :content, class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...' = render 'projects/notes/hints' .clearfix diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index 9544e3d3e17..d9400b1d9fa 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -1,5 +1,5 @@ - project = note.project -- note_url = Gitlab::UrlBuilder.new(:note).build(note.id) +- note_url = Gitlab::UrlBuilder.build(note) - noteable_identifier = note.noteable.try(:iid) || note.noteable.id .search-result-row %h5.note-search-caption.str-truncated diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index 7afbaeddee8..0a38327baa2 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -6,7 +6,7 @@ .commit-message-container .max-width-marker = text_area_tag 'commit_message', - (params[:commit_message] || local_assigns[:text]), + (params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]), class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder], required: true, rows: (local_assigns[:rows] || 3), id: "commit_message-#{nonce}" diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 4b47b0291be..b38c5e18efb 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -1,4 +1,5 @@ %span.label-row - = link_to_label(label, tooltip: false) + %span.label-name + = link_to_label(label, tooltip: false) %span.prepend-left-10 = markdown(label.description, pipeline: :single_line) diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 5a60ff5a5da..fc935166bf6 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -1,9 +1,4 @@ -- if @service.errors.any? - #error_explanation - .alert.alert-danger - %ul - - @service.errors.full_messages.each do |msg| - %li= msg += form_errors(@service) - if @service.help.present? .well diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index e2a9e5bfb92..aed2622a6da 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -1,10 +1,5 @@ -- if issuable.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - issuable.errors.full_messages.each do |msg| - %span= msg - %br += form_errors(issuable) + .form-group = f.label :title, class: 'control-label' .col-sm-10 @@ -34,7 +29,8 @@ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, - classes: 'description form-control' + classes: 'note-textarea', + placeholder: "Write a comment or drag your files here..." = render 'projects/notes/hints' .clearfix .error-alert @@ -53,10 +49,11 @@ .issue-assignee = f.label :assignee_id, "Assignee", class: 'control-label' .col-sm-10 - = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", - placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, - selected: issuable.assignee_id, project: @target_project || @project, - first_user: true, current_user: true, include_blank: true) + .issuable-form-select-holder + = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", + placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, + selected: issuable.assignee_id, project: @target_project || @project, + first_user: true, current_user: true, include_blank: true) = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' .form-group @@ -64,8 +61,9 @@ = f.label :milestone_id, "Milestone", class: 'control-label' .col-sm-10 - if milestone_options(issuable).present? - = f.select(:milestone_id, milestone_options(issuable), - { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) + .issuable-form-select-holder + = f.select(:milestone_id, milestone_options(issuable), + { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) - else .prepend-top-10 %span.light No open milestones available. @@ -73,13 +71,13 @@ - if can? current_user, :admin_milestone, issuable.project = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank .form-group + - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: 'control-label' - .col-sm-10 - - if issuable.project.labels.any? + .col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) } + - if has_labels = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" } - else - .prepend-top-10 %span.light No labels yet. - if can? current_user, :admin_label, issuable.project @@ -131,8 +129,6 @@ - else .pull-right - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) - = link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, - method: :delete, class: 'btn btn-grouped' do - = icon('trash-o') - Delete + = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, + method: :delete, class: 'btn btn-danger btn-grouped' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index fd5e58c1f1f..f722e61eeac 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,7 +1,7 @@ - if params[:label_name].present? = hidden_field_tag(:label_name, params[:label_name]) .dropdown - %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} + %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %span.dropdown-toggle-text = h(params[:label_name].presence || "Label") = icon('chevron-down') diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 47e544acf52..94affa4b59a 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,5 +1,6 @@ %aside.right-sidebar{ class: sidebar_gutter_collapsed_class } .issuable-sidebar + - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.issuable-sidebar-header %span.issuable-count.hide-collapsed.pull-left = issuable.iid @@ -29,7 +30,7 @@ .title.hide-collapsed Assignee = icon('spinner spin', class: 'block-loading') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.bold.hide-collapsed - if issuable.assignee @@ -41,9 +42,10 @@ = issuable.assignee.to_reference - else %span.assign-yourself - No assignee - - %a.js-assign-yourself{ href: '#' } - assign yourself + No assignee + - if can_edit_issuable + %a.js-assign-yourself{ href: '#' } + \- assign yourself .selectbox.hide-collapsed = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id' @@ -60,7 +62,7 @@ .title.hide-collapsed Milestone = icon('spinner spin', class: 'block-loading') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.bold.hide-collapsed - if issuable.milestone @@ -82,7 +84,7 @@ .title.hide-collapsed Labels = icon('spinner spin', class: 'block-loading') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) } - if issuable.labels.any? diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml index 868b2357003..b15e8ea73fe 100644 --- a/app/views/shared/milestones/_labels_tab.html.haml +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -4,15 +4,16 @@ %li %span.label-row - = link_to milestones_label_path(options) do - - render_colored_label(label, tooltip: false) - %span.prepend-left-10 + %span.label-name + = link_to milestones_label_path(options) do + - render_colored_label(label, tooltip: false) + %span.prepend-description-left = markdown(label.description, pipeline: :single_line) - .pull-right - %strong.issues-count + .pull-info-right + %span.append-right-20 = link_to milestones_label_path(options.merge(state: 'opened')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue' - %strong.issues-count + %span.append-right-20 = link_to milestones_label_path(options.merge(state: 'closed')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue' diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 1041eccd1df..47ec09f62c6 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -1,10 +1,6 @@ .snippet-form-holder = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f| - - if @snippet.errors.any? - .alert.alert-danger - %ul - - @snippet.errors.full_messages.each do |msg| - %li= msg + = form_errors(@snippet) .form-group = f.label :title, class: 'control-label' diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml index 7f29918dba3..1de71f37d1a 100644 --- a/app/views/users/calendar.html.haml +++ b/app/views/users/calendar.html.haml @@ -7,4 +7,4 @@ '#{user_calendar_activities_path}' ); -.calendar-hint Summary of issues, merge requests and push events +.calendar-hint Summary of issues, merge requests, and push events diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 8ffcdc4a327..dc249155b92 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,7 +1,7 @@ .awards.votes-block - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| - %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}} - = emoji_icon(emoji) + %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}} + = emoji_icon(emoji, sprite: false) %span.award-control-text.js-counter = notes.count diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 3cc232ef1ae..9e1215b21a6 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -40,7 +40,7 @@ class PostReceive if Gitlab::Git.tag_ref?(ref) GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref) - else + elsif Gitlab::Git.branch_ref?(ref) GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute end end |