diff options
| author | Javier Castro <javier.alejandro.castro@gmail.com> | 2014-03-04 15:09:33 -0300 |
|---|---|---|
| committer | Javier Castro <javier.alejandro.castro@gmail.com> | 2014-03-04 15:09:33 -0300 |
| commit | b61f80babb10d0583d3199d557b4e8234d981a33 (patch) | |
| tree | 747f124631f6547103bd30d4b48586382b91ae89 /app | |
| parent | 556ae5ae8187b89a3785219d5621e7ebc9bb7a8c (diff) | |
| parent | fbbd989770ea48d472a5c4d7d95b970542279099 (diff) | |
| download | gitlab-ce-b61f80babb10d0583d3199d557b4e8234d981a33.tar.gz | |
Merge remote-tracking branch 'upstream/master' into fix-4305
Diffstat (limited to 'app')
354 files changed, 4723 insertions, 3156 deletions
diff --git a/app/assets/images/ajax_loader.gif b/app/assets/images/ajax_loader.gif Binary files differdeleted file mode 100644 index 8a97c91987a..00000000000 --- a/app/assets/images/ajax_loader.gif +++ /dev/null diff --git a/app/assets/images/ajax_loader_gray.gif b/app/assets/images/ajax_loader_gray.gif Binary files differdeleted file mode 100644 index af3f618bd0b..00000000000 --- a/app/assets/images/ajax_loader_gray.gif +++ /dev/null diff --git a/app/assets/images/ajax_loader_tree.gif b/app/assets/images/ajax_loader_tree.gif Binary files differdeleted file mode 100644 index 99d5a0f37f3..00000000000 --- a/app/assets/images/ajax_loader_tree.gif +++ /dev/null diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png Binary files differindex 8759ca589fc..9ecdaf4e2d5 100644 --- a/app/assets/images/bg-header.png +++ b/app/assets/images/bg-header.png diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png Binary files differindex 055a9069b63..6dac6cd8ca1 100644 --- a/app/assets/images/dark-scheme-preview.png +++ b/app/assets/images/dark-scheme-preview.png diff --git a/app/assets/images/file_txt.png b/app/assets/images/file_txt.png Binary files differindex f3638cb4e1e..b3230b5add0 100644 --- a/app/assets/images/file_txt.png +++ b/app/assets/images/file_txt.png diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png Binary files differnew file mode 100644 index 00000000000..32ade0fe9a3 --- /dev/null +++ b/app/assets/images/icon-link.png diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png Binary files differindex 7632915cacc..084b89e3a7c 100644 --- a/app/assets/images/icon-search.png +++ b/app/assets/images/icon-search.png diff --git a/app/assets/images/images.png b/app/assets/images/images.png Binary files differindex 973d3bdd39d..da91f6b1f4c 100644 --- a/app/assets/images/images.png +++ b/app/assets/images/images.png diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png Binary files differindex 31c4a63cd08..4a96572d570 100644 --- a/app/assets/images/logo-black.png +++ b/app/assets/images/logo-black.png diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png Binary files differindex 8a4ec851b1d..bc2ef601a53 100644 --- a/app/assets/images/logo-white.png +++ b/app/assets/images/logo-white.png diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png Binary files differindex 9477941778e..3aeed886a02 100644 --- a/app/assets/images/monokai-scheme-preview.png +++ b/app/assets/images/monokai-scheme-preview.png diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png Binary files differindex 752d26adba7..dac3ab1bb89 100644 --- a/app/assets/images/no_avatar.png +++ b/app/assets/images/no_avatar.png diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png Binary files differnew file mode 100644 index 00000000000..a97d4515982 --- /dev/null +++ b/app/assets/images/no_group_avatar.png diff --git a/app/assets/images/onion_skin_sprites.gif b/app/assets/images/onion_skin_sprites.gif Binary files differindex 85d20260edc..337aa1bfb63 100644 --- a/app/assets/images/onion_skin_sprites.gif +++ b/app/assets/images/onion_skin_sprites.gif diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png Binary files differindex 728964bc4c8..ae092ab5213 100644 --- a/app/assets/images/solarized-dark-scheme-preview.png +++ b/app/assets/images/solarized-dark-scheme-preview.png diff --git a/app/assets/images/swipemode_sprites.gif b/app/assets/images/swipemode_sprites.gif Binary files differindex 327b3c31ffd..b010b4e4482 100644 --- a/app/assets/images/swipemode_sprites.gif +++ b/app/assets/images/swipemode_sprites.gif diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png Binary files differindex 7c11f206593..6b8bde41bc9 100644 --- a/app/assets/images/switch_icon.png +++ b/app/assets/images/switch_icon.png diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif Binary files differindex c7e98e044f5..5f6ed04a43c 100644 --- a/app/assets/images/trans_bg.gif +++ b/app/assets/images/trans_bg.gif diff --git a/app/assets/images/white-scheme-preview.png b/app/assets/images/white-scheme-preview.png Binary files differindex 67eb8763044..d1866e00158 100644 --- a/app/assets/images/white-scheme-preview.png +++ b/app/assets/images/white-scheme-preview.png diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index 5f4a38ebbd0..fafa5cdfaa4 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -3,6 +3,7 @@ user_path: "/api/:version/users/:id.json" notes_path: "/api/:version/projects/:id/notes.json" namespaces_path: "/api/:version/namespaces.json" + project_users_path: "/api/:version/projects/:id/users.json" # Get 20 (depends on api) recent notes # and sort the ascending from oldest to newest @@ -50,6 +51,23 @@ ).done (users) -> callback(users) + # Return project users list. Filtered by query + # Only active users retrieved + projectUsers: (project_id, query, callback) -> + url = Api.buildUrl(Api.project_users_path) + url = url.replace(':id', project_id) + + $.ajax( + url: url + data: + private_token: gon.api_token + search: query + per_page: 20 + active: true + dataType: "json" + ).done (users) -> + callback(users) + # Return namespaces list. Filtered by query namespaces: (query, callback) -> url = Api.buildUrl(Api.namespaces_path) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index aa2db1fc93c..4a393dbfe81 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -24,7 +24,8 @@ //= require g.raphael-min //= require g.bar-min //= require branch-graph -//= require ace-src-noconflict/ace +//= require highlightjs.min +//= require ace/ace //= require_tree . //= require d3 //= require underscore diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 99cb1cec911..584f6faea16 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/app/assets/javascripts/blob.js.coffee @@ -17,7 +17,7 @@ class BlobView setHash(hash) e.preventDefault() - + # See if there are lines selected # "#L12" and "#L34-56" supported highlightBlobLines = (e) -> @@ -64,7 +64,7 @@ class BlobView nodes.attr("id", hash) # initialize multi-line select - $("#tree-content-holder .line_numbers a[id^=L]").on("click", handleMultiSelect) + $("#tree-content-holder .line-numbers a[id^=L]").on("click", handleMultiSelect) # Highlight the correct lines on load highlightBlobLines() diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee index 9f55a1e6368..5f53439ca4b 100644 --- a/app/assets/javascripts/commit.js.coffee +++ b/app/assets/javascripts/commit.js.coffee @@ -1,6 +1,6 @@ class Commit constructor: -> - $('.files .file').each -> + $('.files .diff-file').each -> new CommitFile(this) @Commit = Commit diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index e264e281309..46d6db0f05c 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -4,6 +4,7 @@ $ -> class Dispatcher constructor: () -> @initSearch() + @initHighlight() @initPageScripts() initPageScripts: -> @@ -18,6 +19,8 @@ class Dispatcher switch page when 'projects:issues:index' Issues.init() + when 'projects:issues:show' + new Issue() when 'projects:issues:new', 'projects:merge_requests:new' GitLab.GfmAutoComplete.setup() when 'dashboard:show' @@ -47,5 +50,16 @@ class Dispatcher initSearch: -> - autocomplete_json = $('.search-autocomplete-json').data('autocomplete-opts') - new SearchAutocomplete(autocomplete_json) + opts = $('.search-autocomplete-opts') + path = opts.data('autocomplete-path') + project_id = opts.data('autocomplete-project-id') + project_ref = opts.data('autocomplete-project-ref') + + new SearchAutocomplete(path, project_id, project_ref) + + initHighlight: -> + $('.highlight pre code').each (i, e) -> + hljs.highlightBlock(e) + $(e).html($.map($(e).html().split("\n"), (line, i) -> + "<div class='line' id='LC" + (i + 1) + "'>" + line + "</div>" + ).join("\n")) diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index c0ffccd8f70..7850eb14e74 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -4,3 +4,14 @@ class GroupMembers $(this).fadeOut() @GroupMembers = GroupMembers + +$ -> + # avatar + $('.js-choose-group-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-group-avatar-input").click() + + $('.js-group-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename)
\ No newline at end of file diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee new file mode 100644 index 00000000000..36935a0a159 --- /dev/null +++ b/app/assets/javascripts/issue.js.coffee @@ -0,0 +1,9 @@ +class Issue + constructor: -> + $('.edit-issue.inline-update input[type="submit"]').hide() + $(".issue-box .inline-update").on "change", "select", -> + $(this).submit() + $(".issue-box .inline-update").on "change", "#issue_assignee_id", -> + $(this).submit() + +@Issue = Issue diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 5e60b2775a6..e2f1bc743f7 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -29,10 +29,10 @@ $('#filter_issue_search').val($('#issue_search').val()) initSelects: -> - $("select#update_status").select2() - $("select#update_assignee_id").select2() - $("select#update_milestone_id").select2() - $("select#label_name").select2() + $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true) + $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true) + $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true) + $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true) $("#milestone_id, #assignee_id, #label_name").on "change", -> $(this).closest("form").submit() @@ -77,9 +77,3 @@ $("#update_issues_ids").val [] $(".issues_bulk_update").hide() $(".issues-filters").show() - -$ -> - $('.edit-issue.inline-update input[type="submit"]').hide(); - $("body").on "change", ".edit-issue.inline-update select", -> - $(this).submit() - diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee index 9cf4dba815b..69c731ff1a1 100644 --- a/app/assets/javascripts/main.js.coffee +++ b/app/assets/javascripts/main.js.coffee @@ -117,11 +117,11 @@ $ -> # Commit show suppressed diff - $(".content").on "click", ".supp_diff_link", -> + $(".diff-content").on "click", ".supp_diff_link", -> $(@).next('table').show() $(@).remove() - $(".content").on "click", ".js-details-expand", -> + $(".diff-content").on "click", ".js-details-expand", -> $(@).next('.js-details-contain').removeClass("hide") $(@).remove() diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee new file mode 100644 index 00000000000..c0f83c93021 --- /dev/null +++ b/app/assets/javascripts/merge_request.js.coffee @@ -0,0 +1,103 @@ +class MergeRequest + constructor: (@opts) -> + @initContextWidget() + this.$el = $('.merge-request') + @diffs_loaded = if @opts.action == 'diffs' then true else false + @commits_loaded = false + + this.activateTab(@opts.action) + + this.bindEvents() + + this.initMergeWidget() + this.$('.show-all-commits').on 'click', => + this.showAllCommits() + + modal = $('#modal_merge_info').modal(show: false) + + disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request' + + + # Local jQuery finder + $: (selector) -> + this.$el.find(selector) + + initContextWidget: -> + $('.edit-merge_request.inline-update input[type="submit"]').hide() + $(".issue-box .inline-update").on "change", "select", -> + $(this).submit() + $(".issue-box .inline-update").on "change", "#merge_request_assignee_id", -> + $(this).submit() + + initMergeWidget: -> + this.showState( @opts.current_status ) + + if this.$('.automerge_widget').length and @opts.check_enable + $.get @opts.url_to_automerge_check, (data) => + this.showState( data.merge_status ) + , 'json' + + if @opts.ci_enable + $.get @opts.url_to_ci_check, (data) => + this.showCiState data.status + , 'json' + + bindEvents: -> + this.$('.nav-tabs').on 'click', 'a', (event) => + a = $(event.currentTarget) + + href = a.attr('href') + History.replaceState {path: href}, document.title, href + + event.preventDefault() + + this.$('.nav-tabs').on 'click', 'li', (event) => + this.activateTab($(event.currentTarget).data('action')) + + this.$('.accept_merge_request').on 'click', -> + $('.automerge_widget.can_be_merged').hide() + $('.merge-in-progress').show() + + activateTab: (action) -> + this.$('.nav-tabs li').removeClass 'active' + this.$('.tab-content').hide() + switch action + when 'diffs' + this.$('.nav-tabs .diffs-tab').addClass 'active' + this.loadDiff() unless @diffs_loaded + this.$('.diffs').show() + else + this.$('.nav-tabs .notes-tab').addClass 'active' + this.$('.notes').show() + + showState: (state) -> + $('.automerge_widget').hide() + $('.automerge_widget.' + state).show() + + showCiState: (state) -> + $('.ci_widget').hide() + $('.ci_widget.ci-' + state).show() + + loadDiff: (event) -> + $.ajax + type: 'GET' + url: this.$('.nav-tabs .diffs-tab a').attr('href') + beforeSend: => + this.$('.status').addClass 'loading' + complete: => + @diffs_loaded = true + this.$('.status').removeClass 'loading' + success: (data) => + this.$(".diffs").html(data.html) + dataType: 'json' + + showAllCommits: -> + this.$('.first-commits').remove() + this.$('.all-commits').removeClass 'hide' + + alreadyOrCannotBeMerged: -> + this.$('.automerge_widget').hide() + this.$('.merge-in-progress').hide() + this.$('.automerge_widget.already_cannot_be_merged').show() + +this.MergeRequest = MergeRequest diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 047683f00b2..9201c84c5ed 100644 --- a/app/assets/javascripts/merge_requests.js.coffee +++ b/app/assets/javascripts/merge_requests.js.coffee @@ -6,97 +6,3 @@ $('#milestone_id').select2() $('#milestone_id, #assignee_id').on 'change', -> $(this).closest('form').submit() - -class MergeRequest - - constructor: (@opts) -> - this.$el = $('.merge-request') - @diffs_loaded = if @opts.action == 'diffs' then true else false - @commits_loaded = false - - this.activateTab(@opts.action) - - this.bindEvents() - - this.initMergeWidget() - this.$('.show-all-commits').on 'click', => - this.showAllCommits() - - modal = $('#modal_merge_info').modal(show: false) - - # Local jQuery finder - $: (selector) -> - this.$el.find(selector) - - initMergeWidget: -> - this.showState( @opts.current_status ) - - if this.$('.automerge_widget').length and @opts.check_enable - $.get @opts.url_to_automerge_check, (data) => - this.showState( data.merge_status ) - , 'json' - - if @opts.ci_enable - $.get @opts.url_to_ci_check, (data) => - this.showCiState data.status - , 'json' - - bindEvents: -> - this.$('.nav-tabs').on 'click', 'a', (event) => - a = $(event.currentTarget) - - href = a.attr('href') - History.replaceState {path: href}, document.title, href - - event.preventDefault() - - this.$('.nav-tabs').on 'click', 'li', (event) => - this.activateTab($(event.currentTarget).data('action')) - - this.$('.accept_merge_request').on 'click', -> - $('.automerge_widget.can_be_merged').hide() - $('.merge-in-progress').show() - - activateTab: (action) -> - this.$('.nav-tabs li').removeClass 'active' - this.$('.tab-content').hide() - switch action - when 'diffs' - this.$('.nav-tabs .diffs-tab').addClass 'active' - this.loadDiff() unless @diffs_loaded - this.$('.diffs').show() - else - this.$('.nav-tabs .notes-tab').addClass 'active' - this.$('.notes').show() - - showState: (state) -> - $('.automerge_widget').hide() - $('.automerge_widget.' + state).show() - - showCiState: (state) -> - $('.ci_widget').hide() - $('.ci_widget.ci-' + state).show() - - loadDiff: (event) -> - $.ajax - type: 'GET' - url: this.$('.nav-tabs .diffs-tab a').attr('href') - beforeSend: => - this.$('.status').addClass 'loading' - complete: => - @diffs_loaded = true - this.$('.status').removeClass 'loading' - success: (data) => - this.$(".diffs").html(data.html) - dataType: 'json' - - showAllCommits: -> - this.$('.first-commits').remove() - this.$('.all-commits').removeClass 'hide' - - alreadyOrCannotBeMerged: -> - this.$('.automerge_widget').hide() - this.$('.merge-in-progress').hide() - this.$('.automerge_widget.already_cannot_be_merged').show() - -this.MergeRequest = MergeRequest diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 69e9e67f712..d200d962cae 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -94,6 +94,9 @@ class Notes if @isNewNote(note) @note_ids.push(note.id) $('ul.main-notes-list').append(note.html) + code = "#note_" + note.id + " .highlight pre code" + $(code).each (i, e) -> + hljs.highlightBlock(e) ### @@ -253,6 +256,9 @@ class Notes updateNote: (xhr, note, status) => note_li = $("#note_" + note.id) note_li.replaceWith(note.html) + code = "#note_" + note.id + " .highlight pre code" + $(code).each (i, e) -> + hljs.highlightBlock(e) ### Called in response to clicking the edit note link diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 744f3086d55..b37a78ac197 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -26,3 +26,5 @@ $ -> form = $(this).closest("form") filename = $(this).val().replace(/^.*[\\\/]/, '') form.find(".js-avatar-filename").text(filename) + + $('.profile-groups-avatars').tooltip("placement": "top")
\ No newline at end of file diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee new file mode 100644 index 00000000000..03fad41c490 --- /dev/null +++ b/app/assets/javascripts/project_users_select.js.coffee @@ -0,0 +1,63 @@ +@projectUsersSelect = + init: -> + $('.ajax-project-users-select').each (i, select) -> + project_id = $('body').data('project-id') + + $(select).select2 + placeholder: $(select).data('placeholder') || "Search for a user" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.projectUsers project_id, query.term, (users) -> + data = { results: users } + + nullUser = { + name: 'Unassigned', + avatar: null, + username: 'none', + id: '' + } + + data.results.unshift(nullUser) + + query.callback(data) + + initSelection: (element, callback) -> + id = $(element).val() + if id isnt "" + Api.user(id, callback) + + + formatResult: projectUsersSelect.projectUserFormatResult + formatSelection: projectUsersSelect.projectUserFormatSelection + dropdownCssClass: "ajax-project-users-dropdown" + dropdownAutoWidth: true + escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results + m + + projectUserFormatResult: (user) -> + if user.avatar_url + avatar = user.avatar_url + else if gon.gravatar_enabled + avatar = gon.gravatar_url + avatar = avatar.replace('%{hash}', md5(user.email)) + avatar = avatar.replace('%{size}', '24') + else + avatar = gon.relative_url_root + "/assets/no_avatar.png" + + if user.id == '' + avatarMarkup = '' + else + avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>" + + "<div class='user-result'> + #{avatarMarkup} + <div class='user-name'>#{user.name}</div> + <div class='user-username'>#{user.username}</div> + </div>" + + projectUserFormatSelection: (user) -> + user.name + +$ -> + projectUsersSelect.init() diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 3418690e109..e144dfa1d68 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -1,7 +1,12 @@ class SearchAutocomplete - constructor: (json) -> + constructor: (search_autocomplete_path, project_id, project_ref) -> + project_id = '' unless project_id + project_ref = '' unless project_ref + query = "?project_id=" + project_id + "&project_ref=" + project_ref + $("#search").autocomplete - source: json + source: search_autocomplete_path + query + minLength: 1 select: (event, ui) -> location.href = ui.item.url diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 92cd3a9905b..ce9a505b1e3 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -1,11 +1,13 @@ $ -> userFormatResult = (user) -> - if user.avatar - avatar = user.avatar.url - else + if user.avatar_url + avatar = user.avatar_url + else if gon.gravatar_enabled avatar = gon.gravatar_url avatar = avatar.replace('%{hash}', md5(user.email)) avatar = avatar.replace('%{size}', '24') + else + avatar = gon.relative_url_root + "/assets/no_avatar.png" "<div class='user-result'> <div class='user-image'><img class='avatar s24' src='#{avatar}'></div> diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index eb8bc0d63c0..33466714681 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -5,6 +5,7 @@ *= require jquery.ui.gitlab *= require jquery.atwho *= require select2 + *= require highlightjs.min *= require_self */ @@ -36,8 +37,10 @@ @import "generic/issue_box.scss"; @import "generic/files.scss"; @import "generic/lists.scss"; +@import "generic/flash.scss"; @import "generic/forms.scss"; @import "generic/selects.scss"; +@import "generic/highlight.scss"; /** * Page specific styles (issues, projects etc): @@ -45,6 +48,7 @@ @import "sections/header.scss"; @import "sections/nav.scss"; @import "sections/commits.scss"; +@import "sections/diff.scss"; @import "sections/issues.scss"; @import "sections/projects.scss"; @import "sections/snippets.scss"; @@ -63,9 +67,10 @@ @import "sections/wall.scss"; @import "sections/dashboard.scss"; @import "sections/stat_graph.scss"; +@import "sections/groups.scss"; /** - * Code ighlight + * Code highlight */ @import "highlight/white.scss"; @import "highlight/dark.scss"; diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss index 1cbd7439835..3536a68f416 100644 --- a/app/assets/stylesheets/generic/blocks.scss +++ b/app/assets/stylesheets/generic/blocks.scss @@ -2,3 +2,18 @@ background: #f9f9f9; padding: 15px; } + +.centered-light-block { + text-align: center; + color: #888; + margin: 20px; +} + +.nothing-here-block { + text-align: center; + padding: 20px; + color: #666; + font-weight: normal; + font-size: 16px; + line-height: 36px; +} diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index 219e6ebd68b..dd0a2938cfe 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -118,7 +118,6 @@ @extend .btn-primary; } - &.btn-close, &.btn-remove { @extend .btn-danger; } @@ -143,6 +142,30 @@ line-height: 16px; margin: 2px; } + + &.btn-close { + color: #B94A48; + font-weight: bold; + &:hover { + color: #B94A48; + } + } + + &.btn-reopen { + color: #468847; + font-weight: bold; + &:hover { + color: #468847; + } + } + + &.btn-grouped { + margin-right: 7px; + float: left; + &:last-child { + margin-right: 0px; + } + } } .btn-block { @@ -154,9 +177,8 @@ } } -.btn, .btn-group { - &.grouped { + &.btn-grouped { margin-right: 7px; float: left; &:last-child { diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 79a217186de..6183cefa594 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -11,8 +11,6 @@ .bgred { background: #F2DEDE!important } /** COMMON CLASSES **/ -.left { float:left } - .prepend-top-10 { margin-top:10px } .prepend-top-20 { margin-top:20px } .prepend-left-10 { margin-left:10px } @@ -24,32 +22,9 @@ .append-bottom-20 { margin-bottom:20px } .inline { display: inline-block } -.padded { padding:20px } -.ipadded { padding:20px!important } -.lborder { border-left:1px solid #eee } -.underlined_link { text-decoration: underline; } +.underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } .light { color: #888 } -.tiny { font-weight: normal } -.vtop { vertical-align: top !important; } - - -/** ALERT MESSAGES **/ -.alert.alert-disabled { - background: #EEE; - color: #777; - border-color: #DDD; -} - -/** HELPERS **/ -.nothing_here_message { - text-align: center; - padding: 20px; - color: #666; - font-weight: normal; - font-size: 16px; - line-height: 36px; -} .slead { color: #666; @@ -59,49 +34,23 @@ line-height: 24px; } - .tab-content { overflow: visible; } -@media (max-width: 1200px) { - .only-wide { - display: none; - } -} - -pre.well-pre { - border: 1px solid #EEE; - background: #f9f9f9; - border-radius: 0; - color: #555; -} - -.input-append .btn.active, .input-prepend .btn.active { - background: #CCC; - border-color: #BBB; - text-shadow: 0 1px 1px #fff; - font-weight: bold; - @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); -} - -/** Big Labels **/ -.state-label { - font-size: 14px; - padding: 6px 25px; - text-align: center; - @include border-radius(4px); - text-shadow: none; - margin-left: 10px; - - &.state-label-green { - background: #4A4; - color: #FFF; +pre { + &.clean { + background: none; + border: none; + margin: 0; + padding: 0; } - &.state-label-red { - background: #DA4E49; - color: #FFF; + &.well-pre { + border: 1px solid #EEE; + background: #f9f9f9; + border-radius: 0; + color: #555; } } @@ -112,6 +61,7 @@ pre.well-pre { .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { background: #29b; + color: #FFF } .breadcrumb > li + li:before { @@ -130,31 +80,6 @@ pre.well-pre { } /** FLASH message **/ -.flash-container { - display: none; - cursor: pointer; - margin: 0; - text-align: center; - color: #fff; - font-size: 14px; - position: fixed; - bottom: 0; - width: 100%; - opacity: 0.8; - z-index: 100; - - .flash-notice { - background: #49C; - padding: 10px; - text-shadow: 0 1px 1px #178; - } - - .flash-alert { - background: #C67; - text-shadow: 0 1px 1px #945; - padding: 10px; - } -} .author_link { color: $link_color; } @@ -173,12 +98,10 @@ table a code { .loading { margin: 20px auto; - background: url(ajax_loader.gif) no-repeat center center; - width: 40px; height: 40px; - &.loading-gray { - background: url(ajax_loader_gray.gif) no-repeat center center; - } + color: #555; + font-size: 32px; + text-align: center; } span.update-author { @@ -276,22 +199,6 @@ li.note { cursor: pointer; } -.merge-request, -.issue { - &.today{ - background: #EFE; - border-color: #CEC; - } - &.closed { - background: #F5f5f5; - border-color: #E5E5E5; - } - &.merged { - background: #F5f5f5; - border-color: #E5E5E5; - } -} - .git_error_tips { @extend .col-md-6; text-align: left; @@ -327,6 +234,12 @@ li.note { color: #fff; text-decoration: underline; } + + .links-xs { + text-align: center; + font-size: 16px; + padding: 5px; + } } .warning_message { @@ -349,15 +262,6 @@ li.note { } } -pre { - &.clean { - background: none; - border: none; - margin: 0; - padding: 0; - } -} - .milestone { &.milestone-closed { background: #eee; @@ -441,40 +345,6 @@ table { margin-bottom: 20px; } -.ajax-users-select { - width: 400px; - - &.input-large { - width: 210px; - } - - &.input-clamp { - max-width: 100%; - } -} - -.user-result { - .user-image { - float: left; - } - .user-name { - } - .user-username { - color: #999; - } -} - -.namespace-result { - .namespace-kind { - color: #AAA; - font-weight: normal; - } - .namespace-path { - margin-left: 10px; - font-weight: bolder; - } -} - .btn-sign-in { margin-top: 7px; text-shadow: none; @@ -485,3 +355,7 @@ table { margin-bottom: 15px; } } + +@media (max-width: $screen-xs-max) { + .container .content { margin-top: 20px; } +} diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss index 6607ae327e0..12559f76051 100644 --- a/app/assets/stylesheets/generic/files.scss +++ b/app/assets/stylesheets/generic/files.scss @@ -45,14 +45,14 @@ text-align: center; img { padding: 100px; - max-width: 300px; + max-width: 50%; } } &.wiki { - padding: 20px; font-size: 14px; line-height: 1.6; + padding: 25px; .highlight { margin-bottom: 9px; @@ -143,75 +143,6 @@ */ &.code { padding: 0; - - table.lines { - border: none; - box-shadow: none; - margin: 0px; - padding: 0px; - table-layout: fixed; - - pre { - border: none; - border-radius: 0; - font-family: $monospace_font; - font-size: 12px !important; - line-height: 16px !important; - margin: 0; - padding: 10px 0; - } - td { - border: none; - margin: 0; - padding: 0; - vertical-align: top; - - &:first-child { - background: #eee; - width: 50px; - } - &:last-child { - } - } - tr:hover { - background: none; - } - - pre.line_numbers { - color: #666; - padding: 10px 6px 10px 0; - text-align: right; - background: #EEE; - - a { - color: #666; - - i { - display: none; - font-size: 14px; - line-height: 14px; - } - &:hover i { - display: inherit; - } - } - } - - .highlight { - border-left: 1px solid #DEE2E3; - overflow: auto; - overflow-y: hidden; - - pre { - white-space: pre; - word-wrap: normal; - - .line { - padding: 0 10px; - } - } - } - } } } } diff --git a/app/assets/stylesheets/generic/flash.scss b/app/assets/stylesheets/generic/flash.scss new file mode 100644 index 00000000000..95d28aaef6c --- /dev/null +++ b/app/assets/stylesheets/generic/flash.scss @@ -0,0 +1,25 @@ +.flash-container { + display: none; + cursor: pointer; + margin: 0; + text-align: center; + color: #fff; + font-size: 14px; + position: fixed; + bottom: 0; + width: 100%; + opacity: 0.8; + z-index: 100; + + .flash-notice { + background: #49C; + padding: 10px; + text-shadow: 0 1px 1px #178; + } + + .flash-alert { + background: #C67; + text-shadow: 0 1px 1px #945; + padding: 10px; + } +} diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss index 931b75a3234..19e3e7a9536 100644 --- a/app/assets/stylesheets/generic/forms.scss +++ b/app/assets/stylesheets/generic/forms.scss @@ -51,3 +51,27 @@ label { .input-mn-300 { min-width: 300px; } + +.custom-form-control { + width: 150px; +} + +@media (min-width: $screen-sm-min) { + .custom-form-control { + width: 150px; + } +} + +/* Medium devices (desktops, 992px and up) */ +@media (min-width: $screen-md-min) { + .custom-form-control { + width: 170px; + } +} + +/* Large devices (large desktops, 1200px and up) */ +@media (min-width: $screen-lg-min) { + .custom-form-control { + width: 200px; + } +} diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss new file mode 100644 index 00000000000..4110bddf4f3 --- /dev/null +++ b/app/assets/stylesheets/generic/highlight.scss @@ -0,0 +1,64 @@ +.highlighted-data { + border: none; + box-shadow: none; + margin: 0px; + padding: 0px; + table-layout: fixed; + + pre { + padding: 10px; + border: none; + border-radius: 0; + font-family: $monospace_font; + font-size: 12px !important; + line-height: 16px !important; + margin: 0; + + code { + white-space: pre; + word-wrap: normal; + padding: 0; + + .line { + display: inline; + } + } + } + + .hljs { + padding: 0; + } + + .line-numbers { + padding: 10px; + text-align: right; + float: left; + + a { + font-family: $monospace_font; + display: block; + font-size: 12px !important; + line-height: 16px !important; + white-space: nowrap; + + i { + visibility: hidden; + @extend .pull-left; + } + + &:hover i { + visibility: visible; + } + } + } + + .highlight { + overflow: auto; + overflow-y: hidden; + + pre { + white-space: pre; + word-wrap: normal; + } + } +} diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 6330523ea22..3db4d908d9c 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -10,37 +10,103 @@ .issue-box { color: #666; margin:20px 0; - background: #FAFAFA; - border: 1px solid #DDD; + background: #FFF; + border: 1px solid #EEE; + + &.issue-box-closed { + border-color: #DA4E49; + .state { + background-color: #f2dede; + border-color: #ebccd1; + color: #a94442; + .state-label { + background: #DA4E49; + color: #FFF; + } + } + } + + &.issue-box-merged { + border-color: #31708f; + .state { + background-color: #d9edf7; + border-color: #bce8f1; + color: #31708f; + .state-label { + background: #31708f; + color: #FFF; + } + } + } + + &.issue-box-open { + border-color: #4A4; + .state { + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; + .state-label { + background: #4A4; + color: #FFF; + } + } + } + + &.issue-box-expired { + border-color: #cea61b; + .state { + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; + .state-label { + background: #cea61b; + color: #FFF; + } + } + } .control-group { margin-bottom: 0; } + .state { + height: 34px; + border-bottom: 1px solid #DDD; + line-height: 32px; + } + .title { - font-size: 20px; + font-size: 22px; font-weight: 500; - line-height: 28px; + line-height: 1.5; margin: 0; - color: #444; + color: #333; + padding-bottom: 0; + padding: 15px 25px; } .context { border: none; - background-color: #f5f5f5; - border: none; border-top: 1px solid #eee; + padding: 15px 25px; } .description { - border-top: 1px solid #eee; + padding: 0 25px 15px 25px; } .title, .context, .description { - padding: 15px; - .clearfix { margin: 0; } } + + .state-label { + font-size: 14px; + padding: 1px 25px; + text-align: center; + text-shadow: none; + margin-right: 20px; + display: inline-block; + } } diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss index 245cccf855d..de70e47333f 100644 --- a/app/assets/stylesheets/generic/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -23,6 +23,12 @@ } } + &.warning-row { + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; + } + &.smoke { background-color: #f5f5f5; } &:hover { diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss index fa10e85664a..03597c1fc7d 100644 --- a/app/assets/stylesheets/generic/selects.scss +++ b/app/assets/stylesheets/generic/selects.scss @@ -1,6 +1,5 @@ /** Select2 selectbox style override **/ - -.select2-container { +.select2-container, .select2-container.select2-drop-above { .select2-choice { background: #FFF; border-color: #BBB; @@ -12,8 +11,24 @@ } .select2-drop-active { - border: 1px solid #BBB; + border: 1px solid #BBB !important; margin-top: 4px; + + &.select2-drop-above { + margin-bottom: 8px; + } + + .select2-search input { + background: #fafafa; + border-color: #DDD; + } + + .select2-results { + max-height: 350px; + .select2-highlighted { + background: $bg_style_color; + } + } } select { @@ -66,3 +81,43 @@ select { .project-refs-form .select2-container { margin-right: 10px; } + +.ajax-users-dropdown, .ajax-project-users-dropdown { + .select2-search { + padding-top: 4px; + } +} + +.ajax-users-select { + width: 400px; + + &.input-large { + width: 210px; + } + + &.input-clamp { + max-width: 100%; + } +} + +.user-result { + .user-image { + float: left; + } + .user-name { + } + .user-username { + color: #999; + } +} + +.namespace-result { + .namespace-kind { + color: #AAA; + font-weight: normal; + } + .namespace-path { + margin-left: 10px; + font-weight: bolder; + } +} diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index 4490566d4e8..1a07fde53f1 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -90,9 +90,27 @@ a:focus { font-size: 14px; line-height: 1.6; - .white .highlight pre { - background: #f5f5f5; + + /* Link to current header. */ + h1, h2, h3, h4, h5, h6 { + position: relative; + &:hover > :last-child { + $size: 16px; + position: absolute; + right: 100%; + top: 50%; + margin-top: -$size/2; + margin-right: 0px; + padding-right: 20px; + display: inline-block; + width: $size; + height: $size; + background-image: url("icon-link.png"); + background-size: contain; + background-repeat: no-repeat; + } } + ul { padding: 0; margin: 0 0 9px 25px !important; diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss index 7f45d64fb7c..169f1268cd7 100644 --- a/app/assets/stylesheets/gl_bootstrap.scss +++ b/app/assets/stylesheets/gl_bootstrap.scss @@ -108,6 +108,8 @@ $pagination-active-bg: $bg_style_color; // Nav tabs .nav.nav-tabs { + margin-bottom: 15px; + li { > a { padding: 8px 20px; diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 129d33dcac3..ca51da3fdd4 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,71 +1,199 @@ -.dark .highlight { +.dark { + background-color: #232323; - background-color: #333; + .line.hll { + background: #558; + } + + .highlight{ + border-left: 1px solid #444; + } + + .no-highlight { + color: #DDD; + } + + .line-numbers a { + color: #666; + } pre { - background-color: #333; - color: #eee; - } - - .hll { display: block; background-color: darken($hover, 65%) } - .c { color: #888888; font-style: italic } /* Comment */ - .err { color: #a61717; background-color: #e3d2d2 } /* Error */ - .k { color: #CDA869; font-weight: bold } /* Keyword */ - .kp { color: #CDA869; font-weight: bold } /* Keyword */ - .cm { color: #888888 } /* Comment.Multiline */ - .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ - .c1 { color: #888888 } /* Comment.Single */ - .cs { color: #cc0000; font-weight: bold; background-color: transparent } /* Comment.Special */ - .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ - .ge { font-style: italic } /* Generic.Emph */ - .gr { color: #aa0000 } /* Generic.Error */ - .gh { color: #303030 } /* Generic.Heading */ - .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ - .go { color: #888888 } /* Generic.Output */ - .gp { color: #555555 } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #606060 } /* Generic.Subheading */ - .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight: bold;} /* Keyword.Constant */ - .kd{font-weight: bold;} /* Keyword.Declaration */ - .kn{font-weight: bold;} /* Keyword.Namespace */ - .kp{font-weight: bold;} /* Keyword.Pseudo */ - .kr{font-weight: bold;} /* Keyword.Reserved */ - .kt{color: #458;font-weight: bold;} /* Keyword.Type */ - .m { color: #0000DD; font-weight: bold } /* Literal.Number */ - .p { color: #eee; } - .s { color: #0AD; background-color: transparent } /* Literal.String */ - .na{color: #008080;} /* Name.Attribute */ - .nb{color: #0086B3;} /* Name.Builtin */ - .nc{color: #ccc;font-weight: bold;} /* Name.Class */ - .no{color: turquoise;} /* Name.Constant */ - .ni{color: #800080;} - .ne{color: #900;font-weight: bold;} /* Name.Exception */ - .nf{color: #ccc;font-weight: bold;} /* Name.Function */ - .nn{color: #79C3E0;font-weight: bold;} /* Name.Namespace */ - .nt{color: #fc5;} /* Name.Tag */ - .nv{color: #FA4;} /* Name.Variable */ - .py { color: #336699; font-weight: bold } /* Name.Property */ - .ow { color: #008800 } /* Operator.Word */ - .w { color: #bbbbbb } /* Text.Whitespace */ - .mf { color: #7AC; font-weight: bold } /* Literal.Number.Float */ - .mh { color: #7AC; font-weight: bold } /* Literal.Number.Hex */ - .mi {color: #099;} /* Literal.Number.Integer */ - .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ - .sb { color: #dd2200; background-color: transparent; } /* Literal.String.Backtick */ - .sc{color: #d14;} /* Literal.String.Char */ - .sd { color: #dd2200; background-color: transparent; } /* Literal.String.Doc */ - .s2{color: orange;} /* Literal.String.Double */ - .se{color: orange;} /* Literal.String.Escape */ - .sh{color: orange;} /* Literal.String.Heredoc */ - .si{color: orange;} /* Literal.String.Interpol */ - .sx{color: orange;} /* Literal.String.Other */ - .sr{color: orange;} /* Literal.String.Regex */ - .s1{color: orange;} /* Literal.String.Single */ - .ss{color: orange;} /* Literal.String.Symbol */ - .bp { color: #D58 } /* Name.Builtin.Pseudo */ - .vc { color: #336699 } /* Name.Variable.Class */ - .vg { color: #dd7700 } /* Name.Variable.Global */ - .vi { color: cyan } -} + background-color: #232323; + } + + .hljs { + display: block; + background: #232323; + color: #E6E1DC; + } + + .hljs-comment, + .hljs-template_comment, + .hljs-javadoc, + .hljs-shebang { + color: #BC9458; + font-style: italic; + } + + .hljs-keyword, + .ruby .hljs-function .hljs-keyword, + .hljs-request, + .hljs-status, + .nginx .hljs-title, + .method, + .hljs-list .hljs-title { + color: #C26230; + } + + .hljs-string, + .hljs-number, + .hljs-regexp, + .hljs-tag .hljs-value, + .hljs-cdata, + .hljs-filter .hljs-argument, + .hljs-attr_selector, + .apache .hljs-cbracket, + .hljs-date, + .tex .hljs-command, + .markdown .hljs-link_label { + color: #A5C261; + } + + .hljs-subst { + color: #519F50; + } + + .hljs-tag, + .hljs-tag .hljs-keyword, + .hljs-tag .hljs-title, + .hljs-doctype, + .hljs-sub .hljs-identifier, + .hljs-pi, + .input_number { + color: #E8BF6A; + } + + .hljs-identifier { + color: #D0D0FF; + } + + .hljs-class .hljs-title, + .haskell .hljs-type, + .smalltalk .hljs-class, + .hljs-javadoctag, + .hljs-yardoctag, + .hljs-phpdoc { + text-decoration: none; + } + + .hljs-constant { + color: #DA4939; + } + + + .hljs-symbol, + .hljs-built_in, + .ruby .hljs-symbol .hljs-string, + .ruby .hljs-symbol .hljs-identifier, + .markdown .hljs-link_url, + .hljs-attribute { + color: #6D9CBE; + } + + .markdown .hljs-link_url { + text-decoration: underline; + } + + + .hljs-params, + .hljs-variable, + .clojure .hljs-attribute { + color: #D0D0FF; + } + + .css .hljs-tag, + .hljs-rules .hljs-property, + .hljs-pseudo, + .tex .hljs-special { + color: #CDA869; + } + + .css .hljs-class { + color: #9B703F; + } + + .hljs-rules .hljs-keyword { + color: #C5AF75; + } + + .hljs-rules .hljs-value { + color: #CF6A4C; + } + + .css .hljs-id { + color: #8B98AB; + } + + .hljs-annotation, + .apache .hljs-sqbracket, + .nginx .hljs-built_in { + color: #9B859D; + } + + .hljs-preprocessor, + .hljs-preprocessor *, + .hljs-pragma { + color: #8996A8 !important; + } + + .hljs-hexcolor, + .css .hljs-value .hljs-number { + color: #A5C261; + } + + .hljs-title, + .hljs-decorator, + .css .hljs-function { + color: #FFC66D; + } + + .diff .hljs-header, + .hljs-chunk { + background-color: #2F33AB; + color: #E6E1DC; + display: inline-block; + width: 100%; + } + + .diff .hljs-change { + background-color: #4A410D; + color: #F8F8F8; + display: inline-block; + width: 100%; + } + + .hljs-addition { + background-color: #144212; + color: #E6E1DC; + display: inline-block; + width: 100%; + } + + .hljs-deletion { + background-color: #600; + color: #E6E1DC; + display: inline-block; + width: 100%; + } + + .coffeescript .javascript, + .javascript .xml, + .tex .hljs-formula, + .xml .javascript, + .xml .vbscript, + .xml .css, + .xml .hljs-cdata { + opacity: 0.7; + } +} diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index c9709fa7f12..36bc5df2f44 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,89 +1,148 @@ -$monokai-fg: #f8f8f2; -$monokai-comment: #75715e; -$monokai-pink: #f92672; -$monokai-blue: #66d9ef; -$monokai-green: #a6e22e; -$monokai-gold: #e6db74; -$monokai-dark: #3b3a32; -$monokai-purple: #ae81ff; +.monokai { + background-color: #272822; -.monokai .highlight { + .highlight{ + border-left: 1px solid #444; + } - background-color: #272822; + .line.hll { + background: #558; + } + + .no-highlight { + color: #DDD; + } + + .line-numbers a { + color: #666; + } pre { background-color: #272822; - color: $monokai-fg; + color: #f8f8f2; } - .hll { background-color: darken($hover, 65%) } - .c { color: $monokai-comment } /* Comment */ - .err { color: $monokai-fg } /* Error */ - .g { color: $monokai-fg } /* Generic */ - .k { color: $monokai-pink } /* Keyword */ - .l { color: $monokai-fg } /* Literal */ - .n { color: $monokai-blue } /* Name */ - .o { color: $monokai-fg } /* Operator */ - .x { color: $monokai-fg } /* Other */ - .p { color: $monokai-fg } /* Punctuation */ - .cm { color: $monokai-comment } /* Comment.Multiline */ - .cp { color: $monokai-comment } /* Comment.Preproc */ - .c1 { color: $monokai-comment } /* Comment.Single */ - .cs { color: $monokai-comment } /* Comment.Special */ - .gd { color: #8b0807 } /* Generic.Deleted */ - .ge { color: $monokai-fg; text-decoration: underline } /* Generic.Emph */ - .gr { color: $monokai-fg } /* Generic.Error */ - .gh { color: $monokai-fg; font-weight: bold } /* Generic.Heading */ - .gi { color: $monokai-fg; font-weight: bold; background-color: #46830c } /* Generic.Inserted */ - .go { color: $monokai-dark; background-color: #31322c } /* Generic.Output */ - .gp { color: $monokai-fg } /* Generic.Prompt */ - .gs { color: $monokai-fg } /* Generic.Strong */ - .gu { color: $monokai-fg; font-weight: bold } /* Generic.Subheading */ - .gt { color: #f8f8f0; background-color: $monokai-pink } /* Generic.Traceback */ - .kc { color: $monokai-purple } /* Keyword.Constant */ - .kd { color: $monokai-pink } /* Keyword.Declaration */ - .kn { color: $monokai-pink } /* Keyword.Namespace */ - .kp { color: $monokai-pink } /* Keyword.Pseudo */ - .kr { color: $monokai-pink } /* Keyword.Reserved */ - .kt { color: $monokai-fg } /* Keyword.Type */ - .ld { color: $monokai-fg } /* Literal.Date */ - .m { color: $monokai-purple } /* Literal.Number */ - .s { color: $monokai-gold } /* Literal.String */ - .na { color: $monokai-purple } /* Name.Attribute */ - .nb { color: $monokai-blue } /* Name.Builtin */ - .nc { color: $monokai-fg } /* Name.Class */ - .no { color: $monokai-fg } /* Name.Constant */ - .nd { color: $monokai-fg } /* Name.Decorator */ - .ni { color: $monokai-fg } /* Name.Entity */ - .ne { color: $monokai-fg } /* Name.Exception */ - .nf { color: $monokai-green } /* Name.Function */ - .nl { color: $monokai-gold } /* Name.Label */ - .nn { color: $monokai-fg } /* Name.Namespace */ - .nx { color: $monokai-fg } /* Name.Other */ - .nt { color: $monokai-pink } /* Name.Tag */ - .nv { color: $monokai-blue; font-style: italic } /* Name.Variable */ - .py { color: $monokai-fg } /* Name.Property */ - .ow { color: $monokai-pink } /* Operator.Word */ - .w { color: $monokai-fg } /* Text.Whitespace */ - .mf { color: $monokai-purple } /* Literal.Number.Float */ - .mh { color: $monokai-purple } /* Literal.Number.Hex */ - .mi { color: $monokai-purple } /* Literal.Number.Integer */ - .mo { color: $monokai-purple } /* Literal.Number.Oct */ - .sb { color: $monokai-gold } /* Literal.String.Backtick */ - .sc { color: $monokai-gold } /* Literal.String.Char */ - .sd { color: $monokai-gold } /* Literal.String.Doc */ - .s2 { color: $monokai-gold } /* Literal.String.Double */ - .se { color: $monokai-gold } /* Literal.String.Escape */ - .sh { color: $monokai-gold } /* Literal.String.Heredoc */ - .si { color: $monokai-gold } /* Literal.String.Interpol */ - .sx { color: $monokai-gold } /* Literal.String.Other */ - .sr { color: $monokai-gold } /* Literal.String.Regex */ - .s1 { color: $monokai-gold } /* Literal.String.Single */ - .ss { color: $monokai-gold } /* Literal.String.Symbol */ - .bp { color: $monokai-fg } /* Name.Builtin.Pseudo */ - .vc { color: $monokai-blue; font-style: italic } /* Name.Variable.Class */ - .vg { color: $monokai-blue; font-style: italic } /* Name.Variable.Global */ - .vi { color: $monokai-blue; font-style: italic } /* Name.Variable.Instance */ - .il { color: $monokai-purple } /* Literal.Number.Integer.Long */ -} + .hljs { + display: block; + background: #272822; + } + + .hljs-tag, + .hljs-tag .hljs-title, + .hljs-keyword, + .hljs-literal, + .hljs-strong, + .hljs-change, + .hljs-winutils, + .hljs-flow, + .lisp .hljs-title, + .clojure .hljs-built_in, + .nginx .hljs-title, + .tex .hljs-special { + color: #F92672; + } + + .hljs { + color: #DDD; + } + .hljs .hljs-constant, + .asciidoc .hljs-code { + color: #66D9EF; + } + + .hljs-code, + .hljs-class .hljs-title, + .hljs-header { + color: white; + } + + .hljs-link_label, + .hljs-attribute, + .hljs-symbol, + .hljs-symbol .hljs-string, + .hljs-value, + .hljs-regexp { + color: #BF79DB; + } + + .hljs-link_url, + .hljs-tag .hljs-value, + .hljs-string, + .hljs-bullet, + .hljs-subst, + .hljs-title, + .hljs-emphasis, + .haskell .hljs-type, + .hljs-preprocessor, + .hljs-pragma, + .ruby .hljs-class .hljs-parent, + .hljs-built_in, + .sql .hljs-aggregate, + .django .hljs-template_tag, + .django .hljs-variable, + .smalltalk .hljs-class, + .hljs-javadoc, + .django .hljs-filter .hljs-argument, + .smalltalk .hljs-localvars, + .smalltalk .hljs-array, + .hljs-attr_selector, + .hljs-pseudo, + .hljs-addition, + .hljs-stream, + .hljs-envvar, + .apache .hljs-tag, + .apache .hljs-cbracket, + .tex .hljs-command, + .hljs-prompt { + color: #A6E22E; + } + + .hljs-comment, + .java .hljs-annotation, + .smartquote, + .hljs-blockquote, + .hljs-horizontal_rule, + .python .hljs-decorator, + .hljs-template_comment, + .hljs-pi, + .hljs-doctype, + .hljs-deletion, + .hljs-shebang, + .apache .hljs-sqbracket, + .tex .hljs-formula { + color: #75715E; + } + + .hljs-keyword, + .hljs-literal, + .css .hljs-id, + .hljs-phpdoc, + .hljs-title, + .hljs-header, + .haskell .hljs-type, + .vbscript .hljs-built_in, + .sql .hljs-aggregate, + .rsl .hljs-built_in, + .smalltalk .hljs-class, + .diff .hljs-header, + .hljs-chunk, + .hljs-winutils, + .bash .hljs-variable, + .apache .hljs-tag, + .tex .hljs-special, + .hljs-request, + .hljs-status { + font-weight: bold; + } + + .coffeescript .javascript, + .javascript .xml, + .tex .hljs-formula, + .xml .javascript, + .xml .vbscript, + .xml .css, + .xml .hljs-cdata { + opacity: 0.5; + } +} diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index cc82f39ac93..b9bec225188 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,80 +1,125 @@ -.solarized-dark .highlight { - +.solarized-dark { background-color: #002B36; - + + .highlight{ + border-left: 1px solid #113b46; + } + + .line.hll { + background: #000; + } + + .no-highlight { + color: #DDD; + } + pre { background-color: #002B36; color: #eee; } - .hll { background-color: #073642 } - .c { color: #586E75 } /* Comment */ - .err { color: #93A1A1 } /* Error */ - .g { color: #93A1A1 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #93A1A1 } /* Literal */ - .n { color: #93A1A1 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #CB4B16 } /* Other */ - .p { color: #93A1A1 } /* Punctuation */ - .cm { color: #586E75 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #586E75 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2AA198 } /* Generic.Deleted */ - .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */ - .gr { color: #DC322F } /* Generic.Error */ - .gh { color: #CB4B16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #93A1A1 } /* Generic.Output */ - .gp { color: #93A1A1 } /* Generic.Prompt */ - .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */ - .gu { color: #CB4B16 } /* Generic.Subheading */ - .gt { color: #93A1A1 } /* Generic.Traceback */ - .kc { color: #CB4B16 } /* Keyword.Constant */ - .kd { color: #268BD2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268BD2 } /* Keyword.Reserved */ - .kt { color: #DC322F } /* Keyword.Type */ - .ld { color: #93A1A1 } /* Literal.Date */ - .m { color: #2AA198 } /* Literal.Number */ - .s { color: #2AA198 } /* Literal.String */ - .na { color: #93A1A1 } /* Name.Attribute */ - .nb { color: #B58900 } /* Name.Builtin */ - .nc { color: #268BD2 } /* Name.Class */ - .no { color: #CB4B16 } /* Name.Constant */ - .nd { color: #268BD2 } /* Name.Decorator */ - .ni { color: #CB4B16 } /* Name.Entity */ - .ne { color: #CB4B16 } /* Name.Exception */ - .nf { color: #268BD2 } /* Name.Function */ - .nl { color: #93A1A1 } /* Name.Label */ - .nn { color: #93A1A1 } /* Name.Namespace */ - .nx { color: #93A1A1 } /* Name.Other */ - .py { color: #93A1A1 } /* Name.Property */ - .nt { color: #268BD2 } /* Name.Tag */ - .nv { color: #268BD2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #93A1A1 } /* Text.Whitespace */ - .mf { color: #2AA198 } /* Literal.Number.Float */ - .mh { color: #2AA198 } /* Literal.Number.Hex */ - .mi { color: #2AA198 } /* Literal.Number.Integer */ - .mo { color: #2AA198 } /* Literal.Number.Oct */ - .sb { color: #586E75 } /* Literal.String.Backtick */ - .sc { color: #2AA198 } /* Literal.String.Char */ - .sd { color: #93A1A1 } /* Literal.String.Doc */ - .s2 { color: #2AA198 } /* Literal.String.Double */ - .se { color: #CB4B16 } /* Literal.String.Escape */ - .sh { color: #93A1A1 } /* Literal.String.Heredoc */ - .si { color: #2AA198 } /* Literal.String.Interpol */ - .sx { color: #2AA198 } /* Literal.String.Other */ - .sr { color: #DC322F } /* Literal.String.Regex */ - .s1 { color: #2AA198 } /* Literal.String.Single */ - .ss { color: #2AA198 } /* Literal.String.Symbol */ - .bp { color: #268BD2 } /* Name.Builtin.Pseudo */ - .vc { color: #268BD2 } /* Name.Variable.Class */ - .vg { color: #268BD2 } /* Name.Variable.Global */ - .vi { color: #268BD2 } /* Name.Variable.Instance */ - .il { color: #2AA198 } /* Literal.Number.Integer.Long */ -} + .line-numbers a { + color: #666; + } + + .hljs { + display: block; + background: #002b36; + color: #839496; + } + + .hljs-comment, + .hljs-template_comment, + .diff .hljs-header, + .hljs-doctype, + .hljs-pi, + .lisp .hljs-string, + .hljs-javadoc { + color: #586e75; + } + + /* Solarized Green */ + .hljs-keyword, + .hljs-winutils, + .method, + .hljs-addition, + .css .hljs-tag, + .hljs-request, + .hljs-status, + .nginx .hljs-title { + color: #859900; + } + /* Solarized Cyan */ + .hljs-number, + .hljs-command, + .hljs-string, + .hljs-tag .hljs-value, + .hljs-rules .hljs-value, + .hljs-phpdoc, + .tex .hljs-formula, + .hljs-regexp, + .hljs-hexcolor, + .hljs-link_url { + color: #2aa198; + } + + /* Solarized Blue */ + .hljs-title, + .hljs-localvars, + .hljs-chunk, + .hljs-decorator, + .hljs-built_in, + .hljs-identifier, + .vhdl .hljs-literal, + .hljs-id, + .css .hljs-function { + color: #268bd2; + } + + /* Solarized Yellow */ + .hljs-attribute, + .hljs-variable, + .lisp .hljs-body, + .smalltalk .hljs-number, + .hljs-constant, + .hljs-class .hljs-title, + .hljs-parent, + .haskell .hljs-type, + .hljs-link_reference { + color: #b58900; + } + + /* Solarized Orange */ + .hljs-preprocessor, + .hljs-preprocessor .hljs-keyword, + .hljs-pragma, + .hljs-shebang, + .hljs-symbol, + .hljs-symbol .hljs-string, + .diff .hljs-change, + .hljs-special, + .hljs-attr_selector, + .hljs-subst, + .hljs-cdata, + .clojure .hljs-title, + .css .hljs-pseudo, + .hljs-header { + color: #cb4b16; + } + + /* Solarized Red */ + .hljs-deletion, + .hljs-important { + color: #dc322f; + } + + /* Solarized Violet */ + .hljs-link_label { + color: #6c71c4; + } + + .tex .hljs-formula { + background: #073642; + } +} diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index df127a7c491..c06bed3c21e 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,72 +1,186 @@ -.white .highlight { - +.white { background-color: #fff; - + + .line.hll { + background: #FFA; + } + + .highlight{ + border-left: 1px solid #eee; + } + pre { background-color: #fff; color: #333; } - .hll { display: block; background-color: $hover } - .c { color: #888888; font-style: italic } /* Comment */ - .err { color: #a61717; background-color: #e3d2d2 } /* Error */ - .k { color: #000000; font-weight: bold } /* Keyword */ - .cm { color: #888888 } /* Comment.Multiline */ - .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ - .c1 { color: #888888 } /* Comment.Single */ - .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ - .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ - .ge { font-style: italic } /* Generic.Emph */ - .gr { color: #aa0000 } /* Generic.Error */ - .gh { color: #303030 } /* Generic.Heading */ - .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ - .go { color: #888888 } /* Generic.Output */ - .gp { color: #555555 } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #606060 } /* Generic.Subheading */ - .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight: bold;} /* Keyword.Constant */ - .kd{font-weight: bold;} /* Keyword.Declaration */ - .kn{font-weight: bold;} /* Keyword.Namespace */ - .kp{font-weight: bold;} /* Keyword.Pseudo */ - .kr{font-weight: bold;} /* Keyword.Reserved */ - .kt{color: #458;font-weight: bold;} /* Keyword.Type */ - .m { color: #0000DD; font-weight: bold } /* Literal.Number */ - .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ - .na{color: #008080;} /* Name.Attribute */ - .nb{color: #0086B3;} /* Name.Builtin */ - .nc{color: #458;font-weight: bold;} /* Name.Class */ - .no{color: #008080;} /* Name.Constant */ - .ni{color: #800080;} - .ne{color: #900;font-weight: bold;} /* Name.Exception */ - .nf{color: #900;font-weight: bold;} /* Name.Function */ - .nn{color: #005;font-weight: bold;} /* Name.Namespace */ - .nt{color: #000080;} /* Name.Tag */ - .nv{color: #008080;} /* Name.Variable */ - .py { color: #336699; font-weight: bold } /* Name.Property */ - .ow { color: #008800 } /* Operator.Word */ - .w { color: #bbbbbb } /* Text.Whitespace */ - .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ - .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ - .mi {color: #099;} /* Literal.Number.Integer */ - .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ - .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ - .sc{color: #d14;} /* Literal.String.Char */ - .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ - .s2{color: #d14;} /* Literal.String.Double */ - .se{color: #d14;} /* Literal.String.Escape */ - .sh{color: #d14;} /* Literal.String.Heredoc */ - .si{color: #d14;} /* Literal.String.Interpol */ - .sx{color: #d14;} /* Literal.String.Other */ - .sr{color: #d14;} /* Literal.String.Regex */ - .s1{color: #d14;} /* Literal.String.Single */ - .ss{color: #d14;} /* Literal.String.Symbol */ - .bp { color: #003388 } /* Name.Builtin.Pseudo */ - .vc { color: #336699 } /* Name.Variable.Class */ - .vg { color: #dd7700 } /* Name.Variable.Global */ - .vi { color: #3333bb } + .hljs { + background: #FFF; + } + + .line-numbers a { + color: #999; + } + + .hljs { + display: block; + background: #fff; color: black; + } + + .hljs-comment, + .hljs-template_comment, + .hljs-javadoc, + .hljs-comment * { + color: #006a00; + } + + .hljs-keyword, + .hljs-literal, + .nginx .hljs-title { + color: #aa0d91; + } + .method, + .hljs-list .hljs-title, + .hljs-tag .hljs-title, + .setting .hljs-value, + .hljs-winutils, + .tex .hljs-command, + .http .hljs-title, + .hljs-request, + .hljs-status { + color: #008; + } + + .hljs-envvar, + .tex .hljs-special { + color: #660; + } + + .hljs-string { + color: #c41a16; + } + .hljs-tag .hljs-value, + .hljs-cdata, + .hljs-filter .hljs-argument, + .hljs-attr_selector, + .apache .hljs-cbracket, + .hljs-date, + .hljs-regexp { + color: #080; + } + + .hljs-sub .hljs-identifier, + .hljs-pi, + .hljs-tag, + .hljs-tag .hljs-keyword, + .hljs-decorator, + .ini .hljs-title, + .hljs-shebang, + .hljs-prompt, + .hljs-hexcolor, + .hljs-rules .hljs-value, + .hljs-symbol, + .hljs-symbol .hljs-string, + .hljs-number, + .css .hljs-function, + .clojure .hljs-title, + .clojure .hljs-built_in, + .hljs-function .hljs-title, + .coffeescript .hljs-attribute { + color: #1c00cf; + } + + .hljs-class .hljs-title, + .haskell .hljs-type, + .smalltalk .hljs-class, + .hljs-javadoctag, + .hljs-yardoctag, + .hljs-phpdoc, + .hljs-typename, + .hljs-tag .hljs-attribute, + .hljs-doctype, + .hljs-class .hljs-id, + .hljs-built_in, + .setting, + .hljs-params, + .clojure .hljs-attribute { + color: #5c2699; + } + + .hljs-variable { + color: #3f6e74; + } + .css .hljs-tag, + .hljs-rules .hljs-property, + .hljs-pseudo, + .hljs-subst { + color: #000; + } + + .css .hljs-class, + .css .hljs-id { + color: #9B703F; + } + + .hljs-value .hljs-important { + color: #ff7700; + font-weight: bold; + } + + .hljs-rules .hljs-keyword { + color: #C5AF75; + } + + .hljs-annotation, + .apache .hljs-sqbracket, + .nginx .hljs-built_in { + color: #9B859D; + } + + .hljs-preprocessor, + .hljs-preprocessor *, + .hljs-pragma { + color: #643820; + } + + .tex .hljs-formula { + background-color: #EEE; + font-style: italic; + } + + .diff .hljs-header, + .hljs-chunk { + color: #808080; + font-weight: bold; + } + + .diff .hljs-change { + background-color: #BCCFF9; + } + + .hljs-addition { + background-color: #BAEEBA; + } + + .hljs-deletion { + background-color: #FFC8BD; + } + + .hljs-comment .hljs-yardoctag { + font-weight: bold; + } + + .method .hljs-id { + color: #000; + } } .shadow { @include box-shadow(0 5px 15px #000); } + +.wiki, .note-body { + .highlight { + border: 1px solid #DDD; + } +} diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/main/layout.scss index a4d889f26dd..e28da65c01f 100644 --- a/app/assets/stylesheets/main/layout.scss +++ b/app/assets/stylesheets/main/layout.scss @@ -1,11 +1,11 @@ html { overflow-y: scroll; + + &.touch .tooltip { display: none !important; } } body { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - margin-bottom: 20px; + padding-bottom: 20px; } .container { diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss index e637b645b55..4afe61d756c 100644 --- a/app/assets/stylesheets/main/mixins.scss +++ b/app/assets/stylesheets/main/mixins.scss @@ -106,12 +106,12 @@ h3 { margin-top: 35px; - font-size: 2em; + font-size: 1.5em; } h4 { margin-top: 30px; - font-size: 1.5em; + font-size: 1.2em; } blockquote p { @@ -128,7 +128,7 @@ } } - code { + p > code { font-size: inherit; font-weight: inherit; color: #555; diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss index decc5f50469..4b5fa0979be 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -5,6 +5,7 @@ $primary_color: #2FA0BB; $link_color: #3A89A3; $style_color: #474D57; $bg_style_color: #2299BB; +$list-group-active-bg: $bg_style_color; $hover: #D9EDF7; /** diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss index 8ad9bc732b2..a558633d112 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/sections/admin.scss @@ -2,7 +2,7 @@ * Admin area * */ -.admin_dash { +.admin-dashboard { .data { a { h1 { @@ -14,6 +14,10 @@ } } } + + .str-truncated { + max-width: 60%; + } } .admin-filter form { diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index ff293bc4a00..f3355c8f1d6 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -10,331 +10,6 @@ } } -.file { - border: 1px solid #CCC; - margin-bottom: 1em; - - .header { - @extend .clearfix; - background: #DDD; - border-bottom: 1px solid #CCC; - padding: 5px 5px 5px 10px; - color: #555; - - > span { - font-family: $monospace_font; - font-size: 14px; - line-height: 2; - } - - .view-file { - font-weight: bold; - float: right; - background-color: #EEE; - } - - .commit-short-id { - font-family: $monospace_font; - font-size: smaller; - } - - .file-mode { - font-family: $monospace_font; - } - } - .content { - overflow: auto; - overflow-y: hidden; - background: #FFF; - color: #333; - font-size: 12px; - .old { - span.idiff { - background-color: #FAA; - } - } - .new { - span.idiff { - background-color: #AFA; - } - } - - table { - width: 100%; - font-family: $monospace_font; - border: none; - margin: 0px; - padding: 0px; - td { - line-height: 18px; - font-size: 12px; - } - } - .old_line, .new_line, .diff_line { - margin: 0px; - padding: 0px; - border: none; - background: #EEE; - color: #666; - padding: 0px 5px; - border-right: 1px solid #ccc; - text-align: right; - min-width: 35px; - max-width: 50px; - width: 35px; - @include user-select(none); - a { - float: left; - width: 35px; - font-weight: normal; - color: #666; - &:hover { - text-decoration: underline; - } - } - &.new { - background: #CFD; - } - &.old { - background: #FDD; - } - } - .diff_line { - padding: 0; - } - .line_holder { - &.old .old_line, - &.old .new_line { - background: #FCC; - border-color: #E7BABA; - } - &.new .old_line, - &.new .new_line { - background: #CFC; - border-color: #B9ECB9; - } - } - .line_content { - display: block; - white-space: pre; - height: 18px; - margin: 0px; - padding: 0px 0.5em; - border: none; - &.new { - background: #CFD; - } - &.old { - background: #FDD; - } - &.matched { - color: #ccc; - background: #fafafa; - } - &.parallel { - display: table-cell; - overflow: hidden; - width: 50%; - } - } - } - .image { - background: #ddd; - text-align: center; - padding: 30px; - .wrap{ - display: inline-block; - } - - .frame { - display: inline-block; - background-color: #fff; - line-height: 0; - img{ - border: 1px solid #FFF; - background: url('trans_bg.gif'); - } - &.deleted { - border: 1px solid $deleted; - } - - &.added { - border: 1px solid $added; - } - } - .image-info{ - font-size: 12px; - margin: 5px 0 0 0; - color: grey; - } - - .view.swipe{ - position: relative; - - .swipe-frame{ - display: block; - margin: auto; - position: relative; - } - .swipe-wrap{ - overflow: hidden; - border-left: 1px solid #999; - position: absolute; - display: block; - top: 13px; - right: 7px; - } - .frame{ - top: 0; - right: 0; - position: absolute; - &.deleted{ - margin: 0; - display: block; - top: 13px; - right: 7px; - } - } - .swipe-bar{ - display: block; - height: 100%; - width: 15px; - z-index: 100; - position: absolute; - cursor: pointer; - &:hover{ - .top-handle{ - background-position: -15px 3px; - } - .bottom-handle{ - background-position: -15px -11px; - } - }; - .top-handle{ - display: block; - height: 14px; - width: 15px; - position: absolute; - top: 0px; - background: url('swipemode_sprites.gif') 0 3px no-repeat; - } - .bottom-handle{ - display: block; - height: 14px; - width: 15px; - position: absolute; - bottom: 0px; - background: url('swipemode_sprites.gif') 0 -11px no-repeat; - } - } - } //.view.swipe - .view.onion-skin{ - .onion-skin-frame{ - display: block; - margin: auto; - position: relative; - } - .frame.added, .frame.deleted { - position: absolute; - display: block; - top: 0px; - left: 0px; - } - .controls{ - display: block; - height: 14px; - width: 300px; - z-index: 100; - position: absolute; - bottom: 0px; - left: 50%; - margin-left: -150px; - - .drag-track{ - display: block; - position: absolute; - left: 12px; - height: 10px; - width: 276px; - background: url('onion_skin_sprites.gif') -4px -20px repeat-x; - } - - .dragger { - display: block; - position: absolute; - left: 0px; - top: 0px; - height: 14px; - width: 14px; - background: url('onion_skin_sprites.gif') 0px -34px repeat-x; - cursor: pointer; - } - - .transparent { - display: block; - position: absolute; - top: 2px; - right: 0px; - height: 10px; - width: 10px; - background: url('onion_skin_sprites.gif') -2px 0px no-repeat; - } - - .opaque { - display: block; - position: absolute; - top: 2px; - left: 0px; - height: 10px; - width: 10px; - background: url('onion_skin_sprites.gif') -2px -10px no-repeat; - } - } - } //.view.onion-skin - } - .view-modes{ - - padding: 10px; - text-align: center; - - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - - ul, li{ - list-style: none; - margin: 0; - padding: 0; - display: inline-block; - } - - li{ - color: grey; - border-left: 1px solid #c1c1c1; - padding: 0 12px 0 16px; - cursor: pointer; - &:first-child{ - border-left: none; - } - &:hover{ - text-decoration: underline; - } - &.active{ - &:hover{ - text-decoration: none; - } - cursor: default; - color: #333; - } - &.disabled{ - display: none; - } - } - } -} - /** COMMIT BLOCK **/ .commit-title{ display: block; @@ -493,6 +168,25 @@ li.commit { text-decoration: underline; } } + + .text-expander { + background: #eee; + color: #555; + padding: 0 5px; + cursor: pointer; + margin-left: 4px; + &:hover { + background-color: #ddd; + } + } + } + + .commit-row-description { + font-size: 14px; + border-left: 1px solid #e5e5e5; + padding: 0 15px 0 7px; + margin: 5px 0 10px 5px; + display: none; } .commit-row-info { diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index 1fd82c84fc9..6fc394e2e2b 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -41,7 +41,7 @@ .dash-sidebar-tabs { margin-bottom: 2px; border: none; - margin: 0; + margin: 0 !important; li { &.active { diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss new file mode 100644 index 00000000000..74d144dfb06 --- /dev/null +++ b/app/assets/stylesheets/sections/diff.scss @@ -0,0 +1,329 @@ +.diff-file { + border: 1px solid #CCC; + margin-bottom: 1em; + + .diff-header { + @extend .clearfix; + background: #DDD; + border-bottom: 1px solid #CCC; + padding: 5px 5px 5px 10px; + color: #555; + + > span { + font-family: $monospace_font; + font-size: 14px; + line-height: 2; + } + + .diff-btn-group { + float: right; + + .btn { + background-color: #EEE; + color: #666; + font-weight: bolder; + } + } + + .commit-short-id { + font-family: $monospace_font; + font-size: smaller; + } + + .file-mode { + font-family: $monospace_font; + } + } + .diff-content { + overflow: auto; + overflow-y: hidden; + background: #FFF; + color: #333; + font-size: 12px; + .old { + span.idiff { + background-color: #FAA; + } + } + .new { + span.idiff { + background-color: #AFA; + } + } + + table { + width: 100%; + font-family: $monospace_font; + border: none; + margin: 0px; + padding: 0px; + td { + line-height: 18px; + font-size: 12px; + } + } + .old_line, .new_line, .diff_line { + margin: 0px; + padding: 0px; + border: none; + background: #EEE; + color: #666; + padding: 0px 5px; + border-right: 1px solid #ccc; + text-align: right; + min-width: 35px; + max-width: 50px; + width: 35px; + @include user-select(none); + a { + float: left; + width: 35px; + font-weight: normal; + color: #666; + &:hover { + text-decoration: underline; + } + } + &.new { + background: #CFD; + } + &.old { + background: #FDD; + } + } + .diff_line { + padding: 0; + } + .line_holder { + &.old .old_line, + &.old .new_line { + background: #FCC; + border-color: #E7BABA; + } + &.new .old_line, + &.new .new_line { + background: #CFC; + border-color: #B9ECB9; + } + } + .line_content { + display: block; + white-space: pre; + height: 18px; + margin: 0px; + padding: 0px 0.5em; + border: none; + &.new { + background: #CFD; + } + &.old { + background: #FDD; + } + &.matched { + color: #ccc; + background: #fafafa; + } + &.parallel { + display: table-cell; + overflow: hidden; + width: 50%; + } + } + } + .image { + background: #ddd; + text-align: center; + padding: 30px; + .wrap{ + display: inline-block; + } + + .frame { + display: inline-block; + background-color: #fff; + line-height: 0; + img{ + border: 1px solid #FFF; + background: url('trans_bg.gif'); + max-width: 100%; + } + &.deleted { + border: 1px solid $deleted; + } + + &.added { + border: 1px solid $added; + } + } + .image-info{ + font-size: 12px; + margin: 5px 0 0 0; + color: grey; + } + + .view.swipe{ + position: relative; + + .swipe-frame{ + display: block; + margin: auto; + position: relative; + } + .swipe-wrap{ + overflow: hidden; + border-left: 1px solid #999; + position: absolute; + display: block; + top: 13px; + right: 7px; + } + .frame{ + top: 0; + right: 0; + position: absolute; + &.deleted{ + margin: 0; + display: block; + top: 13px; + right: 7px; + } + } + .swipe-bar{ + display: block; + height: 100%; + width: 15px; + z-index: 100; + position: absolute; + cursor: pointer; + &:hover{ + .top-handle{ + background-position: -15px 3px; + } + .bottom-handle{ + background-position: -15px -11px; + } + }; + .top-handle{ + display: block; + height: 14px; + width: 15px; + position: absolute; + top: 0px; + background: url('swipemode_sprites.gif') 0 3px no-repeat; + } + .bottom-handle{ + display: block; + height: 14px; + width: 15px; + position: absolute; + bottom: 0px; + background: url('swipemode_sprites.gif') 0 -11px no-repeat; + } + } + } //.view.swipe + .view.onion-skin{ + .onion-skin-frame{ + display: block; + margin: auto; + position: relative; + } + .frame.added, .frame.deleted { + position: absolute; + display: block; + top: 0px; + left: 0px; + } + .controls{ + display: block; + height: 14px; + width: 300px; + z-index: 100; + position: absolute; + bottom: 0px; + left: 50%; + margin-left: -150px; + + .drag-track{ + display: block; + position: absolute; + left: 12px; + height: 10px; + width: 276px; + background: url('onion_skin_sprites.gif') -4px -20px repeat-x; + } + + .dragger { + display: block; + position: absolute; + left: 0px; + top: 0px; + height: 14px; + width: 14px; + background: url('onion_skin_sprites.gif') 0px -34px repeat-x; + cursor: pointer; + } + + .transparent { + display: block; + position: absolute; + top: 2px; + right: 0px; + height: 10px; + width: 10px; + background: url('onion_skin_sprites.gif') -2px 0px no-repeat; + } + + .opaque { + display: block; + position: absolute; + top: 2px; + left: 0px; + height: 10px; + width: 10px; + background: url('onion_skin_sprites.gif') -2px -10px no-repeat; + } + } + } //.view.onion-skin + } + .view-modes{ + + padding: 10px; + text-align: center; + + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + + ul, li{ + list-style: none; + margin: 0; + padding: 0; + display: inline-block; + } + + li{ + color: grey; + border-left: 1px solid #c1c1c1; + padding: 0 12px 0 16px; + cursor: pointer; + &:first-child{ + border-left: none; + } + &:hover{ + text-decoration: underline; + } + &.active{ + &:hover{ + text-decoration: none; + } + cursor: default; + color: #333; + } + &.disabled{ + display: none; + } + } + } +} diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index a576fefdea1..6ef12b20a5a 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -37,8 +37,8 @@ &.event-inline { .avatar { - width: 16px; - height: 16px; + position: relative; + top: -2px; } } @@ -113,6 +113,7 @@ &.commit { background: transparent; padding: 3px; + padding-left: 0; border: none; color: #666; .commit-row-title { @@ -122,6 +123,7 @@ &.commits-stat { display: block; padding: 3px; + padding-left: 0; &:hover { background: none; diff --git a/app/assets/stylesheets/sections/groups.scss b/app/assets/stylesheets/sections/groups.scss new file mode 100644 index 00000000000..60ec79acadb --- /dev/null +++ b/app/assets/stylesheets/sections/groups.scss @@ -0,0 +1,9 @@ +.new-group-member-holder { + margin-top: 50px; + background: #f9f9f9; + padding-top: 20px; +} + +.member-search-form { + float: left; +} diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index b0dd0d08356..68873327845 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -19,7 +19,7 @@ header { line-height: 32px; padding: 6px 10px; - &:hover { + &:hover, &:focus, &:active { background: none; } } @@ -29,6 +29,64 @@ header { float: right; margin-right: 0; } + + .navbar-toggle { + color: $style_color; + margin: 0 -15px 0 0; + padding: 10px; + border-radius: 0; + + button i { font-size: 22px; } + + &.collapsed { background-color: transparent !important;} + + &:hover { + background-color: #EEE; + } + } + } + + .turbolink-spinner { + font-size: 20px; + margin-right: 10px; + } + + @media (max-width: $screen-xs-max) { + border-width: 0; + font-size: 18px; + + .app_logo { margin-left: -15px; } + .title { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; + white-space: nowrap; + max-width: 70%; + } + + .navbar-collapse { + padding-right: 0; + padding-left: 0; + } + + .navbar-nav { + margin: 5px 0; + + .visible-xs, .visable-sm { + display: table-cell !important; + } + } + + li { + display: table-cell; + width: 1%; + + a { + text-align: center; + font-size: 18px !important; + } + } } } @@ -50,7 +108,7 @@ header { h1 { margin: 0; - background: url('logo-black.png') no-repeat center center; + background: image-url('logo-black.png') no-repeat center center; background-size: 32px; float: left; height: 46px; @@ -69,7 +127,7 @@ header { * Project / Area name * */ - .project_name { + .title { position: relative; float: left; margin: 0; @@ -128,11 +186,14 @@ header { background: #708090; border-bottom: 1px solid #AAA; + .navbar-toggle { color: #fff; } + .nav > li > a { color: #AAA; text-shadow: 0 1px 0 #444; - &:hover { + &:hover, &:focus, &:active { + background: none; color: #FFF; } } @@ -160,18 +221,18 @@ header { .app_logo { a { h1 { - background: url('logo-white.png') no-repeat center center; + background: image-url('logo-white.png') no-repeat center center; background-size: 32px; color: #fff; text-shadow: 0 1px 1px #444; } } } - .project_name { + .title { a { - color: #BBB; + color: #FFF; &:hover { - color: #FFF; + text-decoration: underline; } } color: #fff; diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 81d945a9b3c..d4f8c8108ab 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -14,8 +14,8 @@ .issue-check { float: left; - padding: 8px 0; padding-right: 8px; + margin-bottom: 10px; min-width: 15px; } @@ -38,13 +38,21 @@ } } -input.check_all_issues { +.check-all-holder { + height: 32px; float: left; - padding: 0; - margin: 0; - margin-right: 10px; - position: relative; - top: 13px; + margin-right: 12px; + padding: 6px 10px; + border: 1px solid #ccc; + @include border-radius(4px); + + + input.check_all_issues { + padding: 0; + margin: 0; + position: relative; + top: 3px; + } } .issues_content { @@ -57,23 +65,6 @@ input.check_all_issues { } } -.btn.close_issue { - color: #B94A48; - font-weight: bold; - @include shade; - &:hover { - color: #B94A48; - } -} -.btn.reopen_issue { - color: #468847; - font-weight: bold; - @include shade; - &:hover { - color: #468847; - } -} - @media (min-width: 800px) { .issues_filters select { width: 160px; } } @media (min-width: 1200px) { .issues_filters select { width: 220px; } } @@ -93,6 +84,13 @@ input.check_all_issues { .update_selected_issues { margin-left: 4px; } + + .select2-container .select2-choice { + height: 32px; + line-height: 28px; + color: #444 !important; + font-weight: 500; + } } } @@ -127,3 +125,21 @@ input.check_all_issues { form.edit-issue { margin: 0; } + +.merge-request, +.issue { + &.today { + background: #EFE; + border-color: #CEC; + } + + &.closed { + background: #F5f5f5; + border-color: #E5E5E5; + } + + &.merged { + background: #F5f5f5; + border-color: #E5E5E5; + } +} diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss index 186294424a2..a78a9cd4879 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -6,11 +6,10 @@ } .login-box{ - width: 304px; + max-width: 304px; position: relative; @include border-radius(5px); margin: auto; - padding: 20px; background: white; } @@ -23,7 +22,7 @@ background-color: #f1f1f1; font-size: 16px; padding: 14px 10px; - width: 280px; + width: 100%; height: auto; &.top { diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index d3462d6aaa7..5341f411e54 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -31,10 +31,10 @@ .mr_source_commit, .mr_target_commit { + margin-top: 10px; .commit { margin: 0; - padding: 0; - padding: 5px 0; + padding: 2px 0; list-style: none; &:hover { background: none; @@ -90,15 +90,8 @@ padding-top: 15px; } -.merge-request-branches { - .commit-row-message { - font-weight: normal !important; - } - - .select2-container .select2-single { - span { - font-weight: bold; - color: #555; - } - } +// hide mr close link for inline diff comment form +.diff-file .close-mr-link, +.diff-file .reopen-mr-link { + display: none; } diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index f706854b4fe..88f16a21596 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -35,9 +35,8 @@ width: 1%; &.active { a { - color: $style_color; - font-weight: bolder; - + color: #333; + font-weight: bold; &:after { content: ''; display: block; @@ -46,7 +45,7 @@ left: 50%; width: 0; height: 0; - border-color: transparent transparent #777 transparent; + border-color: transparent transparent #333 transparent; border-style: solid; border-width: 6px; margin-left: -6px; @@ -56,7 +55,20 @@ &:hover { a { - color: $style_color; + color: $link_color; + &:after { + content: ''; + display: block; + position: relative; + bottom: 8px; + left: 50%; + width: 0; + height: 0; + border-color: transparent transparent #29b transparent; + border-style: solid; + border-width: 6px; + margin-left: -6px; + } } } @@ -73,7 +85,7 @@ a { display: block; text-align: center; - font-weight: normal; + font-weight: 500; height: 38px; line-height: 34px; color: #777; @@ -83,4 +95,38 @@ padding-top: 2px; } } + + @media (max-width: $screen-xs-max) { + font-size: 18px; + margin: 0; + + max-height: none; + + &, .container { + padding: 0; + border-top: 0; + } + + ul { + height: auto; + + li { + display: list-item; + width: auto; + padding: 5px 0; + + &.active { + background-color: $primary_color; + + a { + color: #fff; + font-weight: normal; + text-shadow: none; + + &:after { display: none; } + } + } + } + } + } } diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 1ae0e161aa2..8a38cd5a939 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -47,13 +47,13 @@ ul.notes { .discussion-body { margin-left: 50px; - .file, + .diff-file, .discussion-hidden, .notes { @extend .borders; background-color: #F9F9F9; } - .file .notes { + .diff-file .notes { /* reset */ background: inherit; border: none; @@ -87,15 +87,10 @@ ul.notes { } .attachment { font-size: 14px; - margin-top: -20px; } .note-body { @include md-typography; margin-left: 45px; - - .highlight { - @include border-radius(4px); - } } .note-header { padding-bottom: 5px; @@ -118,7 +113,7 @@ ul.notes { } } -.file .notes_holder { +.diff-file .notes_holder { font-size: 13px; line-height: 18px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; @@ -188,7 +183,7 @@ ul.notes { } } } -.file .note .note-actions { +.diff-file .note .note-actions { right: 0; top: 0; } @@ -199,7 +194,7 @@ ul.notes { * Line note button on the side of diffs */ -.file tr.line_holder { +.diff-file tr.line_holder { .add-diff-note { background: url("diff_note_add.png") no-repeat left 0; height: 22px; @@ -239,22 +234,25 @@ ul.notes { .reply-btn { @extend .btn-primary; } -.file .content tr.line_holder:hover { - &> td.line_content { - background: $hover !important; - border-color: darken($hover, 10%) !important; +.diff-file .diff-content { + tr.line_holder:hover { + &> td.line_content { + background: $hover !important; + border-color: darken($hover, 10%) !important; + } + &> td.new_line, + &> td.old_line { + background: darken($hover, 4%) !important; + border-color: darken($hover, 10%) !important; + } } - &> td.new_line, - &> td.old_line { - background: darken($hover, 4%) !important; - border-color: darken($hover, 10%) !important; + + tr.line_holder:hover > td .line_note_link { + opacity: 1.0; + filter: alpha(opacity=100); } } -.file .content tr.line_holder:hover > td .line_note_link { - opacity: 1.0; - filter: alpha(opacity=100); -} -.file, +.diff-file, .discussion { .new_note { margin: 0; @@ -292,7 +290,7 @@ ul.notes { box-shadow: none; font-size: 14px; height: 80px; - width: 98.6%; + width: 100%; } } } @@ -306,6 +304,7 @@ ul.notes { @extend .col-md-4; @extend .thumbnail; margin-left: 45px; + float: none; } @@ -341,7 +340,7 @@ ul.notes { box-shadow: none; font-size: 14px; height: 80px; - width: 98.6%; + width: 100%; } .form-actions { diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 3a3bf7cdf2a..7a696c21e47 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -105,3 +105,23 @@ } } } + +.profile-groups-avatars { + margin: 0 5px 10px 0; + + img { + width: 50px; + height: 50px; + } +} + +.global-notifications-form .level-title { + font-size: 15px; + color: #333; + font-weight: bold; +} + +.notification-icon-holder { + width: 20px; + float: left; +} diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 178364835ae..5757858a1ce 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -123,14 +123,9 @@ } .save-project-loader { - img { - margin-top: 50px; - margin-bottom: 50px; - } - h3 { - @extend .page-title; - } - + margin-top: 50px; + margin-bottom: 50px; + color: #555; } ul.nav.nav-projects-tabs { diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index dfdcbb59b6b..55a5819b55a 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -127,9 +127,27 @@ border-top: 1px dashed #CCC; padding-top: 10px; - h4 { + .readme-file-title { font-size: 14px; margin-bottom: 20px; color: #777; } } + +.blob-commit-info { + list-style: none; + margin: 0; + padding: 0; + margin-bottom: 10px; + + .commit { + .commit-row-title { + font-size: 13px; + + .commit-row-message { + font-weight: normal; + color: #555; + } + } + } +} diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss index 7d9cab215c8..edac4290e74 100644 --- a/app/assets/stylesheets/themes/ui_color.scss +++ b/app/assets/stylesheets/themes/ui_color.scss @@ -18,7 +18,7 @@ .navbar-inner { background: #547; border-bottom: 1px solid #435; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #435; } @@ -36,4 +36,8 @@ } } } + + .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { + background: #769; + } } diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss index 41c08c840e2..959febad6fe 100644 --- a/app/assets/stylesheets/themes/ui_gray.scss +++ b/app/assets/stylesheets/themes/ui_gray.scss @@ -18,7 +18,7 @@ .navbar-inner { background: #373737; border-bottom: 1px solid #272727; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #272727; } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index aba3e0ca827..9af5adbf10a 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -18,7 +18,7 @@ .navbar-inner { background: #474D57; border-bottom: 1px solid #373D47; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #373D47; } diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss index 015a4bbf0c3..b0827deb1ac 100644 --- a/app/assets/stylesheets/themes/ui_modern.scss +++ b/app/assets/stylesheets/themes/ui_modern.scss @@ -18,7 +18,7 @@ .navbar-inner { background: #345; border-bottom: 1px solid #234; - .app_logo { + .app_logo, .navbar-toggle { &:hover { background-color: #234; } diff --git a/app/contexts/commit_load_context.rb b/app/contexts/commit_load_context.rb deleted file mode 100644 index 0c684976b64..00000000000 --- a/app/contexts/commit_load_context.rb +++ /dev/null @@ -1,34 +0,0 @@ -class CommitLoadContext < BaseContext - def execute - result = { - commit: nil, - suppress_diff: false, - line_notes: [], - notes_count: 0, - note: nil, - status: :ok - } - - commit = project.repository.commit(params[:id]) - - if commit - line_notes = project.notes.for_commit_id(commit.id).inline - - result[:commit] = commit - result[:note] = project.build_commit_note(commit) - result[:line_notes] = line_notes - result[:notes_count] = project.notes.for_commit_id(commit.id).count - result[:branches] = project.repository.branch_names_contains(commit.id) - - begin - result[:suppress_diff] = true if commit.diff_suppress? && !params[:force_show_diff] - result[:force_suppress_diff] = commit.diff_force_suppress? - rescue Grit::Git::GitTimeout - result[:suppress_diff] = true - result[:status] = :huge_commit - end - end - - result - end -end diff --git a/app/contexts/filter_context.rb b/app/contexts/filter_context.rb deleted file mode 100644 index fb0d42cf87d..00000000000 --- a/app/contexts/filter_context.rb +++ /dev/null @@ -1,58 +0,0 @@ -class FilterContext - attr_accessor :klass, :current_user, :params - - def initialize(klass, current_user, params) - @klass = klass - @current_user = current_user - @params = params - end - - def execute - items = by_scope - items = by_state(items) - items = by_project(items) - items = by_search(items) - end - - private - - def by_scope - table_name = klass.table_name - - case params[:scope] - when 'authored' then - current_user.send(table_name) - when 'all' then - klass.of_projects(current_user.authorized_projects.pluck(:id)) - else - current_user.send("assigned_#{table_name}") - end - end - - def by_state(items) - case params[:status] - when 'closed' - items.closed - when 'all' - items - else - items.opened - end - end - - def by_project(items) - if params[:project_id].present? - items = items.of_projects(params[:project_id]) - end - - items - end - - def by_search(items) - if params[:search].present? - items = items.search(params[:search]) - end - - items - end -end diff --git a/app/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb deleted file mode 100644 index b256fd4c732..00000000000 --- a/app/contexts/issues/list_context.rb +++ /dev/null @@ -1,53 +0,0 @@ -module Issues - class ListContext < BaseContext - attr_accessor :issues - - def execute - @issues = @project.issues - - @issues = case params[:state] - when 'all' then @issues - when 'closed' then @issues.closed - else @issues.opened - end - - @issues = case params[:scope] - when 'assigned-to-me' then @issues.assigned_to(current_user) - when 'created-by-me' then @issues.authored(current_user) - else @issues - end - - @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present? - @issues = @issues.includes(:author, :project) - - # Filter by specific assignee_id (or lack thereof)? - if params[:assignee_id].present? - @issues = @issues.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) - end - - # Filter by specific milestone_id (or lack thereof)? - if params[:milestone_id].present? - @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) - end - - # Sort by :sort param - @issues = sort(@issues, params[:sort]) - - @issues - end - - private - - def sort(issues, condition) - case condition - when 'newest' then issues.except(:order).order('created_at DESC') - when 'oldest' then issues.except(:order).order('created_at ASC') - when 'recently_updated' then issues.except(:order).order('updated_at DESC') - when 'last_updated' then issues.except(:order).order('updated_at ASC') - when 'milestone_due_soon' then issues.except(:order).joins(:milestone).order("milestones.due_date ASC") - when 'milestone_due_later' then issues.except(:order).joins(:milestone).order("milestones.due_date DESC") - else issues - end - end - end -end diff --git a/app/contexts/merge_requests_load_context.rb b/app/contexts/merge_requests_load_context.rb deleted file mode 100644 index c3408db6e11..00000000000 --- a/app/contexts/merge_requests_load_context.rb +++ /dev/null @@ -1,35 +0,0 @@ -# Build collection of Merge Requests -# based on filtering passed via params for @project -class MergeRequestsLoadContext < BaseContext - def execute - merge_requests = @project.merge_requests - - merge_requests = case params[:state] - when 'all' then merge_requests - when 'closed' then merge_requests.closed - else merge_requests.opened - end - - merge_requests = case params[:scope] - when 'assigned-to-me' then merge_requests.assigned_to(current_user) - when 'created-by-me' then merge_requests.authored(current_user) - else merge_requests - end - - - merge_requests = merge_requests.page(params[:page]).per(20) - merge_requests = merge_requests.includes(:author, :source_project, :target_project).order("created_at desc") - - # Filter by specific assignee_id (or lack thereof)? - if params[:assignee_id].present? - merge_requests = merge_requests.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) - end - - # Filter by specific milestone_id (or lack thereof)? - if params[:milestone_id].present? - merge_requests = merge_requests.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) - end - - merge_requests - end -end diff --git a/app/contexts/notes/load_context.rb b/app/contexts/notes/load_context.rb deleted file mode 100644 index 234e9ac3cdd..00000000000 --- a/app/contexts/notes/load_context.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Notes - class LoadContext < BaseContext - def execute - target_type = params[:target_type] - target_id = params[:target_id] - - - @notes = case target_type - when "commit" - project.notes.for_commit_id(target_id).not_inline.fresh - when "issue" - project.issues.find(target_id).notes.inc_author.fresh - when "merge_request" - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh - when "snippet" - project.snippets.find(target_id).notes.fresh - end - end - end -end diff --git a/app/contexts/test_hook_context.rb b/app/contexts/test_hook_context.rb deleted file mode 100644 index 63eda6c7d06..00000000000 --- a/app/contexts/test_hook_context.rb +++ /dev/null @@ -1,7 +0,0 @@ -class TestHookContext < BaseContext - def execute - hook = project.hooks.find(params[:id]) - data = GitPushService.new.sample_data(project, current_user) - hook.execute(data) - end -end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 89b395786b3..4bb3cf07da0 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -52,6 +52,6 @@ class Admin::GroupsController < Admin::ApplicationController private def group - @group = Group.find_by_path(params[:id]) + @group = Group.find_by(path: params[:id]) end end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 7e3e29f59fb..13a7bdcf34a 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController def index owner_id = params[:owner_id] - user = User.find_by_id(owner_id) + user = User.find_by(id: owner_id) @projects = user ? user.owned_projects : Project.all @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @@ -19,7 +19,7 @@ class Admin::ProjectsController < Admin::ApplicationController end def transfer - result = ::Projects::TransferContext.new(@project, current_user, project: params).execute(:admin) + result = ::Projects::TransferService.new(@project, current_user, project: params).execute(:admin) if result redirect_to [:admin, @project] diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 2f981627137..5b06af79d5a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -68,7 +68,9 @@ class Admin::UsersController < Admin::ApplicationController params[:user].delete(:password_confirmation) end - user.admin = (admin && admin.to_i > 0) + if admin.present? + user.admin = !admin.to_i.zero? + end respond_to do |format| if user.update_attributes(params[:user], as: :admin) @@ -100,6 +102,6 @@ class Admin::UsersController < Admin::ApplicationController protected def user - @user ||= User.find_by_username!(params[:id]) + @user ||= User.find_by!(username: params[:id]) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b6501eacea3..9ed46c23942 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -135,12 +135,12 @@ class ApplicationController < ActionController::Base end end - def render_404 - render file: Rails.root.join("public", "404"), layout: false, status: "404" + def render_403 + head :forbidden end - def render_403 - render file: Rails.root.join("public", "403"), layout: false, status: "403" + def render_404 + render file: Rails.root.join("public", "404"), layout: false, status: "404" end def require_non_empty_project @@ -154,7 +154,6 @@ class ApplicationController < ActionController::Base end def dev_tools - Rack::MiniProfiler.authorize_request end def default_headers @@ -171,6 +170,7 @@ class ApplicationController < ActionController::Base gon.api_token = current_user.private_token if current_user gon.gravatar_url = request.ssl? || Gitlab.config.gitlab.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url gon.relative_url_root = Gitlab.config.gitlab.relative_url_root + gon.gravatar_enabled = Gitlab.config.gravatar.enabled end def check_password_expiration @@ -207,7 +207,11 @@ class ApplicationController < ActionController::Base end def configure_permitted_parameters - devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password) } + devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) } devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) } end + + def hexdigest(string) + Digest::SHA1.hexdigest string + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 27955c62488..a74e97ac253 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -3,6 +3,8 @@ class DashboardController < ApplicationController before_filter :load_projects, except: [:projects] before_filter :event_filter, only: :show + before_filter :default_filter, only: [:issues, :merge_requests] + def show # Fetch only 30 projects. @@ -39,7 +41,7 @@ class DashboardController < ApplicationController current_user.authorized_projects end - @projects = @projects.where(namespace_id: Group.find_by_name(params[:group])) if params[:group].present? + @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.includes(:namespace) @projects = @projects.tagged_with(params[:label]) if params[:label].present? @@ -51,14 +53,15 @@ class DashboardController < ApplicationController end def merge_requests - @merge_requests = FilterContext.new(MergeRequest, current_user, params).execute - @merge_requests = @merge_requests.recent.page(params[:page]).per(20) + @merge_requests = MergeRequestsFinder.new.execute(current_user, params) + @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = @merge_requests.preload(:author, :target_project) end def issues - @issues = FilterContext.new(Issue, current_user, params).execute - @issues = @issues.recent.page(params[:page]).per(20) - @issues = @issues.includes(:author, :project) + @issues = IssuesFinder.new.execute(current_user, params) + @issues = @issues.page(params[:page]).per(20) + @issues = @issues.preload(:author, :project) respond_to do |format| format.html @@ -71,4 +74,10 @@ class DashboardController < ApplicationController def load_projects @projects = current_user.authorized_projects.sorted_by_activity.non_archived end + + def default_filter + params[:scope] = 'assigned-to-me' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + params[:authorized_only] = true + end end diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb deleted file mode 100644 index a0c8a000fc7..00000000000 --- a/app/controllers/errors_controller.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ErrorsController < ApplicationController -end diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb new file mode 100644 index 00000000000..38071410f40 --- /dev/null +++ b/app/controllers/groups/avatars_controller.rb @@ -0,0 +1,12 @@ +class Groups::AvatarsController < ApplicationController + layout "profile" + + def destroy + @group = Group.find_by(path: params[:group_id]) + @group.remove_avatar! + + @group.save + + redirect_to edit_group_path(@group) + end +end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 5e8efe35a46..ebddf36de97 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,4 +1,5 @@ class GroupsController < ApplicationController + skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests] respond_to :html before_filter :group, except: [:new, :create] @@ -10,6 +11,8 @@ class GroupsController < ApplicationController # Load group projects before_filter :projects, except: [:new, :create] + before_filter :default_filter, only: [:issues, :merge_requests] + layout :determine_layout before_filter :set_title, only: [:new, :create] @@ -34,7 +37,7 @@ class GroupsController < ApplicationController @events = Event.in_projects(project_ids) @events = event_filter.apply_filter(@events) @events = @events.limit(20).offset(params[:offset] || 0) - @last_push = current_user.recent_push + @last_push = current_user.recent_push if current_user respond_to do |format| format.html @@ -43,19 +46,16 @@ class GroupsController < ApplicationController end end - # Get authored or assigned open merge requests def merge_requests - @merge_requests = FilterContext.new(MergeRequest, current_user, params).execute - @merge_requests = @merge_requests.of_group(@group) - @merge_requests = @merge_requests.recent.page(params[:page]).per(20) + @merge_requests = MergeRequestsFinder.new.execute(current_user, params) + @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = @merge_requests.preload(:author, :target_project) end - # Get only assigned issues def issues - @issues = FilterContext.new(Issue, current_user, params).execute - @issues = @issues.of_group(@group) - @issues = @issues.recent.page(params[:page]).per(20) - @issues = @issues.includes(:author, :project) + @issues = IssuesFinder.new.execute(current_user, params) + @issues = @issues.page(params[:page]).per(20) + @issues = @issues.preload(:author, :project) respond_to do |format| format.html @@ -65,7 +65,14 @@ class GroupsController < ApplicationController def members @project = group.projects.find(params[:project_id]) if params[:project_id] - @members = group.users_groups.order('group_access DESC') + @members = group.users_groups + + if params[:search].present? + users = group.users.search(params[:search]) + @members = @members.where(user_id: users) + end + + @members = @members.order('group_access DESC').page(params[:page]).per(50) @users_group = UsersGroup.new end @@ -89,21 +96,25 @@ class GroupsController < ApplicationController protected def group - @group ||= Group.find_by_path(params[:id]) + @group ||= Group.find_by(path: params[:id]) end def projects - @projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity + @projects ||= ProjectsFinder.new.execute(current_user, group: group) end def project_ids - projects.map(&:id) + projects.pluck(:id) end # Dont allow unauthorized access to group def authorize_read_group! unless @group and (projects.present? or can?(current_user, :read_group, @group)) - return render_404 + if current_user.nil? + return authenticate_user! + else + return render_404 + end end end @@ -126,8 +137,22 @@ class GroupsController < ApplicationController def determine_layout if [:new, :create].include?(action_name.to_sym) 'navless' - else + elsif current_user 'group' + else + 'public_group' + end + end + + def default_filter + if params[:scope].blank? + if current_user + params[:scope] = 'assigned-to-me' + else + params[:scope] = 'all' + end end + params[:state] = 'opened' if params[:state].blank? + params[:group_id] = @group.id end end diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb new file mode 100644 index 00000000000..9996b67a8a4 --- /dev/null +++ b/app/controllers/profiles/emails_controller.rb @@ -0,0 +1,26 @@ +class Profiles::EmailsController < ApplicationController + layout "profile" + + def index + @primary = current_user.email + @emails = current_user.emails + end + + def create + @email = current_user.emails.new(params[:email]) + + flash[:alert] = @email.errors.full_messages.first unless @email.save + + redirect_to profile_emails_url + end + + def destroy + @email = current_user.emails.find(params[:id]) + @email.destroy + + respond_to do |format| + format.html { redirect_to profile_emails_url } + format.js { render nothing: true } + end + end +end diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb index 378ff6bcf34..9a4d088651e 100644 --- a/app/controllers/profiles/groups_controller.rb +++ b/app/controllers/profiles/groups_controller.rb @@ -7,18 +7,17 @@ class Profiles::GroupsController < ApplicationController def leave @users_group = group.users_groups.where(user_id: current_user.id).first - - if group.last_owner?(current_user) - redirect_to(profile_groups_path, alert: "You can't leave group. You must add at least one more owner to it.") - else + if can?(current_user, :destroy, @users_group) @users_group.destroy redirect_to(profile_groups_path, info: "You left #{group.name} group.") + else + return render_403 end end private def group - @group ||= Group.find_by_path(params[:id]) + @group ||= Group.find_by(path: params[:id]) end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 541319e8d0f..b4f14e649e2 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -1,5 +1,6 @@ class Profiles::KeysController < ApplicationController layout "profile" + skip_before_filter :authenticate_user!, only: [:get_keys] def index @keys = current_user.keys.order('id DESC') @@ -32,4 +33,24 @@ class Profiles::KeysController < ApplicationController format.js { render nothing: true } end end + + # Get all keys of a user(params[:username]) in a text format + # Helpful for sysadmins to put in respective servers + def get_keys + if params[:username].present? + begin + user = User.find_by_username(params[:username]) + if user.present? + render text: user.all_ssh_keys.join("\n") + else + render_404 and return + end + rescue => e + render text: e.message + end + else + render_404 and return + end + end + end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index fc9807e3c47..a1a8bed09f4 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -6,6 +6,7 @@ class Projects::BlobController < Projects::ApplicationController before_filter :authorize_read_project! before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :authorize_push!, only: [:destroy] before_filter :blob @@ -13,7 +14,7 @@ class Projects::BlobController < Projects::ApplicationController end def destroy - result = Files::DeleteContext.new(@project, current_user, params, @ref, @path).execute + result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 65b8a7283a7..c56df65dcba 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -6,34 +6,35 @@ class Projects::CommitController < Projects::ApplicationController before_filter :authorize_read_project! before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :commit def show - result = CommitLoadContext.new(project, current_user, params).execute + return git_not_found! unless @commit - @commit = result[:commit] + @line_notes = project.notes.for_commit_id(commit.id).inline + @branches = project.repository.branch_names_contains(commit.id) - if @commit.nil? - git_not_found! - return + begin + @suppress_diff = true if commit.diff_suppress? && !params[:force_show_diff] + @force_suppress_diff = commit.diff_force_suppress? + rescue Grit::Git::GitTimeout + @suppress_diff = true + @status = :huge_commit end - @suppress_diff = result[:suppress_diff] - @force_suppress_diff = result[:force_suppress_diff] - - @note = result[:note] - @line_notes = result[:line_notes] - @branches = result[:branches] - @notes_count = result[:notes_count] + @note = project.build_commit_note(commit) + @notes_count = project.notes.for_commit_id(commit.id).count @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh @noteable = @commit - @comments_allowed = @reply_allowed = true - @comments_target = { noteable_type: 'Commit', - commit_id: @commit.id } + @comments_target = { + noteable_type: 'Commit', + commit_id: @commit.id + } respond_to do |format| format.html do - if result[:status] == :huge_commit + if @status == :huge_commit render "huge_commit" and return end end @@ -42,4 +43,8 @@ class Projects::CommitController < Projects::ApplicationController format.patch { render text: @commit.to_patch } end end + + def commit + @commit ||= project.repository.commit(params[:id]) + end end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index b7531e2cefb..234b6058ff0 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -8,13 +8,14 @@ class Projects::CompareController < Projects::ApplicationController end def show - compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to]) + compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to], MergeRequestDiff::COMMITS_SAFE_SIZE) @commits = compare.commits @commit = compare.commit @diffs = compare.diffs @refs_are_same = compare.same @line_notes = [] + @timeout = compare.timeout diff_line_count = Commit::diff_line_count(@diffs) @suppress_diff = Commit::diff_suppress?(@diffs, diff_line_count) && !params[:force_show_diff] diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index 3921273620c..ff5206b6fa1 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -1,17 +1,25 @@ class Projects::EditTreeController < Projects::BaseTreeController before_filter :require_branch_head before_filter :blob + before_filter :authorize_push! + before_filter :from_merge_request + before_filter :after_edit_path def show @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha end def update - result = Files::UpdateContext.new(@project, current_user, params, @ref, @path).execute + result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to project_blob_path(@project, @id) + + if from_merge_request + from_merge_request.reload_code + end + + redirect_to after_edit_path else flash[:alert] = result[:error] render :show @@ -23,4 +31,19 @@ class Projects::EditTreeController < Projects::BaseTreeController def blob @blob ||= @repository.blob_at(@commit.id, @path) end + + def after_edit_path + @after_edit_path ||= + if from_merge_request + diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) + + "#file-path-#{hexdigest(@path)}" + else + project_blob_path(@project, @id) + end + end + + def from_merge_request + # If blob edit was initiated from merge request page + @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) + end end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 314d87df034..a863b318324 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -24,15 +24,20 @@ class Projects::HooksController < Projects::ApplicationController end def test - TestHookContext.new(project, current_user, params).execute + TestHookService.new.execute(hook, current_user) redirect_to :back end def destroy - @hook = @project.hooks.find(params[:id]) - @hook.destroy + hook.destroy redirect_to project_hooks_path(@project) end + + private + + def hook + @hook ||= @project.hooks.find(params[:id]) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c58ac71277e..9d97c820f38 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -11,6 +11,9 @@ class Projects::IssuesController < Projects::ApplicationController # Allow modify issue before_filter :authorize_modify_issue!, only: [:edit, :update] + # Allow issues bulk update + before_filter :authorize_admin_issues!, only: [:bulk_update] + respond_to :html def index @@ -25,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? sort_param = params[:sort] || 'newest' @sort = sort_param.humanize unless sort_param.empty? - + @assignees = User.where(id: @project.issues.pluck(:assignee_id)) respond_to do |format| format.html @@ -89,7 +92,7 @@ class Projects::IssuesController < Projects::ApplicationController end def bulk_update - result = Issues::BulkUpdateContext.new(project, current_user, params).execute + result = Issues::BulkUpdateService.new(project, current_user, params).execute redirect_to :back, notice: "#{result[:count]} issues updated" end @@ -97,7 +100,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue @issue ||= begin - @project.issues.find_by_iid!(params[:id]) + @project.issues.find_by!(iid: params[:id]) rescue ActiveRecord::RecordNotFound redirect_old end @@ -107,8 +110,8 @@ class Projects::IssuesController < Projects::ApplicationController return render_404 unless can?(current_user, :modify_issue, @issue) end - def authorize_admin_issue! - return render_404 unless can?(current_user, :admin_issue, @issue) + def authorize_admin_issues! + return render_404 unless can?(current_user, :admin_issue, @project) end def module_enabled @@ -116,7 +119,9 @@ class Projects::IssuesController < Projects::ApplicationController end def issues_filtered - @issues = Issues::ListContext.new(project, current_user, params).execute + params[:scope] = 'all' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id)) end # Since iids are implemented only in 6.1 @@ -125,7 +130,7 @@ class Projects::IssuesController < Projects::ApplicationController # To prevent 404 errors we provide a redirect to correct iids until 7.0 release # def redirect_old - issue = @project.issues.find_by_id(params[:id]) + issue = @project.issues.find_by(id: params[:id]) if issue redirect_to project_issue_path(@project, issue) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 65f9e2b9d57..0166ca9ff00 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -13,7 +13,7 @@ class Projects::LabelsController < Projects::ApplicationController def generate Gitlab::IssuesLabels.generate(@project) - redirect_to project_labels_path(@project) + redirect_to project_issues_path(@project) end protected diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index bba08bdd782..38e567f3b13 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -17,10 +17,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] def index - @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute + params[:sort] ||= 'newest' + params[:scope] = 'all' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + + @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id)) + @merge_requests = @merge_requests.page(params[:page]).per(20) + + @sort = params[:sort].humanize assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? + @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id)) end def show @@ -52,8 +60,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController def new @merge_request = MergeRequest.new(params[:merge_request]) @merge_request.source_project = @project unless @merge_request.source_project - @merge_request.target_project = @project unless @merge_request.target_project + @merge_request.target_project ||= (@project.forked_from_project || @project) @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names + + @merge_request.target_branch ||= @merge_request.target_project.default_branch + @source_project = @merge_request.source_project @merge_request end @@ -69,7 +80,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.author = current_user @target_branches ||= [] if @merge_request.save - @merge_request.reload_code redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.' else @source_project = @merge_request.source_project @@ -100,10 +110,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:merge_request].delete(:target_project_id) if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id)) - @merge_request.reload_code - @merge_request.mark_as_unchecked @merge_request.reset_events_cache - redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.' + + respond_to do |format| + format.js + format.html do + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.' + end + end else render "edit" end @@ -121,9 +135,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController def automerge return access_denied! unless allowed_to_merge? - if @merge_request.opened? && @merge_request.can_be_merged? + if @merge_request.open? && @merge_request.can_be_merged? @merge_request.should_remove_source_branch = params[:should_remove_source_branch] - @merge_request.automerge!(current_user) + @merge_request.automerge!(current_user, params[:merge_commit_message]) @status = true else @status = false @@ -145,10 +159,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController @target_project = selected_target_project @target_branches = @target_project.repository.branch_names @target_branches + + respond_to do |format| + format.js + end end def ci_status - status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) + status = @merge_request.source_project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) response = {status: status} render json: response @@ -157,11 +175,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController protected def selected_target_project - ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project + if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil? + @project + else + @project.forked_project_link.forked_from_project + end end def merge_request - @merge_request ||= @project.merge_requests.find_by_iid!(params[:id]) + @merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) end def closes_issues @@ -206,8 +228,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController # or from cache if already merged @commits = @merge_request.commits + @merge_request_diff = @merge_request.merge_request_diff @allowed_to_merge = allowed_to_merge? - @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge + @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge end def allowed_to_merge? diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index ecb1fc1d566..aea92a19f34 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -76,7 +76,7 @@ class Projects::MilestonesController < Projects::ApplicationController protected def milestone - @milestone ||= @project.milestones.find_by_iid!(params[:id]) + @milestone ||= @project.milestones.find_by!(iid: params[:id]) end def authorize_admin_milestone! diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb index 933a0cb876e..3a51a78ef6f 100644 --- a/app/controllers/projects/new_tree_controller.rb +++ b/app/controllers/projects/new_tree_controller.rb @@ -1,12 +1,13 @@ class Projects::NewTreeController < Projects::BaseTreeController before_filter :require_branch_head + before_filter :authorize_push! def show end def update file_path = File.join(@path, File.basename(params[:file_name])) - result = Files::CreateContext.new(@project, current_user, params, @ref, file_path).execute + result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 261841f1ea2..85d042a89b5 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -5,7 +5,7 @@ class Projects::NotesController < Projects::ApplicationController before_filter :authorize_admin_note!, only: [:update, :destroy] def index - @notes = Notes::LoadContext.new(project, current_user, params).execute + @notes = NotesFinder.new.execute(project, current_user, params) notes_json = { notes: [] } @@ -20,7 +20,7 @@ class Projects::NotesController < Projects::ApplicationController end def create - @note = Notes::CreateContext.new(project, current_user, params).execute + @note = Notes::CreateService.new(project, current_user, params).execute respond_to do |format| format.json { render_note_json(@note) } diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 18ace028b0c..a6b7ae3f127 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -11,11 +11,7 @@ class Projects::RawController < Projects::ApplicationController @blob = @repository.blob_at(@commit.id, @path) if @blob - type = if @blob.mime_type =~ /html|javascript/ - 'text/plain; charset=utf-8' - else - @blob.mime_type - end + type = get_blob_type headers['X-Content-Type-Options'] = 'nosniff' @@ -29,5 +25,17 @@ class Projects::RawController < Projects::ApplicationController not_found! end end + + private + + def get_blob_type + if @blob.mime_type =~ /html|javascript/ + 'text/plain; charset=utf-8' + elsif @blob.name =~ /(?:msi|exe|rar|r0\d|7z|7zip|zip)$/ + 'application/octet-stream' + else + @blob.mime_type + end + end end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 16621c0371e..7b8026cff9f 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -34,7 +34,7 @@ class Projects::RefsController < Projects::ApplicationController contents = tree.entries @logs = contents.map do |content| file = params[:path] ? File.join(params[:path], content.name) : content.name - last_commit = @repo.commits(@commit.id, file, 1).last + last_commit = @repo.last_commit_for_path(@commit.id, file) { file_name: content.name, commit: last_commit diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 9dbb0d81888..818c5d971e9 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -8,7 +8,7 @@ class Projects::TagsController < Projects::ApplicationController before_filter :authorize_admin_project!, only: [:destroy] def index - @tags = Kaminari.paginate_array(@repository.tags).page(params[:page]).per(30) + @tags = Kaminari.paginate_array(@repository.tags.reverse).page(params[:page]).per(30) end def create diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb index 2c40b3c75f2..44068878cd1 100644 --- a/app/controllers/projects/team_members_controller.rb +++ b/app/controllers/projects/team_members_controller.rb @@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def update - @user_project_relation = project.users_projects.find_by_user_id(member) + @user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation.update_attributes(params[:team_member]) unless @user_project_relation.valid? @@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def destroy - @user_project_relation = project.users_projects.find_by_user_id(member) + @user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation.destroy respond_to do |format| @@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def leave - project.users_projects.find_by_user_id(current_user).destroy + project.users_projects.find_by(user_id: current_user).destroy respond_to do |format| format.html { redirect_to :back } @@ -65,6 +65,6 @@ class Projects::TeamMembersController < Projects::ApplicationController protected def member - @member ||= User.find_by_username(params[:id]) + @member ||= User.find_by(username: params[:id]) end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e1c55e7d913..6ec109b9145 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -20,7 +20,7 @@ class ProjectsController < ApplicationController end def create - @project = ::Projects::CreateContext.new(current_user, params[:project]).execute + @project = ::Projects::CreateService.new(current_user, params[:project]).execute respond_to do |format| flash[:notice] = 'Project was successfully created.' if @project.saved? @@ -36,7 +36,7 @@ class ProjectsController < ApplicationController end def update - status = ::Projects::UpdateContext.new(@project, current_user, params).execute + status = ::Projects::UpdateService.new(@project, current_user, params).execute respond_to do |format| if status @@ -51,7 +51,7 @@ class ProjectsController < ApplicationController end def transfer - ::Projects::TransferContext.new(project, current_user, params).execute + ::Projects::TransferService.new(project, current_user, params).execute end def show @@ -89,7 +89,7 @@ class ProjectsController < ApplicationController end def fork - @forked_project = ::Projects::ForkContext.new(project, current_user).execute + @forked_project = ::Projects::ForkService.new(project, current_user).execute respond_to do |format| format.html do diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index d7297161c22..d6238f79547 100644 --- a/app/controllers/public/projects_controller.rb +++ b/app/controllers/public/projects_controller.rb @@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController layout 'public' def index - @projects = Project.public_or_internal_only(current_user) + @projects = Project.publicish(current_user) @projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.includes(:namespace).page(params[:page]).per(20) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index ba8f08c5b82..c1648d6c387 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,13 +1,23 @@ class SearchController < ApplicationController + include SearchHelper + def show - @project = Project.find_by_id(params[:project_id]) if params[:project_id].present? - @group = Group.find_by_id(params[:group_id]) if params[:group_id].present? + @project = Project.find_by(id: params[:project_id]) if params[:project_id].present? + @group = Group.find_by(id: params[:group_id]) if params[:group_id].present? if @project return access_denied! unless can?(current_user, :download_code, @project) - @search_results = Search::ProjectContext.new(@project, current_user, params).execute + @search_results = Search::ProjectService.new(@project, current_user, params).execute else - @search_results = Search::GlobalContext.new(current_user, params).execute + @search_results = Search::GlobalService.new(current_user, params).execute end end + + def autocomplete + term = params[:term] + @project = Project.find(params[:project_id]) if params[:project_id].present? + @ref = params[:project_ref] if params[:project_ref].present? + + render json: search_autocomplete_opts(term).to_json + end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index b91f68aab5e..e54a968326f 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -18,7 +18,7 @@ class SnippetsController < ApplicationController end def user_index - @user = User.find_by_username(params[:username]) + @user = User.find_by(username: params[:username]) @snippets = @user.snippets.fresh.non_expired if @user == current_user diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4947c33f959..9461174b950 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,11 +1,23 @@ class UsersController < ApplicationController - layout 'navless' + skip_before_filter :authenticate_user!, only: [:show] + layout :determine_layout def show @user = User.find_by_username!(params[:username]) - @projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id)) - @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20) - + @projects = @user.authorized_projects.accessible_to(current_user) + if !current_user && @projects.empty? + return authenticate_user! + end + @groups = @user.groups.accessible_to(current_user) + @events = @user.recent_events.where(project_id: @projects.pluck(:id)).limit(20) @title = @user.name end + + def determine_layout + if current_user + 'navless' + else + 'public_users' + end + end end diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb index 749da1e1413..b9bdc189522 100644 --- a/app/controllers/users_groups_controller.rb +++ b/app/controllers/users_groups_controller.rb @@ -19,18 +19,21 @@ class UsersGroupsController < ApplicationController def destroy @users_group = @group.users_groups.find(params[:id]) - @users_group.destroy - - respond_to do |format| - format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' } - format.js { render nothing: true } + if can?(current_user, :destroy, @users_group) # May fail if last owner. + @users_group.destroy + respond_to do |format| + format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' } + format.js { render nothing: true } + end + else + return render_403 end end protected def group - @group ||= Group.find_by_path(params[:group_id]) + @group ||= Group.find_by(path: params[:group_id]) end def authorize_admin_group! diff --git a/app/finders/README.md b/app/finders/README.md new file mode 100644 index 00000000000..47823c51efb --- /dev/null +++ b/app/finders/README.md @@ -0,0 +1,22 @@ +# Finders + +This type of classes responsible for collectiong items based on different conditions. +To prevent lookup methods in models like this: + +```ruby +class Project + def issues_for_user_filtered_by(user, filter) + # A lot of logic not related to project model itself + end +end + +issues = project.issues_for_user_filtered_by(user, params) +``` + +Better use this: + +```ruby +issues = IssuesFinder.new.execute(project, user, filter) +``` + +It will help keep models thiner diff --git a/app/finders/base_finder.rb b/app/finders/base_finder.rb new file mode 100644 index 00000000000..d20716fb170 --- /dev/null +++ b/app/finders/base_finder.rb @@ -0,0 +1,137 @@ +# BaseFinder +# +# Used to filter Issues and MergeRequests collections by set of params +# +# Arguments: +# klass - actual class like Issue or MergeRequest +# current_user - which user use +# params: +# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# state: 'open' or 'closed' or 'all' +# group_id: integer +# project_id: integer +# milestone_id: integer +# assignee_id: integer +# search: string +# label_name: string +# sort: string +# +class BaseFinder + attr_accessor :current_user, :params + + def execute(current_user, params) + @current_user = current_user + @params = params + + items = init_collection + items = by_scope(items) + items = by_state(items) + items = by_group(items) + items = by_project(items) + items = by_search(items) + items = by_milestone(items) + items = by_assignee(items) + items = by_label(items) + items = sort(items) + end + + private + + def init_collection + table_name = klass.table_name + + if project + if project.public? || (current_user && current_user.can?(:read_project, project)) + project.send(table_name) + else + [] + end + elsif current_user && params[:authorized_only].presence + klass.of_projects(current_user.authorized_projects) + else + klass.of_projects(Project.accessible_to(current_user)) + end + end + + def by_scope(items) + case params[:scope] + when 'created-by-me', 'authored' then + items.where(author_id: current_user.id) + when 'all' then + items + when 'assigned-to-me' then + items.where(assignee_id: current_user.id) + else + raise 'You must specify default scope' + end + end + + def by_state(items) + case params[:state] + when 'closed' + items.closed + when 'all' + items + when 'opened' + items.opened + else + raise 'You must specify default state' + end + end + + def by_group(items) + if params[:group_id].present? + items = items.of_group(Group.find(params[:group_id])) + end + + items + end + + def by_project(items) + if params[:project_id].present? + items = items.of_projects(params[:project_id]) + end + + items + end + + def by_search(items) + if params[:search].present? + items = items.search(params[:search]) + end + + items + end + + def sort(items) + items.sort(params[:sort]) + end + + def by_milestone(items) + if params[:milestone_id].present? + items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) + end + + items + end + + def by_assignee(items) + if params[:assignee_id].present? + items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) + end + + items + end + + def by_label(items) + if params[:label_name].present? + items = items.tagged_with(params[:label_name]) + end + + items + end + + def project + Project.where(id: params[:project_id]).first if params[:project_id].present? + end +end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb new file mode 100644 index 00000000000..8e0c606249e --- /dev/null +++ b/app/finders/issues_finder.rb @@ -0,0 +1,22 @@ +# Finders::Issues class +# +# Used to filter Issues collections by set of params +# +# Arguments: +# current_user - which user use +# params: +# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# state: 'open' or 'closed' or 'all' +# group_id: integer +# project_id: integer +# milestone_id: integer +# assignee_id: integer +# search: string +# label_name: string +# sort: string +# +class IssuesFinder < BaseFinder + def klass + Issue + end +end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb new file mode 100644 index 00000000000..3727149c8fb --- /dev/null +++ b/app/finders/merge_requests_finder.rb @@ -0,0 +1,22 @@ +# Finders::MergeRequest class +# +# Used to filter MergeRequests collections by set of params +# +# Arguments: +# current_user - which user use +# params: +# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# state: 'open' or 'closed' or 'all' +# group_id: integer +# project_id: integer +# milestone_id: integer +# assignee_id: integer +# search: string +# label_name: string +# sort: string +# +class MergeRequestsFinder < BaseFinder + def klass + MergeRequest + end +end diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb new file mode 100644 index 00000000000..384316e14b7 --- /dev/null +++ b/app/finders/notes_finder.rb @@ -0,0 +1,17 @@ +class NotesFinder + def execute(project, current_user, params) + target_type = params[:target_type] + target_id = params[:target_id] + + case target_type + when "commit" + project.notes.for_commit_id(target_id).not_inline.fresh + when "issue" + project.issues.find(target_id).notes.inc_author.fresh + when "merge_request" + project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh + when "snippet" + project.snippets.find(target_id).notes.fresh + end + end +end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb new file mode 100644 index 00000000000..bfaba758788 --- /dev/null +++ b/app/finders/projects_finder.rb @@ -0,0 +1,63 @@ +class ProjectsFinder + def execute(current_user, options) + group = options[:group] + + if group + group_projects(current_user, group) + else + all_projects(current_user) + end + end + + private + + def group_projects(current_user, group) + if current_user + if group.users.include?(current_user) + # User is group member + # + # Return ALL group projects + group.projects + else + projects_members = UsersProject.where( + project_id: group.projects, + user_id: current_user + ) + + if projects_members.any? + # User is a project member + # + # Return only: + # public projects + # internal projects + # joined projects + # + group.projects.where( + "projects.id IN (?) OR projects.visibility_level IN (?)", + projects_members.pluck(:project_id), + Project.public_and_internal_levels + ) + else + # User has no access to group or group projects + # + # Return only: + # public projects + # internal projects + # + group.projects.public_and_internal_only + end + end + else + # Not authenticated + # + # Return only: + # public projects + group.projects.public_only + end + end + + def all_projects + # TODO: implement + raise 'Not implemented yet' + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0db43fa1809..4e7d01acd2a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -49,8 +49,17 @@ module ApplicationHelper args.any? { |v| v.to_s.downcase == action_name } end + def group_icon(group_path) + group = Group.find_by(path: group_path) + if group && group.avatar.present? + group.avatar.url + else + '/assets/no_group_avatar.png' + end + end + def avatar_icon(user_email = '', size = nil) - user = User.find_by_email(user_email) + user = User.find_by(email: user_email) if user && user.avatar.present? user.avatar.url else @@ -153,15 +162,6 @@ module ApplicationHelper alias_method :url_to_image, :image_url - def users_select_tag(id, opts = {}) - css_class = "ajax-users-select " - css_class << "multiselect " if opts[:multiple] - css_class << (opts[:class] || '') - value = opts[:selected] || '' - - hidden_field_tag(id, value, class: css_class) - end - def body_data_page path = controller.controller_path.split('/') namespace = path.first if path.second @@ -203,8 +203,14 @@ module ApplicationHelper def highlight_js(&block) string = capture(&block) - content_tag :div, class: user_color_scheme_class do - Pygments::Lexer[:js].highlight(string).html_safe + content_tag :div, class: "highlighted-data #{user_color_scheme_class}" do + content_tag :div, class: 'highlight' do + content_tag :pre do + content_tag :code do + string.html_safe + end + end + end end end @@ -221,4 +227,10 @@ module ApplicationHelper def render_markup(file_name, file_content) GitHub::Markup.render(file_name, file_content).html_safe end + + def spinner(text = nil) + content_tag :div, class: 'loading hide' do + content_tag(:i, nil, class: 'icon-spinner icon-spin') + text + end + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 663369e4584..5e5f3f77a21 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -122,17 +122,18 @@ module CommitsHelper def commit_person_link(commit, options = {}) source_name = commit.send "#{options[:source]}_name".to_sym source_email = commit.send "#{options[:source]}_email".to_sym + + user = User.find_for_commit(source_email, source_name) + person_name = user.nil? ? source_name : user.name + person_email = user.nil? ? source_email : user.email + text = if options[:avatar] - avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") - %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>} + avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") + %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>} else - source_name + person_name end - # Prefer email match over name match - user = User.where(email: source_email).first - user ||= User.where(name: source_name).first - options = { class: "commit-#{options[:source]}-link has_tooltip", data: { :'original-title' => sanitize(source_email) } diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 7f86a833cb0..d5712ab3374 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -1,7 +1,7 @@ module DashboardHelper def filter_path(entity, options={}) exist_opts = { - status: params[:status], + state: params[:state], scope: params[:scope], project_id: params[:project_id], } diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 8894a01eaea..6cad5e4658e 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -28,14 +28,16 @@ module GitlabMarkdownHelper link_to(gfm_body.html_safe, url, html_options) end - def markdown(text) - unless @markdown - gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, - # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- - filter_html: true, - with_toc_data: true, - hard_wrap: true, - safe_links_only: true) + def markdown(text, options={}) + unless (@markdown and options == @options) + @options = options + gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, { + # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- + filter_html: true, + with_toc_data: true, + hard_wrap: true, + safe_links_only: true + }.merge(options)) @markdown = Redcarpet::Markdown.new(gitlab_renderer, # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use no_intra_emphasis: true, @@ -47,7 +49,6 @@ module GitlabMarkdownHelper space_after_headers: true, superscript: true) end - @markdown.render(text).html_safe end @@ -69,10 +70,17 @@ module GitlabMarkdownHelper project_path_with_namespace = project.path_with_namespace paths = extract_paths(text) paths.each do |file_path| - new_path = rebuild_path(project_path_with_namespace, file_path, requested_path, ref) - # Replacing old string with a new one with brackets ]() to prevent replacing occurence of a word - # e.g. If we have a markdown like [test](test) this will replace ](test) and not the word test - text.gsub!("](#{file_path})", "](/#{new_path})") + original_file_path = extract(file_path) + new_path = rebuild_path(project_path_with_namespace, original_file_path, requested_path, ref) + if reference_path?(file_path) + # Replacing old string with a new one that contains updated path + # eg. [some document]: document.md will be replaced with [some document] /namespace/project/master/blob/document.md + text.gsub!(file_path, file_path.gsub(original_file_path, "/#{new_path}")) + else + # Replacing old string with a new one with brackets ]() to prevent replacing occurence of a word + # e.g. If we have a markdown like [test](test) this will replace ](test) and not the word test + text.gsub!("](#{file_path})", "](/#{new_path})") + end end text end @@ -83,9 +91,11 @@ module GitlabMarkdownHelper select_relative(paths) end - # Split the markdown text to each line and find all paths, this will match anything with - ]("some_text") + # Split the markdown text to each line and find all paths, this will match anything with - ]("some_text") and [some text]: file.md def pick_out_paths(markdown_text) - markdown_text.split("\n").map { |text| text.scan(/\]\(([^(]+)\)/) } + inline_paths = markdown_text.split("\n").map { |text| text.scan(/\]\(([^(]+)\)/) } + reference_paths = markdown_text.split("\n").map { |text| text.scan(/\[.*\]:.*/) } + inline_paths + reference_paths end # Removes any empty result produced by not matching the regexp @@ -93,23 +103,35 @@ module GitlabMarkdownHelper paths.reject{|l| l.empty? }.flatten end + # If a path is a reference style link we need to omit ]: + def extract(path) + path.split("]: ").last + end + # Reject any path that contains ignored protocol # eg. reject "https://gitlab.org} but accept "doc/api/README.md" def select_relative(paths) paths.reject{|path| ignored_protocols.map{|protocol| path.include?(protocol)}.any?} end + # Check whether a path is a reference-style link + def reference_path?(path) + path.include?("]: ") + end + def ignored_protocols ["http://","https://", "ftp://", "mailto:"] end def rebuild_path(path_with_namespace, path, requested_path, ref) + path.gsub!(/(#.*)/, "") + id = $1 || "" file_path = relative_file_path(path, requested_path) [ path_with_namespace, path_with_ref(file_path, ref), file_path - ].compact.join("/") + ].compact.join("/").gsub(/\/*$/, '') + id end # Checks if the path exists in the repo @@ -134,6 +156,7 @@ module GitlabMarkdownHelper # If we are at doc/api and the README.md shown in below the tree view # this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md def build_nested_path(path, request_path) + return request_path if path == "" return path unless request_path if local_path(request_path) == "tree" base = request_path.split("/").push(path) @@ -146,19 +169,28 @@ module GitlabMarkdownHelper end def file_exists?(path) - return false if path.nil? || path.empty? - File.exists?(path_on_fs(path)) + return false if path.nil? + return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any? end # Check if the path is pointing to a directory(tree) or a file(blob) # eg. doc/api is directory and doc/README.md is file def local_path(path) - File.directory?(path_on_fs(path)) ? "tree" : "blob" + return "tree" if @repository.tree(current_sha, path).entries.any? + return "raw" if @repository.blob_at(current_sha, path).image? + return "blob" end - # Path to the file in the satellites repository on the filesystem - def path_on_fs(path) - [@path_to_satellite, path].join("/") + def current_ref + @commit.nil? ? "master" : @commit.id + end + + def current_sha + if @commit + @commit.id + else + @repository.head_commit.sha + end end # We will assume that if no ref exists we can point to master diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 7c09273d53e..cfc9a572cac 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,6 +1,18 @@ module GroupsHelper def remove_user_from_group_message(group, user) - "You are going to remove #{user.name} from #{group.name} Group. Are you sure?" + "Are you sure you want to remove \"#{user.name}\" from \"#{group.name}\"?" + end + + def leave_group_message(group) + "Are you sure you want to leave \"#{group}\" group?" + end + + def should_user_see_group_roles?(user, group) + if user + user.is_admin? || group.members.exists?(user_id: user.id) + else + false + end end def group_head_title diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index cdba6ce84dc..95f0eff58b1 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -70,11 +70,11 @@ module IssuesHelper end def bulk_update_milestone_options - options_for_select(["None (backlog)", nil]) + options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id]) + options_for_select(["None (backlog)"]) + options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id]) end def bulk_update_assignee_options - options_for_select(["None (unassigned)", nil]) + options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]) + options_for_select(["None (unassigned)"]) + options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]) end def assignee_options object @@ -84,4 +84,16 @@ module IssuesHelper def milestone_options object options_from_collection_for_select(@project.milestones.active, 'id', 'title', object.milestone_id) end + + def issue_box_class(item) + if item.respond_to?(:expired?) && item.expired? + 'issue-box-expired' + elsif item.respond_to?(:merged?) && item.merged? + 'issue-box-merged' + elsif item.closed? + 'issue-box-closed' + else + 'issue-box-open' + end + end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 5e3f82fe9ce..1381b0220d6 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,8 +1,9 @@ module MergeRequestsHelper def new_mr_path_from_push_event(event) + target_project = event.project.forked_from_project || event.project new_project_merge_request_path( event.project, - new_mr_from_push_event(event, event.project) + new_mr_from_push_event(event, target_project) ) end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 695147cd6db..7ae104b8fd1 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -17,7 +17,7 @@ module NotesHelper def link_to_merge_request_diff_line_note(note) if note.for_merge_request_diff_line? and note.diff - link_to "#{note.diff_file_name}:L#{note.diff_new_line}", diffs_project_merge_request_path(note.project, note.noteable_id, anchor: note.line_code) + link_to "#{note.diff_file_name}:L#{note.diff_new_line}", diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code) end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index ae3402b2617..b2399bb6db1 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -1,11 +1,11 @@ module NotificationsHelper def notification_icon(notification) if notification.disabled? - content_tag :i, nil, class: 'icon-circle cred' + content_tag :i, nil, class: 'icon-volume-off cred' elsif notification.participating? - content_tag :i, nil, class: 'icon-circle cblue' + content_tag :i, nil, class: 'icon-volume-down cblue' elsif notification.watch? - content_tag :i, nil, class: 'icon-circle cgreen' + content_tag :i, nil, class: 'icon-volume-up cgreen' else content_tag :i, nil, class: 'icon-circle-blank cblue' end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a6a507360bd..e943045e414 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -64,6 +64,31 @@ module ProjectsHelper project_nav_tabs.include? name end + def selected_label?(label_name) + params[:label_name].to_s.split(',').include?(label_name) + end + + def labels_filter_path(label_name) + label_name = + if selected_label?(label_name) + params[:label_name].split(',').reject { |l| l == label_name }.join(',') + elsif params[:label_name].present? + "#{params[:label_name]},#{label_name}" + else + label_name + end + + project_filter_path(label_name: label_name) + end + + def label_filter_class(label_name) + if selected_label?(label_name) + 'label-filter-item active' + else + 'label-filter-item light' + end + end + def project_filter_path(options={}) exist_opts = { state: params[:state], diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index f24156e4d85..d7f3da7e537 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,16 +1,21 @@ module SearchHelper - def search_autocomplete_source + def search_autocomplete_opts(term) return unless current_user + + resources_results = [ + groups_autocomplete(term), + projects_autocomplete(term) + ].flatten + + generic_results = project_autocomplete + default_autocomplete + help_autocomplete + generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") } + [ - groups_autocomplete, - projects_autocomplete, - public_projects_autocomplete, - default_autocomplete, - project_autocomplete, - help_autocomplete + resources_results, + generic_results ].flatten.uniq do |item| item[:label] - end.to_json + end end private @@ -43,7 +48,7 @@ module SearchHelper # Autocomplete results for the current project, if it's defined def project_autocomplete if @project && @project.repository.exists? && @project.repository.root_ref - prefix = simple_sanitize(@project.name_with_namespace) + prefix = search_result_sanitize(@project.name_with_namespace) ref = @ref || @project.repository.root_ref [ @@ -65,23 +70,26 @@ module SearchHelper end # Autocomplete results for the current user's groups - def groups_autocomplete - current_user.authorized_groups.map do |group| - { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) } + def groups_autocomplete(term, limit = 5) + current_user.authorized_groups.search(term).limit(limit).map do |group| + { + label: "group: #{search_result_sanitize(group.name)}", + url: group_path(group) + } end end # Autocomplete results for the current user's projects - def projects_autocomplete - current_user.authorized_projects.non_archived.map do |p| - { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } + def projects_autocomplete(term, limit = 5) + Project.accessible_to(current_user).search_by_title(term).non_archived.limit(limit).map do |p| + { + label: "project: #{search_result_sanitize(p.name_with_namespace)}", + url: project_path(p) + } end end - # Autocomplete results for the current user's projects - def public_projects_autocomplete - Project.public_or_internal_only(current_user).non_archived.map do |p| - { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } - end + def search_result_sanitize(str) + Sanitize.clean(str) end end diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb new file mode 100644 index 00000000000..a1fe4488ae9 --- /dev/null +++ b/app/helpers/selects_helper.rb @@ -0,0 +1,20 @@ +module SelectsHelper + def users_select_tag(id, opts = {}) + css_class = "ajax-users-select " + css_class << "multiselect " if opts[:multiple] + css_class << (opts[:class] || '') + value = opts[:selected] || '' + + hidden_field_tag(id, value, class: css_class) + end + + def project_users_select_tag(id, opts = {}) + css_class = "ajax-project-users-select " + css_class << "multiselect " if opts[:multiple] + css_class << (opts[:class] || '') + value = opts[:selected] || '' + placeholder = opts[:placeholder] || 'Select user' + + hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder) + end +end diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb new file mode 100644 index 00000000000..56f6e4d9cc1 --- /dev/null +++ b/app/helpers/submodule_helper.rb @@ -0,0 +1,43 @@ +module SubmoduleHelper + include Gitlab::ShellAdapter + + # links to files listing for submodule if submodule is a project on this server + def submodule_links(submodule_item) + url = @repository.submodule_url_for(@ref, submodule_item.path) + + return url, nil unless url =~ /([^\/:]+\/[^\/]+\.git)\Z/ + + project = $1 + project.chomp!('.git') + + if self_url?(url, project) + return project_path(project), project_tree_path(project, submodule_item.id) + elsif github_dot_com_url?(url) + standard_links('github.com', project, submodule_item.id) + elsif gitlab_dot_com_url?(url) + standard_links('gitlab.com', project, submodule_item.id) + else + return url, nil + end + end + + protected + + def github_dot_com_url?(url) + url =~ /github\.com[\/:][^\/]+\/[^\/]+\Z/ + end + + def gitlab_dot_com_url?(url) + url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/ + end + + def self_url?(url, project) + return true if url == [ Gitlab.config.gitlab.url, '/', project, '.git' ].join('') + url == gitlab_shell.url_to_repo(project) + end + + def standard_links(host, project, commit) + base = [ 'https://', host, '/', project ].join('') + return base, [ base, '/tree/', commit ].join('') + end +end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 5abdf99529c..3adb47dc5b1 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -3,22 +3,27 @@ module Emails def new_issue_email(recipient_id, issue_id) @issue = Issue.find(issue_id) @project = @issue.project - mail(to: recipient(recipient_id), subject: subject("New issue ##{@issue.iid}", @issue.title)) + mail(from: sender(@issue.author_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end - def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) + def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) @issue = Issue.find(issue_id) - @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id + @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @issue.project - mail(to: recipient(recipient_id), subject: subject("Changed issue ##{@issue.iid}", @issue.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @issue = Issue.find issue_id @project = @issue.project @updated_by = User.find updated_by_user_id - mail(to: recipient(recipient_id), - subject: subject("Closed issue ##{@issue.iid}", @issue.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @@ -26,8 +31,9 @@ module Emails @issue_status = status @project = @issue.project @updated_by = User.find updated_by_user_id - mail(to: recipient(recipient_id), - subject: subject("Changed issue ##{@issue.iid}", @issue.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 25b8bf755e2..0845e14edc7 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -3,27 +3,35 @@ module Emails def new_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(@merge_request.author_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end - def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) + def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) - @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id + @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) @updated_by = User.find updated_by_user_id @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end def merged_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(@merge_request.author_id_of_changes), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end end @@ -57,7 +65,7 @@ module Emails # >> subject('Lorem ipsum', 'Dolor sit amet') # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet" def subject(*extra) - subject = "GitLab Merge Request |" + subject = "Merge Request | " if @merge_request.for_fork? subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}" else diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index e967cf6dc73..00b127da429 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,27 +4,35 @@ module Emails @note = Note.find(note_id) @commit = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note for commit #{@commit.short_id}", @commit.title)) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@commit.title} (#{@commit.short_id})")) end def note_issue_email(recipient_id, note_id) @note = Note.find(note_id) @issue = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note for issue ##{@issue.iid}")) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def note_merge_request_email(recipient_id, note_id) @note = Note.find(note_id) @merge_request = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note for merge request ##{@merge_request.iid}")) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end def note_wall_email(recipient_id, note_id) @note = Note.find(note_id) @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note on wall")) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("Note on wall")) end end end diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index bcd44f9476c..c91660a02b5 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -6,6 +6,12 @@ module Emails mail(to: @user.email, subject: subject("Account was created for you")) end + def new_email_email(email_id) + @email = Email.find(email_id) + @user = @email.user + mail(to: @user.email, subject: subject("Email was added to your account")) + end + def new_ssh_key_email(key_id) @key = Key.find(key_id) @user = @key.user diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index df21d7b5b02..46f24e9fb7c 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -17,11 +17,14 @@ module Emails def repository_push_email(project_id, recipient, author_id, branch, compare) @project = Project.find(project_id) @author = User.find(author_id) + @compare = compare @commits = Commit.decorate(compare.commits) @diffs = compare.diffs @branch = branch - mail(to: recipient, subject: subject("New push to repository")) + mail(from: sender(author_id), + to: recipient, + subject: subject("New push to repository")) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index aec4c57cba8..554f53cf148 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -12,19 +12,36 @@ class Notify < ActionMailer::Base default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:protocol] = Gitlab.config.gitlab.protocol - default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port? + default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root - default from: Gitlab.config.gitlab.email_from + default from: Proc.new { default_sender_address.format } default reply_to: "noreply@#{Gitlab.config.gitlab.host}" - # Just send email with 3 seconds delay + # Just send email with 2 seconds delay def self.delay delay_for(2.seconds) end private + # The default email address to send emails from + def default_sender_address + address = Mail::Address.new(Gitlab.config.gitlab.email_from) + address.display_name = "GitLab" + address + end + + # Return an email address that displays the name of the sender. + # Only the displayed name changes; the actual email address is always the same. + def sender(sender_id) + if sender = User.find(sender_id) + address = default_sender_address + address.display_name = sender.name + address.format + end + end + # Look up a User by their ID and return their email address # # recipient_id - User ID @@ -43,21 +60,21 @@ class Notify < ActionMailer::Base # Examples # # >> subject('Lorem ipsum') - # => "GitLab | Lorem ipsum" + # => "Lorem ipsum" # # # Automatically inserts Project name when @project is set # >> @project = Project.last # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...> # >> subject('Lorem ipsum') - # => "GitLab | Ruby on Rails | Lorem ipsum " + # => "Ruby on Rails | Lorem ipsum " # # # Accepts multiple arguments # >> subject('Lorem ipsum', 'Dolor sit amet') - # => "GitLab | Lorem ipsum | Dolor sit amet" + # => "Lorem ipsum | Dolor sit amet" def subject(*extra) - subject = "GitLab" - subject << (@project ? " | #{@project.name_with_namespace}" : "") - subject << " | " + extra.join(' | ') if extra.present? + subject = "" + subject << "#{@project.name} | " if @project + subject << extra.join(' | ') if extra.present? subject end end diff --git a/app/models/ability.rb b/app/models/ability.rb index cf925141f2d..69ada753d02 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -14,6 +14,7 @@ class Ability when "MergeRequest" then merge_request_abilities(user, subject) when "Group" then group_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject) + when "UsersGroup" then users_group_abilities(user, subject) else [] end.concat(global_abilities(user)) end @@ -42,7 +43,19 @@ class Ability :download_code ] else - [] + group = if subject.kind_of?(Group) + subject + elsif subject.respond_to?(:group) + subject.group + else + nil + end + + if group && group.has_projects_accessible_to?(nil) + [:read_group] + else + [] + end end end @@ -125,6 +138,8 @@ class Ability project_report_rules + [ :write_merge_request, :write_wiki, + :modify_issue, + :admin_issue, :push_code ] end @@ -169,7 +184,7 @@ class Ability def group_abilities user, group rules = [] - if group.users.include?(user) || user.admin? + if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? rules << :read_group end @@ -217,5 +232,19 @@ class Ability end end end + + def users_group_abilities(user, subject) + rules = [] + target_user = subject.user + group = subject.group + can_manage = group_abilities(user, group).include?(:manage_group) + if can_manage && (user != target_user) + rules << :modify + end + if !group.last_owner?(user) && (can_manage || (user == target_user)) + rules << :destroy + end + rules + end end end diff --git a/app/models/commit.rb b/app/models/commit.rb index dd1f9801878..c313aeb7572 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -16,29 +16,31 @@ class Commit DIFF_HARD_LIMIT_FILES = 500 DIFF_HARD_LIMIT_LINES = 10000 - def self.decorate(commits) - commits.map { |c| self.new(c) } - end + class << self + def decorate(commits) + commits.map { |c| self.new(c) } + end - # Calculate number of lines to render for diffs - def self.diff_line_count(diffs) - diffs.reduce(0){|sum, d| sum + d.diff.lines.count} - end + # Calculate number of lines to render for diffs + def diff_line_count(diffs) + diffs.reduce(0){|sum, d| sum + d.diff.lines.count} + end - def self.diff_suppress?(diffs, line_count = nil) - # optimize - check file count first - return true if diffs.size > DIFF_SAFE_FILES + def diff_suppress?(diffs, line_count = nil) + # optimize - check file count first + return true if diffs.size > DIFF_SAFE_FILES - line_count ||= Commit::diff_line_count(diffs) - line_count > DIFF_SAFE_LINES - end + line_count ||= Commit::diff_line_count(diffs) + line_count > DIFF_SAFE_LINES + end - def self.diff_force_suppress?(diffs, line_count = nil) - # optimize - check file count first - return true if diffs.size > DIFF_HARD_LIMIT_FILES + def diff_force_suppress?(diffs, line_count = nil) + # optimize - check file count first + return true if diffs.size > DIFF_HARD_LIMIT_FILES - line_count ||= Commit::diff_line_count(diffs) - line_count > DIFF_HARD_LIMIT_LINES + line_count ||= Commit::diff_line_count(diffs) + line_count > DIFF_HARD_LIMIT_LINES + end end attr_accessor :raw @@ -97,14 +99,16 @@ class Commit # # cut off, ellipses (`&hellp;`) are prepended to the commit message. def description - description = safe_message + title_end = safe_message.index(/\n/) + @description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100) + "…".html_safe << safe_message[80..-1] + else + safe_message.split(/\n/, 2)[1].try(:chomp) + end + end - title_end = description.index(/\n/) - if (!title_end && description.length > 100) || (title_end && title_end > 100) - "…".html_safe << description[80..-1] - else - description.split(/\n/, 2)[1].try(:chomp) - end + def description? + description.present? end # Regular expression that identifies commit message clauses that trigger issue closing. diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 58bf621f91b..75989888bfa 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -23,7 +23,8 @@ module Issuable scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :unassigned, -> { where("assignee_id IS NULL") } scope :of_projects, ->(ids) { where(project_id: ids) } - + scope :opened, -> { with_state(:opened, :reopened) } + scope :closed, -> { with_state(:closed) } delegate :name, :email, @@ -45,6 +46,18 @@ module Issuable def search(query) where("title like :query", query: "%#{query}%") end + + def sort(method) + case method.to_s + when 'newest' then reorder("#{table_name}.created_at DESC") + when 'oldest' then reorder("#{table_name}.created_at ASC") + when 'recently_updated' then reorder("#{table_name}.updated_at DESC") + when 'last_updated' then reorder("#{table_name}.updated_at ASC") + when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC") + when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC") + else reorder("#{table_name}.created_at DESC") + end + end end def today? diff --git a/app/models/email.rb b/app/models/email.rb new file mode 100644 index 00000000000..22e71e4f107 --- /dev/null +++ b/app/models/email.rb @@ -0,0 +1,33 @@ +# == Schema Information +# +# Table name: emails +# +# id :integer not null, primary key +# user_id :integer not null +# email :string not null +# created_at :datetime not null +class Email < ActiveRecord::Base + attr_accessible :email, :user_id + + # + # Relations + # + belongs_to :user + + # + # Validations + # + validates :user_id, presence: true + validates :email, presence: true, email: { strict_mode: true }, uniqueness: true + validate :unique_email, if: ->(email) { email.email_changed? } + + before_validation :cleanup_email + + def cleanup_email + self.email = self.email.downcase.strip + end + + def unique_email + self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email) + end +end
\ No newline at end of file diff --git a/app/models/event.rb b/app/models/event.rb index ddb863c1be2..d43d6eb682f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -56,11 +56,13 @@ class Event < ActiveRecord::Base end def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads') + commit = project.repository.commit(ref.target) + if action.to_s == 'add' before = '00000000' - after = ref.commit.id + after = commit.id else - before = ref.commit.id + before = commit.id after = '00000000' end diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb index 7ebaaff61cb..613a7110d12 100644 --- a/app/models/gollum_wiki.rb +++ b/app/models/gollum_wiki.rb @@ -1,4 +1,5 @@ class GollumWiki + include Gitlab::ShellAdapter MARKUPS = { "Markdown" => :markdown, @@ -113,10 +114,6 @@ class GollumWiki "#{@user.username} #{action} page: #{title}" end - def gitlab_shell - @gitlab_shell ||= Gitlab::Shell.new - end - def path_to_repo @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") end diff --git a/app/models/group.rb b/app/models/group.rb index 0b64d5b4f7f..0d4d5f4e836 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -12,10 +12,26 @@ # description :string(255) default(""), not null # +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + class Group < Namespace has_many :users_groups, dependent: :destroy has_many :users, through: :users_groups + attr_accessible :avatar + + validate :avatar_type, if: ->(user) { user.avatar_changed? } + validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + + mount_uploader :avatar, AttachmentUploader + + def self.accessible_to(user) + accessible_ids = Project.accessible_to(user).pluck(:namespace_id) + accessible_ids += user.groups.pluck(:id) if user + where(id: accessible_ids) + end + def human_name name end @@ -50,4 +66,10 @@ class Group < Namespace def members users_groups end + + def avatar_type + unless self.avatar.image? + self.errors.add :avatar, "only images allowed" + end + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 6580c5004af..a8dc6e5fd85 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -28,8 +28,6 @@ class Issue < ActiveRecord::Base scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } - scope :opened, -> { with_state(:opened, :reopened) } - scope :closed, -> { with_state(:closed) } attr_accessible :title, :assignee_id, :position, :description, :milestone_id, :label_list, :author_id_of_changes, @@ -50,9 +48,7 @@ class Issue < ActiveRecord::Base end state :opened - state :reopened - state :closed end diff --git a/app/models/key.rb b/app/models/key.rb index 79f7bbd2590..29a76f53f3d 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -53,7 +53,7 @@ class Key < ActiveRecord::Base Tempfile.open('gitlab_key_file') do |file| file.puts key file.rewind - cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp') + cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp') end if cmd_status.zero? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e59aee8b445..4774cbcf3aa 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -31,6 +31,13 @@ class MergeRequest < ActiveRecord::Base belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" + has_one :merge_request_diff, dependent: :destroy + + after_create :create_merge_request_diff + after_update :update_merge_request_diff + + delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil + attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description attr_accessor :should_remove_source_branch @@ -45,20 +52,26 @@ class MergeRequest < ActiveRecord::Base end event :merge do - transition [:reopened, :opened] => :merged + transition [:reopened, :opened, :locked] => :merged end event :reopen do transition closed: :reopened end - state :opened + event :lock do + transition [:reopened, :opened] => :locked + end - state :reopened + event :unlock do + transition locked: :reopened + end + state :opened + state :reopened state :closed - state :merged + state :locked end state_machine :merge_status, initial: :unchecked do @@ -75,15 +88,10 @@ class MergeRequest < ActiveRecord::Base end state :unchecked - state :can_be_merged - state :cannot_be_merged end - serialize :st_commits - serialize :st_diffs - validates :source_project, presence: true, unless: :allow_broken validates :source_branch, presence: true validates :target_project, presence: true @@ -92,8 +100,6 @@ class MergeRequest < ActiveRecord::Base scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) } scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) } - scope :opened, -> { with_state(:opened) } - scope :closed, -> { with_state(:closed) } scope :merged, -> { with_state(:merged) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } @@ -105,7 +111,7 @@ class MergeRequest < ActiveRecord::Base scope :closed, -> { with_states(:closed, :merged) } def validate_branches - if target_project==source_project && target_branch == source_branch + if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" end @@ -119,9 +125,17 @@ class MergeRequest < ActiveRecord::Base end end + def update_merge_request_diff + if source_branch_changed? || target_branch_changed? + reload_code + mark_as_unchecked + end + end + def reload_code - self.reloaded_commits - self.reloaded_diffs + if merge_request_diff && opened? + merge_request_diff.reload_content + end end def check_if_can_be_merged @@ -132,42 +146,6 @@ class MergeRequest < ActiveRecord::Base end end - def diffs - @diffs ||= (load_diffs(st_diffs) || []) - end - - def reloaded_diffs - if opened? && unmerged_diffs.any? - self.st_diffs = dump_diffs(unmerged_diffs) - self.save - end - end - - def broken_diffs? - diffs == broken_diffs - rescue - true - end - - def valid_diffs? - !broken_diffs? - end - - def unmerged_diffs - diffs = if for_fork? - Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite - else - Gitlab::Git::Diff.between(target_project.repository, source_branch, target_branch) - end - - diffs ||= [] - diffs - end - - def last_commit - commits.first - end - def merge_event self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last end @@ -176,56 +154,19 @@ class MergeRequest < ActiveRecord::Base self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end - def commits - load_commits(st_commits || []) + def automerge!(current_user, commit_message = nil) + MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) end - def probably_merged? - unmerged_commits.empty? && - commits.any? && opened? - end - - def reloaded_commits - if opened? && unmerged_commits.any? - self.st_commits = dump_commits(unmerged_commits) - save - - end - commits - end - - def unmerged_commits - if for_fork? - commits = Gitlab::Satellite::MergeAction.new(self.author, self).commits_between - else - commits = target_project.repository.commits_between(self.target_branch, self.source_branch) - end - - if commits.present? - commits = Commit.decorate(commits). - sort_by(&:created_at). - reverse - end - commits - end - - def merge!(user_id) - self.author_id_of_changes = user_id - self.merge - end - - def automerge!(current_user) - if Gitlab::Satellite::MergeAction.new(current_user, self).merge! && self.unmerged_commits.empty? - self.merge!(current_user.id) - true - end - rescue - mark_as_unmergeable - false + def open? + opened? || reopened? end def mr_and_commit_notes - commit_ids = commits.map(&:id) + # Fetch comments only from last 100 commits + commits_for_notes_limit = 100 + commit_ids = commits.last(commits_for_notes_limit).map(&:id) + project.notes.where( "(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, @@ -247,10 +188,6 @@ class MergeRequest < ActiveRecord::Base Gitlab::Satellite::MergeAction.new(current_user, self).format_patch end - def last_commit_short_sha - @last_commit_short_sha ||= last_commit.sha[0..10] - end - def for_fork? target_project != source_project end @@ -293,6 +230,14 @@ class MergeRequest < ActiveRecord::Base end end + def source_project_namespace + if source_project && source_project.namespace + source_project.namespace.path + else + "(removed)" + end + end + def source_branch_exists? return false unless self.source_project @@ -319,33 +264,32 @@ class MergeRequest < ActiveRecord::Base update_all(updated_at: Time.now) end - private - - def dump_commits(commits) - commits.map(&:to_hash) + def merge_commit_message + message = "Merge branch '#{source_branch}' into '#{target_branch}'" + message << "\n\n" + message << title.to_s + message << "\n\n" + message << description.to_s + message end - def load_commits(array) - array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) } - end - - def dump_diffs(diffs) - if diffs == broken_diffs - broken_diffs - elsif diffs.respond_to?(:map) - diffs.map(&:to_hash) + # Return array of possible target branches + # dependes on target project of MR + def target_branches + if target_project.nil? + [] + else + target_project.repository.branch_names end end - def load_diffs(raw) - if raw == broken_diffs - broken_diffs - elsif raw.respond_to?(:map) - raw.map { |hash| Gitlab::Git::Diff.new(hash) } + # Return array of possible source branches + # dependes on source project of MR + def source_branches + if source_project.nil? + [] + else + source_project.repository.branch_names end end - - def broken_diffs - [Gitlab::Git::Diff::BROKEN_DIFF] - end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb new file mode 100644 index 00000000000..99afffc1db0 --- /dev/null +++ b/app/models/merge_request_diff.rb @@ -0,0 +1,155 @@ +require Rails.root.join("app/models/commit") + +class MergeRequestDiff < ActiveRecord::Base + # Prevent store of diff + # if commits amount more then 200 + COMMITS_SAFE_SIZE = 200 + + attr_reader :commits, :diffs + + belongs_to :merge_request + + attr_accessible :state, :st_commits, :st_diffs + + delegate :target_branch, :source_branch, to: :merge_request, prefix: nil + + state_machine :state, initial: :empty do + state :collected + state :timeout + state :overflow_commits_safe_size + state :overflow_diff_files_limit + state :overflow_diff_lines_limit + end + + serialize :st_commits + serialize :st_diffs + + after_create :reload_content + + def reload_content + reload_commits + reload_diffs + end + + def diffs + @diffs ||= (load_diffs(st_diffs) || []) + end + + def commits + @commits ||= load_commits(st_commits || []) + end + + def last_commit + commits.first + end + + def last_commit_short_sha + @last_commit_short_sha ||= last_commit.sha[0..10] + end + + private + + def dump_commits(commits) + commits.map(&:to_hash) + end + + def load_commits(array) + array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) } + end + + def dump_diffs(diffs) + if diffs.respond_to?(:map) + diffs.map(&:to_hash) + end + end + + def load_diffs(raw) + if raw.respond_to?(:map) + raw.map { |hash| Gitlab::Git::Diff.new(hash) } + end + end + + # Collect array of Git::Commit objects + # between target and source branches + def unmerged_commits + commits = if merge_request.for_fork? + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between + else + repository.commits_between(target_branch, source_branch) + end + + if commits.present? + commits = Commit.decorate(commits). + sort_by(&:created_at). + reverse + end + + commits + end + + # Reload all commits related to current merge request from repo + # and save it as array of hashes in st_commits db field + def reload_commits + commit_objects = unmerged_commits + + if commit_objects.present? + self.st_commits = dump_commits(commit_objects) + end + + save + end + + # Reload diffs between branches related to current merge request from repo + # and save it as array of hashes in st_diffs db field + def reload_diffs + new_diffs = [] + + if commits.size.zero? + self.state = :empty + elsif commits.size > COMMITS_SAFE_SIZE + self.state = :overflow_commits_safe_size + else + new_diffs = unmerged_diffs + end + + if new_diffs.any? + if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES + self.state = :overflow_diff_files_limit + new_diffs = [] + end + + if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES + self.state = :overflow_diff_lines_limit + new_diffs = [] + end + end + + if new_diffs.present? + new_diffs = dump_commits(new_diffs) + self.state = :collected + end + + self.st_diffs = new_diffs + self.save + end + + # Collect array of Git::Diff objects + # between target and source branches + def unmerged_diffs + diffs = if merge_request.for_fork? + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite + else + Gitlab::Git::Diff.between(repository, source_branch, target_branch) + end + + diffs ||= [] + diffs + rescue Gitlab::Git::Diff::TimeoutError => ex + self.state = :timeout + diffs = [] + end + + def repository + merge_request.target_project.repository + end +end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 8f837c72ff5..468c93bd426 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -10,6 +10,7 @@ # updated_at :datetime not null # type :string(255) # description :string(255) default(""), not null +# avatar :string(255) # class Namespace < ActiveRecord::Base @@ -26,7 +27,7 @@ class Namespace < ActiveRecord::Base format: { with: Gitlab::Regex.name_regex, message: "only letters, digits, spaces & '_' '-' '.' allowed." } validates :description, length: { within: 0..255 } - validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, + validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 }, exclusion: { in: Gitlab::Blacklist.path }, format: { with: Gitlab::Regex.path_regex, message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } @@ -46,6 +47,14 @@ class Namespace < ActiveRecord::Base def self.global_id 'GLN' end + + def projects_accessible_to(user) + projects.accessible_to(user) + end + + def has_projects_accessible_to?(user) + projects_accessible_to(user).present? + end def to_param path diff --git a/app/models/note.rb b/app/models/note.rb index 67755f44148..48c03c9d587 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -72,14 +72,20 @@ class Note < ActiveRecord::Base # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note. # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+. def create_cross_reference_note(noteable, mentioner, author, project) - create({ - noteable: noteable, - commit_id: (noteable.sha if noteable.respond_to? :sha), + note_options = { project: project, author: author, note: "_mentioned in #{mentioner.gfm_reference}_", system: true - }, without_protection: true) + } + + if noteable.kind_of?(Commit) + note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id) + else + note_options.merge!(noteable: noteable) + end + + create(note_options, without_protection: true) end def create_assignee_change_note(noteable, project, author, assignee) @@ -123,8 +129,8 @@ class Note < ActiveRecord::Base def commit_author @commit_author ||= - project.users.find_by_email(noteable.author_email) || - project.users.find_by_name(noteable.author_name) + project.users.find_by(email: noteable.author_email) || + project.users.find_by(name: noteable.author_name) rescue nil end diff --git a/app/models/notification.rb b/app/models/notification.rb index ff6a18d6a51..b0f8ed6a4ec 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -9,12 +9,23 @@ class Notification attr_accessor :target - def self.notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH] - end - - def self.project_notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] + class << self + def notification_levels + [N_DISABLED, N_PARTICIPATING, N_WATCH] + end + + def options_with_labels + { + disabled: N_DISABLED, + participating: N_PARTICIPATING, + watch: N_WATCH, + global: N_GLOBAL + } + end + + def project_notification_levels + [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] + end end def initialize(target) @@ -36,4 +47,8 @@ class Notification def global? target.notification_level == N_GLOBAL end + + def level + target.notification_level + end end diff --git a/app/models/project.rb b/app/models/project.rb index a55f7a65b0b..47dc8a1fdb0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -28,6 +28,9 @@ class Project < ActiveRecord::Base include Gitlab::VisibilityLevel extend Enumerize + default_value_for :imported, false + default_value_for :archived, false + ActsAsTaggableOn.strict_case_match = true attr_accessible :name, :path, :description, :issues_tracker, :label_list, @@ -53,6 +56,7 @@ class Project < ActiveRecord::Base has_one :hipchat_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy has_one :assembla_service, dependent: :destroy + has_one :gemnasium_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -114,18 +118,35 @@ class Project < ActiveRecord::Base scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } - scope :public_only, -> { where(visibility_level: PUBLIC) } - scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } + + scope :public_only, -> { where(visibility_level: Project::PUBLIC) } + scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :non_archived, -> { where(archived: false) } enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab class << self + def public_and_internal_levels + [Project::PUBLIC, Project::INTERNAL] + end + def abandoned where('projects.last_activity_at < ?', 6.months.ago) end + def publicish(user) + visibility_levels = [Project::PUBLIC] + visibility_levels += [Project::INTERNAL] if user + where(visibility_level: visibility_levels) + end + + def accessible_to(user) + accessible_ids = publicish(user).pluck(:id) + accessible_ids += user.authorized_projects.pluck(:id) if user + where(id: accessible_ids) + end + def with_push includes(:events).where('events.action = ?', Event::PUSHED) end @@ -138,13 +159,17 @@ class Project < ActiveRecord::Base joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") end + def search_by_title query + where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%") + end + def find_with_namespace(id) if id.include?("/") id = id.split("/") - namespace = Namespace.find_by_path(id.first) + namespace = Namespace.find_by(path: id.first) return nil unless namespace - where(namespace_id: namespace.id).find_by_path(id.second) + where(namespace_id: namespace.id).find_by(path: id.second) else where(path: id, namespace_id: nil).last end @@ -201,6 +226,10 @@ class Project < ActiveRecord::Base [Gitlab.config.gitlab.url, path_with_namespace].join("/") end + def web_url_without_protocol + web_url.split("://")[1] + end + def build_commit_note(commit) notes.new(commit_id: commit.id, noteable_type: "Commit") end @@ -248,7 +277,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium) end def gitlab_ci? @@ -270,9 +299,7 @@ class Project < ActiveRecord::Base end def send_move_instructions - team.members.each do |user| - Notify.delay.project_was_moved_email(self.id, user.id) - end + NotificationService.new.project_was_moved(self) end def owner @@ -290,7 +317,7 @@ class Project < ActiveRecord::Base # Get Team Member record by user id def team_member_by_id(user_id) - users_projects.find_by_user_id(user_id) + users_projects.find_by(user_id: user_id) end def name_with_namespace @@ -344,7 +371,7 @@ class Project < ActiveRecord::Base # Close merge requests mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - mrs.each { |merge_request| merge_request.merge!(user.id) } + mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) } true end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 66ecf394784..2a2c5172916 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -13,9 +13,12 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class AssemblaService < Service + attr_accessible :subdomain + include HTTParty validates :token, presence: true, if: :activated? @@ -34,12 +37,13 @@ class AssemblaService < Service def fields [ - { type: 'text', name: 'token', placeholder: '' } + { type: 'text', name: 'token', placeholder: '' }, + { type: 'text', name: 'subdomain', placeholder: '' } ] end def execute(push) - url = "https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=#{token}" + url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}" AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' }) end end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index fb2a49fd586..f9247e054c7 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class CampfireService < Service diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 2a46eff7846..0a453166342 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class EmailsOnPushService < Service diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index f72d9fa9015..2603a1f67a4 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb new file mode 100644 index 00000000000..0b8e7bad353 --- /dev/null +++ b/app/models/project_services/gemnasium_service.rb @@ -0,0 +1,54 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# api_key :string(255) +# + +require "gemnasium/gitlab_service" + +class GemnasiumService < Service + validates :token, :api_key, presence: true, if: :activated? + + def title + 'Gemnasium' + end + + def description + 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.' + end + + def to_param + 'gemnasium' + end + + def fields + [ + { type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ' }, + { type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com' } + ] + end + + def execute(push_data) + repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + Gemnasium::GitlabService.execute( + ref: push_data[:ref], + before: push_data[:before], + after: push_data[:after], + token: token, + api_key: api_key, + repo: repo_path + ) + end +end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 7f5380a4551..017cd9eeaab 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class GitlabCiService < Service diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index ea2169fb168..3cee047a32a 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class HipchatService < Service @@ -40,7 +41,7 @@ class HipchatService < Service end def execute(push_data) - gate[room].send('Gitlab', create_message(push_data)) + gate[room].send('GitLab', create_message(push_data)) end private @@ -61,7 +62,7 @@ class HipchatService < Service elsif after =~ /000000/ message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n" else - message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> " + message << "pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> " message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> " message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)" for commit in push[:commits] do diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index c5b1b9ab8d3..877b9a77404 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class PivotaltrackerService < Service diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 5630f280aea..eca13e56061 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -22,22 +22,22 @@ class ProjectTeam end def find(user_id) - user = project.users.find_by_id(user_id) + user = project.users.find_by(id: user_id) if group - user ||= group.users.find_by_id(user_id) + user ||= group.users.find_by(id: user_id) end user end def find_tm(user_id) - tm = project.users_projects.find_by_user_id(user_id) + tm = project.users_projects.find_by(user_id: user_id) # If user is not in project members # we should check for group membership if group && !tm - tm = group.users_groups.find_by_user_id(user_id) + tm = group.users_groups.find_by(user_id: user_id) end tm diff --git a/app/models/repository.rb b/app/models/repository.rb index 1255b814533..35ec84f1651 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -57,7 +57,7 @@ class Repository def recent_branches(limit = 20) branches.sort do |a, b| - b.commit.committed_date <=> a.commit.committed_date + commit(b.target).committed_date <=> commit(a.target).committed_date end[0..limit] end @@ -134,6 +134,7 @@ class Repository Rails.cache.delete(cache_key(:commit_count)) Rails.cache.delete(cache_key(:graph_log)) Rails.cache.delete(cache_key(:readme)) + Rails.cache.delete(cache_key(:contribution_guide)) end def graph_log @@ -163,7 +164,55 @@ class Repository def readme Rails.cache.fetch(cache_key(:readme)) do - Tree.new(self, self.root_ref).readme + tree(:head).readme end end + + def contribution_guide + Rails.cache.fetch(cache_key(:contribution_guide)) do + tree(:head).contribution_guide + end + end + + def head_commit + commit(self.root_ref) + end + + def tree(sha = :head, path = nil) + if sha == :head + sha = head_commit.sha + end + + Tree.new(self, sha, path) + end + + def blob_at_branch(branch_name, path) + last_commit = commit(branch_name) + + if last_commit + blob_at(last_commit.sha, path) + else + nil + end + end + + # Returns url for submodule + # + # Ex. + # @repository.submodule_url_for('master', 'rack') + # # => git@localhost:rack.git + # + def submodule_url_for(ref, path) + if submodules(ref).any? + submodule = submodules(ref)[path] + + if submodule + submodule['url'] + end + end + end + + def last_commit_for_path(sha, path) + commits(sha, path, 1).last + end end diff --git a/app/models/service.rb b/app/models/service.rb index 540aaad1ce5..f7e440dcc81 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -13,12 +13,15 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # # To add new service you should build a class inherited from Service # and implement a set of methods class Service < ActiveRecord::Base - attr_accessible :title, :token, :type, :active + default_value_for :active, false + + attr_accessible :title, :token, :type, :active, :api_key belongs_to :project has_one :service_hook diff --git a/app/models/tree.rb b/app/models/tree.rb index ed06cb1a128..ac2183be44b 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -1,5 +1,5 @@ class Tree - attr_accessor :entries, :readme + attr_accessor :entries, :readme, :contribution_guide def initialize(repository, sha, path = '/') path = '/' if path.blank? @@ -10,6 +10,11 @@ class Tree readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) end + + if contribution_tree = @entries.find(&:contributing?) + contribution_path = path == '/' ? contribution_tree.name : File.join(path, contribution_tree.name) + @contribution_guide = Gitlab::Git::Blob.find(git_repo, sha, contribution_path) + end end def trees @@ -23,4 +28,8 @@ class Tree def submodules @entries.select(&:submodule?) end + + def sorted_entries + trees + blobs + submodules + end end diff --git a/app/models/user.rb b/app/models/user.rb index f2cd554f9c3..855fe58ffe8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -41,7 +41,8 @@ # confirmed_at :datetime # confirmation_sent_at :datetime # unconfirmed_email :string(255) -# hide_no_ssh_key :boolean default(FALSE), not null +# hide_no_ssh_key :boolean default(FALSE) +# website_url :string(255) default(""), not null # require 'carrierwave/orm/activerecord' @@ -52,7 +53,7 @@ class User < ActiveRecord::Base :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, - :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password, + :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, as: [:default, :admin] @@ -77,6 +78,7 @@ class User < ActiveRecord::Base # Profile has_many :keys, dependent: :destroy + has_many :emails, dependent: :destroy # Groups has_many :users_groups, dependent: :destroy @@ -103,11 +105,11 @@ class User < ActiveRecord::Base # Validations # validates :name, presence: true - validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }, uniqueness: true + validates :email, presence: true, email: {strict_mode: true}, uniqueness: true validates :bio, length: { maximum: 255 }, allow_blank: true validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} - validates :username, presence: true, uniqueness: true, + validates :username, presence: true, uniqueness: { case_sensitive: false }, exclusion: { in: Gitlab::Blacklist.path }, format: { with: Gitlab::Regex.username_regex, message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } @@ -115,6 +117,7 @@ class User < ActiveRecord::Base validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? } + validate :unique_email, if: ->(user) { user.email_changed? } validates :avatar, file_size: { maximum: 100.kilobytes.to_i } before_validation :generate_password, on: :create @@ -182,6 +185,13 @@ class User < ActiveRecord::Base where(conditions).first end end + + def find_for_commit(email, name) + # Prefer email match over name match + User.where(email: email).first || + User.joins(:emails).where(emails: { email: email }).first || + User.where(name: name).first + end def filter filter_name case filter_name @@ -238,7 +248,7 @@ class User < ActiveRecord::Base def namespace_uniq namespace_name = self.username - if Namespace.find_by_path(namespace_name) + if Namespace.find_by(path: namespace_name) self.errors.add :username, "already exist" end end @@ -249,6 +259,10 @@ class User < ActiveRecord::Base end end + def unique_email + self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email) + end + # Groups user has access to def authorized_groups @authorized_groups ||= begin @@ -382,7 +396,7 @@ class User < ActiveRecord::Base end def created_by - User.find_by_id(created_by_id) if created_by_id + User.find_by(id: created_by_id) if created_by_id end def sanitize_attrs @@ -424,4 +438,18 @@ class User < ActiveRecord::Base order('id DESC').limit(1000). update_all(updated_at: Time.now) end + + def full_website_url + return "http://#{website_url}" if website_url !~ /^https?:\/\// + + website_url + end + + def short_website_url + website_url.gsub(/https?:\/\//, '') + end + + def all_ssh_keys + keys.map(&:key) + end end diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index c0aa3734917..45a795391a2 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -17,6 +17,10 @@ class WebHook < ActiveRecord::Base include HTTParty + default_value_for :push_events, true + default_value_for :issues_events, false + default_value_for :merge_requests_events, false + attr_accessible :url # HTTParty timeout @@ -28,7 +32,7 @@ class WebHook < ActiveRecord::Base def execute(data) parsed_url = URI.parse(url) if parsed_url.userinfo.blank? - WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }) + WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) else post_url = url.gsub("#{parsed_url.userinfo}@", "") auth = { @@ -38,6 +42,7 @@ class WebHook < ActiveRecord::Base WebHook.post(post_url, body: data.to_json, headers: {"Content-Type" => "application/json"}, + verify: false, basic_auth: auth) end end diff --git a/app/observers/email_observer.rb b/app/observers/email_observer.rb new file mode 100644 index 00000000000..026ad8b1d9a --- /dev/null +++ b/app/observers/email_observer.rb @@ -0,0 +1,5 @@ +class EmailObserver < BaseObserver + def after_create(email) + notification.new_email(email) + end +end diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb index 0ac555fce7c..f2e2d16c943 100644 --- a/app/observers/merge_request_observer.rb +++ b/app/observers/merge_request_observer.rb @@ -18,27 +18,12 @@ class MergeRequestObserver < ActivityObserver execute_hooks(merge_request) end - def after_merge(merge_request, transition) - notification.merge_mr(merge_request) - # Since MR can be merged via sidekiq - # to prevent event duplication do this check - return true if merge_request.merge_event - - Event.create( - project: merge_request.target_project, - target_id: merge_request.id, - target_type: merge_request.class.name, - action: Event::MERGED, - author_id: merge_request.author_id_of_changes - ) - - execute_hooks(merge_request) - end - def after_reopen(merge_request, transition) create_event(merge_request, Event::REOPENED) create_note(merge_request) execute_hooks(merge_request) + merge_request.reload_code + merge_request.mark_as_unchecked end def after_update(merge_request) diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb index 3a649fd590d..80de177b9a2 100644 --- a/app/observers/system_hook_observer.rb +++ b/app/observers/system_hook_observer.rb @@ -2,10 +2,16 @@ class SystemHookObserver < BaseObserver observe :user, :project, :users_project def after_create(model) - SystemHooksService.execute_hooks_for(model, :create) + system_hook_service.execute_hooks_for(model, :create) end def after_destroy(model) - SystemHooksService.execute_hooks_for(model, :destroy) + system_hook_service.execute_hooks_for(model, :destroy) + end + + private + + def system_hook_service + SystemHooksService.new end end diff --git a/app/contexts/base_context.rb b/app/services/base_service.rb index 6accd9b2457..610f0474872 100644 --- a/app/contexts/base_context.rb +++ b/app/services/base_service.rb @@ -1,4 +1,4 @@ -class BaseContext +class BaseService attr_accessor :project, :current_user, :params def initialize(project, user, params) diff --git a/app/contexts/files/base_context.rb b/app/services/files/base_service.rb index 44f9826652c..f1765d38976 100644 --- a/app/contexts/files/base_context.rb +++ b/app/services/files/base_service.rb @@ -1,5 +1,5 @@ module Files - class BaseContext < ::BaseContext + class BaseService < ::BaseService attr_reader :ref, :path def initialize(project, user, params, ref, path = nil) diff --git a/app/contexts/files/create_context.rb b/app/services/files/create_service.rb index b3d62a028c7..1998fb79e7d 100644 --- a/app/contexts/files/create_context.rb +++ b/app/services/files/create_service.rb @@ -1,7 +1,7 @@ -require_relative "base_context" +require_relative "base_service" module Files - class CreateContext < BaseContext + class CreateService < BaseService def execute allowed = if project.protected_branch?(ref) can?(current_user, :push_code_to_protected_branches, project) @@ -24,7 +24,7 @@ module Files return error("Your changes could not be committed, because file name contains not allowed characters") end - blob = repository.blob_at(ref, file_path) + blob = repository.blob_at_branch(ref, file_path) if blob return error("Your changes could not be committed, because file with such name exists") @@ -33,7 +33,8 @@ module Files new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) created_successfully = new_file_action.commit!( params[:content], - params[:commit_message] + params[:commit_message], + params[:encoding] ) if created_successfully diff --git a/app/contexts/files/delete_context.rb b/app/services/files/delete_service.rb index 39ff7c2a4b1..ff5dc6ef34c 100644 --- a/app/contexts/files/delete_context.rb +++ b/app/services/files/delete_service.rb @@ -1,7 +1,7 @@ -require_relative "base_context" +require_relative "base_service" module Files - class DeleteContext < BaseContext + class DeleteService < BaseService def execute allowed = if project.protected_branch?(ref) can?(current_user, :push_code_to_protected_branches, project) @@ -17,7 +17,7 @@ module Files return error("You can only create files if you are on top of a branch") end - blob = repository.blob_at(ref, path) + blob = repository.blob_at_branch(ref, path) unless blob return error("You can only edit text files") diff --git a/app/contexts/files/update_context.rb b/app/services/files/update_service.rb index 556027a3256..c631f28749c 100644 --- a/app/contexts/files/update_context.rb +++ b/app/services/files/update_service.rb @@ -1,7 +1,7 @@ -require_relative "base_context" +require_relative "base_service" module Files - class UpdateContext < BaseContext + class UpdateService < BaseService def execute allowed = if project.protected_branch?(ref) can?(current_user, :push_code_to_protected_branches, project) @@ -17,16 +17,17 @@ module Files return error("You can only create files if you are on top of a branch") end - blob = repository.blob_at(ref, path) + blob = repository.blob_at_branch(ref, path) unless blob return error("You can only edit text files") end - new_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) - created_successfully = new_file_action.commit!( + edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) + created_successfully = edit_file_action.commit!( params[:content], - params[:commit_message] + params[:commit_message], + params[:encoding] ) if created_successfully diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index e54f88e42de..fcc03c3e4b8 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -188,8 +188,6 @@ class GitPushService end def commit_user commit - User.where(email: commit.author_email).first || - User.where(name: commit.author_name).first || - user + User.find_for_commit(commit.author_email, commit.author_name) || user end end diff --git a/app/contexts/issues/bulk_update_context.rb b/app/services/issues/bulk_update_service.rb index ab38a4f8ab5..f72a346af6f 100644 --- a/app/contexts/issues/bulk_update_context.rb +++ b/app/services/issues/bulk_update_service.rb @@ -1,5 +1,5 @@ module Issues - class BulkUpdateContext < BaseContext + class BulkUpdateService < BaseService def execute update_data = params[:update] diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb new file mode 100644 index 00000000000..d60d61ed54a --- /dev/null +++ b/app/services/merge_requests/auto_merge_service.rb @@ -0,0 +1,30 @@ +module MergeRequests + # AutoMergeService class + # + # Do git merge in satellite and in case of success + # mark merge request as merged and execute all hooks and notifications + # Called when you do merge via GitLab UI + class AutoMergeService < BaseMergeService + def execute(merge_request, current_user, commit_message) + merge_request.lock + + if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) + merge_request.author_id_of_changes = current_user.id + merge_request.merge + + notification.merge_mr(merge_request) + create_merge_event(merge_request) + execute_project_hooks(merge_request) + + true + else + merge_request.unlock + false + end + rescue + merge_request.unlock if merge_request.locked? + merge_request.mark_as_unmergeable + false + end + end +end diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb new file mode 100644 index 00000000000..dbdb0063074 --- /dev/null +++ b/app/services/merge_requests/base_merge_service.rb @@ -0,0 +1,26 @@ +module MergeRequests + class BaseMergeService + + private + + def notification + NotificationService.new + end + + def create_merge_event(merge_request) + Event.create( + project: merge_request.target_project, + target_id: merge_request.id, + target_type: merge_request.class.name, + action: Event::MERGED, + author_id: merge_request.author_id_of_changes + ) + end + + def execute_project_hooks(merge_request) + if merge_request.project + merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) + end + end + end +end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb new file mode 100644 index 00000000000..1d5af04cdbb --- /dev/null +++ b/app/services/merge_requests/merge_service.rb @@ -0,0 +1,22 @@ +module MergeRequests + # MergeService class + # + # Mark existing merge request as merged + # and execute all hooks and notifications + # Called when you do merge via command line and push code + # to target branch + class MergeService < BaseMergeService + def execute(merge_request, current_user, commit_message) + merge_request.author_id_of_changes = current_user.id + merge_request.merge + + notification.merge_mr(merge_request) + create_merge_event(merge_request) + execute_project_hooks(merge_request) + + true + rescue + false + end + end +end diff --git a/app/contexts/notes/create_context.rb b/app/services/notes/create_service.rb index 36ea76ff949..ff6dfb61139 100644 --- a/app/contexts/notes/create_context.rb +++ b/app/services/notes/create_service.rb @@ -1,8 +1,9 @@ module Notes - class CreateContext < BaseContext + class CreateService < BaseService def execute note = project.notes.new(params[:note]) note.author = current_user + note.system = false note.save note end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index eb42cac3f83..5daf573630d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -17,6 +17,13 @@ class NotificationService end end + # Always notify user about email added to profile + def new_email(email) + if email.user + mailer.new_email_email(email.id) + end + end + # When create an issue we should send next emails: # # * issue assignee if their notification level is not Disabled @@ -157,6 +164,15 @@ class NotificationService mailer.group_access_granted_email(users_group.id) end + def project_was_moved(project) + recipients = project.team.members + recipients = reject_muted_users(recipients, project) + + recipients.each do |recipient| + mailer.project_was_moved_email(project.id, recipient.id) + end + end + protected # Get project users with WATCH notification level @@ -186,10 +202,10 @@ class NotificationService users.reject do |user| next user.notification.disabled? unless project - tm = project.users_projects.find_by_user_id(user.id) + tm = project.users_projects.find_by(user_id: user.id) if !tm && project.group - tm = project.group.users_groups.find_by_user_id(user.id) + tm = project.group.users_groups.find_by(user_id: user.id) end # reject users who globally disabled notification and has no membership @@ -241,7 +257,7 @@ class NotificationService recipients.delete(current_user) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, target.assignee_id_was) + mailer.send(method, recipient.id, target.id, target.assignee_id_was, current_user.id) end end diff --git a/app/contexts/projects/create_context.rb b/app/services/projects/create_service.rb index 2acb9fbfe14..ba131d8ffbe 100644 --- a/app/contexts/projects/create_context.rb +++ b/app/services/projects/create_service.rb @@ -1,5 +1,5 @@ module Projects - class CreateContext < BaseContext + class CreateService < BaseService def initialize(user, params) @current_user, @params = user, params.dup end @@ -73,7 +73,7 @@ module Projects end def allowed_namespace?(user, namespace_id) - namespace = Namespace.find_by_id(namespace_id) + namespace = Namespace.find_by(id: namespace_id) current_user.can?(:manage_namespace, namespace) end end diff --git a/app/contexts/projects/fork_context.rb b/app/services/projects/fork_service.rb index fbc67220d5d..2f1c7b18aa0 100644 --- a/app/contexts/projects/fork_context.rb +++ b/app/services/projects/fork_service.rb @@ -1,5 +1,5 @@ module Projects - class ForkContext < BaseContext + class ForkService < BaseService include Gitlab::ShellAdapter def initialize(project, user) diff --git a/app/contexts/projects/transfer_context.rb b/app/services/projects/transfer_service.rb index 3011984e3f8..f8e27f6f1c6 100644 --- a/app/contexts/projects/transfer_context.rb +++ b/app/services/projects/transfer_service.rb @@ -1,5 +1,5 @@ module Projects - class TransferContext < BaseContext + class TransferService < BaseService def execute(role = :default) namespace_id = params[:project].delete(:namespace_id) allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin diff --git a/app/contexts/projects/update_context.rb b/app/services/projects/update_service.rb index 94de10de0f6..d9d371da5c4 100644 --- a/app/contexts/projects/update_context.rb +++ b/app/services/projects/update_service.rb @@ -1,5 +1,5 @@ module Projects - class UpdateContext < BaseContext + class UpdateService < BaseService def execute(role = :default) params[:project].delete(:namespace_id) # check that user is allowed to set specified visibility_level diff --git a/app/contexts/search/global_context.rb b/app/services/search/global_service.rb index 74e746018e6..09c7cb25dd5 100644 --- a/app/contexts/search/global_context.rb +++ b/app/services/search/global_service.rb @@ -1,5 +1,5 @@ module Search - class GlobalContext + class GlobalService attr_accessor :current_user, :params def initialize(user, params) @@ -11,12 +11,8 @@ module Search query = Shellwords.shellescape(query) if query.present? return result unless query.present? - authorized_projects_ids = [] - authorized_projects_ids += current_user.authorized_projects.pluck(:id) if current_user - authorized_projects_ids += Project.public_or_internal_only(current_user).pluck(:id) - - group = Group.find_by_id(params[:group_id]) if params[:group_id].present? - projects = Project.where(id: authorized_projects_ids) + group = Group.find_by(id: params[:group_id]) if params[:group_id].present? + projects = Project.accessible_to(current_user) projects = projects.where(namespace_id: group.id) if group projects = projects.search(query) project_ids = projects.pluck(:id) diff --git a/app/contexts/search/project_context.rb b/app/services/search/project_service.rb index 690652b2cdb..3ebaafc752c 100644 --- a/app/contexts/search/project_context.rb +++ b/app/services/search/project_service.rb @@ -1,5 +1,5 @@ module Search - class ProjectContext + class ProjectService attr_accessor :project, :current_user, :params def initialize(project, user, params) diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index de4fa9b79e7..4969198b8c2 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -1,21 +1,21 @@ class SystemHooksService - def self.execute_hooks_for(model, event) + def execute_hooks_for(model, event) execute_hooks(build_event_data(model, event)) end private - def self.execute_hooks(data) + def execute_hooks(data) SystemHook.all.each do |sh| async_execute_hook sh, data end end - def self.async_execute_hook(hook, data) + def async_execute_hook(hook, data) Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data) end - def self.build_event_data(model, event) + def build_event_data(model, event) data = { event_name: build_event_name(model, event), created_at: model.created_at @@ -51,7 +51,7 @@ class SystemHooksService end end - def self.build_event_name(model, event) + def build_event_name(model, event) case model when UsersProject return "user_add_to_team" if event == :create diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb new file mode 100644 index 00000000000..17d86a7a274 --- /dev/null +++ b/app/services/test_hook_service.rb @@ -0,0 +1,6 @@ +class TestHookService + def execute(hook, current_user) + data = GitPushService.new.sample_data(hook.project, current_user) + hook.execute(data) + end +end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index dd663945ea9..bbd60bc6224 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -3,136 +3,137 @@ %p.light You can manage projects, users and other GitLab data from here. %hr -.admin_dash.row - .col-sm-4 - .light-well - %h4 Projects - .data - = link_to admin_projects_path do - %h1= Project.count - %hr - = link_to 'New Project', new_project_path, class: "btn btn-new" - .col-sm-4 - .light-well - %h4 Users - .data - = link_to admin_users_path do - %h1= User.count - %hr - = link_to 'New User', new_admin_user_path, class: "btn btn-new" - .col-sm-4 - .light-well - %h4 Groups - .data - = link_to admin_groups_path do - %h1= Group.count - %hr - = link_to 'New Group', new_admin_group_path, class: "btn btn-new" +.admin-dashboard + .row + .col-sm-4 + .light-well + %h4 Projects + .data + = link_to admin_projects_path do + %h1= Project.count + %hr + = link_to 'New Project', new_project_path, class: "btn btn-new" + .col-sm-4 + .light-well + %h4 Users + .data + = link_to admin_users_path do + %h1= User.count + %hr + = link_to 'New User', new_admin_user_path, class: "btn btn-new" + .col-sm-4 + .light-well + %h4 Groups + .data + = link_to admin_groups_path do + %h1= Group.count + %hr + = link_to 'New Group', new_admin_group_path, class: "btn btn-new" -.row.prepend-top-10 - .col-md-4 - %h4 Latest projects - %hr - - @projects.each do |project| + .row.prepend-top-10 + .col-md-4 + %h4 Latest projects + %hr + - @projects.each do |project| + %p + = link_to project.name_with_namespace, [:admin, project], class: 'str-truncated' + %span.light.pull-right + #{time_ago_with_tooltip(project.created_at)} + + .col-md-4 + %h4 Latest users + %hr + - @users.each do |user| + %p + = link_to [:admin, user], class: 'str-truncated' do + = user.name + %span.light.pull-right + #{time_ago_with_tooltip(user.created_at)} + + .col-md-4 + %h4 Latest groups + %hr + - @groups.each do |group| + %p + = link_to [:admin, group], class: 'str-truncated' do + = group.name + %span.light.pull-right + #{time_ago_with_tooltip(group.created_at)} + + %br + .row + .col-md-4 + %h4 Stats + %hr %p - = link_to project.name_with_namespace, [:admin, project] + Forks %span.light.pull-right - #{time_ago_with_tooltip(project.created_at)} - - .col-md-4 - %h4 Latest users - %hr - - @users.each do |user| + = ForkedProjectLink.count %p - = link_to [:admin, user] do - = user.name + Issues %span.light.pull-right - #{time_ago_with_tooltip(user.created_at)} - - .col-md-4 - %h4 Latest groups - %hr - - @groups.each do |group| + = Issue.count %p - = link_to [:admin, group] do - = group.name + Merge Requests %span.light.pull-right - #{time_ago_with_tooltip(group.created_at)} - -%br -.row - .col-md-4 - %h4 Stats - %hr - %p - Forks - %span.light.pull-right - = ForkedProjectLink.count - %p - Issues - %span.light.pull-right - = Issue.count - %p - Merge Requests - %span.light.pull-right - = MergeRequest.count - %p - Notes - %span.light.pull-right - = Note.count - %p - Snippets - %span.light.pull-right - = Snippet.count - %p - SSH Keys - %span.light.pull-right - = Key.count - %p - Milestones - %span.light.pull-right - = Milestone.count - .col-md-4 - %h4 - Features - %hr - %p - Sign up - %span.light.pull-right - = boolean_to_icon gitlab_config.signup_enabled - %p - LDAP - %span.light.pull-right - = boolean_to_icon Gitlab.config.ldap.enabled - %p - Gravatar - %span.light.pull-right - = boolean_to_icon Gitlab.config.gravatar.enabled - %p - OmniAuth - %span.light.pull-right - = boolean_to_icon Gitlab.config.omniauth.enabled - .col-md-4 - %h4 Components - %hr - %p - GitLab - %span.pull-right - = Gitlab::VERSION - %p - GitLab Shell - %span.pull-right - = Gitlab::Shell.new.version - %p - GitLab API - %span.pull-right - = API::API::version - %p - Ruby - %span.pull-right - #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + = MergeRequest.count + %p + Notes + %span.light.pull-right + = Note.count + %p + Snippets + %span.light.pull-right + = Snippet.count + %p + SSH Keys + %span.light.pull-right + = Key.count + %p + Milestones + %span.light.pull-right + = Milestone.count + .col-md-4 + %h4 + Features + %hr + %p + Sign up + %span.light.pull-right + = boolean_to_icon gitlab_config.signup_enabled + %p + LDAP + %span.light.pull-right + = boolean_to_icon Gitlab.config.ldap.enabled + %p + Gravatar + %span.light.pull-right + = boolean_to_icon Gitlab.config.gravatar.enabled + %p + OmniAuth + %span.light.pull-right + = boolean_to_icon Gitlab.config.omniauth.enabled + .col-md-4 + %h4 Components + %hr + %p + GitLab + %span.pull-right + = Gitlab::VERSION + %p + GitLab Shell + %span.pull-right + = Gitlab::Shell.new.version + %p + GitLab API + %span.pull-right + = API::API::version + %p + Ruby + %span.pull-right + #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} - %p - Rails - %span.pull-right - #{Rails::VERSION::STRING} + %p + Rails + %span.pull-right + #{Rails::VERSION::STRING} diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 7a373ee586c..9a0d5967927 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,11 +1,12 @@ %h3.page-title Groups (#{@groups.total_count}) - %small - allows you to keep projects organized. - Use groups for uniting related projects. - = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right" -%br + +%p.light + Group allows you to keep projects organized. + Use groups for uniting related projects. + +%hr = form_tag admin_groups_path, method: :get, class: 'form-inline' do .form-group = text_field_tag :name, params[:name], class: "form-control input-mn-300" @@ -23,24 +24,18 @@ %h4 = link_to [:admin, group] do + %i.icon-folder-close = group.name → %span.monospace - %i.icon-folder-close %strong #{group.path}/ - - .clearfix.light.append-bottom-10 - %span - %b Members: - %span.badge= group.members.size - \| - %span - %b Projects: - %span.badge= group.projects.count - .clearfix %p = truncate group.description, length: 150 + .clearfix + %p.light + #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')} + = paginate @groups, theme: "gitlab" diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb deleted file mode 100644 index 5b16dfb398a..00000000000 --- a/app/views/admin/hooks/_data_ex.html.erb +++ /dev/null @@ -1,71 +0,0 @@ -<% data_ex_str = <<eos -1. Project created: -{ - "created_at": "2012-07-21T07:30:54Z", - "event_name": "project_create", - "name": "StoreCloud", - "owner_email": "johnsmith@gmail.com" - "owner_name": "John Smit", - "path": "stormcloud", - "path_with_namespace": "jsmith/stormcloud", - "project_id": 74, -} - -2. Project destroyed: -{ - "created_at": "2012-07-21T07:30:58Z", - "event_name": "project_destroy", - "name": "Underscore", - "owner_email": "johnsmith@gmail.com" - "owner_name": "John Smith", - "path": "underscore", - "path_with_namespace": "jsmith/underscore", - "project_id": 73, -} - -3. New Team Member: -{ - "created_at": "2012-07-21T07:30:56Z", - "event_name": "user_add_to_team", - "project_access": "Master", - "project_id": 74, - "project_name": "StoreCloud", - "project_path": "storecloud", - "user_email": "johnsmith@gmail.com", - "user_name": "John Smith", -} - -4. Team Member Removed: -{ - "created_at": "2012-07-21T07:30:56Z", - "event_name": "user_remove_from_team", - "project_access": "Master", - "project_id": 74, - "project_name": "StoreCloud", - "project_path": "storecloud", - "user_email": "johnsmith@gmail.com", - "user_name": "John Smith", -} - -5. User created: -{ - "created_at": "2012-07-21T07:44:07Z", - "email": "js@gitlabhq.com", - "event_name": "user_create", - "name": "John Smith", - "user_id": 41 -} - -6. User removed: -{ - "created_at": "2012-07-21T07:44:07Z", - "email": "js@gitlabhq.com", - "event_name": "user_destroy", - "name": "John Smith", - "user_id": 41 -} - -eos -%> -<% js_lexer = Pygments::Lexer[:js] %> -<%= raw js_lexer.highlight(data_ex_str) %> diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 940a539d5e1..5f19d21f106 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -53,5 +53,5 @@ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove" - if @projects.blank? - %p.nothing_here_message 0 projects matches + .nothing-here-block 0 projects matches = paginate @projects, theme: "gitlab" diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 98bf65bc420..881a043f36f 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -80,6 +80,9 @@ .form-group = f.label :twitter, class: 'control-label' .col-sm-10= f.text_field :twitter, class: 'form-control' + .form-group + = f.label :website_url, 'Website', class: 'control-label' + .col-sm-10= f.text_field :website_url, class: 'form-control' .form-actions - if @user.new_record? diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 1fa6fdfaff1..0b3934a712d 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,12 +1,6 @@ .row .col-md-3 .admin-filter - = form_tag admin_users_path, method: :get, class: 'form-inline' do - .append-bottom-10 - .form-group - = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control' - = button_tag type: 'submit', class: 'btn btn-primary' do - %i.icon-search %ul.nav.nav-pills.nav-stacked %li{class: "#{'active' unless params[:filter]}"} = link_to admin_users_path do @@ -25,6 +19,12 @@ Without projects %small.pull-right= User.without_projects.count %hr + = form_tag admin_users_path, method: :get, class: 'form-inline' do + .form-group + = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control' + = button_tag type: 'submit', class: 'btn btn-primary' do + %i.icon-search + %hr = link_to 'Reset', admin_users_path, class: "btn btn-cancel" .col-md-9 diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index bd276f22221..d66119e2712 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -7,7 +7,7 @@ %span.cred (Admin) .pull-right - = link_to edit_admin_user_path(@user), class: "btn grouped" do + = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do %i.icon-edit Edit %hr @@ -78,7 +78,6 @@ - if @user.blocked? .alert.alert-info %h4 This user is blocked - %br %p Blocking user has the following effects: %ul %li User will not be able to login @@ -86,11 +85,11 @@ %li User will be removed from joined projects and groups %li Personal projects will be left %li Owned groups will be left + %br = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", data: { confirm: 'Are you sure?' } - else - .alert + .alert.alert-warning %h4 Block this user - %br %p Blocking user has the following effects: %ul %li User will not be able to login @@ -98,12 +97,12 @@ %li User will be removed from joined projects and groups %li Personal projects will be left %li Owned groups will be left + %br = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-remove" .alert.alert-danger %h4 Remove user - %br %p Deleting a user has the following effects: %ul %li All user content like authored issues, snippets, comments will be removed @@ -114,6 +113,7 @@ %li Next groups with all content will be removed: %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %br = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" .col-md-6 @@ -150,5 +150,3 @@ - if tm.respond_to? :project = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do %i.icon-remove - - diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index 89117726317..fdf96dd6f56 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -4,5 +4,6 @@ - if @events.any? .content_list - else - %p.nothing_here_message Projects activity will be displayed here -.loading.hide + .nothing-here-block Projects activity will be displayed here + += spinner diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml index d6937ca4813..a6bc946bedc 100644 --- a/app/views/dashboard/_groups.html.haml +++ b/app/views/dashboard/_groups.html.haml @@ -10,10 +10,11 @@ - groups.each do |group| %li.group-row = link_to group_path(id: group.path), class: dom_class(group) do + = image_tag group_icon(group.path), class: "avatar s24" %span.group-name.filter-title = truncate(group.name, length: 35) %span.arrow %i.icon-angle-right - if groups.blank? %li - %h3.nothing_here_message You have no groups yet. + .nothing-here-block You have no groups yet. diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 8313cc07b5e..44c7a4b8c80 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -14,7 +14,7 @@ - if projects.blank? %li - %h3.nothing_here_message There are no projects here. + .nothing-here-block There are no projects here. - if @projects_count > @projects_limit %li.bottom %span.light diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 19bd4e7bd54..af627588ad9 100644..100755 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -3,7 +3,7 @@ %span.pull-right #{@issues.total_count} issues %p.light - List all issues from all project's you have access to. + List all issues from all projects you have access to. %hr .row diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index b487a4d6666..550a93e178c 100644..100755 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -4,7 +4,7 @@ %p.light - List all merge requests from all project's you have access to. + List all merge requests from all projects you have access to. %hr .row .col-md-3 diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 8feef97c732..e1c9a5941e9 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -67,7 +67,7 @@ - if @projects.blank? %li - %h3.nothing_here_message There are no projects here. + .nothing-here-block There are no projects here. .bottom = paginate @projects, theme: "gitlab" diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index c4018d4b6d8..e5b7fbf097e 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -2,7 +2,7 @@ .dashboard.row .activities.col-md-8 = render 'activities' - .side.col-md-4.hidden-sm + .side.col-md-4.hidden-sm.hidden-xs = render 'sidebar' - else diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index dd63a232fe2..bf634d9de60 100644..100755 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -3,7 +3,8 @@ = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| .devise-errors = devise_error_messages! - = f.email_field :email, placeholder: 'Email', class: "form-control", required: true + .clearfix.append-bottom-20 + = f.email_field :email, placeholder: 'Email', class: "form-control", required: true .clearfix.append-bottom-10 = f.submit "Resend confirmation instructions", class: 'btn btn-success' %hr diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index a14ef2995c8..040821ca32a 100644..100755 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -2,7 +2,8 @@ %h3.page-title Reset password .devise-errors = devise_error_messages! - = f.email_field :email, placeholder: "Email", class: "form-control", required: true + .clearfix.append-bottom-20 + = f.email_field :email, placeholder: "Email", class: "form-control", required: true .clearfix.append-bottom-10 = f.submit "Reset password", class: "btn-primary btn" %hr diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 938f61d2093..bb87d9ecb4a 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,7 +1,7 @@ .login-box %h3.page-title Sign in - if ldap_enabled? - %ul.nav.nav-tabs.append-bottom-20 + %ul.nav.nav-tabs %li.active = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab' %li diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml index 9fbc6c190cc..fe8c0669c0e 100644 --- a/app/views/groups/_filter.html.haml +++ b/app/views/groups/_filter.html.haml @@ -21,7 +21,7 @@ = project.name_with_namespace %small.pull-right= entities_per_project(project, entity) - if @projects.blank? - %p.nothing_here_message This group has no projects yet + .nothing-here-block This group has no projects yet %fieldset %hr diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml index 5801139a9f2..3ab9276c541 100644 --- a/app/views/groups/_new_group_member.html.haml +++ b/app/views/groups/_new_group_member.html.haml @@ -1,15 +1,8 @@ = form_for @users_group, url: group_users_groups_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| - %h4.append-bottom-20 - New member(s) for - %strong #{@group.name} - group - - %p 1. Choose users you want in the group .form-group = f.label :user_ids, "People", class: 'control-label' .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large') - %p 2. Set access level for them .form-group = f.label :group_access, "Group Access", class: 'control-label' .col-sm-10= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select select2" diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 029d6cb0efa..bd4e3156af0 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -8,7 +8,7 @@ New project %ul.well-list - if projects.blank? - %p.nothing_here_message This groups has no projects yet + .nothing-here-block This groups has no projects yet - projects.each do |project| %li.project-row = link_to project_path(project), class: dom_class(project) do diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 77734815b8a..9308bd8124e 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -4,13 +4,15 @@ %li.active = link_to '#tab-edit', 'data-toggle' => 'tab' do %i.icon-edit - Edit Group + Group %li = link_to '#tab-projects', 'data-toggle' => 'tab' do %i.icon-folder-close Projects %li - = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab' + = link_to '#tab-remove', 'data-toggle' => 'tab' do + %i.icon-remove-sign + Remove .col-md-10 .tab-content @@ -20,7 +22,7 @@ %strong= @group.name group settings: %div.form-holder - = form_for @group, html: { class: "form-horizontal" } do |f| + = 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 @@ -35,6 +37,26 @@ .col-sm-10 = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + .form-group + .col-sm-2 + .col-sm-10 + = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160' + %p.light + - if @group.avatar? + You can change your group avatar here + - else + You can upload an group avatar here + %a.choose-btn.btn.btn-small.js-choose-group-avatar-button + %i.icon-paper-clip + %span Choose File ... + + %span.file_name.js-avatar-filename File name... + = f.file_field :avatar, class: "js-group-avatar-input hidden" + .light The maximum file size allowed is 100KB. + - if @group.avatar? + %hr + = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" + .form-actions = f.submit 'Save group', class: "btn btn-save" @@ -58,7 +80,7 @@ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove" - if @group.projects.blank? - %p.nothing_here_message This group has no projects yet + .nothing-here-block This group has no projects yet .tab-pane#tab-remove .ui-box.ui-box-danger diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index e24df310910..4b11d91dc98 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -5,7 +5,9 @@ %p.light Only issues from %strong #{@group.name} - group are listed here. To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. + group are listed here. + - if current_user + To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. %hr .row diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml index 124560e4786..0c972622f88 100644 --- a/app/views/groups/members.html.haml +++ b/app/views/groups/members.html.haml @@ -1,20 +1,41 @@ +- show_roles = should_user_see_group_roles?(current_user, @group) %h3.page-title Group members -%p.light - Members of group have access to all group projects. - Read more about permissions - %strong= link_to "here", help_permissions_path, class: "vlink" +- if show_roles + %p.light + Members of group have access to all group projects. + Read more about permissions + %strong= link_to "here", help_permissions_path, class: "vlink" %hr -- can_manage_group = current_user.can? :manage_group, @group -.ui-box + +.clearfix + = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find member by name', class: 'form-control search-text-input input-mn-300' } + = submit_tag 'Search', class: 'btn' + + - if current_user && current_user.can?(:manage_group, @group) + .pull-right + = link_to '#', class: 'btn btn-new js-toggle-visibility-link' do + Add members + %i.icon-chevron-down + + .js-toggle-visibility-container.hide.new-group-member-holder + = render "new_group_member" + +.ui-box.prepend-top-20 .title %strong #{@group.name} group members %small - (#{@members.count}) + (#{@members.total_count}) %ul.well-list - @members.each do |member| - = render 'users_groups/users_group', member: member, show_controls: can_manage_group -- if can_manage_group - = render "new_group_member" + = render 'users_groups/users_group', member: member, show_roles: show_roles, show_controls: true += paginate @members, theme: 'gitlab' + +:coffeescript + $('form.member-search-form').on 'submit', (event) -> + event.preventDefault() + Turbolinks.visit @.action + '?' + $(@).serialize() diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index eaf85bbdbc8..209130ec444 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -5,7 +5,9 @@ %p.light Only merge requests from %strong #{@group.name} - group are listed here. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. + group are listed here. + - if current_user + To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. %hr .row .col-md-3 diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 5332d383c4c..0343670c203 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,27 +1,33 @@ .dashboard .activities.col-md-8.hidden-sm - = render "events/event_last_push", event: @last_push - = link_to dashboard_path, class: 'btn btn-tiny' do - ← To dashboard - + - if current_user + = render "events/event_last_push", event: @last_push + = link_to dashboard_path, class: 'btn btn-tiny' do + ← To dashboard + %span.cgray You will only see events from projects in this group %hr = render 'shared/event_filter' - if @events.any? .content_list - else - %p.nothing_here_message Project activity will be displayed here - .loading.hide + .nothing-here-block Project activity will be displayed here + = spinner .side.col-md-4 - - if @group.description.present? - .description-block - = @group.description + .light-well.append-bottom-20 + = image_tag group_icon(@group.path), class: "avatar s90" + .clearfix.light + %h3.page-title + = @group.name + - if @group.description.present? + %p= @group.description = render "projects", projects: @projects - .prepend-top-20 - = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do - %strong - %i.icon-rss - News Feed + - if current_user + .prepend-top-20 + = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do + %strong + %i.icon-rss + News Feed %hr = render 'shared/promo' diff --git a/app/views/help/_api_layout.html.haml b/app/views/help/_api_layout.html.haml index c211b658410..af723f906d9 100644 --- a/app/views/help/_api_layout.html.haml +++ b/app/views/help/_api_layout.html.haml @@ -5,7 +5,7 @@ %i.icon-angle-left Back to help %ul.nav.nav-pills.nav-stacked - - %w(README projects project_snippets repositories deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file| + - %w(README projects project_snippets repositories repository_files commits deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file| %li{class: file == @category ? 'active' : nil} = link_to file.titleize, help_api_file_path(file) diff --git a/app/views/help/_layout.html.haml b/app/views/help/_layout.html.haml index fc0ac277a48..201d63ca243 100644 --- a/app/views/help/_layout.html.haml +++ b/app/views/help/_layout.html.haml @@ -1,5 +1,5 @@ .row - .col-md-3{:"data-spy" => 'affix'} + .col-md-3 %h3.page-title Help %ul.nav.nav-pills.nav-stacked - links = {:"Workflow" => help_workflow_path, :"SSH Keys" => help_ssh_path, :"GitLab Markdown" => help_markdown_path, :"Permissions" => help_permissions_path, :"API" => help_api_path, :"Web Hooks" => help_web_hooks_path, :"Rake Tasks" => help_raketasks_path, :"System Hooks" => help_system_hooks_path, :"Public Access" => help_public_access_path, :"Security" => help_security_path} @@ -7,5 +7,6 @@ %li{class: current_page?(path) ? 'active' : nil} = link_to title, path - .col-md-9.pull-right - = yield + .col-md-9 + .wiki + = yield diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index f1cb723ebac..c82acd917d0 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -6,9 +6,15 @@ %p.slead GitLab is open source software to collaborate on code. %br - Create projects and repositories, manage access and do code reviews. + Manage git repositories with fine grained access controls that keep your code secure. %br - Read more about GitLab at #{link_to "gitlab.org", "http://gitlab.org/", target: "_blank"}. + Perform code reviews and enhance collaboration with merge requests. + %br + Each project can also have an issue tracker and a wiki. + %br + Used by more than 50,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. + %br + Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}. .row .col-md-4 diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml index 15e3bf3a135..5d3ee4526fe 100644 --- a/app/views/help/permissions.html.haml +++ b/app/views/help/permissions.html.haml @@ -1,212 +1,6 @@ = render layout: 'help/layout' do - %h3.page-title Permissions - %p.light Users have different abilities depending on the access level they have in particular group or project. - %p.light If a user is both in a project group and in the project itself the highest permission level is used. - %p.light If a user is a GitLab administrator they receive all permissions. - %hr + %h3.page-title Permissions - %h4 Project: - %table.table - %thead - %tr - %th Action - %th Guest - %th Reporter - %th Developer - %th Master - %th Owner - %tbody - %tr - %td Create new issue - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Leave comments - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Write on project wall - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Pull project code - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Download project - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Create code snippets - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Create new merge request - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Create new branches - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Push to non-protected branches - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Remove non-protected branches - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Add tags - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Write a wiki - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Add new team members - %td - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Push to protected branches - %td - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Remove protected branches - %td - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Edit project - %td - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Add Deploy Keys to project - %td - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Configure Project Hooks - %td - %td - %td - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Switch visibility level - %td - %td - %td - %td - %td.permission-x ✓ - %tr - %td Transfer project to another namespace - %td - %td - %td - %td - %td.permission-x ✓ - %tr - %td Remove project - %td - %td - %td - %td - %td.permission-x ✓ - - %h4 Group - %table.table - %thead - %tr - %th Action - %th Guest - %th Reporter - %th Developer - %th Master - %th Owner - %tbody - %tr - %td Browse group - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %td.permission-x ✓ - %tr - %td Edit group - %td - %td - %td - %td - %td.permission-x ✓ - %tr - %td Create project in group - %td - %td - %td - %td - %td.permission-x ✓ - %tr - %td Manage group members - %td - %td - %td - %td - %td.permission-x ✓ - %tr - %td Remove group - %td - %td - %td - %td - %td.permission-x ✓ + .help_body + = preserve do + = markdown File.read(Rails.root.join("doc", "permissions", "permissions.md")) diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml index ba2df8c3553..d1ec3f5d5c1 100644 --- a/app/views/help/public_access.html.haml +++ b/app/views/help/public_access.html.haml @@ -1,46 +1,6 @@ = render layout: 'help/layout' do - %h3.page-title Public Access + %h3.page-title Public Access - %p.slead - GitLab allows you to open selected projects to be accessed - %strong publicly - or - %strong internally - \. - %br - Projects with either of these visibility levels will be listed in the #{link_to "public access directory", public_root_path}. - %br - Internal projects will only be available to authenticated users. - - .clearfix - .dashboard-intro-icon - = public_icon - %h4 - Public projects - %p - Public project can be cloned - %strong without any - authentication. - %br - It will also be listed on the #{link_to "public access directory", public_root_path}. - %br - %strong Any logged in user - will have #{link_to "Guest", help_permissions_path} permissions on the repository. - - .clearfix - .dashboard-intro-icon - = internal_icon - %h4 - Internal projects - %p - Internal project can be cloned by any logged in user. - %br - It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users. - %br - Any logged in user will have #{link_to "Guest", help_permissions_path} permissions on the repository. - - %h4 How to change project visibility - %ol - %li Go to your project dashboard - %li Click on the "Edit" tab - %li Change "Visibility Level" + .help_body + = preserve do + = markdown File.read(Rails.root.join("doc", "public_access", "public_access.md")) diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml index 773e490ef80..8ae1ed1ab6e 100644 --- a/app/views/help/ssh.html.haml +++ b/app/views/help/ssh.html.haml @@ -1,17 +1,6 @@ = render layout: 'help/layout' do - %h3.page-title SSH Keys + %h3.page-title SSH Keys - %p.slead - SSH key allows you to establish a secure connection between your computer and GitLab - - %p.slead - To generate a new SSH key just open your terminal and use code below. Press enter to accept the defaults when generating the key. - - %pre.dark - ssh-keygen -t rsa -C "#{current_user.email}" - - %p.slead - Next just use code below to dump your public key and add to GitLab SSH Keys - - %pre.dark - cat ~/.ssh/id_rsa.pub + .help_body + = preserve do + = markdown File.read(Rails.root.join("doc", "ssh", "ssh.md")) diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml index 3e401a6e19f..e24f566121d 100644 --- a/app/views/help/system_hooks.html.haml +++ b/app/views/help/system_hooks.html.haml @@ -1,11 +1,6 @@ = render layout: 'help/layout' do - %h3.page-title System hooks + %h3.page-title System hooks - %p.slead - Your GitLab instance can perform HTTP POST requests on the following events: create_project, delete_project, create_user, delete_user, change_team_member. - %br - %br - System Hooks can be used, e.g. for logging or changing information in a LDAP server. - %br - %h5 Hooks request example: - = render "admin/hooks/data_ex" + .help_body + = preserve do + = markdown File.read(Rails.root.join("doc", "system_hooks", "system_hooks.md")) diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml index 7bde7fcc3d0..9e2b54ab6ec 100644 --- a/app/views/help/web_hooks.html.haml +++ b/app/views/help/web_hooks.html.haml @@ -1,115 +1,6 @@ = render layout: 'help/layout' do - %h3.page-title Project web hooks - %p.light - Project web hooks allow you to trigger url if new code is pushed or new issue is created - %hr + %h3.page-title Project web hooks - %p.slead - You can configure web hook to listen for specific events like pushes, issues, merge requests. - %br - GitLab will send POST request with data to web hook url. - %br - Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. - %hr - - %h4 Push events - %p.light - Triggered when you push to the repository except pushing tags. - %br - Request body: - = highlight_js do - :erb - { - "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", - "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "ref": "refs/heads/master", - "user_id": 4, - "user_name": "John Smith", - "project_id": 15, - "repository": { - "name": "Diaspora", - "url": "git@localhost:diaspora.git", - "description": "", - "homepage": "http://localhost/diaspora", - }, - "commits": [ - { - "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "message": "Update Catalan translation to e38cb41.", - "timestamp": "2011-12-12T14:27:31+02:00", - "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "author": { - "name": "Jordi Mallach", - "email": "jordi@softcatala.org", - } - }, - // ... - { - "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "message": "fixed readme", - "timestamp": "2012-01-03T23:36:29+02:00", - "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "author": { - "name": "GitLab dev user", - "email": "gitlabdev@dv6700.(none)", - }, - }, - ], - "total_commits_count": 4, - }; - - - %h4.prepend-top-20 Issues events - %p.light - Triggered when new issue created or existing issue was updated/closed/reopened. - %br - Request body: - = highlight_js do - :erb - { - "object_kind":"issue", - "object_attributes":{ - "id":301, - "title":"New API: create/update/delete file", - "assignee_id":51, - "author_id":51, - "project_id":14, - "created_at":"2013-12-03T17:15:43Z", - "updated_at":"2013-12-03T17:15:43Z", - "position":0, - "branch_name":null, - "description":"Create new API for manipulations with repository", - "milestone_id":null, - "state":"opened", - "iid":23 - } - } - %h4.prepend-top-20 Merge request events - %p.light - Triggered when new merge request created or existing merge request was updated/merged/closed. - %br - Request body: - = highlight_js do - :erb - { - "object_kind":"merge_request", - "object_attributes":{ - "id":99, - "target_branch":"master", - "source_branch":"ms-viewport", - "source_project_id":14, - "author_id":51, - "assignee_id":6, - "title":"MS-Viewport", - "created_at":"2013-12-03T17:23:34Z", - "updated_at":"2013-12-03T17:23:34Z", - "st_commits":null, - "st_diffs":null, - "milestone_id":null, - "state":"opened", - "merge_status":"unchecked", - "target_project_id":14, - "iid":1, - "description":"" - } - } + .help_body + = preserve do + = markdown File.read(Rails.root.join("doc", "web_hooks", "web_hooks.md")) diff --git a/app/views/help/workflow.html.haml b/app/views/help/workflow.html.haml index 2b8950cd5c2..47de4ad796c 100644 --- a/app/views/help/workflow.html.haml +++ b/app/views/help/workflow.html.haml @@ -1,37 +1,6 @@ = render layout: 'help/layout' do - %h3.page-title Workflow - - %ol.help - %li - %p Clone project - .bash - %pre.dark - git clone git@example.com:project-name.git - - %li - %p Create branch with your feature - .bash - %pre.dark - git checkout -b $feature_name - - %li - %p Write code. Commit changes - .bash - %pre.dark - git commit -am "My feature is ready" - - %li - %p Push your branch to GitLab - .bash - %pre.dark - git push origin $feature_name - - %li - %p Review your code on Commits page - - %li - %p Create a merge request - - %li - %p Your team lead will review code & merge it to main branch + %h3.page-title Workflow + .help_body + = preserve do + = markdown File.read(Rails.root.join("doc", "workflow", "workflow.md")) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 68146e661eb..9ba20f1347d 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -12,6 +12,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') + = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') -# Atom feed - if current_user @@ -22,3 +23,8 @@ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}") - if current_controller?(:issues) = auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") + + -# Go repository retrieval support. + - if controller_name == 'projects' && action_name == 'show' + %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"} + diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 58b216f2e98..5080a1b7ef6 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -6,7 +6,7 @@ = link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do %h1 GITLAB %span.separator - %h1.project_name= title + %h1.title= title %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} %span.sr-only Toggle navigation @@ -14,14 +14,13 @@ .navbar-collapse.collapse %ul.nav.navbar-nav - %li + %li.hidden-sm.hidden-xs %a %div.hide.turbolink-spinner %i.icon-refresh.icon-spin - Loading... - %li.hidden-sm + %li.hidden-sm.hidden-xs = render "layouts/search" - %li.visible-sm + %li.visible-sm.visible-xs = link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do %i.icon-search %li diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml new file mode 100644 index 00000000000..135e8daca26 --- /dev/null +++ b/app/views/layouts/_piwik.html.haml @@ -0,0 +1,12 @@ +:javascript + var _paq = _paq || []; + _paq.push(["trackPageView"]); + _paq.push(["enableLinkTracking"]); + + (function() { + var u=(("https:" == document.location.protocol) ? "https" : "http") + "://#{extra_config.piwik_url}/"; + _paq.push(["setTrackerUrl", u+"piwik.php"]); + _paq.push(["setSiteId", "#{extra_config.piwik_site_id}"]); + var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0]; g.type="text/javascript"; + g.defer=true; g.async=true; g.src=u+"piwik.js"; s.parentNode.insertBefore(g,s); + })(); diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index 7d188fb28e4..99e11cd2b5c 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -6,11 +6,7 @@ = link_to public_root_path, class: "home" do %h1 GITLAB %span.separator - %h1.project_name - - if @project - = project_title(@project) - - else - Public Projects + %h1.title= title .pull-right = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in btn-new' @@ -20,4 +16,3 @@ %a %div.hide.turbolink-spinner %i.icon-refresh.icon-spin - Loading... diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 9a0db99332a..a0e55b21c32 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -7,4 +7,4 @@ = hidden_field_tag :search_code, true = hidden_field_tag :repository_ref, @ref = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test' - .search-autocomplete-json.hide{:'data-autocomplete-opts' => search_autocomplete_source } + .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 3a23cbdb376..53e0dbaef9b 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -2,9 +2,10 @@ %html{ lang: "en"} = render "layouts/head", title: "Admin area" %body{class: "#{app_theme} admin", :'data-page' => body_data_page} + = render "layouts/broadcast" = render "layouts/head_panel", title: "Admin area" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/admin' .container diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 92edc718235..511db389e0f 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -5,7 +5,7 @@ = render "layouts/broadcast" = render "layouts/head_panel", title: "Dashboard" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/dashboard' .container diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index b546a9fa84f..fb4a3a3ba95 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -5,7 +5,7 @@ = render "layouts/broadcast" = render "layouts/head_panel", title: "group: #{@group.name}" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/group' .container diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index a8bb3b91559..36b102dc25a 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -5,11 +5,13 @@ = nav_link(path: 'groups#issues') do = link_to issues_group_path(@group) do Issues - %span.count= current_user.assigned_issues.opened.of_group(@group).count + - if current_user + %span.count= current_user.assigned_issues.opened.of_group(@group).count = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group) do Merge Requests - %span.count= current_user.cared_merge_requests.opened.of_group(@group).count + - if current_user + %span.count= current_user.cared_merge_requests.opened.of_group(@group).count = nav_link(path: 'groups#members') do = link_to "Members", members_group_path(@group) diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index d44cb975ea5..35d0d417502 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -4,6 +4,10 @@ %i.icon-home = nav_link(controller: :accounts) do = link_to "Account", profile_account_path + = nav_link(controller: :emails) do + = link_to profile_emails_path do + Emails + %span.count= current_user.emails.count + 1 - unless current_user.ldap_user? = nav_link(controller: :passwords) do = link_to "Password", edit_profile_password_path diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml index 7325664bc19..c43d688a2cb 100644 --- a/app/views/layouts/navless.html.haml +++ b/app/views/layouts/navless.html.haml @@ -2,6 +2,7 @@ %html{ lang: "en"} = render "layouts/head", title: @title %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" = render "layouts/head_panel", title: @title = render "layouts/flash" diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 72849639571..2d869a6cdcb 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -5,7 +5,7 @@ = render "layouts/broadcast" = render "layouts/head_panel", title: "Profile" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/profile' .container diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index a55f043f2cf..5659cfab31d 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -9,7 +9,7 @@ - if can?(current_user, :download_code, @project) = render 'shared/no_ssh' - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/project' .container diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index 55214c6a5c9..3ae4961b137 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -9,7 +9,7 @@ - if can?(current_user, :download_code, @project) = render 'shared/no_ssh' - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/project' .container diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index 8f490f61a9c..3c76bbb9575 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -2,10 +2,10 @@ %html{ lang: "en"} = render "layouts/head", title: "Public Projects" %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" - if current_user = render "layouts/head_panel", title: "Public Projects" - else - = render "layouts/public_head_panel" - + = render "layouts/public_head_panel", title: "Public Projects" .container.navless-container .content= yield diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml new file mode 100644 index 00000000000..a289b784725 --- /dev/null +++ b/app/views/layouts/public_group.html.haml @@ -0,0 +1,10 @@ +!!! 5 +%html{ lang: "en"} + = render "layouts/head", title: group_head_title + %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" + = render "layouts/public_head_panel", title: "group: #{@group.name}" + %nav.main-nav.navbar-collapse.collapse + .container= render 'layouts/nav/group' + .container + .content= yield diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml index 1e8814134f5..cf4ca9c7a84 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -2,7 +2,8 @@ %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace %body{class: "#{app_theme} application", :'data-page' => body_data_page} - = render "layouts/public_head_panel" + = render "layouts/broadcast" + = render "layouts/public_head_panel", title: project_title(@project) %nav.main-nav .container= render 'layouts/nav/project' .container diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml new file mode 100644 index 00000000000..4aa258fea0d --- /dev/null +++ b/app/views/layouts/public_users.html.haml @@ -0,0 +1,8 @@ +!!! 5 +%html{ lang: "en"} + = render "layouts/head", title: @title + %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" + = render "layouts/public_head_panel", title: @title + .container.navless-container + .content= yield diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml index 177b3a4f8f4..97ed8ba12df 100644 --- a/app/views/layouts/search.html.haml +++ b/app/views/layouts/search.html.haml @@ -2,6 +2,7 @@ %html{ lang: "en"} = render "layouts/head", title: "Search" %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" = render "layouts/head_panel", title: "Search" = render "layouts/flash" diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml index e64e68d2446..ce13853ed7f 100644 --- a/app/views/layouts/user_team.html.haml +++ b/app/views/layouts/user_team.html.haml @@ -2,9 +2,10 @@ %html{ lang: "en"} = render "layouts/head", title: "#{@team.name}" %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" = render "layouts/head_panel", title: "team: #{@team.name}" = render "layouts/flash" - %nav.main-nav + %nav.main-nav.navbar-collapse.collapse .container= render 'layouts/nav/team' .container diff --git a/app/views/notify/new_email_email.html.haml b/app/views/notify/new_email_email.html.haml new file mode 100644 index 00000000000..4a0448a573c --- /dev/null +++ b/app/views/notify/new_email_email.html.haml @@ -0,0 +1,10 @@ +%p + Hi #{@user.name}! +%p + A new email was added to your account: +%p + email: + %code= @email.email +%p + If this email was added in error, you can remove it here: + = link_to "Emails", profile_emails_url diff --git a/app/views/notify/new_email_email.text.erb b/app/views/notify/new_email_email.text.erb new file mode 100644 index 00000000000..51cba99ad0d --- /dev/null +++ b/app/views/notify/new_email_email.text.erb @@ -0,0 +1,7 @@ +Hi <%= @user.name %>! + +A new email was added to your account: + +email.................. <%= @email.email %> + +If this email was added in error, you can remove it here: <%= profile_emails_url %> diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index d0b30c08338..ab0d6c653b9 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -21,3 +21,8 @@ %pre = diff.diff %br + +- if @compare.timeout + %h5 Huge diff. To prevent performance issues it was hidden +- elsif @compare.commits_over_limit? + %h5 Diff for big amount of commits is disabled diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml index 6718ca68359..93b344d2c82 100644 --- a/app/views/notify/repository_push_email.text.haml +++ b/app/views/notify/repository_push_email.text.haml @@ -18,3 +18,8 @@ Diff: = diff.new_path || diff.old_path \===================================== = diff.diff +\ +- if @compare.timeout + Huge diff. To prevent performance issues it was hidden +- elsif @compare.commits_over_limit? + Diff for big amount of commits is disabled diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 3220736b078..c60e1ca9297 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -50,7 +50,10 @@ %div = f.text_field :username, required: true, class: 'form-control' - %span.loading-gif.hide= image_tag "ajax_loader.gif" + .loading-gif.hide + %p + %i.icon-spinner.icon-spin + Saving new username %p.light = user_url(@user) %div diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml new file mode 100644 index 00000000000..b5f1e438ccb --- /dev/null +++ b/app/views/profiles/emails/index.html.haml @@ -0,0 +1,33 @@ +%h3.page-title + My email addresses +%p.light + Your + %b Primary Email + will be used for account notifications, avatar detection and web based operations, such as edits and merges. + %br + All email addresses will be used to identify your commits. + +%hr + +.ui-box + .title + Emails (#{@emails.count + 1}) + %ul.well-list#emails-table + %li + %strong= @primary + %span.label.label-success Primary Email + - @emails.each do |email| + %li + %strong= email.email + %span.cgray + added #{time_ago_with_tooltip(email.created_at)} + = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right' + +%h4 Add email address += form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f| + .form-group + = f.label :email, class: 'control-label' + .col-sm-10 + = f.text_field :email, class: 'form-control' + .form-actions + = f.submit 'Add', class: 'btn btn-create' diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml index 1d24e636bf4..cebc0b6bccb 100644 --- a/app/views/profiles/groups/index.html.haml +++ b/app/views/profiles/groups/index.html.haml @@ -18,13 +18,14 @@ %li .pull-right - if can?(current_user, :manage_group, group) - = link_to edit_group_path(group), class: "btn-small btn grouped" do + = link_to edit_group_path(group), class: "btn-small btn btn-grouped" do %i.icon-cogs Settings - = link_to leave_profile_group_path(group), data: { confirm: "Are you sure you want to leave #{group.name} group?"}, method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do - %i.icon-signout - Leave + - if can?(current_user, :destroy, user_group) + = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do + %i.icon-signout + Leave = link_to group, class: 'group-name' do %strong= group.name diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 79fdb164089..71a4ca91d42 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -17,6 +17,6 @@ = render @keys - if @keys.blank? %li - %h3.nothing_here_message There are no SSH keys with access to your account. + .nothing-here-block There are no SSH keys with access to your account. diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml index d123b8f9407..218d51d31af 100644 --- a/app/views/profiles/notifications/_settings.html.haml +++ b/app/views/profiles/notifications/_settings.html.haml @@ -1,31 +1,17 @@ %li - .row - .col-sm-4 - %span - = notification_icon(notification) - - - if membership.kind_of? UsersGroup - = link_to membership.group.name, membership.group - - else - = link_to_project(membership.project) - .col-sm-8 - = 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') - - = label_tag nil, class: 'radio-inline' do - = radio_button_tag :notification_level, Notification::N_GLOBAL, notification.global?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' - %span Use global setting - - = label_tag nil, class: 'radio-inline' do - = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' - %span Disabled - - = label_tag nil, class: 'radio-inline' do - = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' - %span Participating - - = label_tag nil, class: 'radio-inline' do - = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit' - %span Watch + %span.notification-icon-holder + - if notification.global? + = notification_icon(@notification) + - else + = notification_icon(notification) + %span.str-truncated + - if membership.kind_of? UsersGroup + = 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: 'trigger-submit' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 878d7f77430..efe9c032190 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -3,56 +3,49 @@ %p.light GitLab uses the email specified in your profile for notifications %hr -.alert.alert-info - %p - %i.icon-circle.cred - %strong Disabled - – You will not get any notifications via email - %p - %i.icon-circle.cblue - %strong Participating - – You will only receive notifications from related resources (e.g. from your commits or assigned issues) - %p - %i.icon-circle.cgreen - %strong Watch - – You will receive all notifications from projects in which you participate += form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications form-horizontal global-notifications-form' do + = hidden_field_tag :notification_type, 'global' -.row - .col-sm-4 - %h4 - = notification_icon(@notification) - Global setting - .col-sm-8 - = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do - = hidden_field_tag :notification_type, 'global' - - = label_tag nil, class: 'radio-inline' do + = label_tag :notification_level, 'Notification level', class: 'control-label' + .col-sm-10 + .radio + = label_tag nil, class: '' do = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit' - %span Disabled + .level-title + Disabled + %p You will not get any notifications via email - = label_tag nil, class: 'radio-inline' do + .radio + = label_tag nil, class: '' do = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' - %span Participating + .level-title + Participating + %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) - = label_tag nil, class: 'radio-inline' do + .radio + = label_tag nil, class: '' do = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit' - %span Watch + .level-title + Watch + %p You will receive all notifications from projects in which you participate -%br -= link_to '#', class: 'js-toggle-visibility-link' do - %span.btn.btn-tiny - %i.icon-chevron-down - %span Advanced notifications settings -.js-toggle-visibility-container.hide +.clearfix %hr - %h4 Groups: - %ul.bordered-list - - @users_groups.each do |users_group| - - notification = Notification.new(users_group) - = render 'settings', type: 'group', membership: users_group, notification: notification + %p + You can also specify notification level per group or per project + %br + By default all projects and groups uses notification level set above +.row.all-notifications + .col-md-6 + %h4 Groups: + %ul.bordered-list + - @users_groups.each do |users_group| + - notification = Notification.new(users_group) + = render 'settings', type: 'group', membership: users_group, notification: notification - %h4 Projects: - %ul.bordered-list - - @users_projects.each do |users_project| - - notification = Notification.new(users_project) - = render 'settings', type: 'project', membership: users_project, notification: notification + .col-md-6 + %h4 Projects: + %ul.bordered-list + - @users_projects.each do |users_project| + - notification = Notification.new(users_project) + = render 'settings', type: 'project', membership: users_project, notification: notification diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index f333879cf6c..b72232ee36b 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -1,22 +1,22 @@ -= form_for @user, url: profile_password_path, method: :post do |f| - .light-well.padded - %p.slead - Please set new password before proceed. - %br - After 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 +%h3.page-title Setup new password +%hr += form_for @user, url: profile_password_path, method: :post, html: { class: 'form-horizontal '} do |f| + %p.slead + 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-group - = f.label :password - .col-sm-10= f.password_field :password, required: true - .form-group - = f.label :password_confirmation - .col-sm-10 - = f.password_field :password_confirmation, required: true - .form-group - .col-sm-10 - = f.submit 'Set new password', class: "btn btn-create" + .form-group + = f.label :password, class: 'control-label' + .col-sm-10= f.password_field :password, required: true, class: 'form-control' + .form-group + = f.label :password_confirmation, class: 'control-label' + .col-sm-10 + = f.password_field :password_confirmation, required: true, class: 'form-control' + .form-actions + = f.submit 'Set new password', class: "btn btn-create" diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 6ae1032aebf..523a07db400 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -47,6 +47,9 @@ = f.label :twitter, class: "control-label" .col-sm-10= f.text_field :twitter, class: "form-control" .form-group + = f.label :website_url, 'Website', class: "control-label" + .col-sm-10= f.text_field :website_url, class: "form-control" + .form-group = f.label :bio, class: "control-label" .col-sm-10 = f.text_area :bio, rows: 6, class: "form-control", maxlength: 250 diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index e4cfabc3100..4c7b088ae9a 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,19 +1,21 @@ -%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20 +%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20.project-settings-nav = nav_link(path: 'projects#edit') do = link_to edit_project_path(@project), class: "stat-tab tab " do - Edit Project + %i.icon-edit + Project = nav_link(controller: [:team_members, :teams]) do = link_to project_team_index_path(@project), class: "team-tab tab" do + %i.icon-group Members = nav_link(controller: :deploy_keys) do = link_to project_deploy_keys_path(@project) do - %span + %i.icon-key Deploy Keys = nav_link(controller: :hooks) do = link_to project_hooks_path(@project) do - %span + %i.icon-link Web Hooks = nav_link(controller: :services) do = link_to project_services_path(@project) do - %span + %i.icon-cogs Services diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 32ea967105a..e59d970bf46 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -15,6 +15,10 @@ - else = link_to title, '#' +%ul.blob-commit-info.bs-callout.bs-callout-info + - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path) + = render blob_commit, project: @project + %div#tree-content-holder.tree-content-holder .file-holder .file-title diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index d03b53888ae..7cbea7c3eb6 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -8,7 +8,6 @@ - else .file-content.code - unless blob.empty? - %div{class: user_color_scheme_class} - = raw blob.colorize(formatter: :gitlab) + = render 'shared/file_hljs', blob: blob - else - %p.nothing_here_message Empty file + .nothing-here-block Empty file diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 56220e520f3..69167654c39 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,5 +1,6 @@ %div.tree-ref-holder = render 'shared/ref_switcher', destination: 'blob', path: @path + %div#tree-holder.tree-holder = render 'blob', blob: @blob diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 4fd708517fc..f0731977098 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,4 +1,4 @@ -- commit = Commit.new(Gitlab::Git::Commit.new(branch.commit)) +- commit = @repository.commit(branch.target) %li %h4 = link_to project_commits_path(@project, branch.name) do @@ -10,19 +10,24 @@ %i.icon-lock .pull-right - if can?(current_user, :download_code, @project) - = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'grouped btn-group-small' - = link_to project_compare_index_path(@project, from: branch.name, to: branch.name), class: 'btn grouped btn-small', title: "Compare" do - %i.icon-copy - Compare + = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small' + - if branch.name != @repository.root_ref + = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do + %i.icon-copy + Compare - if can?(current_user, :admin_project, @project) && branch.name != @repository.root_ref - = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do + = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do %i.icon-trash - %p - = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do - = commit.short_id - = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: '' - %span.light - = gfm escape_once(truncate(commit.title, length: 40)) - #{time_ago_with_tooltip(commit.committed_date)} + - if commit + %p + = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do + = commit.short_id + = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: '' + %span.light + = gfm escape_once(truncate(commit.title, length: 40)) + #{time_ago_with_tooltip(commit.committed_date)} + - else + %p + Cant find HEAD commit for this branch diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 690df98a2ab..bee04eb013e 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -4,7 +4,7 @@ = render "filter" .col-md-9 - unless @branches.empty? - %ul.bordered-list.top-list + %ul.bordered-list.top-list.all-branches - @branches.each do |branch| = render "projects/branches/branch", branch: branch = paginate @branches, theme: 'gitlab' diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index d173065afd2..5da2ede2937 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -5,12 +5,11 @@ .form-group = label_tag :branch_name, 'Name for new branch', class: 'control-label' .col-sm-10 - = text_field_tag :branch_name, nil, placeholder: 'feature/dashboard', required: true, tabindex: 1, class: 'form-control' + = text_field_tag :branch_name, nil, placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control' .form-group = label_tag :ref, 'Create from', class: 'control-label' .col-sm-10 - = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2, class: 'form-control' - .light branch name or commit SHA + = text_field_tag :ref, nil, placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control' .form-actions = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 3d666807cf9..df44c30c005 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -5,14 +5,14 @@ %i.icon-comment = @notes_count .pull-left.btn-group - %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } + %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} } %i.icon-download-alt Download as %span.caret %ul.dropdown-menu %li= link_to "Email Patches", project_commit_path(@project, @commit, format: :patch) %li= link_to "Plain Diff", project_commit_path(@project, @commit, format: :diff) - = link_to project_tree_path(@project, @commit), class: "btn btn-primary grouped" do + = link_to project_tree_path(@project, @commit), class: "btn btn-primary btn-grouped" do %span Browse Code » %div diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 9772d3ef2ef..a0606662807 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -1,9 +1,12 @@ -%li.commit +%li.commit.js-toggle-container .commit-row-title = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" %span.str-truncated = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" + - if commit.description? + %a.text-expander.js-toggle-button ... + = link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" .notes_count - notes = project.notes.for_commit_id(commit.id) @@ -12,6 +15,10 @@ %i.icon-comment = notes.count + - if commit.description? + .commit-row-description.js-toggle-content + = simple_format(commit.description) + .commit-row-info = commit_author_link(commit, avatar: true, size: 16) .committed_ago diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml index a41a89bb972..dd287fcc153 100644 --- a/app/views/projects/commits/_diffs.html.haml +++ b/app/views/projects/commits/_diffs.html.haml @@ -8,18 +8,18 @@ - if current_controller?(:commit) or current_controller?(:merge_requests) Please, download the diff as - if current_controller?(:commit) - = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined_link" + = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined-link" or - = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined_link" + = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined-link" - else - = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined_link" + = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined-link" or - = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined_link" + = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined-link" instead. - unless @force_suppress_diff %p If you still want to see the diff - = link_to "click this link", url_for(force_show_diff: true), class: "underlined_link" + = link_to "click this link", url_for(force_show_diff: true), class: "underlined-link" %p.commit-stat-summary Showing @@ -44,25 +44,33 @@ - file = project.repository.blob_at(@commit.id, diff.new_path) - file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file - next unless file - .file{id: "diff-#{i}"} - .header + .diff-file{id: "diff-#{i}"} + .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"} - if diff.deleted_file %span= diff.old_path - - if @commit.parent_ids.present? - = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), { class: 'btn btn-small view-file' } do - View file @ - %span.commit-short-id= @commit.short_id(6) + .diff-btn-group + - if @commit.parent_ids.present? + = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), { class: 'btn btn-small view-file' } do + View file @ + %span.commit-short-id= @commit.short_id(6) - else %span= diff.new_path - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" - = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), { class: 'btn btn-small view-file' } do - View file @ - %span.commit-short-id= @commit.short_id(6) + .diff-btn-group + - if @merge_request && @merge_request.source_project + = link_to project_edit_tree_path(@merge_request.source_project, tree_join(@merge_request.source_branch, diff.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do + Edit + + + = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), { class: 'btn btn-small view-file' } do + View file @ + %span.commit-short-id= @commit.short_id(6) + - .content + .diff-content -# Skipp all non non-supported blobs - next unless file.respond_to?('text?') - if file.text? @@ -74,4 +82,4 @@ - old_file = project.repository.blob_at(@commit.parent_id, diff.old_path) if @commit.parent_id = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i - else - %p.nothing_here_message No preview for this file type + .nothing-here-block No preview for this file type diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index b9ab27d212c..81e33743911 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs.append-bottom-15 +%ul.nav.nav-tabs %li= render partial: 'shared/ref_switcher', locals: {destination: 'commits'} = nav_link(controller: [:commit, :commits]) do diff --git a/app/views/projects/commits/_image.html.haml b/app/views/projects/commits/_image.html.haml index 9a8b7c857e5..6d9ef5964d9 100644 --- a/app/views/projects/commits/_image.html.haml +++ b/app/views/projects/commits/_image.html.haml @@ -15,7 +15,7 @@ %span.meta-filesize= "#{number_to_human_size old_file.size}" | %b W: - %span.meta-width + %span.meta-width | %b H: %span.meta-height @@ -27,7 +27,7 @@ %span.meta-filesize= "#{number_to_human_size file.size}" | %b W: - %span.meta-width + %span.meta-width | %b H: %span.meta-height @@ -49,7 +49,7 @@ %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} .frame.added %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} - .col-sm-10 + .controls .transparent .drag-track .dragger{:style => "left: 0px;"} diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index f5863463fee..b36369b4285 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -2,5 +2,7 @@ .commit-row-title = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" - = link_to_gfm truncate(commit.title, length: 40), project_commit_path(project, commit.id), class: "commit-row-message" - #{time_ago_with_tooltip(commit.committed_date)} + %span.str-truncated + = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" + .pull-right + #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 723c5a1c340..3a4f304a255 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -9,7 +9,7 @@ %div{id: dom_id(@project)} #commits-list= render "commits" .clear -.loading{ style: "display:none;"} += spinner - if @commits.count == @limit :javascript diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 0762655fb93..da6157cf1b6 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -14,7 +14,7 @@ = submit_tag "Compare", class: "btn btn-create commits-compare-btn" - if compare_to_mr_button? - = link_to compare_mr_path, class: 'prepend-left-10' do + = link_to compare_mr_path, class: 'prepend-left-10 btn' do %strong Make a merge request diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index e9456c24960..9bd49855369 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -5,21 +5,32 @@ = render "form" -- if @commits.size > 100 - .alert.alert-warning - %p - %strong Warning! This comparison includes more than 100 commits. - %p To preserve performance the line diff is not shown. - - if @commits.present? %div.ui-box .title Commits (#{@commits.count}) - %ul.well-list= render Commit.decorate(@commits), project: @project + - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + %ul.well-list + - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit| + = render "projects/commits/inline_commit", commit: commit, project: @project + %li.warning-row.unstyled + other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. + - else + %ul.well-list= render Commit.decorate(@commits), project: @project - - unless @diffs.empty? - %h4 Diff + %h4 Diff + - if @diffs.present? = render "projects/commits/diffs", diffs: @diffs, project: @project + - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + .bs-callout.bs-callout-danger + %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. + %p To preserve performance the line diff is not shown. + - elsif @timeout + .bs-callout.bs-callout-danger + %h4 Diff for this comparison is extremely large. + %p Use command line to browse diff for this comparison. + + - else .light-well %center diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index 90d86102aca..f50aeba337a 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -20,7 +20,7 @@ = render @enabled_keys - if @enabled_keys.blank? .light-well - %p.nothing_here_message Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one + .nothing-here-block Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one .col-md-6.available-keys %h5 %strong Deploy keys @@ -29,4 +29,4 @@ = render @available_keys - if @available_keys.blank? .light-well - %p.nothing_here_message Deploy keys from projects you have access to will be displayed here + .nothing-here-block Deploy keys from projects you have access to will be displayed here diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 79234fe4fa9..005d994806e 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -93,10 +93,11 @@ - %center.light.prepend-top-20.padded + .centered-light-block %h3 %i.icon-warning-sign Dangerous settings + %p Project settings below may result in data loss! = link_to '#', class: 'btn js-toggle-visibility-link' do Show it to me @@ -132,7 +133,7 @@ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, method: :post, class: "btn btn-remove" - else - %p.nothing_here_message Only the project owner can archive a project + .nothing-here-block Only the project owner can archive a project - if can?(current_user, :change_namespace, @project) .ui-box.ui-box-danger @@ -153,7 +154,7 @@ .form-actions = f.submit 'Transfer', class: "btn btn-remove" - else - %p.nothing_here_message Only the project owner can transfer a project + .nothing-here-block Only the project owner can transfer a project .ui-box.ui-box-danger .title Rename repository @@ -185,10 +186,11 @@ = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" - else - %p.nothing_here_message Only project owner can remove a project + .nothing-here-block Only project owner can remove a project .save-project-loader.hide %center - = image_tag "ajax_loader.gif" - %h3 Saving project. + %h2 + %i.icon-spinner.icon-spin + Saving project. %p Please wait a moment, this page will automatically refresh when ready. diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index 10dae3c8ff0..3f2e98f3a7f 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -11,7 +11,7 @@ %strong= @ref %span.options .btn-group.tree-btn-group - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } + = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } .file-content.code %pre#editor= @blob.data @@ -23,15 +23,16 @@ .form-actions = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] .commit-button-annotation = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary' .message to branch %strong= @ref - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} + = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message} :javascript - ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") + ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace") var ace_mode = "#{@blob.language.try(:ace_mode)}"; var editor = ace.edit("editor"); if (ace_mode) { diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index eae4949fcd3..489b9b0e951 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -3,8 +3,9 @@ - if @project.import? && !@project.imported .save-project-loader %center - = image_tag "ajax_loader.gif" - %h3 Importing repository. + %h2 + %i.icon-spinner.icon-spin + Importing repository. %p.monospace git clone --bare #{@project.import_url} %p Please wait while we import the repository for you. Refresh at will. :javascript diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index a21cb9e7861..27348232ba2 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,7 +1,8 @@ .loading-graph %center - .loading - %h3.page-title Building repository graph. + %h3.page-title + %i.icon-spinner.icon-spin + Building repository graph. %p Please wait a moment, this page will automatically refresh when ready. .stat-graph diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index a095fd06d2f..00e5ae27779 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -51,8 +51,8 @@ - @hooks.each do |hook| %li .pull-right - = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small grouped" - = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small grouped" + = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small btn-grouped" + = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped" .clearfix %span.monospace= hook.url %p diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index c95e8178594..dd091302c8e 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,6 +1,10 @@ %div.issue-form-holder %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" %hr + - if @repository.contribution_guide && !@issue.persisted? + - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) + .alert.alert-info.col-sm-10.col-sm-offset-2 + ="Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f| -if @issue.errors.any? .alert.alert-danger @@ -24,7 +28,7 @@ %i.icon-user Assign to .col-sm-10 - = f.select(:assignee_id, assignee_options(@issue), { include_blank: "Select a user" }, {class: 'select2'}) + = project_users_select_tag('issue[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @issue.assignee_id) = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' .form-group diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 36549aae7d3..0b7697622b0 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs.append-bottom-15 +%ul.nav.nav-tabs = nav_link(controller: :issues) do = link_to project_issues_path(@project), class: "tab" do Browse Issues @@ -17,10 +17,10 @@ %li.pull-right .pull-right - = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'inline issue-search-form' do + = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do .append-right-10.hidden-xs.hidden-sm - = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input' } + = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } - if can? current_user, :write_issue, @project - = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do + = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do %i.icon-plus New Issue diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 1bd93b774f1..3fc04c26cf2 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -38,10 +38,10 @@ .issue-actions - if can? current_user, :modify_issue, issue - if issue.closed? - = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true + = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue", remote: true - else - = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true - = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do + = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue", remote: true + = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do %i.icon-edit Edit diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml index 7ddf470b6a0..aae101cf40f 100644 --- a/app/views/projects/issues/_issue_context.html.haml +++ b/app/views/projects/issues/_issue_context.html.haml @@ -1,25 +1,22 @@ = form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f| - Created by #{link_to_member(@project, issue.author)} - - if issue.assignee - \ and currently assigned to + %strong.append-right-10 + Assignee: - if can?(current_user, :modify_issue, @issue) - = link_to profile_path(issue.assignee) do - = image_tag(avatar_icon(issue.assignee.email), class: 'avatar avatar-inline s16 assignee') if issue.assignee - = f.select(:assignee_id, assignee_options(@issue), { include_blank: "Assign to user (none):" }, {class: 'select2'}) + = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @issue.assignee_id) - elsif issue.assignee = link_to_member(@project, @issue.assignee) + - else + None - - .pull-right.hidden-sm - - if issue.milestone - - milestone = issue.milestone - %cite.cgray Attached to milestone - + .pull-right + %strong.append-right-10 + Milestone: - if can?(current_user, :modify_issue, @issue) = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone (none):" }, {class: 'select2 select2-compact'}) - = hidden_field_tag :issue_context = f.submit class: 'btn' - elsif issue.milestone = link_to issue.milestone.title, project_milestone_path + - else + None diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index e2ce26feac3..3b5e398c327 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,112 +1,72 @@ -.ui-box - .title +.append-bottom-10 + .check-all-holder = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" - .clearfix - .issues_bulk_update.hide - = form_tag bulk_update_project_issues_path(@project), method: :post do - %span Update selected issues with - = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status") - = select_tag('update[assignee_id]', bulk_update_assignee_options, prompt: "Assignee") - = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") - = hidden_field_tag 'update[issues_ids]', [] - = hidden_field_tag :status, params[:status] - = button_tag "Save", class: "btn update_selected_issues btn-small btn-save" - .issues-filters - %span Filter by - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %i.icon-tags - %span.light labels: - - if params[:label_name].present? - %strong= params[:label_name] - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(label_name: nil) do - Any - - issue_label_names.each do |label_name| - %li - = link_to project_filter_path(label_name: label_name) do - %span{class: "label #{label_css_class(label_name)}"} - %i.icon-tag - = label_name - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %i.icon-user - %span.light assignee: - - if @assignee.present? - %strong= @assignee.name - - elsif params[:assignee_id] == "0" - Unassigned - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(assignee_id: nil) do - Any - = link_to project_filter_path(assignee_id: 0) do - Unassigned - - @project.team.members.sort_by(&:name).each do |user| - %li - = link_to project_filter_path(assignee_id: user.id) do - = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' - = user.name + .issues-filters + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.icon-user + %span.light assignee: + - if @assignee.present? + %strong= @assignee.name + - elsif params[:assignee_id] == "0" + Unassigned + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(assignee_id: nil) do + Any + = link_to project_filter_path(assignee_id: 0) do + Unassigned + - @assignees.sort_by(&:name).each do |user| + %li + = link_to project_filter_path(assignee_id: user.id) do + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' + = user.name - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %i.icon-time - %span.light milestone: - - if @milestone.present? - %strong= @milestone.title - - elsif params[:milestone_id] == "0" - None (backlog) - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(milestone_id: nil) do - Any - = link_to project_filter_path(milestone_id: 0) do - None (backlog) - - project_active_milestones.each do |milestone| - %li - = link_to project_filter_path(milestone_id: milestone.id) do - %strong= milestone.title - %small.light= milestone.expires_at + .dropdown.inline.prepend-left-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.icon-time + %span.light milestone: + - if @milestone.present? + %strong= @milestone.title + - elsif params[:milestone_id] == "0" + None (backlog) + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(milestone_id: nil) do + Any + = link_to project_filter_path(milestone_id: 0) do + None (backlog) + - project_active_milestones.each do |milestone| + %li + = link_to project_filter_path(milestone_id: milestone.id) do + %strong= milestone.title + %small.light= milestone.expires_at - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %span.light sort: - - if @sort.present? - = @sort - - else - Newest - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(sort: 'newest') do - Newest - = link_to project_filter_path(sort: 'oldest') do - Oldest - = link_to project_filter_path(sort: 'recently_updated') do - Recently updated - = link_to project_filter_path(sort: 'last_updated') do - Last updated - = link_to project_filter_path(sort: 'milestone_due_soon') do - Milestone due soon - = link_to project_filter_path(sort: 'milestone_due_later') do - Milestone due later + .pull-right + = render 'shared/sort_dropdown' + .clearfix + .issues_bulk_update.hide + = form_tag bulk_update_project_issues_path(@project), method: :post do + = select_tag('update[status]', options_for_select(['Open', 'Closed']), prompt: "Status") + = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee') + = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") + = hidden_field_tag 'update[issues_ids]', [] + = hidden_field_tag :status, params[:status] + = button_tag "Update issues", class: "btn update_selected_issues btn-save" +.ui-box %ul.well-list.issues-list = render @issues - if @issues.blank? %li - %h4.nothing_here_message No issues to show + .nothing-here-block No issues to show - if @issues.present? .pull-right diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 71a89af61a2..5e899d412c6 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,6 +1,6 @@ = render "head" .row .col-md-3 - = render 'shared/project_filter', project_entities_path: project_issues_path(@project) + = render 'shared/project_filter', project_entities_path: project_issues_path(@project), labels: true .col-md-9.issues-holder = render "issues" diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index cd4a158e427..124eb53571d 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,26 +1,18 @@ %h3.page-title Issue ##{@issue.iid} - %small - created #{time_ago_with_tooltip(@issue.created_at)} - - - if @issue.closed? - %span.state-label.state-label-red Closed - - else - %span.state-label.state-label-green Open - %span.pull-right - if can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), class: "btn grouped", title: "New Issue", id: "new_issue_link" do + = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do %i.icon-plus New Issue - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue" + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - else - = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue" + = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" - = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do + = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do %i.icon-edit Edit @@ -37,26 +29,36 @@ = link_to project_milestone_path(@project, @issue.milestone) do = @issue.milestone.title -.issue-box +.issue-box{ class: issue_box_class(@issue) } + .state + %span.state-label + - if @issue.closed? + Closed + - else + Open + + %span.creator + Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)} + %h4.title = gfm escape_once(@issue.title) - .context - %cite.cgray - = render partial: 'issue_context', locals: { issue: @issue } - - if @issue.description.present? .description .wiki = preserve do = markdown @issue.description + .context + %cite.cgray + = render partial: 'issue_context', locals: { issue: @issue } + - content_for :note_actions do - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue" + = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - else - = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue" + = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" .participants %cite.cgray #{@issue.participants.count} participants diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml index 59524e0f224..5199e9fc61f 100644 --- a/app/views/projects/issues/update.js.haml +++ b/app/views/projects/issues/update.js.haml @@ -3,10 +3,7 @@ :plain $("##{dom_id(@issue)}").fadeOut(); - elsif params[:issue_context] - $('.issue-box .context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}"); $('.issue-box .context').effect('highlight'); - $('.select2').select2(); - $('.edit-issue.inline-update input[type="submit"]').hide(); - if @issue.milestone $('.milestone-nav-link').replaceWith("<span class='milestone-nav-link'>| <span class='light'>Milestone</span> #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))}</span>") - else diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index a058d09a1ce..329cf9ceba8 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -7,4 +7,4 @@ - else .light-well - %h3.nothing_here_message Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels + .nothing-here-block Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 67cb5ccd72d..51fa29ddcbe 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,3 +1,7 @@ +- if @repository.contribution_guide && !@merge_request.persisted? + - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) + .alert.alert-info.col-sm-10.col-sm-offset-2 + ="Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe = form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal" } do |f| -if @merge_request.errors.any? .alert.alert-danger @@ -6,27 +10,30 @@ %li= msg .merge-request-branches - .row - .col-md-5 + .form-group + = label_tag nil, class: 'control-label' do + From + .col-sm-10 .clearfix .pull-left = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? }) .pull-left - = f.select(:source_branch, @merge_request.source_project.repository.branch_names, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'}) - .mr_source_commit.prepend-top-10 - .col-md-2 - .merge-request-angle - %i.icon-long-arrow-right - .col-md-5 + = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'}) + .mr_source_commit + %br + .form-group + = label_tag nil, class: 'control-label' do + To + .col-sm-10 .clearfix .pull-left - 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'), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? }) + = 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? }) .pull-left - = f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'}) - .mr_target_commit.prepend-top-10 + = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'}) + .mr_target_commit %hr .merge-request-form-info @@ -47,7 +54,7 @@ %i.icon-user Assign to .col-sm-10 - = f.select(:assignee_id, assignee_options(@merge_request), { include_blank: "Select a user" }, {class: 'select2'}) + = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id) = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' .form-group @@ -90,6 +97,6 @@ $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() }); }); $('.assign-to-me-link').on('click', function(e){ - $('#merge_request_assignee_id').val("#{current_user.id}").trigger("select2:updated"); + $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index ff763bca307..980ac126742 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -10,14 +10,14 @@ %span.pull-right - if merge_request.for_fork? %span.light - = "#{merge_request.source_project_path}" - = "#{merge_request.source_branch}" + #{merge_request.source_project_namespace}: + = merge_request.source_branch %i.icon-angle-right.light - = "#{merge_request.target_branch}" + = merge_request.target_branch - else - = "#{merge_request.source_branch}" + = merge_request.source_branch %i.icon-angle-right.light - = "#{merge_request.target_branch}" + = merge_request.target_branch .merge-request-info - if merge_request.author authored by #{link_to_member(merge_request.source_project, merge_request.author)} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 42641765c5c..809b01918cf 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -2,7 +2,7 @@ = render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/show/mr_box" - - if @merge_request.opened? + - if @merge_request.open? - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists? = render "projects/merge_requests/show/mr_accept" - else @@ -12,7 +12,7 @@ = render "projects/merge_requests/show/commits" - if @commits.present? - %ul.nav.nav-tabs.append-bottom-10 + %ul.nav.nav-tabs %li.notes-tab{data: {action: 'notes'}} = link_to project_merge_request_path(@project, @merge_request) do %i.icon-comment @@ -22,6 +22,13 @@ %i.icon-list-alt Diff + - content_for :note_actions do + - if can?(current_user, :modify_merge_request, @merge_request) + - unless @merge_request.closed? || @merge_request.merged? + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link", title: "Close merge request" + - if @merge_request.closed? + = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" + .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } = render "projects/notes/notes_with_form" .diffs.tab-content diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml index ec4d7f2121b..d3147188d1c 100644 --- a/app/views/projects/merge_requests/branch_from.js.haml +++ b/app/views/projects/merge_requests/branch_from.js.haml @@ -1,5 +1,5 @@ :plain - $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project)}"); + $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}"); var mrTitle = $('#merge_request_title'); if(mrTitle.val().length == 0) { diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml index f4e2886ee44..f7ede0ded53 100644 --- a/app/views/projects/merge_requests/branch_to.js.haml +++ b/app/views/projects/merge_requests/branch_to.js.haml @@ -1,2 +1,2 @@ :plain - $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project)}"); + $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}"); diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index d6d0cde85be..34faebf619c 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -5,67 +5,67 @@ %h3.page-title Merge Requests %span (#{@merge_requests.total_count}) - - +%hr .row .col-md-3 = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project) .col-md-9 - .ui-box - .title - .mr-filters - %span Filter by - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %i.icon-user - %span.light assignee: - - if @assignee.present? - %strong= @assignee.name - - elsif params[:assignee_id] == "0" - Unassigned - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(assignee_id: nil) do - Any - = link_to project_filter_path(assignee_id: 0) do - Unassigned - - @project.team.members.sort_by(&:name).each do |user| - %li - = link_to project_filter_path(assignee_id: user.id) do - = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' - = user.name + .mr-filters.append-bottom-10 + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.icon-user + %span.light assignee: + - if @assignee.present? + %strong= @assignee.name + - elsif params[:assignee_id] == "0" + Unassigned + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(assignee_id: nil) do + Any + = link_to project_filter_path(assignee_id: 0) do + Unassigned + - @assignees.sort_by(&:name).each do |user| + %li + = link_to project_filter_path(assignee_id: user.id) do + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' + = user.name - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %i.icon-time - %span.light milestone: - - if @milestone.present? - %strong= @milestone.title - - elsif params[:milestone_id] == "0" - None (backlog) - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(milestone_id: nil) do - Any - = link_to project_filter_path(milestone_id: 0) do - None (backlog) - - project_active_milestones.each do |milestone| - %li - = link_to project_filter_path(milestone_id: milestone.id) do - %strong= milestone.title - %small.light= milestone.expires_at + .dropdown.inline.prepend-left-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.icon-time + %span.light milestone: + - if @milestone.present? + %strong= @milestone.title + - elsif params[:milestone_id] == "0" + None (backlog) + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(milestone_id: nil) do + Any + = link_to project_filter_path(milestone_id: 0) do + None (backlog) + - project_active_milestones.each do |milestone| + %li + = link_to project_filter_path(milestone_id: milestone.id) do + %strong= milestone.title + %small.light= milestone.expires_at + + .pull-right + = render 'shared/sort_dropdown' + .ui-box %ul.well-list.mr-list = render @merge_requests - if @merge_requests.blank? %li - %h4.nothing_here_message No merge requests to show + .nothing-here-block No merge requests to show - if @merge_requests.present? .pull-right %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index 7b0e67053a5..9b4271bbffc 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -12,9 +12,16 @@ 8 of #{@commits.count} commits displayed. %strong %a.show-all-commits Click here to show all - %ul.all-commits.hide.well-list - - @commits.each do |commit| - = render "projects/commits/commit", commit: commit, project: @merge_request.source_project + - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + %ul.all-commits.hide.well-list + - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit| + = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project + %li + other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden top prevent performance issues. + - else + %ul.all-commits.hide.well-list + - @commits.each do |commit| + = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project - else %ul.well-list @@ -22,7 +29,7 @@ = render "projects/commits/commit", commit: commit, project: @merge_request.source_project - else - %h4.nothing_here_message + .nothing-here-block Nothing to merge from %span.label-branch #{@merge_request.source_branch} to diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml new file mode 100644 index 00000000000..2bd850426a9 --- /dev/null +++ b/app/views/projects/merge_requests/show/_context.html.haml @@ -0,0 +1,22 @@ += form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f| + %strong.append-right-10 + Assignee: + + - if can?(current_user, :modify_merge_request, @merge_request) + = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @merge_request.assignee_id) + - elsif merge_request.assignee + = link_to_member(@project, @merge_request.assignee) + - else + None + + .pull-right + %strong.append-right-10 + Milestone: + - if can?(current_user, :modify_merge_request, @merge_request) + = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone (none):" }, {class: 'select2 select2-compact'}) + = hidden_field_tag :merge_request_context + = f.submit class: 'btn' + - elsif merge_request.milestone + = link_to merge_request.milestone.title, project_milestone_path + - else + None diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 25f63804858..7c4f43d2d80 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,10 +1,12 @@ -- if @merge_request.valid_diffs? +- if @merge_request_diff.collected? = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project -- elsif @merge_request.broken_diffs? - %h4.nothing_here_message - Can't load diff. - You can - = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request), format: :diff, class: "vlink" - instead. +- elsif @merge_request_diff.empty? + .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else - %h4.nothing_here_message Nothing to merge + .bs-callout.bs-callout-warning + %h4 + Diff for this comparison is extremely large. + %p + You can + = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request, format: :diff), class: "vlink" + instead. diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index d210d4667fd..a9cbe43fb16 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -13,7 +13,21 @@ If you still want to do it manually - %strong = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - for instructions + for instructions. + + %br + If you want to modify merge commit message - + %strong + = link_to "click here", "#", class: "modify-merge-commit-link js-toggle-visibility-link", title: "Modify merge commit message" + + .js-toggle-visibility-container.hide + .form-group + = label_tag :merge_commit_message, "Commit message", class: 'control-label' + .col-sm-10 + = text_area_tag :merge_commit_message, @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true + %p.hint + The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines. + .accept-group .pull-left = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" @@ -41,15 +55,15 @@ .automerge_widget.unchecked .bs-callout.bs-callout-warning %strong - %i.icon-refresh.icon-spin + %i.icon-spinner.icon-spin Checking for ability to automatically merge… .automerge_widget.already_cannot_be_merged.hide .bs-callout.bs-callout-info - %strong This merge request already can not be merged. Try to reload page. + %strong This merge request can not be merged. Try to reload the page. .merge-in-progress.hide .bs-callout.bs-callout-success - %i.icon-refresh.icon-spin + %i.icon-spinner.icon-spin Merge is in progress. Please wait. Page will be automatically reloaded. diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index b4f648ab197..38db4363ade 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -1,18 +1,18 @@ -.issue-box - %h4.title - = gfm escape_once(@merge_request.title) +.issue-box{ class: issue_box_class(@merge_request) } + .state + %span.state-label + - if @merge_request.merged? + Merged + - elsif @merge_request.closed? + Closed + - else + Open - .context - %cite.cgray - Created by #{link_to_member(@project, @merge_request.author)}. - - if @merge_request.assignee - Currently assigned to #{link_to_member(@project, @merge_request.assignee)}. - - if @merge_request.milestone - .pull-right - - milestone = @merge_request.milestone - %cite.cgray Attached to milestone - %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone) + %span.creator + Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)} + %h4.title + = gfm escape_once(@merge_request.title) - if @merge_request.description.present? .description @@ -20,22 +20,26 @@ = preserve do = markdown @merge_request.description - - if @merge_request.closed? - .description.alert-danger - %span - %i.icon-remove - Closed by #{link_to_member(@project, @merge_request.closed_event.author)} - #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}. - - if @merge_request.merged? - .description.alert-success - %span - %i.icon-ok - Merged by #{link_to_member(@project, @merge_request.merge_event.author)} - #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}. - - if !@closes_issues.empty? && @merge_request.opened? - .description.alert-info - %span - %i.icon-ok - Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} - = succeed '.' do - != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence) + .context + %cite.cgray + = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request } + +- if @merge_request.closed? + .alert.alert-info + %span + %i.icon-remove + Closed by #{link_to_member(@project, @merge_request.closed_event.author)} + #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}. +- if @merge_request.merged? + .alert.alert-info + %span + %i.icon-ok + Merged by #{link_to_member(@project, @merge_request.merge_event.author)} + #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}. +- if !@closes_issues.empty? && @merge_request.open? + .alert.alert-info.alert-info + %span + %i.icon-ok + Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} + = succeed '.' do + != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence) diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml index 2e923ec5f35..915d24dd021 100644 --- a/app/views/projects/merge_requests/show/_mr_ci.html.haml +++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml @@ -25,7 +25,7 @@ .ci_widget .alert.alert-warning %strong - %i.icon-refresh + %i.icon-spinner Checking for CI status for #{@merge_request.last_commit_short_sha} .ci_widget.ci-error{style: "display:none"} diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 08a3fdf869a..7676fc137c7 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,26 +1,11 @@ %h3.page-title = "Merge Request ##{@merge_request.iid}" - %small - created #{time_ago_with_tooltip(@merge_request.created_at)} - - - if @merge_request.merged? - %span.state-label.state-label-green - %i.icon-ok - Merged - - elsif @merge_request.closed? - %span.state-label.state-label-red - Closed - - else - %span.state-label.state-label-green - Open - - %span.pull-right - if can?(current_user, :modify_merge_request, @merge_request) - - if @merge_request.opened? - .left.btn-group - %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } + - if @merge_request.open? + .btn-group.pull-left + %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} } %i.icon-download-alt Download as %span.caret @@ -28,13 +13,15 @@ %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" - = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do + = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do %i.icon-edit Edit + - if @merge_request.closed? + = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" -.votes-holder +.votes-holder.hidden-sm.hidden-xs #votes= render 'votes/votes_block', votable: @merge_request .back-link diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml new file mode 100644 index 00000000000..6452cc6382d --- /dev/null +++ b/app/views/projects/merge_requests/update.js.haml @@ -0,0 +1,2 @@ +- if params[:merge_request_context] + $('.issue-box .context').effect('highlight'); diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml index 6a21551e811..ca21b3bc0de 100644 --- a/app/views/projects/merge_requests/update_branches.js.haml +++ b/app/views/projects/merge_requests/update_branches.js.haml @@ -1,5 +1,9 @@ :plain $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}"); - $(".target_branch").trigger("select2:updated"); + + $('select.target_branch').select2({ + width: 'resolve', + dropdownAutoWidth: true + }); + $(".mr_target_commit").html(""); - $(".target_branch").trigger("change"); diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml index 21939ad0132..c277dcc5364 100644 --- a/app/views/projects/milestones/_issues.html.haml +++ b/app/views/projects/milestones/_issues.html.haml @@ -4,7 +4,7 @@ - issues.each do |issue| %li = link_to [@project, issue] do - %span.badge{class: issue.closed? ? 'badge-important' : 'badge-info'} ##{issue.iid} + %span.label{class: issue.closed? ? 'label-danger' : 'label-info'} ##{issue.iid} = link_to_gfm truncate(issue.title, length: 40), [@project, issue] - if issue.assignee .pull-right diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index 8e30a42a608..faa35a25f2f 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -1,5 +1,5 @@ %li = link_to [@project, merge_request] do - %span.badge.badge-info ##{merge_request.iid} + %span.label.label-info ##{merge_request.iid} – = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request] diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 4dec3838d97..5579659d60e 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -1,7 +1,7 @@ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) } .pull-right - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do + = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do %i.icon-edit Edit = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-remove" diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 6cfe4d28778..3537650ad43 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -26,6 +26,6 @@ - if @milestones.blank? %li - %h3.nothing_here_message No milestones to show + .nothing-here-block No milestones to show = paginate @milestones, theme: "gitlab" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index e7c3785c056..06cf9946784 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -3,21 +3,15 @@ Milestone ##{@milestone.iid} %small = @milestone.expires_at - - if @milestone.closed? - %span.state-label.state-label-red Closed - - elsif @milestone.expired? - %span.state-label.state-label-red Expired - - else - %span.state-label.state-label-green Open .pull-right - if can?(current_user, :admin_milestone, @project) - = link_to edit_project_milestone_path(@project, @milestone), class: "btn grouped" do + = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do %i.icon-edit Edit - if @milestone.active? - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove grouped" + = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove btn-grouped" - else - = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn grouped" + = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped" - if @milestone.issues.any? && @milestone.can_be_closed? .alert.alert-success @@ -28,7 +22,16 @@ ← To milestones list -.issue-box +.issue-box{ class: issue_box_class(@milestone) } + .state + %span.state-label + - if @milestone.closed? + Closed + - elsif @milestone.expired? + Expired + - else + Open + %h4.title = gfm escape_once(@milestone.title) @@ -42,14 +45,13 @@ .progress.progress-info .progress-bar{style: "width: #{@milestone.percent_complete}%;"} - - if @milestone.description.present? .description - = preserve do - = markdown @milestone.description - + .wiki + = preserve do + = markdown @milestone.description -%ul.nav.nav-tabs.append-bottom-10 +%ul.nav.nav-tabs %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues @@ -64,10 +66,10 @@ %span.badge= @users.count .pull-right - = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do + = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do %i.icon-plus New Issue - = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link grouped" + = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 492f77341f7..da0cfa84c2d 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -3,7 +3,7 @@ .tip You can move around the graph by using the arrow keys. .network-graph - .loading.loading-gray + = spinner :javascript new Network({ diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index f21cef6f2e3..9ee54fef062 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -62,6 +62,7 @@ .save-project-loader.hide %center - = image_tag "ajax_loader.gif" - %h3 Creating project & repository. + %h2 + %i.icon-spinner.icon-spin + Creating project & repository. %p Please wait a moment, this page will automatically refresh when ready. diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml index 078a4b80d0d..9d7c7afbeac 100644 --- a/app/views/projects/new_tree/show.html.haml +++ b/app/views/projects/new_tree/show.html.haml @@ -15,6 +15,12 @@ %span= @ref .form-group.commit_message-group + = label_tag :encoding, class: "control-label" do + Encoding + .col-sm-10 + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' + + .form-group.commit_message-group = label_tag 'commit_message', class: "control-label" do Commit message .col-sm-10 diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index ee65ae1e2f5..9b1f4d77587 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -36,21 +36,17 @@ .discussion-body - if note.for_diff_line? - if note.active? - .content - .file= render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note + = render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note - else = link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion' %div.hide.outdated-discussion - .content - .notes{ rel: discussion_notes.first.discussion_id } - = render discussion_notes - + .notes{ rel: discussion_notes.first.discussion_id } + = render discussion_notes - else - .content - .notes{ rel: discussion_notes.first.discussion_id } - = render discussion_notes - = render "projects/notes/discussion_reply_button", note: discussion_notes.first + .notes{ rel: discussion_notes.first.discussion_id } + = render discussion_notes + = render "projects/notes/discussion_reply_button", note: discussion_notes.first -# will be shown when the other one is hidden .discussion-hidden.content.hide @@ -59,4 +55,3 @@ = link_to "javascript:;", class: "js-details-target js-toggler-target" do %i.icon-eye-open Show - diff --git a/app/views/projects/notes/_discussion_diff.html.haml b/app/views/projects/notes/_discussion_diff.html.haml index c3f41a1b6b5..687fac04973 100644 --- a/app/views/projects/notes/_discussion_diff.html.haml +++ b/app/views/projects/notes/_discussion_diff.html.haml @@ -1,24 +1,25 @@ - diff = note.diff -.header - - if diff.deleted_file - %span= diff.old_path - - else - %span= diff.new_path - - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" - %br/ -.content - %table - - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old| - %tr.line_holder{ id: line_code } - - if type == "match" - %td.old_line= "..." - %td.new_line= "..." - %td.line_content.matched= line - - else - %td.old_line= raw(type == "new" ? " " : line_old) - %td.new_line= raw(type == "old" ? " " : line_new) - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} " +.diff-file + .diff-header + - if diff.deleted_file + %span= diff.old_path + - else + %span= diff.new_path + - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode + %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" + %br/ + .diff-content + %table + - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old| + %tr.line_holder{ id: line_code } + - if type == "match" + %td.old_line= "..." + %td.new_line= "..." + %td.line_content.matched= line + - else + %td.old_line= raw(type == "new" ? " " : line_old) + %td.new_line= raw(type == "old" ? " " : line_new) + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} " - - if line_code == note.line_code - = render "projects/notes/diff_notes_with_reply", notes: discussion_notes + - if line_code == note.line_code + = render "projects/notes/diff_notes_with_reply", notes: discussion_notes diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index bcaedc8bd7c..3db551e993b 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -22,7 +22,7 @@ .note-form-actions .buttons - = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button" + = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button" = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index be9c6ca79bb..6a67f5242b2 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -31,7 +31,7 @@ .note-body .note-text = preserve do - = markdown(note.note) + = markdown(note.note, {no_header_anchors: true}) .note-edit-form = form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f| diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index e0d058de79e..8b100766e97 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -3,27 +3,27 @@ .col-md-3 = render "projects/branches/filter" .col-md-9 - .alert.alert-info + .bs-callout.bs-callout-info %p Protected branches designed to prevent push for all except #{link_to "masters", help_permissions_path, class: "vlink"}. %p This ability allows: %ul %li keep stable branches secured %li forced code review before merge to protected branches - %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined_link"} + %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"} - if can? current_user, :admin_project, @project - = form_for [@project, @protected_branch] do |f| + = form_for [@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 - .entry.clearfix - = f.label :name, "Branch" - .col-md-3 - = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2 span3"}) - + .form-group + = f.label :name, "Branch", class: 'control-label' + .col-sm-10 + = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"}) + .form-actions = f.submit 'Protect', class: "btn-create btn" - unless @branches.empty? %h5 Already Protected: diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index b03feded0a7..88c1cfa28e0 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -3,7 +3,7 @@ - split_button = split_button || false - if split_button == true %span.btn-group{class: btn_class} - = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn' do + = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do %i.icon-download-alt %span Download zip %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } @@ -12,26 +12,26 @@ Select Archive Format %ul.dropdown-menu{ role: 'menu' } %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'zip') do + = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do %i.icon-download-alt %span Download zip %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz') do + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %i.icon-download-alt %span Download tar.gz %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2') do + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do %i.icon-download-alt %span Download tar.bz2 %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar') do + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do %i.icon-download-alt %span Download tar - else %span.btn-group{class: btn_class} - = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn' do + = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do %i.icon-download-alt %span zip - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn' do + = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do %i.icon-download-alt %span tar.gz
\ No newline at end of file diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 59075abf984..8a1e1d3354b 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -5,7 +5,7 @@ = render "events/event_last_push", event: @last_push = render 'shared/event_filter' .content_list - .loading.hide + = spinner .col-md-3.project-side.hidden-sm .clearfix - if @project.archived? @@ -51,7 +51,7 @@ %p %span.light Owned by - if @project.group - #{link_to @project.group.name, @project.group} Group + #{link_to @project.group.name, @project.group} group - else #{link_to @project.owner_name, @project.owner} diff --git a/app/views/projects/snippets/_blob.html.haml b/app/views/projects/snippets/_blob.html.haml index f14a2bd4ec0..af326a1a99a 100644 --- a/app/views/projects/snippets/_blob.html.haml +++ b/app/views/projects/snippets/_blob.html.haml @@ -6,10 +6,5 @@ .btn-group.tree-btn-group.pull-right - if can?(current_user, :admin_project_snippet, @project) || @snippet.author == current_user = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-tiny", title: 'Edit Snippet' - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank" - .file-content.code - - unless @snippet.content.empty? - %div{class: user_color_scheme_class} - = raw @snippet.colorize(formatter: :gitlab) - - else - %p.nothing_here_message Empty file + = link_to "Raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank" + = render 'snippets/blob_content' diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml index ddd9645ee51..b2c35edc44c 100644 --- a/app/views/projects/snippets/_snippet.html.haml +++ b/app/views/projects/snippets/_snippet.html.haml @@ -5,13 +5,6 @@ %span.cgray.monospace.tiny.pull-right = snippet.file_name - %small.pull-right.cgray - Expires: - - if snippet.expires_at - = snippet.expires_at.to_date.to_s(:short) - - else - Never - .snippet-info = "##{snippet.id}" %span diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index c40f63d05b3..e60f9a44322 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -12,4 +12,4 @@ = render partial: "projects/snippets/snippet", collection: @snippets - if @snippets.empty? %li - %h3.nothing_here_message Nothing here. + .nothing-here-block Nothing here. diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml new file mode 100644 index 00000000000..6629e47ad38 --- /dev/null +++ b/app/views/projects/tags/_tag.html.haml @@ -0,0 +1,22 @@ +- commit = @repository.commit(tag.target) +%li + %h4 + = link_to project_commits_path(@project, tag.name), class: "" do + %i.icon-tag + = tag.name + .pull-right + %small.cdark + %i.icon-calendar + #{time_ago_with_tooltip(commit.committed_date)} + %p.prepend-left-20 + = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace" + – + = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "cdark" + + %span.pull-right + - if can? current_user, :download_code, @project + = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' + - if can?(current_user, :admin_project, @project) + = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do + %i.icon-trash + diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index c88e42a1073..53f3e67ff2c 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -13,34 +13,12 @@ - unless @tags.empty? %ul.bordered-list - @tags.each do |tag| - - commit = Commit.new(Gitlab::Git::Commit.new(tag.commit)) - %li - %h4 - = link_to project_commits_path(@project, tag.name), class: "" do - %i.icon-tag - = tag.name - %small - = truncate(tag.message || '', length: 70) - .pull-right - %small.cdark - %i.icon-calendar - #{time_ago_with_tooltip(commit.committed_date)} - %p.prepend-left-20 - = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace" - – - = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "cdark" - - %span.pull-right - - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'grouped btn-group-small' - - if can?(current_user, :admin_project, @project) - = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do - %i.icon-trash + = render 'tag', tag: tag = paginate @tags, theme: 'gitlab' - else - %h3.nothing_here_message + .nothing-here-block Repository has no tags yet. %br %small diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml index 68f08006854..eceec6627b9 100644 --- a/app/views/projects/team_members/_group_members.html.haml +++ b/app/views/projects/team_members/_group_members.html.haml @@ -1,10 +1,14 @@ +- group_users_count = @group.users_groups.count .ui-box .title %strong #{@group.name} - group members (#{@group.users_groups.count}) + group members (#{group_users_count}) .pull-right = link_to members_group_path(@group), class: 'btn btn-small' do %i.icon-edit %ul.well-list - - @group.users_groups.order('group_access DESC').each do |member| + - @group.users_groups.order('group_access DESC').limit(20).each do |member| = render 'users_groups/users_group', member: member, show_controls: false + - if group_users_count > 20 + %li + and #{group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(@group)} diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml index acbe82919f1..6eccaafe3de 100644 --- a/app/views/projects/team_members/index.html.haml +++ b/app/views/projects/team_members/index.html.haml @@ -3,9 +3,9 @@ - if can? current_user, :admin_team_member, @project %span.pull-right - = link_to new_project_team_member_path(@project), class: "btn btn-new grouped", title: "New project member" do + = link_to new_project_team_member_path(@project), class: "btn btn-new btn-grouped", title: "New project member" do New project member - = link_to import_project_team_members_path(@project), class: "btn grouped", title: "Import members from another project" do + = link_to import_project_team_members_path(@project), class: "btn btn-grouped", title: "Import members from another project" do Import members %p.light diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index 92dfd7537ca..6fee604b554 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -4,7 +4,5 @@ %span.str-truncated = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) %td.tree_time_ago.cgray - %span.log_loading.hide - Loading commit data... - = image_tag "ajax_loader_tree.gif", width: 14 + = render 'spinner' %td.tree_commit{ colspan: 2 } diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index ab572f2e97b..16a9602ac97 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -1,5 +1,5 @@ .readme-holder#README - %h4 + %h4.readme-file-title %i.icon-file = readme.name .wiki diff --git a/app/views/projects/tree/_spinner.html.haml b/app/views/projects/tree/_spinner.html.haml new file mode 100644 index 00000000000..5a9e77b63df --- /dev/null +++ b/app/views/projects/tree/_spinner.html.haml @@ -0,0 +1,3 @@ +%span.log_loading.hide + %i.icon-spinner.icon-spin + Loading commit data... diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index badc7d992bd..ae87dbde67a 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -1,10 +1,15 @@ +- tree, commit = submodule_links(submodule_item) %tr{ class: "tree-item" } %td.tree-item-file-name = image_tag "submodule.png" %span - = link_to truncate(submodule_item.name, length: 40), submodule_item.submodule_url + = link_to truncate(submodule_item.name, length: 40), tree @ - %span.monospace #{submodule_item.id[0..10]} + %span.monospace + - if commit.nil? + #{submodule_item.id[0..10]} + - else + = link_to "#{submodule_item.id[0..10]}", commit %td %td %td diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index 4e80872df48..ee850e2bc1b 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -9,7 +9,7 @@ = link_to truncate(title, length: 40), project_tree_path(@project, path) - else = link_to title, '#' - - if @repository.branch_names.include?(@ref) + - if current_user && @repository.branch_names.include?(@ref) && current_user.can?(:push_code, @project) %li = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do %small diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index b4dd6f48b62..1b3900bcbae 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -4,7 +4,5 @@ %span.str-truncated = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) %td.tree_time_ago.cgray - %span.log_loading.hide - Loading commit data... - = image_tag "ajax_loader_tree.gif", width: 14 + = render 'spinner' %td.tree_commit{ colspan: 2 } diff --git a/app/views/projects/walls/show.html.haml b/app/views/projects/walls/show.html.haml index c6afec443f4..3e3c7c4f8dd 100644 --- a/app/views/projects/walls/show.html.haml +++ b/app/views/projects/walls/show.html.haml @@ -9,7 +9,7 @@ = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on' .note-form-actions .buttons - = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button" + = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button" .note-form-option %a.choose-btn.btn.btn-small.js-choose-note-attachment-button diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 1001bb10c4f..5dd769dcfe1 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,8 +1,8 @@ %span.pull-right - if (@wiki && @wiki.persisted?) - = link_to history_project_wiki_path(@project, @wiki), class: "btn grouped" do + = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-grouped" do Page History - if can?(current_user, :write_wiki, @project) - = link_to edit_project_wiki_path(@project, @wiki), class: "btn grouped" do + = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-grouped" do %i.icon-edit Edit diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 5e5aa5170d6..0a7e51e974c 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs.append-bottom-20 +%ul.nav.nav-tabs = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = link_to 'Home', project_wiki_path(@project, :home) diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index 2fc93c5b742..624ec0b9b95 100644 --- a/app/views/public/projects/index.html.haml +++ b/app/views/public/projects/index.html.haml @@ -63,6 +63,6 @@ %i.icon-warning-sign Empty repository - unless @projects.present? - %h3.nothing_here_message No public projects + .nothing-here-block No public projects = paginate @projects, theme: "gitlab" diff --git a/app/views/search/_project_results.html.haml b/app/views/search/_project_results.html.haml index ea324b3a9aa..f285bda5736 100644 --- a/app/views/search/_project_results.html.haml +++ b/app/views/search/_project_results.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs.append-bottom-10 +%ul.nav.nav-tabs %li{class: ("active" if params[:search_code].present?)} = link_to search_path(params.merge(search_code: true)) do Repository Code diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index a2e4a00b718..f9d217e8408 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -6,5 +6,4 @@ %strong = blob.filename .file-content.code.term - %div{class: user_color_scheme_class} - = raw blob.colorize( formatter: :gitlab, options: { first_line_number: blob.startline } ) + = render 'shared/file_hljs', blob: blob, first_line_number: blob.startline diff --git a/app/views/shared/_file_hljs.html.haml b/app/views/shared/_file_hljs.html.haml new file mode 100644 index 00000000000..338236c0752 --- /dev/null +++ b/app/views/shared/_file_hljs.html.haml @@ -0,0 +1,12 @@ +%div.highlighted-data{class: user_color_scheme_class} + .line-numbers + - blob.data.lines.to_a.size.times do |index| + - offset = defined?(first_line_number) ? first_line_number : 1 + - i = index + offset + = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do + %i.icon-link + = i + .highlight + %pre + %code + = blob.data diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml index 96299f8dcf2..0becf531cc3 100644 --- a/app/views/shared/_filter.html.haml +++ b/app/views/shared/_filter.html.haml @@ -1,28 +1,29 @@ .side-filters.hidden-xs.hidden-sm = form_tag filter_path(entity), method: 'get' do - %fieldset.scope-filter - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:scope].blank?)} - = link_to filter_path(entity, scope: nil) do - Assigned to me - %li{class: ("active" if params[:scope] == 'authored')} - = link_to filter_path(entity, scope: 'authored') do - Created by me - %li{class: ("active" if params[:scope] == 'all')} - = link_to filter_path(entity, scope: 'all') do - Everyone's + - if current_user + %fieldset.scope-filter + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if params[:scope] == 'assigned-to-me')} + = link_to filter_path(entity, scope: 'assigned-to-me') do + Assigned to me + %li{class: ("active" if params[:scope] == 'authored')} + = link_to filter_path(entity, scope: 'authored') do + Created by me + %li{class: ("active" if params[:scope] == 'all')} + = link_to filter_path(entity, scope: 'all') do + Everyone's %fieldset.status-filter %legend State %ul.nav.nav-pills - %li{class: ("active" if params[:status].blank?)} - = link_to filter_path(entity, status: nil) do + %li{class: ("active" if params[:state] == 'opened')} + = link_to filter_path(entity, state: 'opened') do Open - %li{class: ("active" if params[:status] == 'closed')} - = link_to filter_path(entity, status: 'closed') do + %li{class: ("active" if params[:state] == 'closed')} + = link_to filter_path(entity, state: 'closed') do Closed - %li{class: ("active" if params[:status] == 'all')} - = link_to filter_path(entity, status: 'all') do + %li{class: ("active" if params[:state] == 'all')} + = link_to filter_path(entity, state: 'all') do All %fieldset @@ -36,8 +37,8 @@ %small.pull-right= entities_per_project(project, entity) %fieldset - - if params[:status].present? || params[:project_id].present? - = link_to filter_path(entity, status: nil, project_id: nil), class: 'pull-right cgray' do + - if params[:state].present? || params[:project_id].present? + = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do %i.icon-remove %strong Clear filter diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 199000656fe..087b6632e8d 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -11,5 +11,5 @@ = render 'projects/issues/issue', issue: issue = paginate @issues, theme: "gitlab" - else - %p.nothing_here_message No issues to show + .nothing-here-block No issues to show diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index ddad28339c8..f40b7be4864 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -11,4 +11,4 @@ = paginate @merge_requests, theme: "gitlab" - else - %h3.nothing_here_message No merge requests to show + .nothing-here-block No merge requests to show diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index 077e6c6a808..e70eb4d01b9 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -2,7 +2,13 @@ .no-ssh-key-message .container You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile - %div.pull-right + .pull-right.hidden-xs = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true | = link_to 'Remind later', '#', class: 'hide-no-ssh-message' + .links-xs.visible-xs + = link_to "Add key", new_profile_key_path + | + = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true + | + = link_to 'Later', '#', class: 'hide-no-ssh-message' diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml index a4f406a4ab6..d82b08eeaa2 100644 --- a/app/views/shared/_project_filter.html.haml +++ b/app/views/shared/_project_filter.html.haml @@ -3,8 +3,8 @@ - if current_user %fieldset %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:scope].blank?)} - = link_to project_filter_path(scope: nil) do + %li{class: ("active" if params[:scope] == 'all')} + = link_to project_filter_path(scope: 'all') do Everyone's %li{class: ("active" if params[:scope] == 'assigned-to-me')} = link_to project_filter_path(scope: 'assigned-to-me') do @@ -16,8 +16,8 @@ %fieldset %legend State %ul.nav.nav-pills - %li{class: ("active" if params[:state].blank?)} - = link_to project_filter_path(state: nil) do + %li{class: ("active" if params[:state] == 'opened')} + = link_to project_filter_path(state: 'opened') do Open %li{class: ("active" if params[:state] == 'closed')} = link_to project_filter_path(state: 'closed') do @@ -26,6 +26,26 @@ = link_to project_filter_path(state: 'all') do All + - if defined?(labels) + %fieldset + %legend Labels + %ul.nav.nav-pills.nav-stacked.nav-small.labels-filter + - issue_label_names.each do |label_name| + %li{class: label_filter_class(label_name)} + = link_to labels_filter_path(label_name) do + %span{class: "label #{label_css_class(label_name)}"} + %i.icon-tag + = label_name + - if selected_label?(label_name) + .pull-right + %i.icon-remove + + - if issue_label_names.empty? + .light-well + Add first label to your issues + %br + or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels + %fieldset - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any? = link_to project_entities_path, class: 'cgray pull-right' do diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml index c97f8ba0f0e..7dec48e6585 100644 --- a/app/views/shared/_promo.html.haml +++ b/app/views/shared/_promo.html.haml @@ -1,4 +1,4 @@ .gitlab-promo - = link_to "Homepage", "http://gitlab.org" - = link_to "Blog", "http://blog.gitlab.org" + = link_to "Homepage", "https://www.gitlab.com/" + = link_to "Blog", "https://www.gitlab.com/blog/" = link_to "@gitlabhq", "https://twitter.com/gitlabhq" diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml new file mode 100644 index 00000000000..7b37b39780e --- /dev/null +++ b/app/views/shared/_sort_dropdown.html.haml @@ -0,0 +1,22 @@ +.dropdown.inline.prepend-left-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = @sort + - else + Newest + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(sort: 'newest') do + Newest + = link_to project_filter_path(sort: 'oldest') do + Oldest + = link_to project_filter_path(sort: 'recently_updated') do + Recently updated + = link_to project_filter_path(sort: 'last_updated') do + Last updated + = link_to project_filter_path(sort: 'milestone_due_soon') do + Milestone due soon + = link_to project_filter_path(sort: 'milestone_due_later') do + Milestone due later diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml index e6ae33f38f3..15867f071ef 100644 --- a/app/views/snippets/_blob.html.haml +++ b/app/views/snippets/_blob.html.haml @@ -8,18 +8,4 @@ = link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-tiny", title: 'Edit Snippet' = link_to "Delete", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-tiny", title: 'Delete Snippet' = link_to "Raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank" - - unless @snippet.content.empty? - - if gitlab_markdown?(@snippet.file_name) - .file-content.wiki - = preserve do - = markdown(@snippet.data) - - elsif markup?(@snippet.file_name) - .file-content.wiki - = render_markup(@snippet.file_name, @snippet.data) - - else - .file-content.code - %div{class: user_color_scheme_class} - = raw @snippet.colorize(formatter: :gitlab) - - else - .file-content.code - %p.nothing_here_message Empty file + = render 'snippets/blob_content' diff --git a/app/views/snippets/_blob_content.html.haml b/app/views/snippets/_blob_content.html.haml new file mode 100644 index 00000000000..8cec6168ab8 --- /dev/null +++ b/app/views/snippets/_blob_content.html.haml @@ -0,0 +1,14 @@ +- unless @snippet.content.empty? + - if gitlab_markdown?(@snippet.file_name) + .file-content.wiki + = preserve do + = markdown(@snippet.data) + - elsif markup?(@snippet.file_name) + .file-content.wiki + = render_markup(@snippet.file_name, @snippet.data) + - else + .file-content.code + = render 'shared/file_hljs', blob: @snippet +- else + .file-content.code + .nothing-here-block Empty file diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 05365dd8b88..40df42b6cf5 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -2,6 +2,6 @@ = render partial: 'snippet', collection: @snippets - if @snippets.empty? %li - %h3.nothing_here_message Nothing here. + .nothing-here-block Nothing here. = paginate @snippets, theme: 'gitlab' diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index bf712b2c7e7..90ddc7198f6 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -1,9 +1,9 @@ %h3.page-title My Snippets .pull-right - = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do + = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do Add new snippet - = link_to snippets_path, class: "btn grouped" do + = link_to snippets_path, class: "btn btn-grouped" do Discover snippets %p.light diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 2f6c914a159..cea2517a8e1 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -2,9 +2,9 @@ Public snippets .pull-right - = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do + = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do Add new snippet - = link_to user_snippets_path(current_user), class: "btn grouped" do + = link_to user_snippets_path(current_user), class: "btn btn-grouped" do My snippets %p.light diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml new file mode 100644 index 00000000000..412df943fcc --- /dev/null +++ b/app/views/users/_groups.html.haml @@ -0,0 +1,3 @@ +- groups.each do |group| + = link_to group, class: 'profile-groups-avatars', :title => group.name do + = image_tag group_icon(group.path)
\ No newline at end of file diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml index c7eb6fa182e..7ffd43e837d 100644 --- a/app/views/users/_profile.html.haml +++ b/app/views/users/_profile.html.haml @@ -17,6 +17,10 @@ %li %span.light Twitter: %strong= link_to user.twitter, "http://www.twitter.com/#{user.twitter}" + - unless user.website_url.blank? + %li + %span.light Website: + %strong= link_to user.short_website_url, user.full_website_url - unless user.bio.blank? %li %span.light Bio: diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 65f46500a89..edcaf3acf98 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -13,9 +13,11 @@ %br %small member since #{@user.created_at.stamp("Nov 12, 2031")} .clearfix + %h4 Groups: + = render 'groups', groups: @groups %hr %h4 User Activity: = render @events .col-md-4 = render 'profile', user: @user - = render 'projects', user: @user + = render 'projects' diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml index 5f477f3c976..1784dab35b4 100644 --- a/app/views/users_groups/_users_group.html.haml +++ b/app/views/users_groups/_users_group.html.haml @@ -1,5 +1,6 @@ - user = member.user - return unless user +- show_roles = true if show_roles.nil? %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} = image_tag avatar_icon(user.email, 16), class: "avatar s16" %strong= user.name @@ -7,17 +8,23 @@ - if user == current_user %span.label.label-success It's you - %span.pull-right - %strong= member.human_access - - - if show_controls && can?(current_user, :manage_group, @group) && current_user != user - = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do - %i.icon-edit - = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do - %i.icon-minus.icon-white - - .edit-member.hide.js-toggle-content - = form_for [@group, member], remote: true do |f| - .alert.prepend-top-20 - = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access) - = f.submit 'Save', class: 'btn btn-save btn-small' + - if show_roles + %span.pull-right + %strong= member.human_access + - if show_controls + - if can?(current_user, :modify, member) + = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do + %i.icon-edit + - if can?(current_user, :destroy, member) + - if current_user == member.user + = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + %i.icon-minus.icon-white + - else + = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + %i.icon-minus.icon-white + + .edit-member.hide.js-toggle-content + = form_for [@group, member], remote: true do |f| + .alert.prepend-top-20 + = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access) + = f.submit 'Save', class: 'btn btn-save btn-small' diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 9982b362a10..5e81810cbdb 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -13,13 +13,13 @@ class EmailsOnPushWorker return true end - compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha, MergeRequestDiff::COMMITS_SAFE_SIZE) # Do not send emails if git compare failed return false unless compare && compare.commits.present? recipients.split(" ").each do |recipient| - Notify.delay.repository_push_email(project_id, recipient, author_id, branch, compare) + Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver end end end |
