diff options
Diffstat (limited to 'app')
373 files changed, 4446 insertions, 3593 deletions
diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png Binary files differindex ecec2405372..a63fb1c9c0a 100644 --- a/app/assets/images/logo-white.png +++ b/app/assets/images/logo-white.png diff --git a/app/assets/images/monokai.png b/app/assets/images/monokai.png Binary files differnew file mode 100644 index 00000000000..9477941778e --- /dev/null +++ b/app/assets/images/monokai.png diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index 1dafdf4bd8b..c83b74a76a2 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -1,17 +1,30 @@ -$ -> - $('input#user_force_random_password').on 'change', (elem) -> - elems = $('#user_password, #user_password_confirmation') +class Admin + constructor: -> + $('input#user_force_random_password').on 'change', (elem) -> + elems = $('#user_password, #user_password_confirmation') - if $(@).attr 'checked' - elems.val('').attr 'disabled', true - else - elems.removeAttr 'disabled' + if $(@).attr 'checked' + elems.val('').attr 'disabled', true + else + elems.removeAttr 'disabled' - $('.log-tabs a').click (e) -> - e.preventDefault() - $(this).tab('show') + $('.log-tabs a').click (e) -> + e.preventDefault() + $(this).tab('show') - $('.log-bottom').click (e) -> - e.preventDefault() - visible_log = $(".file_content:visible") - visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast") + $('.log-bottom').click (e) -> + e.preventDefault() + visible_log = $(".file_content:visible") + visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast") + + modal = $('.change-owner-holder') + + $('.change-owner-link').bind "click", -> + $(this).hide() + modal.show() + + $('.change-owner-cancel-link').bind "click", -> + modal.hide() + $('.change-owner-link').show() + +@Admin = Admin diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index ca721517867..7cac971f247 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -50,4 +50,5 @@ callback(users) buildUrl: (url) -> + url = gon.relative_url_root + url if gon.relative_url_root.present? return url.replace(':version', gon.api_version) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index adb4009fbc2..ab5fc1b860d 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,6 +14,9 @@ //= require jquery.waitforimages //= require jquery.atwho //= require jquery.scrollto +//= require jquery.blockUI +//= require turbolinks +//= require jquery.turbolinks //= require bootstrap //= require modernizr //= require chosen-jquery diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee index 3fefbf8e121..d2181e7b759 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.coffee +++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee @@ -3,3 +3,11 @@ $ -> container = $(@).closest(".js-toggler-container") container.toggleClass("on") + + $("body").on "click", ".js-toggle-visibility-link", (e) -> + $(@).find('i'). + toggleClass('icon-chevron-down'). + toggleClass('icon-chevron-up') + container = $(".js-toggle-visibility-container") + container.toggleClass("hide") + e.preventDefault() diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee index 796bebe219a..6e069f264f1 100644 --- a/app/assets/javascripts/branch-graph.js.coffee +++ b/app/assets/javascripts/branch-graph.js.coffee @@ -5,10 +5,11 @@ class BranchGraph @mspace = 0 @parents = {} @colors = ["#000"] - @offsetX = 120 + @offsetX = 150 @offsetY = 20 @unitTime = 30 @unitSpace = 10 + @prev_start = -1 @load() load: -> @@ -24,10 +25,18 @@ class BranchGraph prepareData: (@days, @commits) -> @collectParents() + @graphHeight = $(@element).height() + @graphWidth = $(@element).width() + ch = Math.max(@graphHeight, @offsetY + @unitTime * @mtime + 150) + cw = Math.max(@graphWidth, @offsetX + @unitSpace * @mspace + 300) + @r = Raphael(@element.get(0), cw, ch) + @top = @r.set() + @barHeight = Math.max(@graphHeight, @unitTime * @days.length + 320) for c in @commits c.isParent = true if c.id of @parents @preparedCommits[c.id] = c + @markCommit(c) @collectColors() @@ -49,18 +58,12 @@ class BranchGraph k++ buildGraph: -> - graphHeight = $(@element).height() - graphWidth = $(@element).width() - ch = Math.max(graphHeight, @offsetY + @unitTime * @mtime + 150) - cw = Math.max(graphWidth, @offsetX + @unitSpace * @mspace + 300) - @r = r = Raphael(@element.get(0), cw, ch) - top = r.set() + r = @r cuday = 0 cumonth = "" - barHeight = Math.max(graphHeight, @unitTime * @days.length + 320) - r.rect(0, 0, 26, barHeight).attr fill: "#222" - r.rect(26, 0, 20, barHeight).attr fill: "#444" + r.rect(0, 0, 26, @barHeight).attr fill: "#222" + r.rect(26, 0, 20, @barHeight).attr fill: "#444" for day, mm in @days if cuday isnt day[0] @@ -81,42 +84,50 @@ class BranchGraph ) cumonth = day[1] - for commit in @commits - x = @offsetX + @unitSpace * (@mspace - commit.space) - y = @offsetY + @unitTime * commit.time + @renderPartialGraph() + + @bindEvents() - @drawDot(x, y, commit) + renderPartialGraph: -> + start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10 + start = 0 if start < 0 + end = start + 40 + end = @commits.length if @commits.length < end - @drawLines(x, y, commit) + if @prev_start == -1 or Math.abs(@prev_start - start) > 10 + i = start - @appendLabel(x, y, commit.refs) if commit.refs + @prev_start = start - @appendAnchor(top, commit, x, y) + while i < end + commit = @commits[i] + i += 1 - @markCommit(x, y, commit, graphHeight) + if commit.hasDrawn isnt true + x = @offsetX + @unitSpace * (@mspace - commit.space) + y = @offsetY + @unitTime * commit.time - top.toFront() - @bindEvents() + @drawDot(x, y, commit) + + @drawLines(x, y, commit) + + @appendLabel(x, y, commit) + + @appendAnchor(x, y, commit) + + commit.hasDrawn = true + + @top.toFront() bindEvents: -> drag = {} element = @element - dragger = (event) -> - element.scrollLeft drag.sl - (event.clientX - drag.x) - element.scrollTop drag.st - (event.clientY - drag.y) - - element.on mousedown: (event) -> - drag = - x: event.clientX - y: event.clientY - st: element.scrollTop() - sl: element.scrollLeft() - $(window).on "mousemove", dragger + + $(element).scroll (event) => + @renderPartialGraph() $(window).on - mouseup: -> - $(window).off "mousemove", dragger - keydown: (event) -> + keydown: (event) => # left element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37 # top @@ -125,21 +136,24 @@ class BranchGraph element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39 # bottom element.scrollTop element.scrollTop() + 50 if event.keyCode is 40 + @renderPartialGraph() + + appendLabel: (x, y, commit) -> + return unless commit.refs - appendLabel: (x, y, refs) -> r = @r - shortrefs = refs + shortrefs = commit.refs # Truncate if longer than 15 chars shortrefs = shortrefs.substr(0, 15) + "…" if shortrefs.length > 17 - text = r.text(x + 8, y, shortrefs).attr( + text = r.text(x + 4, y, shortrefs).attr( "text-anchor": "start" font: "10px Monaco, monospace" fill: "#FFF" - title: refs + title: commit.refs ) textbox = text.getBBox() # Create rectangle based on the size of the textbox - rect = r.rect(x, y - 7, textbox.width + 15, textbox.height + 5, 4).attr( + rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr( fill: "#000" "fill-opacity": .5 stroke: "none" @@ -156,8 +170,9 @@ class BranchGraph # Set text to front text.toFront() - appendAnchor: (top, commit, x, y) -> + appendAnchor: (x, y, commit) -> r = @r + top = @top options = @options anchor = r.circle(x, y, 10).attr( fill: "#000" @@ -206,22 +221,19 @@ class BranchGraph # Build line shape if parent[1] is commit.space - d1 = [0, 5] - d2 = [0, 10] - arrow = "l-2,5,4,0,-2,-5" + offset = [0, 5] + arrow = "l-2,5,4,0,-2,-5,0,5" else if parent[1] < commit.space - d1 = [3, 3] - d2 = [7, 5] - arrow = "l5,0,-2,4,-3,-4" + offset = [3, 3] + arrow = "l5,0,-2,4,-3,-4,4,2" else - d1 = [-3, 3] - d2 = [-7, 5] - arrow = "l-5,0,2,4,3,-4" + offset = [-3, 3] + arrow = "l-5,0,2,4,3,-4,-4,2" # Start point - route = ["M", x + d1[0], y + d1[1]] + route = ["M", x + offset[0], y + offset[1]] # Add arrow if not first parent if i > 0 @@ -230,7 +242,6 @@ class BranchGraph # Circumvent if overlap if commit.space isnt parentCommit.space or commit.space isnt parent[1] route.push( - "L", x + d2[0], y + d2[1], "L", parentX2, y + 10, "L", parentX2, parentY - 5, ) @@ -244,16 +255,18 @@ class BranchGraph stroke: color "stroke-width": 2) - markCommit: (x, y, commit, graphHeight) -> + markCommit: (commit) -> if commit.id is @options.commit_id r = @r + x = @offsetX + @unitSpace * (@mspace - commit.space) + y = @offsetY + @unitTime * commit.time r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr( fill: "#000" "fill-opacity": .5 stroke: "none" ) # Displayed in the center - @element.scrollTop(y - graphHeight / 2) + @element.scrollTop(y - @graphHeight / 2) Raphael::commitTooltip = (x, y, commit) -> boxWidth = 300 diff --git a/app/assets/javascripts/chart.js.coffee b/app/assets/javascripts/chart.js.coffee new file mode 100644 index 00000000000..989f48e5e75 --- /dev/null +++ b/app/assets/javascripts/chart.js.coffee @@ -0,0 +1,21 @@ +@Chart = + labels: [] + values: [] + + init: (labels, values, title) -> + r = Raphael('activity-chart') + + fin = -> + @flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this) + + fout = -> + @flag.animate + opacity: 0, 300, -> @remove() + + r.text(160, 10, title).attr font: "13px sans-serif" + r.barchart( + 10, 20, 560, 200, + [values], + {colors:["#456"]} + ).label(labels, true) + .hover(fin, fout) diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee new file mode 100644 index 00000000000..9f55a1e6368 --- /dev/null +++ b/app/assets/javascripts/commit.js.coffee @@ -0,0 +1,6 @@ +class Commit + constructor: -> + $('.files .file').each -> + new CommitFile(this) + +@Commit = Commit diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index 47d6fcf8089..de4c06a2728 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -23,7 +23,7 @@ class CommitsList @data.offset = limit this.initLoadMore() - this.showProgress(); + this.showProgress() @getOld: -> this.showProgress() @@ -41,7 +41,8 @@ class CommitsList else @disable = true - @initLoadMore: -> + @initLoadMore: -> + $(document).unbind('scroll') $(document).endlessScroll bottomPixels: 400 fireDelay: 1000 @@ -51,4 +52,4 @@ class CommitsList callback: => this.getOld() -this.CommitsList = CommitsList
\ No newline at end of file +this.CommitsList = CommitsList diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee index 4189c90bbfa..c2fb95ca635 100644 --- a/app/assets/javascripts/dashboard.js.coffee +++ b/app/assets/javascripts/dashboard.js.coffee @@ -1,39 +1,44 @@ -window.dashboardPage = -> - Pager.init 20, true - initSidebarTab() - $(".event_filter_link").bind "click", (event) -> - event.preventDefault() - toggleFilter $(this) - reloadActivities() - -reloadActivities = -> - $(".content_list").html '' - Pager.init 20, true - -toggleFilter = (sender) -> - sender.parent().toggleClass "inactive" - event_filters = $.cookie("event_filter") - filter = sender.attr("id").split("_")[0] - if event_filters - event_filters = event_filters.split(",") - else - event_filters = new Array() - - index = event_filters.indexOf(filter) - if index is -1 - event_filters.push filter - else - event_filters.splice index, 1 - - $.cookie "event_filter", event_filters.join(",") - -initSidebarTab = -> - key = "dashboard_sidebar_filter" - - # store selection in cookie - $('.dash-sidebar-tabs a').on 'click', (e) -> - $.cookie(key, $(e.target).attr('id')) - - # show tab from cookie - sidebar_filter = $.cookie(key) - $("#" + sidebar_filter).tab('show') if sidebar_filter +class Dashboard + constructor: -> + Pager.init 20, true + @initSidebarTab() + + $(".event_filter_link").bind "click", (event) => + event.preventDefault() + @toggleFilter($(event.currentTarget)) + @reloadActivities() + + reloadActivities: -> + $(".content_list").html '' + Pager.init 20, true + + toggleFilter: (sender) -> + sender.parent().toggleClass "inactive" + event_filters = $.cookie("event_filter") + filter = sender.attr("id").split("_")[0] + if event_filters + event_filters = event_filters.split(",") + else + event_filters = new Array() + + index = event_filters.indexOf(filter) + if index is -1 + event_filters.push filter + else + event_filters.splice index, 1 + + $.cookie "event_filter", event_filters.join(","), { path: '/' } + + initSidebarTab: -> + key = "dashboard_sidebar_filter" + + # store selection in cookie + $('.dash-sidebar-tabs a').on 'click', (e) -> + $.cookie(key, $(e.target).attr('id')) + + # show tab from cookie + sidebar_filter = $.cookie(key) + $("#" + sidebar_filter).tab('show') if sidebar_filter + + +@Dashboard = Dashboard diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee new file mode 100644 index 00000000000..fb149b7f677 --- /dev/null +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -0,0 +1,42 @@ +$ -> + new Dispatcher() + +class Dispatcher + constructor: () -> + @initSearch() + @initPageScripts() + + initPageScripts: -> + page = $('body').attr('data-page') + project_id = $('body').attr('data-project-id') + + console.log(page) + + unless page + return false + + path = page.split(':') + + switch page + when 'issues:index' + Issues.init() + when 'dashboard:show' + new Dashboard() + when 'commit:show' + new Commit() + when 'groups:show', 'teams:show', 'projects:show' + Pager.init(20, true) + when 'projects:new', 'projects:edit' + new Project() + when 'walls:show' + new Wall(project_id) + when 'teams:members:index' + new TeamMembers() + + switch path.first() + when 'admin' then new Admin() + when 'wikis' then new Wikis() + + initSearch: -> + autocomplete_json = $('.search-autocomplete-json').data('autocomplete-opts') + new SearchAutocomplete(autocomplete_json) diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee new file mode 100644 index 00000000000..8a997fe318e --- /dev/null +++ b/app/assets/javascripts/extensions/jquery.js.coffee @@ -0,0 +1,9 @@ +$.fn.showAndHide = -> + $(@).show(). + delay(3000). + fadeOut() + +$.fn.enableButton = -> + $(@).removeAttr('disabled'). + removeClass('disabled') + diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 1cc9d34dd80..c8c57b91e03 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -2,37 +2,55 @@ window.GitLab ?= {} GitLab.GfmAutoComplete = + # private_token: '' + dataSource: '' # Emoji Emoji: - data: [] + assetBase: '' template: '<li data-value="${insert}">${name} <img alt="${name}" height="20" src="${image}" width="20" /></li>' # Team Members Members: - data: [] - url: '' - params: - private_token: '' template: '<li data-value="${username}">${username} <small>${name}</small></li>' + Issues: + template: '<li data-value="${id}"><small>${id}</small> ${title} </li>' + # Add GFM auto-completion to all input fields, that accept GFM input. setup: -> input = $('.js-gfm-input') # Emoji - input.atWho ':', - data: @Emoji.data + input.atwho + at: ':' tpl: @Emoji.template + callbacks: + before_save: (emojis) => + $.map emojis, (em) => name: em, insert: em+ ':', image: "#{@Emoji.assetBase}/#{em}.png" # Team Members - input.atWho '@', + input.atwho + at: '@' tpl: @Members.template - callback: (query, callback) => - request_params = $.extend({}, @Members.params, query: query) - $.getJSON(@Members.url, request_params).done (members) => - new_members_data = $.map(members, (m) -> - username: m.username, - name: m.name - ) - callback(new_members_data) + search_key: 'search' + callbacks: + before_save: (members) => + $.map members, (m) => name: m.name, username: m.username, search: "#{m.username} #{m.name}" + + input.atwho + at: '#' + alias: 'issues' + search_key: 'search' + tpl: @Issues.template + callbacks: + before_save: (issues) -> + $.map issues, (i) -> id: i.id, title: i.title, search: "#{i.id} #{i.title}" + input.one "focus", => + $.getJSON(@dataSource).done (data) -> + # load members + input.atwho 'load', "@", data.members + # load issues + input.atwho 'load', "issues", data.issues + # load emojis + input.atwho 'load', ":", data.emojis diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js deleted file mode 100644 index 9ba1a3f1bba..00000000000 --- a/app/assets/javascripts/issues.js +++ /dev/null @@ -1,80 +0,0 @@ -function initIssuesSearch() { - var href = $('#issue_search_form').attr('action'); - var last_terms = ''; - - $('#issue_search').keyup(function() { - var terms = $(this).val(); - var milestone_id = $('#milestone_id').val(); - var status = $('#status').val(); - - if (terms != last_terms) { - last_terms = terms; - - if (terms.length >= 2 || terms.length == 0) { - $.get(href, { 'status': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) { - $('#issues-table').html(response); - }); - } - } - }); -} - -/** - * Init issues page - * - */ -function issuesPage(){ - initIssuesSearch(); - $("#update_status").chosen(); - $("#update_assignee_id").chosen(); - $("#update_milestone_id").chosen(); - - $("#label_name").chosen(); - $("#assignee_id").chosen(); - $("#milestone_id").chosen(); - $("#milestone_id, #assignee_id, #label_name").on("change", function(){ - $(this).closest("form").submit(); - }); - - $('body').on('ajax:success', '.close_issue, .reopen_issue', function(){ - var t = $(this), - totalIssues, - reopen = t.hasClass('reopen_issue'); - $('.issue_counter').each(function(){ - var issue = $(this); - totalIssues = parseInt( $(this).html(), 10 ); - - if( reopen && issue.closest('.main_menu').length ){ - $(this).html( totalIssues+1 ); - }else { - $(this).html( totalIssues-1 ); - } - }); - - }); - - $(".check_all_issues").click(function () { - $('.selected_issue').attr('checked', this.checked); - issuesCheckChanged(); - }); - - $('.selected_issue').bind('change', issuesCheckChanged); -} - -function issuesCheckChanged() { - var checked_issues = $('.selected_issue:checked'); - - if(checked_issues.length > 0) { - var ids = [] - $.each(checked_issues, function(index, value) { - ids.push($(value).attr("data-id")); - }) - $('#update_issues_ids').val(ids); - $('.issues_filters').hide(); - $('.issues_bulk_update').show(); - } else { - $('#update_issues_ids').val([]); - $('.issues_bulk_update').hide(); - $('.issues_filters').show(); - } -} diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee new file mode 100644 index 00000000000..67d9498c50a --- /dev/null +++ b/app/assets/javascripts/issues.js.coffee @@ -0,0 +1,72 @@ +@Issues = + init: -> + Issues.initSearch() + Issues.initSelects() + Issues.initChecks() + + $("body").on "ajax:success", ".close_issue, .reopen_issue", -> + t = $(this) + totalIssues = undefined + reopen = t.hasClass("reopen_issue") + $(".issue_counter").each -> + issue = $(this) + totalIssues = parseInt($(this).html(), 10) + if reopen and issue.closest(".main_menu").length + $(this).html totalIssues + 1 + else + $(this).html totalIssues - 1 + $("body").on "click", ".issues-filters .dropdown-menu a", -> + $('.issues-list').block( + message: null, + overlayCSS: + backgroundColor: '#DDD' + opacity: .4 + ) + + reload: -> + Issues.initSelects() + Issues.initChecks() + $('#filter_issue_search').val($('#issue_search').val()) + + initSelects: -> + $("#update_status").chosen() + $("#update_assignee_id").chosen() + $("#update_milestone_id").chosen() + $("#label_name").chosen() + $("#assignee_id").chosen() + $("#milestone_id").chosen() + $("#milestone_id, #assignee_id, #label_name").on "change", -> + $(this).closest("form").submit() + + initChecks: -> + $(".check_all_issues").click -> + $(".selected_issue").attr "checked", @checked + Issues.checkChanged() + + $(".selected_issue").bind "change", Issues.checkChanged + + + initSearch: -> + form = $("#issue_search_form") + last_terms = "" + $("#issue_search").keyup -> + terms = $(this).val() + unless terms is last_terms + last_terms = terms + if terms.length >= 2 or terms.length is 0 + form.submit() + + checkChanged: -> + checked_issues = $(".selected_issue:checked") + if checked_issues.length > 0 + ids = [] + $.each checked_issues, (index, value) -> + ids.push $(value).attr("data-id") + + $("#update_issues_ids").val ids + $(".issues-filters").hide() + $(".issues_bulk_update").show() + else + $("#update_issues_ids").val [] + $(".issues_bulk_update").hide() + $(".issues-filters").show() diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee index b61df846c7a..fd7e296efde 100644 --- a/app/assets/javascripts/main.js.coffee +++ b/app/assets/javascripts/main.js.coffee @@ -7,6 +7,8 @@ window.slugify = (text) -> window.ajaxGet = (url) -> $.ajax({type: "GET", url: url, dataType: "script"}) +window.showAndHide = (selector) -> + window.errorMessage = (message) -> ehtml = $("<p>") ehtml.addClass("error_message") @@ -39,14 +41,31 @@ window.linkify = (str) -> exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig return str.replace(exp,"<a href='$1'>$1</a>") +window.simpleFormat = (str) -> + linkify(sanitize(str).replace(/\n/g, '<br />')) + +window.startSpinner = -> + $('.turbolink-spinner').fadeIn() + +window.stopSpinner = -> + $('.turbolink-spinner').fadeOut() + +window.stopEndlessScroll = -> + $(document).unbind('scroll') + +document.addEventListener("page:fetch", startSpinner) +document.addEventListener("page:fetch", stopEndlessScroll) +document.addEventListener("page:receive", stopSpinner) $ -> # Click a .one_click_select field, select the contents $(".one_click_select").on 'click', -> $(@).select() # Click a .appear-link, appear-data fadeout - $(".appear-link").on 'click', -> + $(".appear-link").on 'click', (e) -> $('.appear-data').fadeIn() + e.preventDefault() + # Initialize chosen selects $('select.chosen').chosen() diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 890ca400f81..769a940959b 100644 --- a/app/assets/javascripts/merge_requests.js.coffee +++ b/app/assets/javascripts/merge_requests.js.coffee @@ -21,6 +21,13 @@ class MergeRequest this.initMergeWidget() this.$('.show-all-commits').on 'click', => this.showAllCommits() + + modal = $('#modal_merge_info').modal modal: true, show:false + + $('.how_to_merge_link').bind "click", -> + modal.show() + $('.modal-header .close').bind "click", -> + modal.hide() # Local jQuery finder $: (selector) -> @@ -76,7 +83,6 @@ class MergeRequest $('.ci_widget.ci-' + state).show() loadDiff: (event) -> - $('.dashboard-loader').show() $.ajax type: 'GET' url: this.$('.nav-tabs .diffs-tab a').attr('href') diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee index 5f606acdf9c..5bd11d273a7 100644 --- a/app/assets/javascripts/pager.js.coffee +++ b/app/assets/javascripts/pager.js.coffee @@ -30,6 +30,7 @@ @disable = true initLoadMore: -> + $(document).unbind('scroll') $(document).endlessScroll bottomPixels: 400 fireDelay: 1000 diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 42207a390b3..213133bc965 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -15,6 +15,8 @@ $ -> $(this).find('.update-failed').hide() $('.update-username form').on 'ajax:complete', -> - $(this).find('.save-btn').removeAttr('disabled') - $(this).find('.save-btn').removeClass('disabled') + $(this).find('.btn-save').enableButton() $(this).find('.loading-gif').hide() + + $('.update-notifications').on 'ajax:complete', -> + $(this).find('.btn-save').enableButton() diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee new file mode 100644 index 00000000000..780292daa11 --- /dev/null +++ b/app/assets/javascripts/project.js.coffee @@ -0,0 +1,41 @@ +class Project + constructor: -> + $('.project-edit-container').on 'ajax:before', => + $('.project-edit-container').hide() + $('.save-project-loader').show() + + @initEvents() + + + initEvents: -> + disableButtonIfEmptyField '#project_name', '.project-submit' + + $('#project_issues_enabled').change -> + if ($(this).is(':checked') == true) + $('#project_issues_tracker').removeAttr('disabled') + else + $('#project_issues_tracker').attr('disabled', 'disabled') + + $('#project_issues_tracker').change() + + $('#project_issues_tracker').change -> + if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) + $('#project_issues_tracker_id').attr('disabled', 'disabled') + else + $('#project_issues_tracker_id').removeAttr('disabled') + + +@Project = Project + +$ -> + # Git clone panel switcher + scope = $ '.project_clone_holder' + if scope.length > 0 + $('a, button', scope).click -> + $('a, button', scope).removeClass 'active' + $(@).addClass 'active' + $('#project_clone', scope).val $(@).data 'clone' + + # Ref switcher + $('.project-refs-select').on 'change', -> + $(@).parents('form').submit() diff --git a/app/assets/javascripts/projects.js.coffee b/app/assets/javascripts/projects.js.coffee deleted file mode 100644 index 24106c61b75..00000000000 --- a/app/assets/javascripts/projects.js.coffee +++ /dev/null @@ -1,35 +0,0 @@ -window.Projects = -> - $('.new_project, .edit_project').on 'ajax:before', -> - $('.project_new_holder, .project_edit_holder').hide() - $('.save-project-loader').show() - - $('form #project_default_branch').chosen() - disableButtonIfEmptyField '#project_name', '.project-submit' - -$ -> - # Git clone panel switcher - scope = $ '.project_clone_holder' - if scope.length > 0 - $('a, button', scope).click -> - $('a, button', scope).removeClass 'active' - $(@).addClass 'active' - $('#project_clone', scope).val $(@).data 'clone' - - # Ref switcher - $('.project-refs-select').on 'change', -> - $(@).parents('form').submit() - - $('#project_issues_enabled').change -> - if ($(this).is(':checked') == true) - $('#project_issues_tracker').removeAttr('disabled') - else - $('#project_issues_tracker').attr('disabled', 'disabled') - - $('#project_issues_tracker').change() - - $('#project_issues_tracker').change -> - if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) - $('#project_issues_tracker_id').attr('disabled', 'disabled') - else - $('#project_issues_tracker_id').removeAttr('disabled') - diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee new file mode 100644 index 00000000000..3418690e109 --- /dev/null +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -0,0 +1,8 @@ +class SearchAutocomplete + constructor: (json) -> + $("#search").autocomplete + source: json + select: (event, ui) -> + location.href = ui.item.url + +@SearchAutocomplete = SearchAutocomplete diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee new file mode 100644 index 00000000000..5eaa8ad4ff9 --- /dev/null +++ b/app/assets/javascripts/team_members.js.coffee @@ -0,0 +1,6 @@ +class TeamMembers + constructor: -> + $('.team-members .project-access-select').on "change", -> + $(this.form).submit() + +@TeamMembers = TeamMembers diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index 2603b9a96c6..fdc82ff6668 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -1,40 +1,13 @@ # Code browser tree slider +# Make the entire tree-item row clickable, but not if clicking another link (like a commit message) +$(".tree-content-holder .tree-item").live 'click', (e) -> + if (e.target.nodeName != "A") + path = $('.tree-item-file-name a', this).attr('href') + Turbolinks.visit(path) $ -> - if $('#tree-slider').length > 0 - # Show the "Loading commit data" for only the first element - $('span.log_loading:first').removeClass('hide') - - $('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live "click", -> - $("#tree-content-holder").hide("slide", { direction: "left" }, 150) - - # Make the entire tree-item row clickable, but not if clicking another link (like a commit message) - $("#tree-slider .tree-item").live 'click', (e) -> - $('.tree-item-file-name a', this).trigger('click') if (e.target.nodeName != "A") - - # Maintain forward/back history while browsing the file tree - ((window) -> - History = window.History - $ = window.jQuery - document = window.document - - # Check to see if History.js is enabled for our Browser - unless History.enabled - return false - - $('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live 'click', (e) -> - History.pushState(null, null, decodeURIComponent($(@).attr('href'))) - return false - - History.Adapter.bind window, 'statechange', -> - state = History.getState() - $.ajax({ - url: state.url, - dataType: 'script', - beforeSend: -> $('.tree_progress').addClass("loading"), - complete: -> $('.tree_progress').removeClass("loading") - }) - )(window) + # Show the "Loading commit data" for only the first element + $('span.log_loading:first').removeClass('hide') # See if there are lines selected # "#L12" and "#L34-56" supported diff --git a/app/assets/javascripts/wall.js.coffee b/app/assets/javascripts/wall.js.coffee index a35c8c6004e..4f71e6e0c35 100644 --- a/app/assets/javascripts/wall.js.coffee +++ b/app/assets/javascripts/wall.js.coffee @@ -1,32 +1,32 @@ -@Wall = - note_ids: [] - project_id: null - - init: (project_id) -> - Wall.project_id = project_id - Wall.getContent() - Wall.initRefresh() - Wall.initForm() +class Wall + constructor: (project_id) -> + @project_id = project_id + @note_ids = [] + @getContent() + @initRefresh() + @initForm() # # Gets an initial set of notes. # getContent: -> - Api.notes Wall.project_id, (notes) -> - $.each notes, (i, note) -> + Api.notes @project_id, (notes) => + $.each notes, (i, note) => # render note if it not present in loaded list # or skip if rendered - if $.inArray(note.id, Wall.note_ids) == -1 - Wall.note_ids.push(note.id) - Wall.renderNote(note) - Wall.scrollDown() + if $.inArray(note.id, @note_ids) == -1 + @note_ids.push(note.id) + @renderNote(note) + @scrollDown() $("abbr.timeago").timeago() initRefresh: -> - setInterval("Wall.refresh()", 10000) + setInterval => + @refresh() + , 10000 refresh: -> - Wall.getContent() + @getContent() scrollDown: -> notes = $('ul.notes') @@ -36,8 +36,8 @@ form = $('.wall-note-form') form.find("#target_type").val('wall') - form.on 'ajax:success', -> - Wall.refresh() + form.on 'ajax:success', => + @refresh() form.find(".js-note-text").val("").trigger("input") form.on 'ajax:complete', -> @@ -58,14 +58,28 @@ form.show() renderNote: (note) -> - author = '<strong class="wall-author">' + note.author.name + '</strong>' - body = '<span class="wall-text">' + linkify(sanitize(note.body)) + '</span>' - file = '' - time = '<abbr class="timeago" title="' + note.created_at + '">' + note.created_at + '</time>' + template = @noteTemplate() + template = template.replace('{{author_name}}', note.author.name) + template = template.replace(/{{created_at}}/g, note.created_at) + template = template.replace('{{text}}', simpleFormat(note.body)) if note.attachment - file = '<span class="wall-file"><a href="/files/note/' + note.id + '/' + note.attachment + '">' + note.attachment + '</a></span>' - - html = '<li>' + author + body + file + time + '</li>' + file = '<i class="icon-paper-clip"/><a href="/files/note/' + note.id + '/' + note.attachment + '">' + note.attachment + '</a>' + else + file = '' + template = template.replace('{{file}}', file) + + + $('ul.notes').append(template) + + noteTemplate: -> + return '<li> + <strong class="wall-author">{{author_name}}</strong> + <span class="wall-text"> + {{text}} + <span class="wall-file">{{file}}</span> + </span> + <abbr class="timeago" title="{{created_at}}">{{created_at}}</abbr> + </li>' - $('ul.notes').append(html) +@Wall = Wall diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee new file mode 100644 index 00000000000..f2867c8026e --- /dev/null +++ b/app/assets/javascripts/wikis.js.coffee @@ -0,0 +1,19 @@ +class Wikis + constructor: -> + modal = $('#modal-new-wiki').modal({modal: true, show:false}) + + $('.add-new-wiki').bind "click", -> + modal.show() + + $('.build-new-wiki').bind "click", -> + field = $('#new_wiki_path') + slug = field.val() + path = field.attr('data-wikis-path') + + if(slug.length > 0) + location.href = path + "/" + slug + + $('.modal-header .close').bind "click", -> + modal.hide() + +@Wikis = Wikis diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index fd15d5c6097..85e43ed0d35 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -36,10 +36,12 @@ @import "sections/admin.scss"; @import "sections/wiki.scss"; @import "sections/wall.scss"; +@import "sections/dashboard.scss"; @import "highlight/white.scss"; @import "highlight/dark.scss"; @import "highlight/solarized_dark.scss"; +@import "highlight/monokai.scss"; /** * UI themes: diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 4e7aa968ea7..ccc6f7a9d2d 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -17,7 +17,6 @@ body { margin: 0 0; } -.visible_link, .author_link { color: $link_color; } @@ -32,30 +31,12 @@ body { padding-bottom: 0; } -.info_link { - margin-right: 5px; - float: left; - - img { - width: 20px; - } -} - -.download_repo_link { - background: url("images.png") no-repeat 0 -48px; - padding-left: 20px; -} - table a code { position: relative; top: -2px; margin-right: 3px; } -.span12 hr{ - margin-top: 5px; -} - .loading { margin: 20px auto; background: url(ajax_loader.gif) no-repeat center center; @@ -81,10 +62,6 @@ table a code { } } -.git_url_wrapper { - margin-right:50px -} - span.update-author { display: block; color: #999; @@ -96,37 +73,16 @@ span.update-author { } } -.dashboard-loader { - float: left; - margin: 10px; - display: none; -} .user-mention { color: #2FA0BB; font-weight: bold; } -.neib { - margin-right: 10px; -} - .label { - padding: 0px 4px; - font-size: 10px; + padding: 1px 4px; + font-size: 12px; font-style: normal; - background-color: $link_color; - - &.label-success { - background-color: #8D8; - color: #333; - text-shadow: 0 1px 1px white; - } - - &.label-error { - background-color: #D88; - color: #333; - text-shadow: 0 1px 1px white; - } + font-weight: normal; } form { @@ -182,27 +138,6 @@ input[type=text] { } } -.merge-request-form-holder { - select { - width: 300px; - } -} - -/** Issues **/ -#issue_assignee_id { - width: 300px; -} - -#new_issue_dialog textarea{ - height: 100px; -} - -.project_list_url { - width: 250px; - background:#fff !important; -} - - .line_holder { &:hover { td { @@ -234,24 +169,6 @@ p.time { margin: 30px 3px 3px 2px; } - -.styled_image { - border: 2px solid #ddd; -} - - - -/* Fix for readme code (stopped it from being yellow) */ -.readme { - pre { - background: white !important; - - code { - background: none !important; - } - } -} - .search-holder { label, input { height: 30px; @@ -278,6 +195,11 @@ p.time { top: -5px; @include border-radius(4px); + &.success { + background: #4A4; + color: #FFF; + } + &.error { background: #DA4E49; color: #FFF; @@ -345,25 +267,6 @@ li.note { } - -/** - * Admin area - * - */ -.admin_dash { - .data { - a { - h1 { - line-height: 48px; - font-size: 48px; - padding: 20px; - text-align: center; - font-weight: normal; - } - } - } -} - .rss-icon { img { width: 24px; @@ -376,24 +279,12 @@ li.note { } - -/* CHZN reset few styles */ -.chzn-container-single .chzn-single { - background: #FFF; - border: 1px solid #bbb; - box-shadow: none; -} -.chzn-container-active .chzn-single { - background: #fff; -} - - .supp_diff_link, .show-all-commits { cursor: pointer; } -.merge_request, +.merge-request, .issue { &.today{ background: #EFE; @@ -493,27 +384,6 @@ pre { } } -.float-link { - float: left; - margin-right: 15px; - .s16 { - margin-right: 5px; - } -} - -.dashboard-search-filter { - padding:5px; - - .search-text-input { - float:left; - @extend .span2; - } - .btn { - margin-left: 5px; - float:left; - } -} - h1.http_status_code { font-size: 56px; line-height: 100px; @@ -544,14 +414,7 @@ img.emoji { display: none; } -.label-branch { - @include border-radius(4px); - padding: 2px 4px; - border: none; - font-size: 14px; - background: #474D57; - color: #fff; - font-family: $monospace_font; - text-shadow: 0 1px 1px #111; - font-weight: normal; +.chart { + overflow: hidden; + height: 220px; } diff --git a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss index de1fb1551bf..ed6ec77b89b 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss @@ -1,8 +1,24 @@ -/** AVATARS **/ -img.avatar { float: left; margin-right: 12px; width: 40px; border: 1px solid #ddd; padding: 1px; } -img.avatar.s16 { width: 16px; height: 16px; margin-right: 6px; } -img.avatar.s24 { width: 24px; height: 24px; margin-right: 8px; } -img.avatar.s32 { width: 32px; height: 32px; margin-right: 10px; } -img.avatar.s90 { width: 90px; height: 90px; margin-right: 15px; } +.avatar { + float: left; + margin-right: 12px; + width: 40px; + border: 1px solid #ddd; + padding: 1px; + + &.avatar-inline { + float: none; + margin-left: 3px; + + &.s16 { margin-right: 2px; } + &.s24 { margin-right: 2px; } + } + + &.s16 { width: 16px; height: 16px; margin-right: 6px; } + &.s24 { width: 24px; height: 24px; margin-right: 8px; } + &.s32 { width: 32px; height: 32px; margin-right: 10px; } + &.s90 { width: 90px; height: 90px; margin-right: 15px; } +} + + img.lil_av { padding-left: 4px; padding-right: 3px; } img.small { width: 80px; } diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss index cb055a1c08b..867920ad08f 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -13,6 +13,7 @@ background: #F9F9F9; margin-bottom: 25px; border: 1px solid #CCC; + word-wrap: break-word; @include solid-shade; &.ui-box-show { @@ -41,7 +42,6 @@ .ui-box-body, .ui-box-bottom { padding: 15px; - word-wrap: break-word; .clearfix { margin: 0; @@ -100,8 +100,9 @@ margin-top: 0; } - .btn-tiny { - @include box-shadow(0 0px 0px 1px #f1f1f1); + .btn { + position: relative; + top: -2px; } .nav-pills { @@ -170,3 +171,8 @@ margin: 3px 3px 25px 3px; } } + +.light-well { + background: #f9f9f9; + padding: 15px; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss index 03497e32d26..e9b85686fad 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss @@ -23,7 +23,7 @@ &.disabled { color: #fff; - background: #29B; + background: $primary_color; } } @@ -39,7 +39,7 @@ &.disabled { color: #fff; - background: #29B; + background: $primary_color; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index 6bdd1652fd6..d2460c5f022 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -10,10 +10,17 @@ /** COMMON CLASSES **/ .left { float:left } -.append-bottom-10 { margin-bottom:10px } -.append-bottom-20 { margin-bottom:20px } + .prepend-top-10 { margin-top:10px } .prepend-top-20 { margin-top:20px } +.prepend-left-10 { margin-left:10px } +.prepend-left-20 { margin-left:20px } +.append-right-10 { margin-right:10px } +.append-right-20 { margin-right:20px } +.append-bottom-10 { margin-bottom:10px } +.append-bottom-20 { margin-bottom:20px } +.inline { display: inline-block } + .padded { padding:20px } .ipadded { padding:20px!important } .lborder { border-left:1px solid #eee } @@ -31,8 +38,6 @@ border-color: #DDD; } -.well { padding: 15px; } - /** HELPERS **/ .nothing_here_message { text-align: center; @@ -65,15 +70,20 @@ fieldset legend { font-size: 17px; } /** PAGINATION **/ .gitlab_pagination { - span a { color: $link_color; } - .prev, .next, .current, .page a { - padding: 10px; - } - .current { - border-bottom: 2px solid $style_color; - } } .tab-content { overflow: visible; } + +@media (max-width: 1200px) { + .only-wide { + display: none; + } +} + +.pagination ul > li > a, .pagination ul > li >span { + @include linear-gradient(#f1f1f1, #e1e1e1); + color: #333; + text-shadow: 0 1px 1px #FFF; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss index d0bf3bdd6d3..78a3f0b810d 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/files.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss @@ -49,6 +49,15 @@ &.wiki { padding: 20px; font-size: 13px; + + .highlight { + margin-bottom: 9px; + @include border-radius(4px); + + > pre { + margin: 0; + } + } } &.blob_file { diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss index 0f893a553ee..e661e02623e 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/lists.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss @@ -69,5 +69,14 @@ ul.bordered-list { display: block; margin: 0px; &:last-child { border:none } + + &.active { + background: #f9f9f9; + a { font-weight: bold; } + } + + &.light { + a { color: #777; } + } } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/nav.scss b/app/assets/stylesheets/gitlab_bootstrap/nav.scss index 2eaef61ca33..0fc8b21de7b 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/nav.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/nav.scss @@ -16,7 +16,7 @@ padding: 12px; } > .active > a { - border-color: #29B; + border-color: $primary_color; border-radius: 0; background: #F1F1F1; color: $style_color; diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss index 1f0c4802318..ab14624186d 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/typography.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss @@ -41,6 +41,10 @@ a { color: $primary_color; } + &:focus { + text-decoration: underline; + } + &.btn { color: $style_color; &:hover { diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 4196ea7ad29..a56c98cc5f1 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,4 +1,7 @@ .black .highlight { + + background-color: #333; + pre { background-color: #333; color: #eee; diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss new file mode 100644 index 00000000000..c9709fa7f12 --- /dev/null +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -0,0 +1,89 @@ +$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 .highlight { + + background-color: #272822; + + pre { + background-color: #272822; + color: $monokai-fg; + } + + .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 */ +} + diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 3f215b4e0fe..cc82f39ac93 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,4 +1,7 @@ .solarized-dark .highlight { + + background-color: #002B36; + pre { background-color: #002B36; color: #eee; diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index f200e1d7b60..df127a7c491 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,4 +1,7 @@ .white .highlight { + + background-color: #fff; + pre { background-color: #fff; color: #333; diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss index 18b102d7022..e189fd27ac6 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/sections/admin.scss @@ -1,3 +1,21 @@ +/** + * Admin area + * + */ +.admin_dash { + .data { + a { + h1 { + line-height: 48px; + font-size: 48px; + padding: 20px; + text-align: center; + font-weight: normal; + } + } + } +} + .admin-filter form { label { width: 110px; } .controls { margin-left: 130px; } diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index 1e564188892..812587a2ee6 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -99,12 +99,24 @@ } } } + .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; + padding: 0px 0.5em; border: none; &.new { background: #CFD; @@ -348,7 +360,7 @@ .notes_count { float: right; - margin: -6px 8px 6px; + margin-right: 10px; } code { @@ -369,19 +381,25 @@ } .file-stats { - .new-file{ - i{ + .new-file { + a { + color: #090; + } + i { color: #1BCF00; } } - .renamed-file{ - i{ + .renamed-file { + i { color: #FE9300; } } - .deleted-file{ - i{ - color: #FF0000; + .deleted-file { + a { + color: #B00; + } + i { + color: #EE0000; } } .edit-file{ diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss new file mode 100644 index 00000000000..9bc4a09a097 --- /dev/null +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -0,0 +1,48 @@ +.dashboard { + @extend .row; + .activities { + } + + .side { + @extend .pull-right; + + .ui-box { + margin: 3px; + > .title { + padding: 2px 15px; + } + .nav-projects-tabs li { padding: 0; } + .well-list { + li { padding: 15px; } + .arrow { + float: right; + padding: 10px; + margin: 0; + } + .last_activity { + padding-top: 5px; + display: block; + span, strong { + font-size: 12px; + color: #666; + } + } + } + @extend .ui-box; + } + } +} + +.dashboard-search-filter { + padding:5px; + + .search-text-input { + float:left; + @extend .span2; + } + .btn { + margin-left: 5px; + float:left; + } +} + diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 94e1d0b609c..e8680dde507 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -35,13 +35,14 @@ .event-title { color: #333; font-weight: bold; + font-size: 14px; .author_name { color: #333; } } .event-body { .commit p { - color: #555; + color: #666; padding-top: 5px; } .event-info { @@ -52,6 +53,13 @@ margin-top: 5px; margin-left: 40px; + pre { + border: none; + background: #f9f9f9; + border-radius: 0; + color: #555; + } + .note-file-attach { .note-image-attach { margin-top: 4px; @@ -123,7 +131,7 @@ color: #777; padding: 10px; min-height: 22px; - border-left: 5px solid #5AB9C3; + border-left: 5px solid $primary_color; margin-bottom: 20px; background: #f9f9f9; @@ -132,10 +140,10 @@ } .btn-new-mr { - @extend .btn-info; + @extend .btn-primary; @extend .small; @extend .pull-right; - margin: -3px; + margin: -2px; } } @@ -151,7 +159,7 @@ .filter_icon { a { text-align:center; - border-left: 3px solid #29B; + border-left: 3px solid $primary_color; background: #f9f9f9; margin-bottom: 10px; float: left; diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss index 7da00719b33..9be4cb788c1 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/sections/graph.scss @@ -11,9 +11,9 @@ .graph { background: #f1f1f1; - cursor: move; height: 500px; - overflow: hidden; + overflow-y: scroll; + overflow-x: hidden; } } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index 14e4cef011f..e315b4ebcaa 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -119,8 +119,12 @@ header { border-bottom: 1px solid #AAA; .nav > li > a { - color: #fff; - text-shadow: 0 1px 0 #111; + color: #DDD; + text-shadow: 0 1px 0 #444; + + &:hover { + color: #FFF; + } } } } @@ -145,7 +149,7 @@ header { background: url('logo-white.png') no-repeat center 1px; background-size: 38px; color: #fff; - text-shadow: 0 1px 1px #111; + text-shadow: 0 1px 1px #444; } } } @@ -154,7 +158,7 @@ header { color: #FFF; } color: #fff; - text-shadow: 0 1px 1px #111; + text-shadow: 0 1px 1px #444; } } diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 351f2404492..5a1b476fe25 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -1,22 +1,39 @@ -.issues_table { +.issues-list { .issue { padding: 10px; + position: relative; - .issue_check { + .issue-title { + margin-bottom: 5px; + font-size: 14px; + } + + .issue-info { + color: #999; + } + + .issue-check { float: left; padding: 8px 0; padding-right: 8px; min-width: 15px; } - p { - padding-top: 0; - padding-bottom: 2px; + .issue-labels { + display: inline-block; + } + + .issue-actions { + display: none; + position: absolute; + top: 10px; + right: 2px; } - img.avatar { - width: 32px; - margin-top: 1px; + &:hover { + .issue-actions { + display: block; + } } } } @@ -60,7 +77,7 @@ input.check_all_issues { @media (min-width: 800px) { .issues_bulk_update select { width: 120px; } } @media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } } -#issues-table-holder { +.issues-holder { .issues_filters { } diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 4cca0083e44..a84ef71e743 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -53,18 +53,6 @@ } } -li.merge_request { - padding: 10px; - img.avatar { - width: 32px; - margin-top: 1px; - } - p { - padding: 0px; - padding-bottom: 2px; - } -} - .merge-in-progress { @extend .padded; @extend .append-bottom-10; @@ -97,3 +85,31 @@ li.merge_request { .mr_direction_tip { margin-top:40px } + +.label-branch { + @include border-radius(4px); + padding: 2px 4px; + border: none; + font-size: 14px; + background: #474D57; + color: #fff; + font-family: $monospace_font; + text-shadow: 0 1px 1px #111; + font-weight: normal; +} + +.mr-list { + .merge-request { + padding: 10px; + position: relative; + + .merge-request-title { + margin-bottom: 5px; + font-size: 14px; + } + + .merge-request-info { + color: #999; + } + } +} diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 50091cd7365..7b7bcf807b5 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -1,68 +1,64 @@ -/* - * Main Menu of Application - * - */ -ul.main_menu { - margin: auto; +.main-nav { margin: 30px 0; margin-top: 10px; - height: 38px; - position: relative; - overflow: hidden; - .count { + border-bottom: 1px solid #E1E1E1; + + ul { + margin: auto; + height: 39px; position: relative; - top: -1px; - display: inline-block; - height: 15px; - margin: 0 0 0 5px; - padding: 0 8px 1px 8px; - height: auto; - font-size: 0.82em; - line-height: 14px; - text-align: center; - color: #777; - } - .label { - background: $hover; - text-shadow: none; - color: $style_color; - } - li { - list-style-type: none; - margin: 0; - display: table-cell; - width: 1%; - border-bottom: 2px solid #EEE; - &.active { - border-bottom: 2px solid #474D57; - a { - color: $style_color; - } + top: 3px; + overflow: hidden; + .count { + font-weight: normal; + display: inline-block; + height: 15px; + padding: 1px 6px; + height: auto; + font-size: 0.82em; + line-height: 14px; + text-align: center; + color: #777; + background: #eee; + @include border-radius(8px); + } + .label { + background: $hover; + text-shadow: none; + color: $style_color; } + li { + list-style-type: none; + margin: 0; + display: table-cell; + width: 1%; + &.active { + border-bottom: 3px solid #777; + a { + color: $style_color; + font-weight: bolder; + } + } - &.home { - a { - i { - font-size: 20px; - position: relative; - top: 4px; + &.home { + a { + i { + font-size: 20px; + position: relative; + top: 4px; + } } } } - } - a { - display: block; - text-align: center; - font-weight: normal; - height: 36px; - line-height: 36px; - color: #777; - text-shadow: 0 1px 1px white; - padding: 0 10px; + a { + display: block; + text-align: center; + font-weight: normal; + height: 36px; + line-height: 34px; + color: #777; + text-shadow: 0 1px 1px white; + padding: 0 10px; + } } } -/* - * End of Main Menu - * - */ - diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index ae2e1b258d3..9fe7a24b461 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -1,6 +1,13 @@ /** * Notes */ + +@-webkit-keyframes target-note { + from { background:#fffff0; } + 50% { background:#ffffd3; } + to { background:#fffff0; } +} + ul.notes { display: block; list-style: none; @@ -91,6 +98,11 @@ ul.notes { } } + .note:target { + -webkit-animation:target-note 2s linear; + background: #fffff0; + } + // paint top or bottom borders depending on notes direction &:not(.reversed) .note, &:not(.reversed) .discussion { @@ -213,7 +225,17 @@ ul.notes { .reply-btn { @extend .btn-primary; } -.file .content tr.line_holder:hover > td { background: $hover !important; } +.file .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; + } +} .file .content tr.line_holder:hover > td .line_note_link { opacity: 1.0; filter: alpha(opacity=100); @@ -274,6 +296,15 @@ ul.notes { } +.common-note-form { + margin: 0; + height: 140px; + background: #F9F9F9; + padding: 3px; + padding-bottom: 25px; + border: 1px solid #DDD; +} + .note-form-actions { background: #F9F9F9; @@ -281,8 +312,8 @@ ul.notes { padding: 0 5px; .note-form-option { - margin-top: 8px; - margin-left: 15px; + margin-top: 10px; + margin-left: 30px; @extend .pull-left; } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 607daf7a97e..c34cd23a9c9 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -20,3 +20,16 @@ border: 1px solid #ddd; } } + +.save-status-fixed { + position: fixed; + left: 20px; + bottom: 50px; +} + +.update-notifications { + margin-bottom: 0; + label { + margin-bottom: 0; + } +} diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index b6b1423e7d0..22618765eaa 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -1,38 +1,3 @@ -.projects { - @extend .row; - .activities { - } - - .side { - @extend .pull-right; - - .ui-box { - margin: 3px; - > .title { - padding: 2px 15px; - } - .nav-projects-tabs li { padding: 0; } - .well-list { - li { padding: 15px; } - .arrow { - float: right; - padding: 10px; - margin: 0; - } - .last_activity { - padding-top: 5px; - display: block; - span, strong { - font-size: 12px; - color: #666; - } - } - } - @extend .ui-box; - } - } -} - .new_project, .edit_project { .project_name_holder { @@ -68,14 +33,6 @@ } .project_clone_holder { - input[type="text"], - .btn { - font-size: 12px; - line-height: 18px; - margin: 0; - padding: 3px 10px; - } - input[type="text"] { @extend .monospace; border: 1px solid #BBB; diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index def440c7134..ffde6aa3fa6 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -97,7 +97,7 @@ .tree-btn-group { .btn { - margin-right:-3px; + margin-right: 0px; padding: 2px 10px; } } diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/sections/votes.scss index 4686f5422dc..6c81d9a81b8 100644 --- a/app/assets/stylesheets/sections/votes.scss +++ b/app/assets/stylesheets/sections/votes.scss @@ -35,9 +35,4 @@ .votes-inline { display: inline-block; margin: 0 8px; - .progress { - display: inline-block; - padding: 0 0 2px; - width: 45px; - } } diff --git a/app/assets/stylesheets/sections/wall.scss b/app/assets/stylesheets/sections/wall.scss index 598d9df8a6a..d6ac08fcf6f 100644 --- a/app/assets/stylesheets/sections/wall.scss +++ b/app/assets/stylesheets/sections/wall.scss @@ -14,12 +14,31 @@ .notes { margin-bottom: 160px; + background: #FFE; + border: 1px solid #EED; + + > li { + @extend .clearfix; + border-bottom: 1px solid #EED; + padding: 10px; + } .wall-author { color: #666; - margin-right: 10px; - border-right: 1px solid #CCC; - padding-right: 5px + float: left; + font-size: 12px; + width: 120px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .wall-text { + border-left: 1px solid #CCC; + margin-left: 10px; + padding-left: 10px; + float: left; + width: 75%; } .wall-file { diff --git a/app/assets/stylesheets/selects.scss b/app/assets/stylesheets/selects.scss index 07f7db75ffc..7abbe80bd39 100644 --- a/app/assets/stylesheets/selects.scss +++ b/app/assets/stylesheets/selects.scss @@ -1,3 +1,13 @@ +/* CHZN reset few styles */ +.chzn-container-single .chzn-single { + background: #FFF; + border: 1px solid #bbb; + box-shadow: none; +} +.chzn-container-active .chzn-single { + background: #fff; +} + .ajax-users-select { width: 400px; } diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss index 4e34e8b1b6b..b0ee94ef34a 100644 --- a/app/assets/stylesheets/themes/ui_basic.scss +++ b/app/assets/stylesheets/themes/ui_basic.scss @@ -5,7 +5,7 @@ */ .ui_basic { .separator { - background: white; + background: #F9F9F9; border-left: 1px solid #DDD; } } diff --git a/app/contexts/commit_load_context.rb b/app/contexts/commit_load_context.rb index 1f23f633af3..2cf5420d62d 100644 --- a/app/contexts/commit_load_context.rb +++ b/app/contexts/commit_load_context.rb @@ -12,7 +12,6 @@ class CommitLoadContext < BaseContext commit = project.repository.commit(params[:id]) if commit - commit = CommitDecorator.decorate(commit) line_notes = project.notes.for_commit_id(commit.id).inline result[:commit] = commit diff --git a/app/contexts/issues/bulk_update_context.rb b/app/contexts/issues/bulk_update_context.rb new file mode 100644 index 00000000000..73a3c353523 --- /dev/null +++ b/app/contexts/issues/bulk_update_context.rb @@ -0,0 +1,39 @@ +module Issues + class BulkUpdateContext < BaseContext + def execute + update_data = params[:update] + + issues_ids = update_data[:issues_ids].split(",") + milestone_id = update_data[:milestone_id] + assignee_id = update_data[:assignee_id] + status = update_data[:status] + + new_state = nil + + if status.present? + if status == 'closed' + new_state = :close + else + new_state = :reopen + end + end + + opts = {} + opts[:milestone_id] = milestone_id if milestone_id.present? + opts[:assignee_id] = assignee_id if assignee_id.present? + + issues = Issue.where(id: issues_ids).all + issues = issues.select { |issue| can?(current_user, :modify_issue, issue) } + + issues.each do |issue| + issue.update_attributes(opts) + issue.send new_state if new_state + end + + { + count: issues.count, + success: !issues.count.zero? + } + end + end +end diff --git a/app/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb new file mode 100644 index 00000000000..a35bddd6443 --- /dev/null +++ b/app/contexts/issues/list_context.rb @@ -0,0 +1,32 @@ +module Issues + class ListContext < BaseContext + include IssuesHelper + + attr_accessor :issues + + def execute + @issues = case params[:status] + when issues_filter[:all] then @project.issues + when issues_filter[:closed] then @project.issues.closed + when issues_filter[:to_me] then @project.issues.assigned(current_user) + when issues_filter[:by_me] then @project.issues.authored(current_user) + else @project.issues.opened + 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 + + @issues + end + end +end diff --git a/app/contexts/issues_bulk_update_context.rb b/app/contexts/issues_bulk_update_context.rb deleted file mode 100644 index 7c3c1d4f7c3..00000000000 --- a/app/contexts/issues_bulk_update_context.rb +++ /dev/null @@ -1,24 +0,0 @@ -class IssuesBulkUpdateContext < BaseContext - def execute - update_data = params[:update] - - issues_ids = update_data[:issues_ids].split(",") - milestone_id = update_data[:milestone_id] - assignee_id = update_data[:assignee_id] - status = update_data[:status] - - opts = {} - opts[:milestone_id] = milestone_id if milestone_id.present? - opts[:assignee_id] = assignee_id if assignee_id.present? - opts[:closed] = (status == "closed") if status.present? - - issues = Issue.where(id: issues_ids).all - issues = issues.select { |issue| can?(current_user, :modify_issue, issue) } - issues.each { |issue| issue.update_attributes(opts) } - { - count: issues.count, - success: !issues.count.zero? - } - end -end - diff --git a/app/contexts/issues_list_context.rb b/app/contexts/issues_list_context.rb deleted file mode 100644 index 0765b30c354..00000000000 --- a/app/contexts/issues_list_context.rb +++ /dev/null @@ -1,30 +0,0 @@ -class IssuesListContext < BaseContext - include IssuesHelper - - attr_accessor :issues - - def execute - @issues = case params[:status] - when issues_filter[:all] then @project.issues - when issues_filter[:closed] then @project.issues.closed - when issues_filter[:to_me] then @project.issues.assigned(current_user) - when issues_filter[:by_me] then @project.issues.authored(current_user) - else @project.issues.opened - 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 - - @issues - end -end diff --git a/app/contexts/notes/create_context.rb b/app/contexts/notes/create_context.rb index 1367dff4699..36ea76ff949 100644 --- a/app/contexts/notes/create_context.rb +++ b/app/contexts/notes/create_context.rb @@ -3,8 +3,6 @@ module Notes def execute note = project.notes.new(params[:note]) note.author = current_user - note.notify = params[:notify].present? - note.notify_author = params[:notify_author].present? note.save note end diff --git a/app/contexts/projects/create_context.rb b/app/contexts/projects/create_context.rb index 56c4e1c51da..2922564ba20 100644 --- a/app/contexts/projects/create_context.rb +++ b/app/contexts/projects/create_context.rb @@ -8,7 +8,18 @@ module Projects # get namespace id namespace_id = params.delete(:namespace_id) - @project = Project.new(params) + # Load default feature settings + default_features = Gitlab.config.gitlab.default_projects_features + + default_opts = { + issues_enabled: default_features.issues, + wiki_enabled: default_features.wiki, + wall_enabled: default_features.wall, + snippets_enabled: default_features.snippets, + merge_requests_enabled: default_features.merge_requests + } + + @project = Project.new(default_opts.merge(params)) # Parametrize path for project # @@ -32,10 +43,6 @@ module Projects @project.namespace_id = current_user.namespace_id end - # Disable less important features by default - @project.wall_enabled = false - @project.snippets_enabled = false - @project.creator = current_user # Import project from cloneable resource diff --git a/app/contexts/projects/fork_context.rb b/app/contexts/projects/fork_context.rb new file mode 100644 index 00000000000..fbc67220d5d --- /dev/null +++ b/app/contexts/projects/fork_context.rb @@ -0,0 +1,44 @@ +module Projects + class ForkContext < BaseContext + include Gitlab::ShellAdapter + + def initialize(project, user) + @from_project, @current_user = project, user + end + + def execute + project = @from_project.dup + project.name = @from_project.name + project.path = @from_project.path + project.namespace = current_user.namespace + project.creator = current_user + + # If the project cannot save, we do not want to trigger the project destroy + # as this can have the side effect of deleting a repo attached to an existing + # project with the same name and namespace + if project.valid? + begin + Project.transaction do + #First save the DB entries as they can be rolled back if the repo fork fails + project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id) + if project.save + project.users_projects.create(project_access: UsersProject::MASTER, user: current_user) + end + #Now fork the repo + unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path) + raise "forking failed in gitlab-shell" + end + project.ensure_satellite_exists + end + rescue => ex + project.errors.add(:base, "Fork transaction failed.") + project.destroy + end + else + project.errors.add(:base, "Invalid fork destination") + end + project + + end + end +end diff --git a/app/contexts/projects/transfer_context.rb b/app/contexts/projects/transfer_context.rb new file mode 100644 index 00000000000..aed396a5da5 --- /dev/null +++ b/app/contexts/projects/transfer_context.rb @@ -0,0 +1,27 @@ +module Projects + class TransferContext < BaseContext + def execute(role = :default) + namespace_id = params[:project].delete(:namespace_id) + allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin + + if allowed_transfer && namespace_id.present? + if namespace_id == Namespace.global_id + if project.namespace.present? + # Transfer to global namespace from anyone + project.transfer(nil) + end + elsif namespace_id.to_i != project.namespace_id + # Transfer to someone namespace + namespace = Namespace.find(namespace_id) + project.transfer(namespace) + end + end + + rescue ProjectTransferService::TransferError => ex + project.reload + project.errors.add(:namespace_id, ex.message) + false + end + end +end + diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb index e5d09b7df7f..40385fa65b0 100644 --- a/app/contexts/projects/update_context.rb +++ b/app/contexts/projects/update_context.rb @@ -1,24 +1,8 @@ module Projects class UpdateContext < BaseContext def execute(role = :default) - namespace_id = params[:project].delete(:namespace_id) + params[:project].delete(:namespace_id) params[:project].delete(:public) unless can?(current_user, :change_public_mode, project) - - allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin - - if allowed_transfer && namespace_id.present? - if namespace_id == Namespace.global_id - if project.namespace.present? - # Transfer to global namespace from anyone - project.transfer(nil) - end - elsif namespace_id.to_i != project.namespace_id - # Transfer to someone namespace - namespace = Namespace.find(namespace_id) - project.transfer(namespace) - end - end - project.update_attributes(params[:project], as: role) end end diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb index 9becb8d674f..22cda709f69 100644 --- a/app/contexts/search_context.rb +++ b/app/contexts/search_context.rb @@ -10,10 +10,19 @@ class SearchContext return result unless query.present? - result[:projects] = Project.where(id: project_ids).search(query).limit(10) - result[:merge_requests] = MergeRequest.where(project_id: project_ids).search(query).limit(10) - result[:issues] = Issue.where(project_id: project_ids).search(query).limit(10) - result[:wiki_pages] = Wiki.where(project_id: project_ids).search(query).limit(10) + projects = Project.where(id: project_ids) + result[:projects] = projects.search(query).limit(10) + + # Search inside singe project + project = projects.first if projects.length == 1 + + if params[:search_code].present? + result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo? + else + result[:merge_requests] = MergeRequest.where(project_id: project_ids).search(query).limit(10) + result[:issues] = Issue.where(project_id: project_ids).search(query).limit(10) + result[:wiki_pages] = [] + end result end @@ -22,8 +31,8 @@ class SearchContext projects: [], merge_requests: [], issues: [], - wiki_pages: [] + wiki_pages: [], + blobs: [] } end end - diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb new file mode 100644 index 00000000000..994e707965a --- /dev/null +++ b/app/controllers/admin/background_jobs_controller.rb @@ -0,0 +1,4 @@ +class Admin::BackgroundJobsController < Admin::ApplicationController + def show + end +end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 3c27b86180b..3c80b6503fa 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -2,8 +2,5 @@ class Admin::DashboardController < Admin::ApplicationController def index @projects = Project.order("created_at DESC").limit(10) @users = User.order("created_at DESC").limit(10) - - rescue Redis::InheritedError - @resque_accessible = false end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index f552fb595b8..df520bea773 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -69,7 +69,7 @@ class Admin::GroupsController < Admin::ApplicationController def project_teams_update @group.add_users_to_project_teams(params[:user_ids], params[:project_access]) - redirect_to [:admin, @group], notice: 'Users was successfully added.' + redirect_to [:admin, @group], notice: 'Users were successfully added.' end def destroy diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 8ae0bba9a2d..bbb80cbb839 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -19,34 +19,6 @@ class Admin::ProjectsController < Admin::ApplicationController @users = @users.all end - def edit - end - - def team_update - @project.team.add_users_ids(params[:user_ids], params[:project_access]) - - redirect_to [:admin, @project], notice: 'Project was successfully updated.' - end - - def update - project.creator = current_user unless project.creator - - status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin) - - if status - redirect_to [:admin, @project], notice: 'Project was successfully updated.' - else - render action: "edit" - end - end - - def destroy - @project.team.truncate - @project.destroy - - redirect_to admin_projects_path, notice: 'Project was successfully deleted.' - end - protected def project diff --git a/app/controllers/admin/resque_controller.rb b/app/controllers/admin/resque_controller.rb deleted file mode 100644 index 7d489ab4876..00000000000 --- a/app/controllers/admin/resque_controller.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Admin::ResqueController < Admin::ApplicationController - def show - end -end diff --git a/app/controllers/admin/teams/members_controller.rb b/app/controllers/admin/teams/members_controller.rb index e6469874419..590617f67c9 100644 --- a/app/controllers/admin/teams/members_controller.rb +++ b/app/controllers/admin/teams/members_controller.rb @@ -1,7 +1,6 @@ class Admin::Teams::MembersController < Admin::Teams::ApplicationController def new @users = User.potential_team_members(user_team) - @users = UserDecorator.decorate_collection @users end def create @@ -12,7 +11,7 @@ class Admin::Teams::MembersController < Admin::Teams::ApplicationController user_team.add_members(user_ids, access, is_admin) end - redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.' + redirect_to admin_team_path(user_team), notice: 'Members were successfully added into Team of users.' end def edit diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 43e6f09904f..185ad181b2a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -14,7 +14,7 @@ class Admin::UsersController < Admin::ApplicationController @not_in_projects = @not_in_projects.without_user(admin_user) if admin_user.authorized_projects.present? # Projects he already own or joined - @projects = admin_user.authorized_projects.where('projects.id in (?)', admin_user.authorized_projects.map(&:id)) + @projects = admin_user.authorized_projects end def team_update @@ -29,7 +29,7 @@ class Admin::UsersController < Admin::ApplicationController def new - @admin_user = User.new({ projects_limit: Gitlab.config.gitlab.default_projects_limit }, as: :admin) + @admin_user = User.new.with_defaults end def edit @@ -84,6 +84,8 @@ class Admin::UsersController < Admin::ApplicationController format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' } format.json { head :ok } else + # restore username to keep form action url. + admin_user.username = params[:id] format.html { render action: "edit" } format.json { render json: admin_user.errors, status: :unprocessable_entity } end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 32b1246601d..9bb86b80d1e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -32,7 +32,7 @@ class ApplicationController < ActionController::Base def reject_blocked! if current_user && current_user.blocked? sign_out current_user - flash[:alert] = "Your account is blocked. Retry when an admin unblock it." + flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it." redirect_to new_user_session_path end end @@ -40,7 +40,7 @@ class ApplicationController < ActionController::Base def after_sign_in_path_for resource if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked? sign_out resource - flash[:alert] = "Your account is blocked. Retry when an admin unblock it." + flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it." new_user_session_path else super @@ -69,7 +69,7 @@ class ApplicationController < ActionController::Base @project else @project = nil - render_404 + render_404 and return end end @@ -88,7 +88,7 @@ class ApplicationController < ActionController::Base end def authorize_code_access! - return access_denied! unless can?(current_user, :download_code, project) + return access_denied! unless can?(current_user, :download_code, project) or project.public? end def authorize_create_team! @@ -152,8 +152,9 @@ class ApplicationController < ActionController::Base def add_gon_variables gon.default_issues_tracker = Project.issues_tracker.default_value - gon.api_version = Gitlab::API.version + gon.api_version = API::API.version gon.api_token = current_user.private_token if current_user gon.gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url + gon.relative_url_root = Gitlab.config.gitlab.relative_url_root end end diff --git a/app/controllers/blame_controller.rb b/app/controllers/blame_controller.rb index 37d7245ccb4..c950af56e26 100644 --- a/app/controllers/blame_controller.rb +++ b/app/controllers/blame_controller.rb @@ -7,10 +7,8 @@ class BlameController < ProjectResourceController before_filter :authorize_code_access! before_filter :require_non_empty_project - before_filter :assign_ref_vars - def show - @repo = @project.repo - @blame = Grit::Blob.blame(@repo, @commit.id, @path) + @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + @blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path) end end diff --git a/app/controllers/blob_controller.rb b/app/controllers/blob_controller.rb index d4a45d9508e..3547dfe2323 100644 --- a/app/controllers/blob_controller.rb +++ b/app/controllers/blob_controller.rb @@ -7,18 +7,7 @@ class BlobController < ProjectResourceController before_filter :authorize_code_access! before_filter :require_non_empty_project - before_filter :assign_ref_vars - def show - if @tree.is_blob? - send_data( - @tree.data, - type: @tree.mime_type, - disposition: 'inline', - filename: @tree.name - ) - else - not_found! - end + @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) end end diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb index 9dc0d96883e..cde1f459d76 100644 --- a/app/controllers/commits_controller.rb +++ b/app/controllers/commits_controller.rb @@ -13,7 +13,6 @@ class CommitsController < ProjectResourceController @limit, @offset = (params[:limit] || 40), (params[:offset] || 0) @commits = @repo.commits(@ref, @path, @limit, @offset) - @commits = CommitDecorator.decorate_collection(@commits) respond_to do |format| format.html # index.html.erb diff --git a/app/controllers/compare_controller.rb b/app/controllers/compare_controller.rb index bd3f1115173..750e9c2380e 100644 --- a/app/controllers/compare_controller.rb +++ b/app/controllers/compare_controller.rb @@ -8,15 +8,13 @@ class CompareController < ProjectResourceController end def show - result = Commit.compare(project, params[:from], params[:to]) + compare = Gitlab::Git::Compare.new(project.repository, params[:from], params[:to]) - @commits = result[:commits] - @commit = result[:commit] - @diffs = result[:diffs] - @refs_are_same = result[:same] + @commits = compare.commits + @commit = compare.commit + @diffs = compare.diffs + @refs_are_same = compare.same @line_notes = [] - - @commits = CommitDecorator.decorate_collection(@commits) end def create diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 91a67985710..b74c22b1547 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -34,8 +34,11 @@ class DashboardController < ApplicationController @projects end + @projects = @projects.tagged_with(params[:label]) if params[:label].present? @projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.page(params[:page]).per(30) + + @labels = Project.where(id: @projects.map(&:id)).tags_on(:labels) end # Get authored or assigned open merge requests @@ -65,7 +68,7 @@ class DashboardController < ApplicationController end def event_filter - filters = cookies['event_filter'].split(',') if cookies['event_filter'] + filters = cookies['event_filter'].split(',') if cookies['event_filter'].present? @event_filter ||= EventFilter.new(filters) end end diff --git a/app/controllers/deploy_keys_controller.rb b/app/controllers/deploy_keys_controller.rb index a89ebbcb8d5..1c7e4c1b37a 100644 --- a/app/controllers/deploy_keys_controller.rb +++ b/app/controllers/deploy_keys_controller.rb @@ -5,7 +5,8 @@ class DeployKeysController < ProjectResourceController before_filter :authorize_admin_project! def index - @keys = @project.deploy_keys.all + @enabled_keys = @project.deploy_keys.all + @available_keys = available_keys - @enabled_keys end def show @@ -19,8 +20,9 @@ class DeployKeysController < ProjectResourceController end def create - @key = @project.deploy_keys.new(params[:key]) - if @key.save + @key = DeployKey.new(params[:deploy_key]) + + if @key.valid? && @project.deploy_keys << @key redirect_to project_deploy_keys_path(@project) else render "new" @@ -36,4 +38,22 @@ class DeployKeysController < ProjectResourceController format.js { render nothing: true } end end + + def enable + project.deploy_keys << available_keys.find(params[:id]) + + redirect_to project_deploy_keys_path(@project) + end + + def disable + @project.deploy_keys_projects.where(deploy_key_id: params[:id]).last.destroy + + redirect_to project_deploy_keys_path(@project) + end + + protected + + def available_keys + @available_keys ||= current_user.owned_deploy_keys + end end diff --git a/app/controllers/edit_tree_controller.rb b/app/controllers/edit_tree_controller.rb new file mode 100644 index 00000000000..9ed7a2143e4 --- /dev/null +++ b/app/controllers/edit_tree_controller.rb @@ -0,0 +1,49 @@ +# Controller for edit a repository's file +class EditTreeController < ProjectResourceController + include ExtractsPath + + # Authorize + before_filter :authorize_read_project! + before_filter :authorize_code_access! + before_filter :require_non_empty_project + + before_filter :edit_requirements, only: [:show, :update] + + def show + @last_commit = @project.repository.last_commit_for(@ref, @path).sha + end + + def update + edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path) + updated_successfully = edit_file_action.commit!( + params[:content], + params[:commit_message], + params[:last_commit] + ) + + if updated_successfully + redirect_to project_blob_path(@project, @id), notice: "Your changes have been successfully commited" + else + flash[:notice] = "Your changes could not be commited, because the file has been changed" + render :show + end + end + + private + + def edit_requirements + @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + + unless @blob.exists? && @blob.text? + redirect_to project_blob_path(@project, @id), notice: "You can only edit text files" + end + + allowed = if project.protected_branch? @ref + can?(current_user, :push_code_to_protected_branches, project) + else + can?(current_user, :push_code, project) + end + + return access_denied! unless allowed + end +end diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index 3cd2e77322c..bf30de565ed 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -1,12 +1,16 @@ class FilesController < ApplicationController def download note = Note.find(params[:id]) + uploader = note.attachment - if can?(current_user, :read_project, note.project) - uploader = note.attachment - send_file uploader.file.path, disposition: 'attachment' + if uploader.file_storage? + if can?(current_user, :read_project, note.project) + send_file uploader.file.path, disposition: 'attachment' + else + not_found! + end else - not_found! + redirect_to uploader.url end end end diff --git a/app/controllers/graph_controller.rb b/app/controllers/graph_controller.rb index b4bf9565112..c79ed5ca3cc 100644 --- a/app/controllers/graph_controller.rb +++ b/app/controllers/graph_controller.rb @@ -8,21 +8,15 @@ class GraphController < ProjectResourceController before_filter :require_non_empty_project def show - if params.has_key?(:q) - if params[:q].blank? - redirect_to project_graph_path(@project, params[:id]) - return - end - - @q = params[:q] - @commit = @project.repository.commit(@q) || @commit + if @options[:q] + @commit = @project.repository.commit(@options[:q]) || @commit end respond_to do |format| format.html format.json do - @graph = Network::Graph.new(project, @ref, @commit) + @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref]) end end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index bdf3567fef2..8976262f4f7 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -73,7 +73,7 @@ class GroupsController < ApplicationController def team_members @group.add_users_to_project_teams(params[:user_ids], params[:project_access]) - redirect_to people_group_path(@group), notice: 'Users was successfully added.' + redirect_to people_group_path(@group), notice: 'Users were successfully added.' end def edit diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 9917d198cbf..ba92ba2bdae 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -14,9 +14,18 @@ class IssuesController < ProjectResourceController respond_to :js, :html def index + terms = params['issue_search'] + @issues = issues_filtered + @issues = @issues.where("title LIKE ?", "%#{terms}%") if terms.present? @issues = @issues.page(params[:page]).per(20) + + assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] + + @assignee = @project.users.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? + respond_to do |format| format.html # index.html.erb format.js @@ -76,30 +85,8 @@ class IssuesController < ProjectResourceController end end - def sort - return render_404 unless can?(current_user, :admin_issue, @project) - - @issues = @project.issues.where(id: params['issue']) - @issues.each do |issue| - issue.position = params['issue'].index(issue.id.to_s) + 1 - issue.save - end - - render nothing: true - end - - def search - terms = params['terms'] - - @issues = issues_filtered - @issues = @issues.where("title LIKE ?", "%#{terms}%") unless terms.blank? - @issues = @issues.page(params[:page]).per(100) - - render partial: 'issues' - end - def bulk_update - result = IssuesBulkUpdateContext.new(project, current_user, params).execute + result = Issues::BulkUpdateContext.new(project, current_user, params).execute redirect_to :back, notice: "#{result[:count]} issues updated" end @@ -122,6 +109,6 @@ class IssuesController < ProjectResourceController end def issues_filtered - @issues = IssuesListContext.new(project, current_user, params).execute + @issues = Issues::ListContext.new(project, current_user, params).execute end end diff --git a/app/controllers/labels_controller.rb b/app/controllers/labels_controller.rb index 999351e22df..0e78cecf4d1 100644 --- a/app/controllers/labels_controller.rb +++ b/app/controllers/labels_controller.rb @@ -7,7 +7,13 @@ class LabelsController < ProjectResourceController respond_to :js, :html def index - @labels = @project.issues_labels.order('count DESC') + @labels = @project.issues_labels + end + + def generate + Gitlab::IssuesLabels.generate(@project) + + redirect_to project_labels_path(@project) end protected diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index 88e0df16409..17c0392c384 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -20,9 +20,6 @@ class MergeRequestsController < ProjectResourceController end def show - @target_type = :merge_request - @target_id = @merge_request.id - respond_to do |format| format.html format.js @@ -94,12 +91,10 @@ class MergeRequestsController < ProjectResourceController def branch_from @commit = @repository.commit(params[:ref]) - @commit = CommitDecorator.decorate(@commit) end def branch_to @commit = @repository.commit(params[:ref]) - @commit = CommitDecorator.decorate(@commit) end def ci_status @@ -129,11 +124,11 @@ class MergeRequestsController < ProjectResourceController def validates_merge_request # Show git not found page if target branch doesn't exist - return invalid_mr unless @project.repo.heads.map(&:name).include?(@merge_request.target_branch) + return invalid_mr unless @project.repository.branch_names.include?(@merge_request.target_branch) # Show git not found page if source branch doesn't exist # and there is no saved commits between source & target branch - return invalid_mr if !@project.repo.heads.map(&:name).include?(@merge_request.source_branch) && @merge_request.commits.blank? + return invalid_mr if !@project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank? end def define_show_vars @@ -143,10 +138,12 @@ class MergeRequestsController < ProjectResourceController # Get commits from repository # or from cache if already merged @commits = @merge_request.commits - @commits = CommitDecorator.decorate_collection(@commits) @allowed_to_merge = allowed_to_merge? @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge + + @target_type = :merge_request + @target_id = @merge_request.id end def allowed_to_merge? diff --git a/app/controllers/milestones_controller.rb b/app/controllers/milestones_controller.rb index cdac28c1bde..25647f97576 100644 --- a/app/controllers/milestones_controller.rb +++ b/app/controllers/milestones_controller.rb @@ -14,7 +14,7 @@ class MilestonesController < ProjectResourceController @milestones = case params[:f] when 'all'; @project.milestones.order("state, due_date DESC") when 'closed'; @project.milestones.closed.order("due_date DESC") - else @project.milestones.active.order("due_date ASC") + else @project.milestones.active.order("due_date DESC") end @milestones = @milestones.includes(:project) @@ -32,7 +32,7 @@ class MilestonesController < ProjectResourceController def show @issues = @milestone.issues - @users = UserDecorator.decorate_collection(@milestone.participants) + @users = @milestone.participants.uniq @merge_requests = @milestone.merge_requests respond_to do |format| diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index 000c7bbb641..15ca963f281 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -71,7 +71,6 @@ class NotesController < ProjectResourceController # Helps to distinguish e.g. commit notes in mr notes list def note_for_main_target?(note) - note.for_wall? || - (@target_type.camelize == note.noteable_type && !note.for_diff_line?) + (@target_type.camelize == note.noteable_type && !note.for_diff_line?) end end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb new file mode 100644 index 00000000000..4aa3172912f --- /dev/null +++ b/app/controllers/notifications_controller.rb @@ -0,0 +1,21 @@ +class NotificationsController < ApplicationController + layout 'profile' + + def show + @notification = current_user.notification + @users_projects = current_user.users_projects + end + + def update + type = params[:notification_type] + + @saved = if type == 'global' + current_user.notification_level = params[:notification_level] + current_user.save + else + users_project = current_user.users_projects.find(params[:notification_id]) + users_project.notification_level = params[:notification_level] + users_project.save + end + end +end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 6fa114a4194..686edd8af80 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -2,6 +2,9 @@ class ProfilesController < ApplicationController include ActionView::Helpers::SanitizeHelper before_filter :user + before_filter :authorize_change_password!, only: :update_password + before_filter :authorize_change_username!, only: :update_username + layout 'profile' def show @@ -53,9 +56,7 @@ class ProfilesController < ApplicationController end def update_username - if @user.can_change_username? - @user.update_attributes(username: params[:user][:username]) - end + @user.update_attributes(username: params[:user][:username]) respond_to do |format| format.js @@ -75,9 +76,17 @@ class ProfilesController < ApplicationController # validation for this fields %w(name skype linkedin twitter bio).each do |attr| value = user_attributes[attr] - user_attributes[attr] = sanitize(value) if value.present? + user_attributes[attr] = sanitize(strip_tags(value)) if value.present? end user_attributes end + + def authorize_change_password! + return render_404 if @user.ldap_user? + end + + def authorize_change_username! + return render_404 unless @user.can_change_username? + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f2718344a3d..e202ed3234e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -4,7 +4,7 @@ class ProjectsController < ProjectResourceController # Authorize before_filter :authorize_read_project!, except: [:index, :new, :create] - before_filter :authorize_admin_project!, only: [:edit, :update, :destroy] + before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer] before_filter :require_non_empty_project, only: [:blob, :tree, :graph] layout 'application', only: [:new, :create] @@ -33,22 +33,22 @@ class ProjectsController < ProjectResourceController end def update - status = ::Projects::UpdateContext.new(project, current_user, params).execute + status = ::Projects::UpdateContext.new(@project, current_user, params).execute respond_to do |format| if status flash[:notice] = 'Project was successfully updated.' - format.html { redirect_to edit_project_path(project), notice: 'Project was successfully updated.' } + format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' } format.js else format.html { render action: "edit" } format.js end end + end - rescue Project::TransferError => ex - @error = ex - render :update_failed + def transfer + ::Projects::TransferContext.new(project, current_user, params).execute end def show @@ -57,11 +57,11 @@ class ProjectsController < ProjectResourceController respond_to do |format| format.html do - if @project.repository && !@project.repository.empty? + if @project.empty_repo? + render "projects/empty" + else @last_push = current_user.recent_push(@project.id) render :show - else - render "projects/empty" end end format.js @@ -78,4 +78,31 @@ class ProjectsController < ProjectResourceController format.html { redirect_to root_path } end end + + def fork + @project = ::Projects::ForkContext.new(project, current_user).execute + + respond_to do |format| + format.html do + if @project.saved? && @project.forked? + redirect_to(@project, notice: 'Project was successfully forked.') + else + render action: "new" + end + end + format.js + end + end + + def autocomplete_sources + @suggestions = { + emojis: Emoji.names, + issues: @project.issues.select([:id, :title, :description]), + members: @project.users.select([:username, :name]).order(:username) + } + + respond_to do |format| + format.json { render :json => @suggestions } + end + end end diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index b929b23e68c..6c0b397b4c3 100644 --- a/app/controllers/public/projects_controller.rb +++ b/app/controllers/public/projects_controller.rb @@ -7,6 +7,7 @@ class Public::ProjectsController < ApplicationController def index @projects = Project.public_only + @projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) end end diff --git a/app/controllers/raw_controller.rb b/app/controllers/raw_controller.rb new file mode 100644 index 00000000000..18b401fe611 --- /dev/null +++ b/app/controllers/raw_controller.rb @@ -0,0 +1,25 @@ +# Controller for viewing a file's raw +class RawController < ProjectResourceController + include ExtractsPath + + # Authorize + before_filter :authorize_read_project! + before_filter :authorize_code_access! + before_filter :require_non_empty_project + + def show + @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) + + if @blob.exists? + send_data( + @blob.data, + type: @blob.mime_type, + disposition: 'inline', + filename: @blob.name + ) + else + not_found! + end + end +end + diff --git a/app/controllers/refs_controller.rb b/app/controllers/refs_controller.rb index 0e4dba3dc4b..e7def3984f8 100644 --- a/app/controllers/refs_controller.rb +++ b/app/controllers/refs_controller.rb @@ -1,22 +1,22 @@ class RefsController < ProjectResourceController + include ExtractsPath # Authorize before_filter :authorize_read_project! before_filter :authorize_code_access! before_filter :require_non_empty_project - before_filter :ref - before_filter :define_tree_vars, only: [:blob, :logs_tree] - def switch respond_to do |format| format.html do new_path = if params[:destination] == "tree" - project_tree_path(@project, (@ref + "/" + params[:path])) + project_tree_path(@project, (@id)) + elsif params[:destination] == "blob" + project_blob_path(@project, (@id)) elsif params[:destination] == "graph" - project_graph_path(@project, @ref) + project_graph_path(@project, @id, @options) else - project_commits_path(@project, @ref) + project_commits_path(@project, @id) end redirect_to new_path @@ -30,40 +30,14 @@ class RefsController < ProjectResourceController end def logs_tree - contents = @tree.contents + 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 = CommitDecorator.decorate(last_commit) { file_name: content.name, commit: last_commit } end end - - protected - - def define_tree_vars - params[:path] = nil if params[:path].blank? - - @repo = project.repository - @commit = @repo.commit(@ref) - @commit = CommitDecorator.decorate(@commit) - @tree = Tree.new(@commit.tree, @ref, params[:path]) - @tree = TreeDecorator.new(@tree) - @hex_path = Digest::SHA1.hexdigest(params[:path] || "") - - if params[:path] - @logs_path = logs_file_project_ref_path(@project, @ref, params[:path]) - else - @logs_path = logs_tree_project_ref_path(@project, @ref) - end - rescue - return render_404 - end - - def ref - @ref = params[:id] || params[:ref] - end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 507a5c206c6..194dfcd4122 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -16,8 +16,7 @@ class RegistrationsController < Devise::RegistrationsController def build_resource(hash=nil) super - self.resource.projects_limit = Gitlab.config.gitlab.default_projects_limit - self.resource + self.resource.with_defaults end private diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 229cb36949b..a7d393af82b 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -17,7 +17,7 @@ class RepositoriesController < ProjectResourceController end def stats - @stats = Gitlab::GitStats.new(@repository.raw, @repository.root_ref) + @stats = Gitlab::Git::Stats.new(@repository.raw, @repository.root_ref) @graph = @stats.graph end @@ -27,7 +27,9 @@ class RepositoriesController < ProjectResourceController end - file_path = @repository.archive_repo(params[:ref]) + storage_path = Rails.root.join("tmp", "repositories") + + file_path = @repository.archive_repo(params[:ref], storage_path) if file_path # Send file to user diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index bbd67df6c70..f5c3bb133ed 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -6,9 +6,11 @@ class SearchController < ApplicationController project_ids = current_user.authorized_projects.map(&:id) if group_id.present? - group_project_ids = Group.find(group_id).projects.map(&:id) + @group = Group.find(group_id) + group_project_ids = @group.projects.map(&:id) project_ids.select! { |id| group_project_ids.include?(id)} elsif project_id.present? + @project = Project.find(params[:project_id]) project_ids.select! { |id| id == project_id.to_i} end @@ -18,5 +20,7 @@ class SearchController < ApplicationController @merge_requests = result[:merge_requests] @issues = result[:issues] @wiki_pages = result[:wiki_pages] + @blobs = Kaminari.paginate_array(result[:blobs]).page(params[:page]).per(20) + @total_results = @projects.count + @merge_requests.count + @issues.count + @wiki_pages.count + @blobs.total_count end end diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb index 25a06501e07..fcfc4c84a91 100644 --- a/app/controllers/services_controller.rb +++ b/app/controllers/services_controller.rb @@ -1,25 +1,21 @@ class ServicesController < ProjectResourceController # Authorize before_filter :authorize_admin_project! + before_filter :service, only: [:edit, :update, :test] respond_to :html def index - @gitlab_ci_service = @project.gitlab_ci_service + @project.build_missing_services + @services = @project.services.reload end def edit - @service = @project.gitlab_ci_service - - # Create if missing - @service = @project.create_gitlab_ci_service unless @service end def update - @service = @project.gitlab_ci_service - if @service.update_attributes(params[:service]) - redirect_to edit_project_service_path(@project, :gitlab_ci) + redirect_to edit_project_service_path(@project, @service.to_param) else render 'edit' end @@ -28,9 +24,14 @@ class ServicesController < ProjectResourceController def test data = GitPushService.new.sample_data(project, current_user) - @service = project.gitlab_ci_service @service.execute(data) redirect_to :back end + + private + + def service + @service ||= @project.services.find { |service| service.to_param == params[:id] } + end end diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index ba55648adad..35aa315dac1 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -52,7 +52,7 @@ class TeamMembersController < ProjectResourceController status = @project.team.import(giver) notice = status ? "Succesfully imported" : "Import failed" - redirect_to project_team_members_path(project), notice: notice + redirect_to project_team_index_path(project), notice: notice end protected diff --git a/app/controllers/teams/members_controller.rb b/app/controllers/teams/members_controller.rb index f87d422f84e..8ef786d1a66 100644 --- a/app/controllers/teams/members_controller.rb +++ b/app/controllers/teams/members_controller.rb @@ -8,7 +8,6 @@ class Teams::MembersController < Teams::ApplicationController def new @users = User.potential_team_members(user_team) - @users = UserDecorator.decorate_collection @users end def create @@ -19,7 +18,7 @@ class Teams::MembersController < Teams::ApplicationController user_team.add_members(user_ids, access, is_admin) end - redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.' + redirect_to team_members_path(user_team), notice: 'Members were successfully added into Team of users.' end def edit diff --git a/app/controllers/tree_controller.rb b/app/controllers/tree_controller.rb index 2151bd7cbbd..24e1329f926 100644 --- a/app/controllers/tree_controller.rb +++ b/app/controllers/tree_controller.rb @@ -7,53 +7,11 @@ class TreeController < ProjectResourceController before_filter :authorize_code_access! before_filter :require_non_empty_project - before_filter :assign_ref_vars - before_filter :edit_requirements, only: [:edit, :update] - def show - @hex_path = Digest::SHA1.hexdigest(@path) - @logs_path = logs_file_project_ref_path(@project, @ref, @path) - respond_to do |format| format.html # Disable cache so browser history works format.js { no_cache_headers } end end - - def edit - @last_commit = @project.repository.last_commit_for(@ref, @path).sha - end - - def update - edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path) - updated_successfully = edit_file_action.commit!( - params[:content], - params[:commit_message], - params[:last_commit] - ) - - if updated_successfully - redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited" - else - flash[:notice] = "Your changes could not be commited, because the file has been changed" - render :edit - end - end - - private - - def edit_requirements - unless @tree.is_blob? && @tree.text? - redirect_to project_tree_path(@project, @id), notice: "You can only edit text files" - end - - allowed = if project.protected_branch? @ref - can?(current_user, :push_code_to_protected_branches, project) - else - can?(current_user, :push_code, project) - end - - return access_denied! unless allowed - end end diff --git a/app/controllers/wikis_controller.rb b/app/controllers/wikis_controller.rb index 940b1e97340..be9ae4f37a4 100644 --- a/app/controllers/wikis_controller.rb +++ b/app/controllers/wikis_controller.rb @@ -49,9 +49,9 @@ class WikisController < ProjectResourceController end def history - unless @wiki = @gollum_wiki.find_page(params[:id]) - redirect_to project_wiki_path(@project, :home), notice: "Page not found" - end + @wiki = @gollum_wiki.find_page(params[:id]) + + redirect_to(project_wiki_path(@project, :home), notice: "Page not found") unless @wiki end def destroy diff --git a/app/decorators/application_decorator.rb b/app/decorators/application_decorator.rb deleted file mode 100644 index b805b3479b8..00000000000 --- a/app/decorators/application_decorator.rb +++ /dev/null @@ -1,29 +0,0 @@ -class ApplicationDecorator < Draper::Decorator - delegate_all - # Lazy Helpers - # PRO: Call Rails helpers without the h. proxy - # ex: number_to_currency(model.price) - # CON: Add a bazillion methods into your decorator's namespace - # and probably sacrifice performance/memory - # - # Enable them by uncommenting this line: - # lazy_helpers - - # Shared Decorations - # Consider defining shared methods common to all your models. - # - # Example: standardize the formatting of timestamps - # - # def formatted_timestamp(time) - # h.content_tag :span, time.strftime("%a %m/%d/%y"), - # class: 'timestamp' - # end - # - # def created_at - # formatted_timestamp(model.created_at) - # end - # - # def updated_at - # formatted_timestamp(model.updated_at) - # end -end diff --git a/app/decorators/commit_decorator.rb b/app/decorators/commit_decorator.rb deleted file mode 100644 index 0337d8d43ce..00000000000 --- a/app/decorators/commit_decorator.rb +++ /dev/null @@ -1,93 +0,0 @@ -class CommitDecorator < ApplicationDecorator - decorates :commit - - # Returns a string describing the commit for use in a link title - # - # Example - # - # "Commit: Alex Denisov - Project git clone panel" - def link_title - "Commit: #{author_name} - #{title}" - end - - # Returns the commits title. - # - # Usually, the commit title is the first line of the commit message. - # In case this first line is longer than 80 characters, it is cut off - # after 70 characters and ellipses (`&hellp;`) are appended. - def title - title = safe_message - - return no_commit_message if title.blank? - - title_end = title.index(/\n/) - if (!title_end && title.length > 80) || (title_end && title_end > 80) - title[0..69] << "…".html_safe - else - title.split(/\n/, 2).first - end - end - - # Returns the commits description - # - # cut off, ellipses (`&hellp;`) are prepended to the commit message. - def description - description = safe_message - - title_end = description.index(/\n/) - if (!title_end && description.length > 80) || (title_end && title_end > 80) - "…".html_safe << description[70..-1] - else - description.split(/\n/, 2)[1].try(:chomp) - end - end - - # Returns a link to the commit author. If the author has a matching user and - # is a member of the current @project it will link to the team member page. - # Otherwise it will link to the author email as specified in the commit. - # - # options: - # avatar: true will prepend the avatar image - # size: size of the avatar image in px - def author_link(options = {}) - person_link(options.merge source: :author) - end - - # Just like #author_link but for the committer. - def committer_link(options = {}) - person_link(options.merge source: :committer) - end - - protected - - def no_commit_message - "--no commit message" - end - - # Private: Returns a link to a person. If the person has a matching user and - # is a member of the current @project it will link to the team member page. - # Otherwise it will link to the person email as specified in the commit. - # - # options: - # source: one of :author or :committer - # avatar: true will prepend the avatar image - # size: size of the avatar image in px - def person_link(options = {}) - source_name = send "#{options[:source]}_name".to_sym - source_email = send "#{options[:source]}_email".to_sym - text = if options[:avatar] - avatar = h.image_tag h.gravatar_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>} - else - source_name - end - - user = User.where('name like ? or email like ?', source_name, source_email).first - - if user.nil? - h.mail_to(source_email, text.html_safe, class: "commit-#{options[:source]}-link") - else - h.link_to(text.html_safe, h.user_path(user), class: "commit-#{options[:source]}-link") - end - end -end diff --git a/app/decorators/event_decorator.rb b/app/decorators/event_decorator.rb deleted file mode 100644 index 1b0ad0da28f..00000000000 --- a/app/decorators/event_decorator.rb +++ /dev/null @@ -1,44 +0,0 @@ -class EventDecorator < ApplicationDecorator - decorates :event - - def feed_title - if self.issue? - "#{self.author_name} #{self.action_name} issue ##{self.target_id}: #{self.issue_title} at #{self.project.name}" - elsif self.merge_request? - "#{self.author_name} #{self.action_name} MR ##{self.target_id}: #{self.merge_request_title} at #{self.project.name}" - elsif self.push? - "#{self.author_name} #{self.push_action_name} #{self.ref_type} #{self.ref_name} at #{self.project.name}" - elsif self.membership_changed? - "#{self.author_name} #{self.action_name} #{self.project.name}" - else - "" - end - end - - def feed_url - if self.issue? - h.project_issue_url(self.project, self.issue) - elsif self.merge_request? - h.project_merge_request_url(self.project, self.merge_request) - - elsif self.push? - if self.push_with_commits? - if self.commits_count > 1 - h.project_compare_url(self.project, :from => self.parent_commit.id, :to => self.last_commit.id) - else - h.project_commit_url(self.project, :id => self.last_commit.id) - end - else - h.project_commits_url(self.project, self.ref_name) - end - end - end - - def feed_summary - if self.issue? - h.render "events/event_issue", issue: self.issue - elsif self.push? - h.render "events/event_push", event: self - end - end -end diff --git a/app/decorators/tree_decorator.rb b/app/decorators/tree_decorator.rb deleted file mode 100644 index 0e760f97dee..00000000000 --- a/app/decorators/tree_decorator.rb +++ /dev/null @@ -1,33 +0,0 @@ -class TreeDecorator < ApplicationDecorator - decorates :tree - - def breadcrumbs(max_links = 2) - if path - part_path = "" - parts = path.split("\/") - - yield('..', nil) if parts.count > max_links - - parts.each do |part| - part_path = File.join(part_path, part) unless part_path.empty? - part_path = part if part_path.empty? - - next unless parts.last(2).include?(part) if parts.count > max_links - yield(part, h.tree_join(ref, part_path)) - end - end - end - - def up_dir? - path.present? - end - - def up_dir_path - file = File.join(path, "..") - h.tree_join(ref, file) - end - - def readme - @readme ||= contents.find { |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i } - end -end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb deleted file mode 100644 index af9c6a63e75..00000000000 --- a/app/decorators/user_decorator.rb +++ /dev/null @@ -1,11 +0,0 @@ -class UserDecorator < ApplicationDecorator - decorates :user - - def avatar_image size = 16 - h.image_tag h.gravatar_icon(self.email, size), class: "avatar #{"s#{size}"}", width: size - end - - def tm_of(project) - project.team_member_by_id(self.id) - end -end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f03039e4b75..488a55b2e6c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -2,6 +2,13 @@ require 'digest/md5' require 'uri' module ApplicationHelper + COLOR_SCHEMES = { + 1 => 'white', + 2 => 'black', + 3 => 'solarized-dark', + 4 => 'monokai', + } + COLOR_SCHEMES.default = 'white' # Check if a particular controller is the current one # @@ -37,7 +44,7 @@ module ApplicationHelper if !Gitlab.config.gravatar.enabled || user_email.blank? 'no_avatar.png' else - gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url + gravatar_url = request.ssl? || gitlab_config.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url user_email.strip! sprintf gravatar_url, hash: Digest::MD5.hexdigest(user_email.downcase), size: size end @@ -96,7 +103,7 @@ module ApplicationHelper ] project_nav = [] - if @project && @project.repository && @project.repository.root_ref + if @project && @project.repository.exists? && @project.repository.root_ref project_nav = [ { label: "#{simple_sanitize(@project.name_with_namespace)} - Issues", url: project_issues_path(@project) }, { label: "#{simple_sanitize(@project.name_with_namespace)} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, @@ -119,31 +126,29 @@ module ApplicationHelper Emoji.names.to_s end - def ldap_enable? - Devise.omniauth_providers.include?(:ldap) - end - def app_theme Gitlab::Theme.css_class_by_id(current_user.try(:theme_id)) end def user_color_scheme_class - case current_user.color_scheme_id - when 1 then 'white' - when 2 then 'black' - when 3 then 'solarized-dark' - else - 'white' - end + COLOR_SCHEMES[current_user.try(:color_scheme_id)] end + # Define whenever show last push event + # with suggestion to create MR def show_last_push_widget?(event) - event && - event.last_push_to_non_root? && - !event.rm_ref? && - event.project && - event.project.repository && - event.project.merge_requests_enabled + # Skip if event is not about added or modified non-master branch + return false unless event && event.last_push_to_non_root? && !event.rm_ref? + + project = event.project + + # Skip if project repo is empty or MR disabled + return false unless project && !project.empty_repo? && project.merge_requests_enabled + + # Skip if user already created appropriate MR + return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? + + true end def hexdigest(string) @@ -151,9 +156,8 @@ module ApplicationHelper end def project_last_activity project - activity = project.last_activity - if activity && activity.created_at - time_ago_in_words(activity.created_at) + " ago" + if project.last_activity_at + time_ago_in_words(project.last_activity_at) + " ago" else "Never" end @@ -181,4 +185,21 @@ module ApplicationHelper css_class << " multiselect" if opts[:multiple] hidden_field_tag(id, '', class: css_class) end + + def body_data_page + path = controller.controller_path.split('/') + namespace = path.first if path.second + + [namespace, controller.controller_name, controller.action_name].compact.join(":") + end + + # shortcut for gitlab config + def gitlab_config + Gitlab.config.gitlab + end + + # shortcut for gitlab extra config + def extra_config + Gitlab.config.extra + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index acdd48e04eb..95ca294cd2d 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -1,4 +1,20 @@ module CommitsHelper + # Returns a link to the commit author. If the author has a matching user and + # is a member of the current @project it will link to the team member page. + # Otherwise it will link to the author email as specified in the commit. + # + # options: + # avatar: true will prepend the avatar image + # size: size of the avatar image in px + def commit_author_link(commit, options = {}) + commit_person_link(commit, options.merge(source: :author)) + end + + # Just like #author_link but for the committer. + def commit_committer_link(commit, options = {}) + commit_person_link(commit, options.merge(source: :committer)) + end + def identification_type(line) if line[0] == "+" "new" @@ -93,9 +109,7 @@ module CommitsHelper end def commit_to_html commit - if commit.model - escape_javascript(render 'commits/commit', commit: commit) - end + escape_javascript(render 'commits/commit', commit: commit) end def diff_line_content(line) @@ -105,4 +119,58 @@ module CommitsHelper line end end + + # Breadcrumb links for a Project and, if applicable, a tree path + def commits_breadcrumbs + return unless @project && @ref + + # Add the root project link and the arrow icon + crumbs = content_tag(:li) do + content_tag(:span, nil, class: 'arrow') + + link_to(@project.name, project_commits_path(@project, @ref)) + end + + if @path + parts = @path.split('/') + + parts.each_with_index do |part, i| + crumbs += content_tag(:span, '/', class: 'divider') + crumbs += content_tag(:li) do + # The text is just the individual part, but the link needs all the parts before it + link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/'))) + end + end + end + + crumbs.html_safe + end + + protected + + # Private: Returns a link to a person. If the person has a matching user and + # is a member of the current @project it will link to the team member page. + # Otherwise it will link to the person email as specified in the commit. + # + # options: + # source: one of :author or :committer + # avatar: true will prepend the avatar image + # size: size of the avatar image in px + def commit_person_link(commit, options = {}) + source_name = commit.send "#{options[:source]}_name".to_sym + source_email = commit.send "#{options[:source]}_email".to_sym + text = if options[:avatar] + avatar = image_tag(gravatar_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>} + else + source_name + end + + user = User.where('name like ? or email like ?', source_name, source_email).first + + if user.nil? + mail_to(source_email, text.html_safe, class: "commit-#{options[:source]}-link") + else + link_to(text.html_safe, user_path(user), class: "commit-#{options[:source]}-link") + end + end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 38374e3394d..7155036eeed 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -42,4 +42,45 @@ module EventsHelper EventFilter.team => "icon-user", } end + + def event_feed_title(event) + if event.issue? + "#{event.author_name} #{event.action_name} issue ##{event.target_id}: #{event.issue_title} at #{event.project.name}" + elsif event.merge_request? + "#{event.author_name} #{event.action_name} MR ##{event.target_id}: #{event.merge_request_title} at #{event.project.name}" + elsif event.push? + "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project.name}" + elsif event.membership_changed? + "#{event.author_name} #{event.action_name} #{event.project.name}" + else + "" + end + end + + def event_feed_url(event) + if event.issue? + project_issue_url(event.project, event.issue) + elsif event.merge_request? + project_merge_request_url(event.project, event.merge_request) + + elsif event.push? + if event.push_with_commits? + if event.commits_count > 1 + project_compare_url(event.project, from: event.commit_from, to: event.commit_to) + else + project_commit_url(event.project, id: event.commit_to) + end + else + project_commits_url(event.project, event.ref_name) + end + end + end + + def event_feed_summary(event) + if event.issue? + render "events/event_issue", issue: event.issue + elsif event.push? + render "events/event_push", event: event + end + end end diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index 369330151f4..71a07d6cad1 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -1,6 +1,12 @@ module GraphHelper - def join_with_space(ary) - ary.collect{|r|r.name}.join(" ") unless ary.nil? + def get_refs(commit) + refs = "" + refs += commit.refs.collect{|r|r.name}.join(" ") if commit.refs + + # append note count + refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 + + refs end def parents_zip_spaces(parents, parent_spaces) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 54385117c26..dc5aa6e1fb6 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -11,10 +11,6 @@ module IssuesHelper classes end - def issue_tags - @project.issues.tag_counts_on(:labels).map(&:name) - end - # Returns an OpenStruct object suitable for use by <tt>options_from_collection_for_select</tt> # to allow filtering issues by an unassigned User or Milestone def unassigned_filter @@ -32,12 +28,6 @@ module IssuesHelper } end - def labels_autocomplete_source - labels = @project.issues_labels.order('count DESC') - labels = labels.map{ |l| { label: l.name, value: l.name } } - labels.to_json - end - def issues_active_milestones @project.milestones.active.order("id desc").all end @@ -48,19 +38,31 @@ module IssuesHelper if @project.used_default_issues_tracker? project_issues_filter_path(@project) else - url = Settings[:issues_tracker][@project.issues_tracker]["project_url"] + url = Gitlab.config.issues_tracker[@project.issues_tracker]["project_url"] url.gsub(':project_id', @project.id.to_s) .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) end end + def url_for_new_issue + return "" if @project.nil? + + if @project.used_default_issues_tracker? + url = new_project_issue_path project_id: @project + else + url = Gitlab.config.issues_tracker[@project.issues_tracker]["new_issue_url"] + url.gsub(':project_id', @project.id.to_s) + .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) + end + end + def url_for_issue(issue_id) return "" if @project.nil? if @project.used_default_issues_tracker? url = project_issue_url project_id: @project, id: issue_id else - url = Settings[:issues_tracker][@project.issues_tracker]["issues_url"] + url = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"] url.gsub(':id', issue_id.to_s) .gsub(':project_id', @project.id.to_s) .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) @@ -76,4 +78,15 @@ module IssuesHelper "" end end + + def project_issues_with_filter_path(project, opts) + default_opts = { + status: params[:status], + label_name: params[:label_name], + milestone_id: params[:milestone_id], + assignee_id: params[:assignee_id], + } + + project_issues_path(@project, default_opts.merge(opts)) + end end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb new file mode 100644 index 00000000000..9a6aea85ee9 --- /dev/null +++ b/app/helpers/labels_helper.rb @@ -0,0 +1,28 @@ +module LabelsHelper + def issue_label_names + @project.issues_labels.map(&:name) + end + + def labels_autocomplete_source + labels = @project.issues_labels + labels = labels.map{ |l| { label: l.name, value: l.name } } + labels.to_json + end + + def label_css_class(name) + klass = Gitlab::IssuesLabels + + case name + when *klass.warning_labels + 'label-warning' + when *klass.neutral_labels + 'label-inverse' + when *klass.positive_labels + 'label-success' + when *klass.important_labels + 'label-important' + else + 'label-info' + end + end +end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 155d03d1147..05ffec066f8 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -11,7 +11,7 @@ module MergeRequestsHelper end def mr_css_classes mr - classes = "merge_request" + classes = "merge-request" classes << " closed" if mr.closed? classes << " merged" if mr.merged? classes diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb new file mode 100644 index 00000000000..7342393a707 --- /dev/null +++ b/app/helpers/notifications_helper.rb @@ -0,0 +1,2 @@ +module NotificationsHelper +end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb new file mode 100644 index 00000000000..c0177dacbf8 --- /dev/null +++ b/app/helpers/oauth_helper.rb @@ -0,0 +1,19 @@ +module OauthHelper + def ldap_enabled? + Devise.omniauth_providers.include?(:ldap) + end + + def default_providers + [:twitter, :github, :google_oauth2, :ldap] + end + + def enabled_oauth_providers + Devise.omniauth_providers + end + + def enabled_social_providers + enabled_oauth_providers.select do |name| + [:twitter, :github, :google_oauth2].include?(name.to_sym) + end + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e6427ea84c0..9b142714980 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -25,7 +25,7 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(gravatar_icon(author.try(:email)), width: 16, class: "lil_av") if opts[:avatar] + author_html << image_tag(gravatar_icon(author.try(:email)), width: 16, class: "avatar avatar-inline s16") if opts[:avatar] # Build name span tag author_html << content_tag(:span, sanitize(author.name), class: 'author') @@ -44,4 +44,8 @@ module ProjectsHelper project.name end end + + def remove_project_message(project) + "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" + end end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index fab0085ba73..a8491dfe3ba 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -3,26 +3,20 @@ module TreeHelper # their corresponding partials # # contents - A Grit::Tree object for the current tree - def render_tree(contents) + def render_tree(tree) # Render Folders before Files/Submodules - folders, files = contents.partition { |v| v.kind_of?(Grit::Tree) } + folders, files, submodules = tree.trees, tree.blobs, tree.submodules tree = "" # Render folders if we have any tree += render partial: 'tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present? - files.each do |f| - html = if f.respond_to?(:url) - # Object is a Submodule - render partial: 'tree/submodule_item', object: f - else - # Object is a Blob - render partial: 'tree/tree_item', object: f, locals: {type: 'file'} - end + # Render files if we have any + tree += render partial: 'tree/blob_item', collection: files, locals: {type: 'file'} if files.present? - tree += html if html.present? - end + # Render submodules if we have any + tree += render partial: 'tree/submodule_item', collection: submodules if submodules.present? tree.html_safe end @@ -70,28 +64,29 @@ module TreeHelper end end - # Breadcrumb links for a Project and, if applicable, a tree path - def breadcrumbs - return unless @project && @ref + def tree_breadcrumbs(tree, max_links = 2) + if tree.path + part_path = "" + parts = tree.path.split("\/") - # Add the root project link and the arrow icon - crumbs = content_tag(:li) do - content_tag(:span, nil, class: 'arrow') + - link_to(@project.name, project_commits_path(@project, @ref)) - end + yield('..', nil) if parts.count > max_links - if @path - parts = @path.split('/') + parts.each do |part| + part_path = File.join(part_path, part) unless part_path.empty? + part_path = part if part_path.empty? - parts.each_with_index do |part, i| - crumbs += content_tag(:span, '/', class: 'divider') - crumbs += content_tag(:li) do - # The text is just the individual part, but the link needs all the parts before it - link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/'))) - end + next unless parts.last(2).include?(part) if parts.count > max_links + yield(part, tree_join(tree.ref, part_path)) end end + end + + def up_dir_path tree + file = File.join(tree.path, "..") + tree_join(tree.ref, file) + end - crumbs.html_safe + def leave_edit_message + "Leave edit mode?\nAll unsaved changes will be lost." end end diff --git a/app/helpers/user_teams_helper.rb b/app/helpers/user_teams_helper.rb index 2055bb3c8bc..8603ee434a8 100644 --- a/app/helpers/user_teams_helper.rb +++ b/app/helpers/user_teams_helper.rb @@ -22,5 +22,4 @@ module UserTeamsHelper def remove_from_user_team_message(team, member) "You are going to remove #{member.name} from #{team.name}. Are you sure?" end - end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 5b69886f9ce..79731b60f45 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -1,9 +1,9 @@ module Emails module Issues - def new_issue_email(issue_id) + def new_issue_email(recipient_id, issue_id) @issue = Issue.find(issue_id) @project = @issue.project - mail(to: @issue.assignee_email, subject: subject("new issue ##{@issue.id}", @issue.title)) + mail(to: recipient(recipient_id), subject: subject("new issue ##{@issue.id}", @issue.title)) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) @@ -13,6 +13,14 @@ module Emails mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title)) 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.id}", @issue.title)) + end + def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @issue = Issue.find issue_id @issue_status = status diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 35890460e05..806f1b01b72 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -1,9 +1,9 @@ module Emails module MergeRequests - def new_merge_request_email(merge_request_id) + def new_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - mail(to: @merge_request.assignee_email, subject: subject("new merge request !#{@merge_request.id}", @merge_request.title)) + mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @merge_request.title)) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) @@ -12,5 +12,18 @@ module Emails @project = @merge_request.project mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title)) end + + def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) + @merge_request = MergeRequest.find(merge_request_id) + @project = @merge_request.project + @updated_by = User.find updated_by_user_id + mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title)) + 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.id}", @merge_request.title)) + end end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index de51debfeb5..769b6e0b861 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -3,7 +3,6 @@ module Emails def note_commit_email(recipient_id, note_id) @note = Note.find(note_id) @commit = @note.noteable - @commit = CommitDecorator.decorate(@commit) @project = @note.project mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title)) end diff --git a/app/models/ability.rb b/app/models/ability.rb index b86a4b5a044..24b6ad182b4 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -38,11 +38,11 @@ class Ability elsif team.reporters.include?(user) rules << project_report_rules - elsif team.guests.include?(user) + elsif team.guests.include?(user) or project.public? rules << project_guest_rules end - if project.owner == user + if project.owner == user || user.admin? rules << project_admin_rules end @@ -68,6 +68,7 @@ class Ability def project_report_rules project_guest_rules + [ :download_code, + :fork_project :write_project_snippet ] end diff --git a/app/models/campfire_service.rb b/app/models/campfire_service.rb new file mode 100644 index 00000000000..6450ffe7318 --- /dev/null +++ b/app/models/campfire_service.rb @@ -0,0 +1,76 @@ +# == 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) +# + +class CampfireService < Service + attr_accessible :subdomain, :room + + validates :token, presence: true, if: :activated? + + def title + 'Campfire' + end + + def description + 'Simple web-based real-time group chat' + end + + def to_param + 'campfire' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' }, + { type: 'text', name: 'subdomain', placeholder: '' }, + { type: 'text', name: 'room', placeholder: '' } + ] + end + + def execute(push_data) + room = gate.find_room_by_name(self.room) + return true unless room + + message = build_message(push_data) + + room.speak(message) + end + + private + + def gate + @gate ||= Tinder::Campfire.new(subdomain, token: token) + end + + def build_message(push) + ref = push[:ref].gsub("refs/heads/", "") + before = push[:before] + after = push[:after] + + message = "" + message << "[#{project.name_with_namespace}] " + message << "#{push[:user_name]} " + + if before =~ /000000/ + message << "pushed new branch #{ref} \n" + elsif after =~ /000000/ + message << "removed branch #{ref} \n" + else + message << "pushed #{push[:total_commits_count]} commits to #{ref}. " + message << "#{project.web_url}/compare/#{before}...#{after}" + end + + message + end +end diff --git a/app/models/commit.rb b/app/models/commit.rb index 4d0c57b35fd..e3363350997 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -8,174 +8,70 @@ class Commit # DIFF_SAFE_SIZE = 100 - attr_accessor :commit, :head, :refs - - delegate :message, :authored_date, :committed_date, :parents, :sha, - :date, :committer, :author, :diffs, :tree, :id, :stats, - :to_patch, to: :commit - - class << self - def find_or_first(repo, commit_id = nil, root_ref) - commit = if commit_id - repo.commit(commit_id) - else - repo.commits(root_ref).first - end - - Commit.new(commit) if commit - end - - def fresh_commits(repo, n = 10) - commits = repo.heads.map do |h| - repo.commits(h.name, n).map { |c| Commit.new(c, h) } - end.flatten.uniq { |c| c.id } - - commits.sort! do |x, y| - y.committed_date <=> x.committed_date - end - - commits[0...n] - end - - def commits_with_refs(repo, n = 20) - commits = repo.branches.map { |ref| Commit.new(ref.commit, ref) } - - commits.sort! do |x, y| - y.committed_date <=> x.committed_date - end - - commits[0..n] - end - - def commits_since(repo, date) - commits = repo.heads.map do |h| - repo.log(h.name, nil, since: date).each { |c| Commit.new(c, h) } - end.flatten.uniq { |c| c.id } - - commits.sort! do |x, y| - y.committed_date <=> x.committed_date - end - - commits - end - - def commits(repo, ref, path = nil, limit = nil, offset = nil) - if path - repo.log(ref, path, max_count: limit, skip: offset) - elsif limit && offset - repo.commits(ref, limit, offset) - else - repo.commits(ref) - end.map{ |c| Commit.new(c) } - end - - def commits_between(repo, from, to) - repo.commits_between(from, to).map { |c| Commit.new(c) } - end - - def compare(project, from, to) - result = { - commits: [], - diffs: [], - commit: nil, - same: false - } - - return result unless from && to - - first = project.repository.commit(to.try(:strip)) - last = project.repository.commit(from.try(:strip)) - - if first && last - result[:same] = (first.id == last.id) - result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)} - - # Dont load diff for 100+ commits - result[:diffs] = if result[:commits].size > 100 - [] - else - project.repo.diff(last.id, first.id) rescue [] - end - - result[:commit] = Commit.new(first) - end - - result - end - end - - def initialize(raw_commit, head = nil) - raise "Nil as raw commit passed" unless raw_commit - - @commit = raw_commit - @head = head - end - - def short_id(length = 10) - id.to_s[0..length] + def self.decorate(commits) + commits.map { |c| self.new(c) } end - def safe_message - @safe_message ||= message - end - - def created_at - committed_date - end + attr_accessor :raw - def author_email - author.email - end + def initialize(raw_commit) + raise "Nil as raw commit passed" unless raw_commit - def author_name - author.name + @raw = raw_commit end - # Was this commit committed by a different person than the original author? - def different_committer? - author_name != committer_name || author_email != committer_email + def id + @raw.id end - def committer_name - committer.name + # Returns a string describing the commit for use in a link title + # + # Example + # + # "Commit: Alex Denisov - Project git clone panel" + def link_title + "Commit: #{author_name} - #{title}" end - def committer_email - committer.email + # Returns the commits title. + # + # Usually, the commit title is the first line of the commit message. + # In case this first line is longer than 80 characters, it is cut off + # after 70 characters and ellipses (`&hellp;`) are appended. + def title + title = safe_message + + return no_commit_message if title.blank? + + title_end = title.index(/\n/) + if (!title_end && title.length > 80) || (title_end && title_end > 80) + title[0..69] << "…".html_safe + else + title.split(/\n/, 2).first + end end - def prev_commit - @prev_commit ||= if parents.present? - Commit.new(parents.first) - else - nil - end + # Returns the commits description + # + # cut off, ellipses (`&hellp;`) are prepended to the commit message. + def description + description = safe_message + + title_end = description.index(/\n/) + if (!title_end && description.length > 80) || (title_end && title_end > 80) + "…".html_safe << description[70..-1] + else + description.split(/\n/, 2)[1].try(:chomp) + end end - def prev_commit_id - prev_commit.try :id + def method_missing(m, *args, &block) + @raw.send(m, *args, &block) end - # Shows the diff between the commit's parent and the commit. - # - # Cuts out the header and stats from #to_patch and returns only the diff. - def to_diff - # see Grit::Commit#show - patch = to_patch - - # discard lines before the diff - lines = patch.split("\n") - while !lines.first.start_with?("diff --git") do - lines.shift - end - lines.pop if lines.last =~ /^[\d.]+$/ # Git version - lines.pop if lines.last == "-- " # end of diff - lines.join("\n") - end + def respond_to?(method) + return true if @raw.respond_to?(method) - def has_zero_stats? - stats.total.zero? - rescue - true + super end end diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb new file mode 100644 index 00000000000..548ef4f9a27 --- /dev/null +++ b/app/models/deploy_key.rb @@ -0,0 +1,6 @@ +class DeployKey < Key + has_many :deploy_keys_projects, dependent: :destroy + has_many :projects, through: :deploy_keys_projects + + scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } +end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb new file mode 100644 index 00000000000..48350a3e4d9 --- /dev/null +++ b/app/models/deploy_keys_project.rb @@ -0,0 +1,11 @@ +class DeployKeysProject < ActiveRecord::Base + attr_accessible :key_id, :project_id + + belongs_to :project + belongs_to :deploy_key + + validates :deploy_key_id, presence: true + validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" } + + validates :project_id, presence: true +end diff --git a/app/models/event.rb b/app/models/event.rb index d39445c4ffe..4b75087dc2a 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -134,7 +134,7 @@ class Event < ActiveRecord::Base if closed? "closed" elsif merged? - "merged" + "accepted" elsif joined? 'joined' elsif left? @@ -200,7 +200,7 @@ class Event < ActiveRecord::Base # Max 20 commits from push DESC def commits - @commits ||= data[:commits].map { |commit| repository.commit(commit[:id]) }.reverse + @commits ||= data[:commits].reverse end def commits_count @@ -221,26 +221,8 @@ class Event < ActiveRecord::Base end end - def repository - project.repository - end - - def parent_commit - repository.commit(commit_from) - rescue => ex - nil - end - - def last_commit - repository.commit(commit_to) - rescue => ex - nil - end - def push_with_commits? - md_ref? && commits.any? && parent_commit && last_commit - rescue Grit::NoSuchPathError - false + md_ref? && commits.any? && commit_from && commit_to end def last_push_to_non_root? diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb new file mode 100644 index 00000000000..c3199ca264e --- /dev/null +++ b/app/models/forked_project_link.rb @@ -0,0 +1,8 @@ +class ForkedProjectLink < ActiveRecord::Base + attr_accessible :forked_from_project_id, :forked_to_project_id + + # Relations + belongs_to :forked_to_project, class_name: Project + belongs_to :forked_from_project, class_name: Project + +end diff --git a/app/models/gitlab_ci_service.rb b/app/models/gitlab_ci_service.rb index 4eb39c7ef4d..bdbe7724be0 100644 --- a/app/models/gitlab_ci_service.rb +++ b/app/models/gitlab_ci_service.rb @@ -46,4 +46,31 @@ class GitlabCiService < Service def build_page sha project_url + "/builds/#{sha}" end + + def builds_path + project_url + "?ref=" + project.default_branch + end + + def status_img_path + project_url + "/status.png?ref=" + project.default_branch + end + + def title + 'GitLab CI' + end + + def description + 'Continuous integration server from GitLab' + end + + def to_param + 'gitlab_ci' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' }, + { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'} + ] + end end diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb index a1ee3a0899a..d1edbab4533 100644 --- a/app/models/gollum_wiki.rb +++ b/app/models/gollum_wiki.rb @@ -16,6 +16,10 @@ class GollumWiki @user = user end + def path + @project.path + '.wiki' + end + def path_with_namespace @project.path_with_namespace + ".wiki" end @@ -47,12 +51,6 @@ class GollumWiki wiki.pages.map { |page| WikiPage.new(self, page, true) } end - # Returns the last 30 Commit objects across the entire - # repository. - def recent_history - Commit.fresh_commits(wiki.repo, 30) - end - # Finds a page within the repository based on a tile # or slug. # @@ -90,13 +88,17 @@ class GollumWiki private def create_repo! - if gitlab_shell.add_repository(path_with_namespace) + if init_repo(path_with_namespace) Gollum::Wiki.new(path_to_repo) else raise CouldNotCreateWikiError end end + def init_repo(path_with_namespace) + gitlab_shell.add_repository(path_with_namespace) + end + def commit_details(action, message = nil, title = nil) commit_message = message || default_message(action, title) @@ -114,5 +116,4 @@ class GollumWiki def path_to_repo @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") end - end diff --git a/app/models/group.rb b/app/models/group.rb index 5d838d2b9b0..17671c3defe 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -13,6 +13,7 @@ # class Group < Namespace + def add_users_to_project_teams(user_ids, project_access) UsersProject.add_users_into_projects( projects.map(&:id), diff --git a/app/models/hipchat_service.rb b/app/models/hipchat_service.rb new file mode 100644 index 00000000000..13429fa83b4 --- /dev/null +++ b/app/models/hipchat_service.rb @@ -0,0 +1,73 @@ +# == 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) +# + +class HipchatService < Service + attr_accessible :room + + validates :token, presence: true, if: :activated? + + def title + 'Hipchat' + end + + def description + 'Simple web-based real-time group chat' + end + + def to_param + 'hipchat' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' }, + { type: 'text', name: 'room', placeholder: '' } + ] + end + + def execute(push_data) + gate[room].send('Gitlab', create_message(push_data)) + end + + private + + def gate + @gate ||= HipChat::Client.new(token) + end + + def create_message(push) + ref = push[:ref].gsub("refs/heads/", "") + before = push[:before] + after = push[:after] + + message = "" + message << "#{push[:user_name]} " + if before =~ /000000/ + message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n" + 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 << "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 + message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)" + end + end + + message + end + +end
\ No newline at end of file diff --git a/app/models/issue.rb b/app/models/issue.rb index 54d9af7e67e..91dd6477b04 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -25,19 +25,9 @@ class Issue < ActiveRecord::Base acts_as_taggable_on :labels - class << self - def cared(user) - where('assignee_id = :user', user: user.id) - end - - def authored(user) - where('author_id = :user', user: user.id) - end - - def open_for(user) - opened.assigned(user) - end - end + scope :cared, ->(user) { where(assignee_id: user) } + scope :authored, ->(user) { where(author_id: user) } + scope :open_for, ->(user) { opened.assigned(user) } state_machine :state, initial: :opened do event :close do @@ -54,4 +44,7 @@ class Issue < ActiveRecord::Base state :closed end + + # Both open and reopened issues should be listed as opened + scope :opened, -> { with_state(:opened, :reopened) } end diff --git a/app/models/key.rb b/app/models/key.rb index 53eee511e13..185aef46e9e 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -16,20 +16,19 @@ require 'digest/md5' class Key < ActiveRecord::Base belongs_to :user - belongs_to :project attr_accessible :key, :title before_validation :strip_white_space validates :title, presence: true, length: { within: 0..255 } - validates :key, presence: true, length: { within: 0..5000 }, format: { :with => /ssh-.{3} / }, uniqueness: true + validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\Assh-.*\Z/ }, uniqueness: true validate :fingerprintable_key delegate :name, :email, to: :user, prefix: true def strip_white_space - self.key = self.key.strip unless self.key.blank? + self.key = key.strip unless key.blank? end def fingerprintable_key @@ -47,20 +46,12 @@ class Key < ActiveRecord::Base errors.add(:key, "can't be fingerprinted") if $?.exitstatus != 0 end - def is_deploy_key - !!project_id - end - # projects that has this key def projects - if is_deploy_key - [project] - else - user.authorized_projects - end + user.authorized_projects end def shell_id - "key-#{self.id}" + "key-#{id}" end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 9d42b1e1f32..b2ad1b76f1f 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -24,8 +24,6 @@ require Rails.root.join("lib/static_model") class MergeRequest < ActiveRecord::Base include Issuable - BROKEN_DIFF = "--broken-diff" - attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id, :author_id_of_changes, :state_event @@ -109,22 +107,18 @@ class MergeRequest < ActiveRecord::Base end def diffs - st_diffs || [] + load_diffs(st_diffs) || [] end def reloaded_diffs if opened? && unmerged_diffs.any? - self.st_diffs = unmerged_diffs + self.st_diffs = dump_diffs(unmerged_diffs) self.save end - - rescue Grit::Git::GitTimeout - self.st_diffs = [BROKEN_DIFF] - self.save end def broken_diffs? - diffs == [BROKEN_DIFF] + diffs == broken_diffs end def valid_diffs? @@ -132,11 +126,7 @@ class MergeRequest < ActiveRecord::Base end def unmerged_diffs - # Only show what is new in the source branch compared to the target branch, not the other way around. - # The linex below with merge_base is equivalent to diff with three dots (git diff branch1...branch2) - # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B" - common_commit = project.repo.git.native(:merge_base, {}, [target_branch, source_branch]).strip - diffs = project.repo.diff(common_commit, source_branch) + project.repository.diffs_between(source_branch, target_branch) end def last_commit @@ -152,7 +142,7 @@ class MergeRequest < ActiveRecord::Base end def commits - st_commits || [] + load_commits(st_commits || []) end def probably_merged? @@ -162,16 +152,15 @@ class MergeRequest < ActiveRecord::Base def reloaded_commits if opened? && unmerged_commits.any? - self.st_commits = unmerged_commits + self.st_commits = dump_commits(unmerged_commits) save end commits end def unmerged_commits - self.project.repo. + self.project.repository. commits_between(self.target_branch, self.source_branch). - map {|c| Commit.new(c)}. sort_by(&:created_at). reverse end @@ -213,4 +202,34 @@ class MergeRequest < ActiveRecord::Base 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 == broken_diffs + broken_diffs + elsif diffs.respond_to?(:map) + diffs.map(&:to_hash) + 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) } + end + end + + def broken_diffs + [Gitlab::Git::Diff::BROKEN_DIFF] + end end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2a9b9e4482c..023b8ddf04d 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -19,6 +19,7 @@ class Milestone < ActiveRecord::Base belongs_to :project has_many :issues has_many :merge_requests + has_many :participants, through: :issues, source: :assignee scope :active, -> { with_state(:active) } scope :closed, -> { with_state(:closed) } @@ -48,10 +49,6 @@ class Milestone < ActiveRecord::Base end end - def participants - User.where(id: issues.pluck(:assignee_id)) - end - def open_items_count self.issues.opened.count + self.merge_requests.opened.count end @@ -75,9 +72,9 @@ class Milestone < ActiveRecord::Base if due_date.past? "expired at #{due_date.stamp("Aug 21, 2011")}" else - "expires at #{due_date.stamp("Aug 21, 2011")}" + "expires at #{due_date.stamp("Aug 21, 2011")}" end - end + end end def can_be_closed? diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb index d0bc61c3bf7..3cd0c015fa0 100644 --- a/app/models/network/commit.rb +++ b/app/models/network/commit.rb @@ -8,7 +8,7 @@ module Network attr_accessor :time, :spaces, :parent_spaces def initialize(raw_commit, refs) - @commit = ::Commit.new(raw_commit) + @commit = Gitlab::Git::Commit.new(raw_commit) @time = -1 @spaces = [] @parent_spaces = [] diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 2957adbfc19..ffec4712e45 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -2,33 +2,42 @@ require "grit" module Network class Graph - attr_reader :days, :commits, :map + attr_reader :days, :commits, :map, :notes def self.max_count @max_count ||= 650 end - def initialize project, ref, commit + def initialize project, ref, commit, filter_ref @project = project @ref = ref @commit = commit + @filter_ref = filter_ref @repo = project.repo @commits = collect_commits @days = index_commits + @notes = collect_notes end protected + def collect_notes + h = Hash.new(0) + @project.notes.where('noteable_type = ?' ,"Commit").group('notes.commit_id').select('notes.commit_id, count(notes.id) as note_count').each do |item| + h[item["commit_id"]] = item["note_count"] + end + h + end + # Get commits from repository # def collect_commits refs_cache = build_refs_cache - find_commits(count_to_display_commit_in_center) - .map do |commit| - # Decorate with app/model/network/commit.rb - Network::Commit.new(commit, refs_cache[commit.id]) + find_commits(count_to_display_commit_in_center).map do |commit| + # Decorate with app/model/network/commit.rb + Network::Commit.new(commit, refs_cache[commit.id]) end end @@ -93,15 +102,15 @@ module Network end def find_commits(skip = 0) - Grit::Commit.find_all( - @repo, - nil, - { - date_order: true, - max_count: self.class.max_count, - skip: skip - } - ) + opts = { + date_order: true, + max_count: self.class.max_count, + skip: skip + } + + ref = @ref if @filter_ref + + Grit::Commit.find_all(@repo, ref, opts) end def commits_sort_by_ref @@ -184,7 +193,7 @@ module Network l.spaces << space # Also add space to parent l.parents(@map).each do |parent| - if parent.space > 0 + if 0 < parent.space && parent.space < space parent.spaces << space end end diff --git a/app/models/note.rb b/app/models/note.rb index 2f3b059918a..9a3481faaaa 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -22,9 +22,6 @@ class Note < ActiveRecord::Base attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, :attachment, :line_code, :commit_id - attr_accessor :notify - attr_accessor :notify_author - belongs_to :project belongs_to :noteable, polymorphic: true belongs_to :author, class_name: "User" @@ -44,7 +41,7 @@ class Note < ActiveRecord::Base # Scopes scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } scope :inline, -> { where("line_code IS NOT NULL") } - scope :not_inline, -> { where("line_code IS NULL") } + scope :not_inline, -> { where(line_code: [nil, '']) } scope :common, ->{ where(noteable_type: ["", nil]) } scope :fresh, ->{ order("created_at ASC, id ASC") } @@ -71,8 +68,8 @@ class Note < ActiveRecord::Base def diff if noteable.diffs.present? noteable.diffs.select do |d| - if d.b_path - Digest::SHA1.hexdigest(d.b_path) == diff_file_index + if d.new_path + Digest::SHA1.hexdigest(d.new_path) == diff_file_index end end.first end @@ -83,7 +80,7 @@ class Note < ActiveRecord::Base end def diff_file_name - diff.b_path + diff.new_path end def diff_new_line @@ -91,7 +88,7 @@ class Note < ActiveRecord::Base end def discussion_id - @discussion_id ||= [:discussion, noteable_type.try(:underscore), noteable_id, line_code].join("-").to_sym + @discussion_id ||= [:discussion, noteable_type.try(:underscore), noteable_id || commit_id, line_code].join("-").to_sym end # Returns true if this is a downvote note, @@ -143,14 +140,6 @@ class Note < ActiveRecord::Base nil end - def notify - @notify ||= false - end - - def notify_author - @notify_author ||= false - end - # Returns true if this is an upvote note, # otherwise false is returned def upvote? diff --git a/app/models/notification.rb b/app/models/notification.rb new file mode 100644 index 00000000000..ff6a18d6a51 --- /dev/null +++ b/app/models/notification.rb @@ -0,0 +1,39 @@ +class Notification + # + # Notification levels + # + N_DISABLED = 0 + N_PARTICIPATING = 1 + N_WATCH = 2 + N_GLOBAL = 3 + + 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] + end + + def initialize(target) + @target = target + end + + def disabled? + target.notification_level == N_DISABLED + end + + def participating? + target.notification_level == N_PARTICIPATING + end + + def watch? + target.notification_level == N_WATCH + end + + def global? + target.notification_level == N_GLOBAL + end +end diff --git a/app/models/project.rb b/app/models/project.rb index a0f014a15ab..8cb290f6601 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -18,6 +18,8 @@ # public :boolean default(FALSE), not null # issues_tracker :string(255) default("gitlab"), not null # issues_tracker_id :string(255) +# snippets_enabled :boolean default(TRUE), not null +# last_activity_at :datetime # require "grit" @@ -26,14 +28,14 @@ class Project < ActiveRecord::Base include Gitlab::ShellAdapter extend Enumerize - class TransferError < StandardError; end - - attr_accessible :name, :path, :description, :default_branch, :issues_tracker, + attr_accessible :name, :path, :description, :default_branch, :issues_tracker, :label_list, :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, - :wiki_enabled, :public, :import_url, as: [:default, :admin] + :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin] attr_accessible :namespace_id, :creator_id, as: :admin + acts_as_taggable_on :labels, :issues_default_labels + attr_accessor :import_url # Relations @@ -43,7 +45,12 @@ class Project < ActiveRecord::Base has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' has_one :gitlab_ci_service, dependent: :destroy + has_one :campfire_service, dependent: :destroy + has_one :hipchat_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 + has_many :services, dependent: :destroy has_many :events, dependent: :destroy has_many :merge_requests, dependent: :destroy has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC" @@ -51,9 +58,7 @@ class Project < ActiveRecord::Base has_many :users_projects, dependent: :destroy has_many :notes, dependent: :destroy has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" - has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id" has_many :hooks, dependent: :destroy, class_name: "ProjectHook" - has_many :wikis, dependent: :destroy has_many :protected_branches, dependent: :destroy has_many :user_team_project_relationships, dependent: :destroy @@ -62,6 +67,9 @@ class Project < ActiveRecord::Base has_many :user_team_user_relationships, through: :user_teams has_many :user_teams_members, through: :user_team_user_relationships + has_many :deploy_keys_projects, dependent: :destroy + has_many :deploy_keys, through: :deploy_keys_projects + delegate :name, to: :owner, allow_nil: true, prefix: true # Validations @@ -87,17 +95,18 @@ class Project < ActiveRecord::Base validate :check_limit, :repo_name # Scopes - scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } - scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } - scope :without_team, ->(team) { team.projects.present? ? where("id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped } - scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) } + scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } + scope :without_team, ->(team) { team.projects.present? ? where("projects.id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped } + scope :not_in_group, ->(group) { where("projects.id NOT IN (:ids)", ids: group.project_ids ) } + scope :in_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) } scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } - scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } + scope :in_group_namespace, -> { joins(:group) } + scope :sorted_by_activity, -> { order("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(public: true) } - enumerize :issues_tracker, :in => (Gitlab.config.issues_tracker.keys).append(:gitlab), :default => :gitlab + enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab class << self def abandoned @@ -142,13 +151,7 @@ class Project < ActiveRecord::Base end def repository - if path - @repository ||= Repository.new(path_with_namespace, default_branch) - else - nil - end - rescue Grit::NoSuchPathError - nil + @repository ||= Repository.new(path_with_namespace, default_branch) end def saved? @@ -196,7 +199,7 @@ class Project < ActiveRecord::Base end def last_activity_date - last_event.try(:created_at) || updated_at + last_activity_at || updated_at end def project_id @@ -204,7 +207,7 @@ class Project < ActiveRecord::Base end def issues_labels - issues.tag_counts_on(:labels) + @issues_labels ||= (issues_default_labels + issues.tags_on(:labels)).uniq.sort_by(&:name) end def issue_exists?(issue_id) @@ -223,8 +226,18 @@ class Project < ActiveRecord::Base self.issues_enabled && !self.used_default_issues_tracker? end - def services - [gitlab_ci_service].compact + def build_missing_services + available_services_names.each do |service_name| + service = services.find { |service| service.to_param == service_name } + + # If service is available but missing in db + # we should create an instance. Ex `create_gitlab_ci_service` + service = self.send :"create_#{service_name}_service" if service.nil? + end + end + + def available_services_names + %w(gitlab_ci campfire hipchat) end def gitlab_ci? @@ -333,14 +346,14 @@ class Project < ActiveRecord::Base end def valid_repo? - repo + repository.exists? rescue errors.add(:path, "Invalid repository path") false end def empty_repo? - !repository || repository.empty? + !repository.exists? || repository.empty? end def ensure_satellite_exists @@ -364,18 +377,25 @@ class Project < ActiveRecord::Base end def repo_exists? - @repo_exists ||= (repository && repository.branches.present?) + @repo_exists ||= repository.exists? rescue @repo_exists = false end def open_branches - if protected_branches.empty? - self.repo.heads - else - pnames = protected_branches.map(&:name) - self.repo.heads.reject { |h| pnames.include?(h.name) } - end.sort_by(&:name) + all_branches = repository.branches + + if protected_branches.present? + all_branches.reject! do |branch| + protected_branches_names.include?(branch.name) + end + end + + all_branches + end + + def protected_branches_names + @protected_branches_names ||= protected_branches.map(&:name) end def root_ref?(branch) @@ -397,6 +417,34 @@ class Project < ActiveRecord::Base # Check if current branch name is marked as protected in the system def protected_branch? branch_name - protected_branches.map(&:name).include?(branch_name) + protected_branches_names.include?(branch_name) + end + + def forked? + !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) + end + + def rename_repo + old_path_with_namespace = File.join(namespace_dir, path_was) + new_path_with_namespace = File.join(namespace_dir, path) + + if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) + # If repository moved successfully we need to remove old satellite + # and send update instructions to users. + # However we cannot allow rollback since we moved repository + # So we basically we mute exceptions in next actions + begin + gitlab_shell.rm_satellites(old_path_with_namespace) + send_move_instructions + rescue + # Returning false does not rolback after_* transaction but gives + # us information about failing some of tasks + false + end + else + # if we cannot move namespace directory we should rollback + # db changes in order to prevent out of sync between db and fs + raise Exception.new('repository cannot be renamed') + end end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 934c1a6e086..daf176576ad 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,169 +1,75 @@ class Repository - include Gitlab::Popen + attr_accessor :raw_repository - # Repository directory name with namespace direcotry - # Examples: - # gitlab/gitolite - # diaspora - # - attr_accessor :path_with_namespace - - # Grit repo object - attr_accessor :repo - - # Default branch in the repository - attr_accessor :root_ref - - def initialize(path_with_namespace, root_ref = 'master') - @root_ref = root_ref || "master" - @path_with_namespace = path_with_namespace - - # Init grit repo object - repo - end - - def raw - repo + def initialize(path_with_namespace, default_branch) + @raw_repository = Gitlab::Git::Repository.new(path_with_namespace, default_branch) + rescue Gitlab::Git::Repository::NoRepository + nil end - def path_to_repo - @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") + def exists? + raw_repository end - def repo - @repo ||= Grit::Repo.new(path_to_repo) - end - - def commit(commit_id = nil) - Commit.find_or_first(repo, commit_id, root_ref) - end - - def fresh_commits(n = 10) - Commit.fresh_commits(repo, n) - end - - def commits_with_refs(n = 20) - Commit.commits_with_refs(repo, n) + def empty? + raw_repository.empty? end - def commits_since(date) - Commit.commits_since(repo, date) + def commit(id = nil) + commit = raw_repository.commit(id) + commit = Commit.new(commit) if commit + commit end def commits(ref, path = nil, limit = nil, offset = nil) - Commit.commits(repo, ref, path, limit, offset) + commits = raw_repository.commits(ref, path, limit, offset) + commits = Commit.decorate(commits) if commits.present? + commits end - def last_commit_for(ref, path = nil) - commits(ref, path, 1).first + def commits_between(target, source) + commits = raw_repository.commits_between(target, source) + commits = Commit.decorate(commits) if commits.present? + commits end - def commits_between(from, to) - Commit.commits_between(repo, from, to) - end - - # Returns an Array of branch names def branch_names - repo.branches.collect(&:name).sort - end - - # Returns an Array of Branches - def branches - repo.branches.sort_by(&:name) + Rails.cache.fetch(cache_key(:branch_names)) do + raw_repository.branch_names + end end - # Returns an Array of tag names def tag_names - repo.tags.collect(&:name).sort.reverse - end - - # Returns an Array of Tags - def tags - repo.tags.sort_by(&:name).reverse - end - - # Returns an Array of branch and tag names - def ref_names - [branch_names + tag_names].flatten - end - - def heads - @heads ||= repo.heads - end - - def tree(fcommit, path = nil) - fcommit = commit if fcommit == :head - tree = fcommit.tree - path ? (tree / path) : tree - end - - def has_commits? - !!commit - rescue Grit::NoSuchPathError - false - end - - def empty? - !has_commits? - end - - # Discovers the default branch based on the repository's available branches - # - # - If no branches are present, returns nil - # - If one branch is present, returns its name - # - If two or more branches are present, returns the one that has a name - # matching root_ref (default_branch or 'master' if default_branch is nil) - def discover_default_branch - if branch_names.length == 0 - nil - elsif branch_names.length == 1 - branch_names.first - else - branch_names.select { |v| v == root_ref }.first + Rails.cache.fetch(cache_key(:tag_names)) do + raw_repository.tag_names end end - # Archive Project to .tar.gz - # - # Already packed repo archives stored at - # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz - # - def archive_repo(ref) - ref = ref || self.root_ref - commit = self.commit(ref) - return nil unless commit - - # Build file path - file_name = self.path_with_namespace.gsub("/","_") + "-" + commit.id.to_s + ".tar.gz" - storage_path = Rails.root.join("tmp", "repositories") - file_path = File.join(storage_path, self.path_with_namespace, file_name) - - # Put files into a directory before archiving - prefix = File.basename(self.path_with_namespace) + "/" - - # Create file if not exists - unless File.exists?(file_path) - FileUtils.mkdir_p File.dirname(file_path) - file = self.repo.archive_to_file(ref, prefix, file_path) - end - - file_path + def method_missing(m, *args, &block) + raw_repository.send(m, *args, &block) end # Return repo size in megabytes # Cached in redis def size Rails.cache.fetch(cache_key(:size)) do - size = popen('du -s', path_to_repo).first.strip.to_i - (size.to_f / 1024).round(2) + raw_repository.size end end def expire_cache Rails.cache.delete(cache_key(:size)) + Rails.cache.delete(cache_key(:branch_names)) + Rails.cache.delete(cache_key(:tag_names)) end def cache_key(type) "#{type}:#{path_with_namespace}" end + + def respond_to?(method) + return true if raw_repository.respond_to?(method) + + super + end end diff --git a/app/models/service.rb b/app/models/service.rb index d3486d29200..3e945aa898c 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -13,6 +13,8 @@ # project_url :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 @@ -24,4 +26,25 @@ class Service < ActiveRecord::Base def activated? active end + + def title + # implement inside child + end + + def description + # implement inside child + end + + def to_param + # implement inside child + end + + def fields + # implement inside child + [] + end + + def execute + # implement inside child + end end diff --git a/app/models/tree.rb b/app/models/tree.rb index 96395a42394..042050527c1 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -1,29 +1,17 @@ class Tree - include Linguist::BlobHelper + attr_accessor :raw - attr_accessor :path, :tree, :ref - - delegate :contents, :basename, :name, :data, :mime_type, - :mode, :size, :text?, :colorize, to: :tree - - def initialize(raw_tree, ref = nil, path = nil) - @ref, @path = ref, path - @tree = if path.present? - raw_tree / path - else - raw_tree - end + def initialize(repository, sha, ref = nil, path = nil) + @raw = Gitlab::Git::Tree.new(repository, sha, ref, path) end - def is_blob? - tree.is_a?(Grit::Blob) + def method_missing(m, *args, &block) + @raw.send(m, *args, &block) end - def invalid? - tree.nil? - end + def respond_to?(method) + return true if @raw.respond_to?(method) - def empty? - data.blank? + super end end diff --git a/app/models/user.rb b/app/models/user.rb index e6ab66f4bc7..0aed0ada757 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,6 +33,7 @@ # can_create_team :boolean default(TRUE), not null # state :string(255) # color_scheme_id :integer default(1), not null +# notification_level :integer default(1), not null # class User < ActiveRecord::Base @@ -46,16 +47,22 @@ class User < ActiveRecord::Base attr_accessor :force_random_password + # Virtual attribute for authenticating by either username or email + attr_accessor :login + + # Add login to attr_accessible + attr_accessible :login + + # # Relations # # Namespace for personal projects - has_one :namespace, - dependent: :destroy, - foreign_key: :owner_id, - class_name: "Namespace", - conditions: 'type IS NULL' + has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL' + + # Namespaces (owned groups and own namespace) + has_many :namespaces, foreign_key: :owner_id # Profile has_many :keys, dependent: :destroy @@ -64,15 +71,11 @@ class User < ActiveRecord::Base has_many :groups, class_name: "Group", foreign_key: :owner_id # Teams - has_many :own_teams, - class_name: "UserTeam", - foreign_key: :owner_id, - dependent: :destroy - - has_many :user_team_user_relationships, dependent: :destroy - has_many :user_teams, through: :user_team_user_relationships + has_many :own_teams, dependent: :destroy, class_name: "UserTeam", foreign_key: :owner_id + has_many :user_team_user_relationships, dependent: :destroy + has_many :user_teams, through: :user_team_user_relationships has_many :user_team_project_relationships, through: :user_teams - has_many :team_projects, through: :user_team_project_relationships + has_many :team_projects, through: :user_team_project_relationships # Projects has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" @@ -81,14 +84,14 @@ class User < ActiveRecord::Base has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" + has_many :recent_events, foreign_key: :author_id, class_name: "Event", order: "id DESC" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" - has_many :projects, through: :users_projects - has_many :recent_events, - class_name: "Event", - foreign_key: :author_id, - order: "id DESC" + has_many :personal_projects, through: :namespace, source: :projects + has_many :projects, through: :users_projects + has_many :own_projects, foreign_key: :creator_id, class_name: 'Project' + has_many :owned_projects, through: :namespaces, source: :projects # # Validations @@ -102,6 +105,7 @@ class User < ActiveRecord::Base format: { with: Gitlab::Regex.username_regex, message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } + validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } @@ -135,12 +139,25 @@ class User < ActiveRecord::Base scope :alphabetically, -> { order('name ASC') } scope :in_team, ->(team){ where(id: team.member_ids) } scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } + scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped } + scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') } + scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } # # Class methods # class << self + # Devise method overriden to allow sing in with email or username + def find_for_database_authentication(warden_conditions) + conditions = warden_conditions.dup + if login = conditions.delete(:login) + where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first + else + where(conditions).first + end + end + def filter filter_name case filter_name when "admins"; self.admins @@ -151,18 +168,6 @@ class User < ActiveRecord::Base end end - def not_in_project(project) - if project.users.present? - where("id not in (:ids)", ids: project.users.map(&:id) ) - else - scoped - end - end - - def without_projects - where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') - end - def create_from_omniauth(auth, ldap = false) gitlab_auth.create_from_omniauth(auth, ldap) end @@ -192,6 +197,18 @@ class User < ActiveRecord::Base username end + def with_defaults + tap do |u| + u.projects_limit = Gitlab.config.gitlab.default_projects_limit + u.can_create_group = Gitlab.config.gitlab.default_can_create_group + u.can_create_team = Gitlab.config.gitlab.default_can_create_team + end + end + + def notification + @notification ||= Notification.new(self) + end + def generate_password if self.force_random_password self.password = self.password_confirmation = Devise.friendly_token.first(8) @@ -205,56 +222,36 @@ class User < ActiveRecord::Base end end - # Namespaces user has access to - def namespaces - namespaces = [] - - # Add user account namespace - namespaces << self.namespace if self.namespace - - # Add groups you can manage - namespaces += groups.all - - namespaces - end - # Groups where user is an owner def owned_groups groups end + def owned_teams + own_teams + end + # Groups user has access to def authorized_groups - @authorized_groups ||= begin - groups = Group.where(id: self.authorized_projects.pluck(:namespace_id)).all - groups = groups + self.groups - groups.uniq - end + @group_ids ||= (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) + Group.where(id: @group_ids) end # Projects user has access to def authorized_projects - project_ids = users_projects.pluck(:project_id) - project_ids = project_ids | owned_projects.pluck(:id) - Project.where(id: project_ids) - end - - # Projects in user namespace - def personal_projects - Project.personal(self) + @project_ids ||= (owned_projects.pluck(:id) + projects.pluck(:id)).uniq + Project.where(id: @project_ids) end - # Projects where user is an owner - def owned_projects - Project.where("(projects.namespace_id IN (:namespaces)) OR - (projects.namespace_id IS NULL AND projects.creator_id = :user_id)", - namespaces: namespaces.map(&:id), user_id: self.id) + def authorized_teams + @team_ids ||= (user_teams.pluck(:id) + own_teams.pluck(:id)).uniq + UserTeam.where(id: @team_ids) end # Team membership in authorized projects def tm_in_authorized_projects - UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id) + UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id) end def is_admin? @@ -301,9 +298,13 @@ class User < ActiveRecord::Base MergeRequest.cared(self) end + def projects_limit_left + projects_limit - owned_projects.count + end + def projects_limit_percent return 100 if projects_limit.zero? - (personal_projects.count.to_f / projects_limit) * 100 + (owned_projects.count.to_f / projects_limit) * 100 end def recent_push project_id = nil @@ -320,29 +321,40 @@ class User < ActiveRecord::Base end def several_namespaces? - namespaces.size > 1 + namespaces.many? end def namespace_id namespace.try :id end - def authorized_teams - @authorized_teams ||= begin - ids = [] - ids << UserTeam.with_member(self).pluck('user_teams.id') - ids << UserTeam.created_by(self).pluck('user_teams.id') - ids.flatten + def name_with_username + "#{name} (#{username})" + end - UserTeam.where(id: ids) - end + def tm_of(project) + project.team_member_by_id(self.id) end - def owned_teams - UserTeam.where(owner_id: self.id) + def already_forked? project + !!fork_of(project) end - def name_with_username - "#{name} (#{username})" + def fork_of project + links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects) + + if links.any? + links.first.forked_to_project + else + nil + end + end + + def ldap_user? + extern_uid && provider == 'ldap' + end + + def owned_deploy_keys + DeployKey.in_projects(self.owned_projects).uniq end end diff --git a/app/models/user_team.rb b/app/models/user_team.rb index 5de2ac6ae9e..364ea0d7dd1 100644 --- a/app/models/user_team.rb +++ b/app/models/user_team.rb @@ -113,5 +113,4 @@ class UserTeam < ActiveRecord::Base def admin?(member) user_team_user_relationships.with_user(member).first.group_admin? end - end diff --git a/app/models/users_project.rb b/app/models/users_project.rb index 8051c0604d9..935ecede42c 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -2,12 +2,13 @@ # # Table name: users_projects # -# id :integer not null, primary key -# user_id :integer not null -# project_id :integer not null -# created_at :datetime not null -# updated_at :datetime not null -# project_access :integer default(0), not null +# id :integer not null, primary key +# user_id :integer not null +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# project_access :integer default(0), not null +# notification_level :integer default(3), not null # class UsersProject < ActiveRecord::Base @@ -29,6 +30,7 @@ class UsersProject < ActiveRecord::Base validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" } validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true validates :project, presence: true + validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true delegate :name, :username, :email, to: :user, prefix: true @@ -38,7 +40,7 @@ class UsersProject < ActiveRecord::Base scope :masters, -> { where(project_access: MASTER) } scope :in_project, ->(project) { where(project_id: project.id) } - scope :in_projects, ->(projects) { where(project_id: project_ids) } + scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) } scope :with_user, ->(user) { where(user_id: user.id) } class << self @@ -134,4 +136,8 @@ class UsersProject < ActiveRecord::Base def skip_git? !!@skip_git end + + def notification + @notification ||= Notification.new(self) + end end diff --git a/app/models/wiki.rb b/app/models/wiki.rb deleted file mode 100644 index 7f488ca7625..00000000000 --- a/app/models/wiki.rb +++ /dev/null @@ -1,55 +0,0 @@ -# == Schema Information -# -# Table name: wikis -# -# id :integer not null, primary key -# title :string(255) -# content :text -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# slug :string(255) -# user_id :integer -# - -class Wiki < ActiveRecord::Base - attr_accessible :title, :content, :slug - - belongs_to :project - belongs_to :user - has_many :notes, as: :noteable, dependent: :destroy - - validates :content, presence: true - validates :user, presence: true - validates :title, presence: true, length: 1..250 - - before_update :set_slug - - scope :ordered, order("created_at DESC") - - def to_param - slug - end - - class << self - def search(query) - where("title like :query OR content like :query", query: "%#{query}%") - end - end - - protected - - def self.regenerate_from wiki - regenerated_field = [:slug, :content, :title] - - new_wiki = Wiki.new - regenerated_field.each do |field| - new_wiki.send("#{field}=", wiki.send(field)) - end - new_wiki - end - - def set_slug - self.slug = self.title.parameterize - end -end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index adc77b22231..497d69e8e90 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -79,14 +79,14 @@ class WikiPage def version return nil unless persisted? - @version ||= Commit.new(@page.version) + @version ||= Commit.new(Gitlab::Git::Commit.new(@page.version)) end # Returns an array of Gitlab Commit instances. def versions return [] unless persisted? - @page.versions.map { |v| Commit.new(v) } + @page.versions.map { |v| Commit.new(Gitlab::Git::Commit.new(v)) } end # Returns the Date that this latest version was diff --git a/app/observers/activity_observer.rb b/app/observers/activity_observer.rb index c040c4c5ca2..ee3e4629b4c 100644 --- a/app/observers/activity_observer.rb +++ b/app/observers/activity_observer.rb @@ -1,4 +1,4 @@ -class ActivityObserver < ActiveRecord::Observer +class ActivityObserver < BaseObserver observe :issue, :merge_request, :note, :milestone def after_create(record) diff --git a/app/observers/base_observer.rb b/app/observers/base_observer.rb new file mode 100644 index 00000000000..182d3b7b73c --- /dev/null +++ b/app/observers/base_observer.rb @@ -0,0 +1,9 @@ +class BaseObserver < ActiveRecord::Observer + def notification + NotificationService.new + end + + def log_info message + Gitlab::AppLogger.info message + end +end diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb index 29e24040378..03ce4b95ac8 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -1,42 +1,30 @@ -class IssueObserver < ActiveRecord::Observer +class IssueObserver < BaseObserver cattr_accessor :current_user def after_create(issue) - if issue.assignee && issue.assignee != current_user - Notify.delay.new_issue_email(issue.id) - end + notification.new_issue(issue, current_user) end def after_close(issue, transition) - send_reassigned_email(issue) if issue.is_being_reassigned? + notification.close_issue(issue, current_user) create_note(issue) end def after_reopen(issue, transition) - send_reassigned_email(issue) if issue.is_being_reassigned? - create_note(issue) end def after_update(issue) - send_reassigned_email(issue) if issue.is_being_reassigned? + if issue.is_being_reassigned? + notification.reassigned_issue(issue, current_user) + end end protected + # Create issue note with service comment like 'Status changed to closed' def create_note(issue) Note.create_status_change_note(issue, current_user, issue.state) - [issue.author, issue.assignee].compact.uniq.each do |recipient| - Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id) - end - end - - def send_reassigned_email(issue) - recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id } - - recipient_ids.each do |recipient_id| - Notify.delay.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was) - end end end diff --git a/app/observers/key_observer.rb b/app/observers/key_observer.rb index 0bc71a663e8..28fef55abf9 100644 --- a/app/observers/key_observer.rb +++ b/app/observers/key_observer.rb @@ -1,6 +1,4 @@ -class KeyObserver < ActiveRecord::Observer - include Gitlab::ShellAdapter - +class KeyObserver < BaseObserver def after_save(key) GitlabShellWorker.perform_async( :add_key, @@ -8,8 +6,7 @@ class KeyObserver < ActiveRecord::Observer key.key ) - # Notify about ssh key being added - Notify.delay.new_ssh_key_email(key.id) if key.user + notification.new_key(key) end def after_destroy(key) diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb index d89e7734c1a..d0dfad8869d 100644 --- a/app/observers/merge_request_observer.rb +++ b/app/observers/merge_request_observer.rb @@ -1,36 +1,25 @@ -class MergeRequestObserver < ActiveRecord::Observer +class MergeRequestObserver < BaseObserver cattr_accessor :current_user def after_create(merge_request) - if merge_request.assignee && merge_request.assignee != current_user - Notify.delay.new_merge_request_email(merge_request.id) - end + notification.new_merge_request(merge_request, current_user) end def after_close(merge_request, transition) - send_reassigned_email(merge_request) if merge_request.is_being_reassigned? - Note.create_status_change_note(merge_request, current_user, merge_request.state) + + notification.close_mr(merge_request, current_user) end - def after_reopen(merge_request, transition) - send_reassigned_email(merge_request) if merge_request.is_being_reassigned? + def after_merge(merge_request, transition) + notification.merge_mr(merge_request) + end + def after_reopen(merge_request, transition) Note.create_status_change_note(merge_request, current_user, merge_request.state) end def after_update(merge_request) - send_reassigned_email(merge_request) if merge_request.is_being_reassigned? - end - - protected - - def send_reassigned_email(merge_request) - recipients_ids = merge_request.assignee_id_was, merge_request.assignee_id - recipients_ids.delete current_user.id - - recipients_ids.each do |recipient_id| - Notify.delay.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was) - end + notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? end end diff --git a/app/observers/note_observer.rb b/app/observers/note_observer.rb index 0f820a263b3..7b79161cce4 100644 --- a/app/observers/note_observer.rb +++ b/app/observers/note_observer.rb @@ -1,38 +1,5 @@ -class NoteObserver < ActiveRecord::Observer +class NoteObserver < BaseObserver def after_create(note) - send_notify_mails(note) - end - - protected - - def send_notify_mails(note) - if note.notify - notify_team(note) - elsif note.notify_author - # Notify only author of resource - if note.commit_author - Notify.delay.note_commit_email(note.commit_author.id, note.id) - end - else - # Otherwise ignore it - nil - end - end - - # Notifies the whole team except the author of note - def notify_team(note) - # Note: wall posts are not "attached" to anything, so fall back to "Wall" - noteable_type = note.noteable_type.presence || "Wall" - notify_method = "note_#{noteable_type.underscore}_email".to_sym - - if Notify.respond_to? notify_method - team_without_note_author(note).map do |u| - Notify.delay.send(notify_method, u.id, note.id) - end - end - end - - def team_without_note_author(note) - note.project.users.reject { |u| u.id == note.author.id } + notification.new_note(note) end end diff --git a/app/observers/project_activity_cache_observer.rb b/app/observers/project_activity_cache_observer.rb new file mode 100644 index 00000000000..96ced90db80 --- /dev/null +++ b/app/observers/project_activity_cache_observer.rb @@ -0,0 +1,8 @@ +class ProjectActivityCacheObserver < BaseObserver + observe :event + + def after_create(event) + event.project.update_column(:last_activity_at, event.created_at) if event.project + end +end + diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb index 89dc97ac140..bd88bb838ef 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -1,15 +1,22 @@ -class ProjectObserver < ActiveRecord::Observer +class ProjectObserver < BaseObserver def after_create(project) - GitlabShellWorker.perform_async( - :add_repository, - project.path_with_namespace - ) - - log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"") + unless project.forked? + GitlabShellWorker.perform_async( + :add_repository, + project.path_with_namespace + ) + + log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"") + end end def after_update(project) project.send_move_instructions if project.namespace_id_changed? + project.rename_repo if project.path_changed? + end + + def before_destroy(project) + project.repository.expire_cache unless project.empty_repo? end def after_destroy(project) @@ -27,10 +34,4 @@ class ProjectObserver < ActiveRecord::Observer log_info("Project \"#{project.name}\" was removed") end - - protected - - def log_info message - Gitlab::AppLogger.info message - end end diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb index be2594b4916..3a649fd590d 100644 --- a/app/observers/system_hook_observer.rb +++ b/app/observers/system_hook_observer.rb @@ -1,4 +1,4 @@ -class SystemHookObserver < ActiveRecord::Observer +class SystemHookObserver < BaseObserver observe :user, :project, :users_project def after_create(model) diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb index 6c461e07865..6bb3c471d0c 100644 --- a/app/observers/user_observer.rb +++ b/app/observers/user_observer.rb @@ -1,9 +1,8 @@ -class UserObserver < ActiveRecord::Observer +class UserObserver < BaseObserver def after_create(user) log_info("User \"#{user.name}\" (#{user.email}) was created") - # Dont email omniauth created users - Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid? + notification.new_user(user) end def after_destroy user @@ -19,10 +18,4 @@ class UserObserver < ActiveRecord::Observer end end end - - protected - - def log_info message - Gitlab::AppLogger.info message - end end diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb index 66b421753f0..ca9649c76ab 100644 --- a/app/observers/users_project_observer.rb +++ b/app/observers/users_project_observer.rb @@ -1,7 +1,6 @@ -class UsersProjectObserver < ActiveRecord::Observer +class UsersProjectObserver < BaseObserver def after_commit(users_project) return if users_project.destroyed? - Notify.delay.project_access_granted_email(users_project.id) end def after_create(users_project) @@ -10,6 +9,12 @@ class UsersProjectObserver < ActiveRecord::Observer action: Event::JOINED, author_id: users_project.user.id ) + + notification.new_team_member(users_project) + end + + def after_update(users_project) + notification.update_team_member(users_project) end def after_destroy(users_project) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 383e6398b74..e8b32f52ce1 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -104,7 +104,7 @@ class GitPushService data[:commits] << { id: commit.id, message: commit.safe_message, - timestamp: commit.date.xmlschema, + timestamp: commit.committed_date.xmlschema, url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{commit.id}", author: { name: commit.author_name, diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb new file mode 100644 index 00000000000..379d2c54629 --- /dev/null +++ b/app/services/notification_service.rb @@ -0,0 +1,223 @@ +# NotificationService class +# +# Used for notifing users with emails about different events +# +# Ex. +# NotificationService.new.new_issue(issue, current_user) +# +class NotificationService + # Always notify user about ssh key added + # only if ssh key is not deploy key + # + # This is security email so it will be sent + # even if user disabled notifications + def new_key(key) + if key.user + mailer.new_ssh_key_email(key.id) + end + end + + # When create an issue we should send next emails: + # + # * issue assignee if his notification level is not Disabled + # * project team members with notification level higher then Participating + # + def new_issue(issue, current_user) + new_resource_email(issue, 'new_issue_email') + end + + # When we close an issue we should send next emails: + # + # * issue author if his notification level is not Disabled + # * issue assignee if his notification level is not Disabled + # * project team members with notification level higher then Participating + # + def close_issue(issue, current_user) + close_resource_email(issue, current_user, 'closed_issue_email') + end + + # When we reassign an issue we should send next emails: + # + # * issue old assignee if his notification level is not Disabled + # * issue new assignee if his notification level is not Disabled + # + def reassigned_issue(issue, current_user) + reassign_resource_email(issue, current_user, 'reassigned_issue_email') + end + + + # When create a merge request we should send next emails: + # + # * mr assignee if his notification level is not Disabled + # + def new_merge_request(merge_request, current_user) + new_resource_email(merge_request, 'new_merge_request_email') + end + + # When we reassign a merge_request we should send next emails: + # + # * merge_request old assignee if his notification level is not Disabled + # * merge_request assignee if his notification level is not Disabled + # + def reassigned_merge_request(merge_request, current_user) + reassign_resource_email(merge_request, current_user, 'reassigned_merge_request_email') + end + + # When we close a merge request we should send next emails: + # + # * merge_request author if his notification level is not Disabled + # * merge_request assignee if his notification level is not Disabled + # * project team members with notification level higher then Participating + # + def close_mr(merge_request, current_user) + close_resource_email(merge_request, current_user, 'closed_merge_request_email') + end + + # When we merge a merge request we should send next emails: + # + # * merge_request author if his notification level is not Disabled + # * merge_request assignee if his notification level is not Disabled + # * project team members with notification level higher then Participating + # + def merge_mr(merge_request) + recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.project) + recipients = recipients.concat(project_watchers(merge_request.project)).uniq + + recipients.each do |recipient| + mailer.merged_merge_request_email(recipient.id, merge_request.id) + end + end + + # Notify new user with email after creation + def new_user(user) + # Dont email omniauth created users + mailer.new_user_email(user.id, user.password) unless user.extern_uid? + end + + # Notify users on new note in system + # + # TODO: split on methods and refactor + # + def new_note(note) + # ignore wall messages + return true unless note.noteable_type.present? + + opts = { noteable_type: note.noteable_type, project_id: note.project_id } + + if note.commit_id.present? + opts.merge!(commit_id: note.commit_id) + recipients = [note.commit_author] + else + opts.merge!(noteable_id: note.noteable_id) + target = note.noteable + recipients = [] + recipients << target.assignee if target.respond_to?(:assignee) + recipients << target.author if target.respond_to?(:author) + end + + # Get users who left comment in thread + recipients = recipients.concat(User.where(id: Note.where(opts).pluck(:author_id))) + + # Merge project watchers + recipients = recipients.concat(project_watchers(note.project)).compact.uniq + + # Reject mutes users + recipients = reject_muted_users(recipients, note.project) + + # Reject author + recipients.delete(note.author) + + # build notify method like 'note_commit_email' + notify_method = "note_#{note.noteable_type.underscore}_email".to_sym + + recipients.each do |recipient| + mailer.send(notify_method, recipient.id, note.id) + end + end + + def new_team_member(users_project) + mailer.project_access_granted_email(users_project.id) + end + + def update_team_member(users_project) + mailer.project_access_granted_email(users_project.id) + end + + protected + + # Get project users with WATCH notification level + def project_watchers(project) + + # Get project notification settings since it has higher priority + user_ids = project.users_projects.where(notification_level: Notification::N_WATCH).pluck(:user_id) + project_watchers = User.where(id: user_ids) + + # next collect users who use global settings with watch state + user_ids = project.users_projects.where(notification_level: Notification::N_GLOBAL).pluck(:user_id) + project_watchers += User.where(id: user_ids, notification_level: Notification::N_WATCH) + + project_watchers.uniq + end + + # Remove users with disabled notifications from array + # Also remove duplications and nil recipients + def reject_muted_users(users, project = nil) + users = users.compact.uniq + + users.reject do |user| + next user.notification.disabled? unless project + + tm = project.users_projects.find_by_user_id(user.id) + + # reject users who globally disabled notification and has no membership + next user.notification.disabled? unless tm + + # reject users who disabled notification in project + next true if tm.notification.disabled? + + # reject users who have N_GLOBAL in project and disabled in global settings + tm.notification.global? && user.notification.disabled? + end + end + + def new_resource_email(target, method) + recipients = reject_muted_users([target.assignee], target.project) + recipients = recipients.concat(project_watchers(target.project)).uniq + recipients.delete(target.author) + + recipients.each do |recipient| + mailer.send(method, recipient.id, target.id) + end + end + + def close_resource_email(target, current_user, method) + recipients = reject_muted_users([target.author, target.assignee], target.project) + recipients = recipients.concat(project_watchers(target.project)).uniq + recipients.delete(current_user) + + recipients.each do |recipient| + mailer.send(method, recipient.id, target.id, current_user.id) + end + end + + def reassign_resource_email(target, current_user, method) + recipients = User.where(id: [target.assignee_id, target.assignee_id_was]) + + # Add watchers to email list + recipients = recipients.concat(project_watchers(target.project)) + + # reject users with disabled notifications + recipients = reject_muted_users(recipients, target.project) + + # Reject me from recipients if I reassign an item + recipients.delete(current_user) + + recipients.each do |recipient| + mailer.send(method, recipient.id, target.id, target.assignee_id_was) + end + end + + def mailer + Notify.delay + end +end diff --git a/app/services/project_transfer_service.rb b/app/services/project_transfer_service.rb index 719e0d3db23..3b8c4847f20 100644 --- a/app/services/project_transfer_service.rb +++ b/app/services/project_transfer_service.rb @@ -5,6 +5,8 @@ class ProjectTransferService include Gitlab::ShellAdapter + class TransferError < StandardError; end + attr_accessor :project def transfer(project, new_namespace) @@ -19,14 +21,16 @@ class ProjectTransferService project.namespace = new_namespace project.save! + # Move main repository unless gitlab_shell.mv_repository(old_path, new_path) raise TransferError.new('Cannot move project') end + # Move wiki repo also if present + gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki") + true end - rescue => ex - raise Project::TransferError.new(ex.message) end end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 132bb14a675..a52d13d5237 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -26,6 +26,7 @@ class SystemHooksService data.merge!({ name: model.name, path: model.path, + path_with_namespace: model.path_with_namespace, project_id: model.id, owner_name: model.owner.name, owner_email: model.owner.email diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index 200700b8810..c0afe9686f1 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -8,12 +8,12 @@ class AttachmentUploader < CarrierWave::Uploader::Base end def image? - img_ext = %w(png jpg jpeg) + img_ext = %w(png jpg jpeg gif bmp tiff) if file.respond_to?(:extension) - img_ext.include?(file.extension) + img_ext.include?(file.extension.downcase) else # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last + ext = file.path.split('.').last.downcase img_ext.include?(ext) end rescue @@ -21,10 +21,10 @@ class AttachmentUploader < CarrierWave::Uploader::Base end def secure_url - if self.class.storage == CarrierWave::Storage::File - "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" - else - url - end + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File end end diff --git a/app/views/admin/resque/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 499738f9a06..499738f9a06 100644 --- a/app/views/admin/resque/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 46a876294ce..d3c938bb8f2 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -9,20 +9,20 @@ = link_to 'New Project', new_project_path, class: "btn btn-small" .span4 .ui-box - %h5.title Groups - .data.padded - = link_to admin_groups_path do - %h1= Group.count - %hr - = link_to 'New Group', new_admin_group_path, class: "btn btn-small" - .span4 - .ui-box %h5.title Users .data.padded = link_to admin_users_path do %h1= User.count %hr = link_to 'New User', new_admin_user_path, class: "btn btn-small" + .span4 + .ui-box + %h5.title Groups + .data.padded + = link_to admin_groups_path do + %h1= Group.count + %hr + = link_to 'New Group', new_admin_group_path, class: "btn btn-small" .row .span4 diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index bb1398f66cd..af87503128e 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -27,5 +27,5 @@ %li It will change the git path to repositories under this group. .form-actions - = f.submit 'Edit group', class: "btn btn-remove" + = f.submit 'Save changes', class: "btn btn-primary" = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 0029cc78e3e..d880ff61c9b 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,5 +1,5 @@ %h3.page_title - Groups + Groups (#{@groups.total_count}) %small allows you to keep projects organized. Use groups for uniting related projects. @@ -7,31 +7,42 @@ = link_to 'New Group', new_admin_group_path, class: "btn btn-small pull-right" %br = form_tag admin_groups_path, method: :get, class: 'form-inline' do - = text_field_tag :name, params[:name], class: "xlarge" + = text_field_tag :name, params[:name], class: "span6" = submit_tag "Search", class: "btn submit btn-primary" -%table - %thead - %tr - %th - Name - %i.icon-sort-down - %th Description - %th Path - %th Projects - %th Owner - %th.cred Danger Zone! +%hr +%ul.bordered-list - @groups.each do |group| - %tr - %td - %strong= link_to group.name, [:admin, group] - %td= truncate group.description - %td= group.path - %td= group.projects.count - %td - = link_to group.owner_name, admin_user_path(group.owner) - %td.bgred - = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" - = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" -= paginate @groups, theme: "admin" + %li + .clearfix + .pull-right.prepend-top-10 + = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" + = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" + + %h4 + = link_to [:admin, group] do + = group.name + + → + %span.monospace + %i.icon-folder-close + %strong #{group.path}/ + + .clearfix.light.append-bottom-10 + %span + %b Owner: + - if group.owner + = link_to group.owner_name, admin_user_path(group.owner) + - else + (deleted) + \| + %span + %b Projects: + %span.badge= group.projects.count + + .clearfix + %p + = truncate group.description, length: 150 + += paginate @groups, theme: "gitlab" diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml index 3fa63e1ba25..2da654ec764 100644 --- a/app/views/admin/groups/new.html.haml +++ b/app/views/admin/groups/new.html.haml @@ -15,7 +15,7 @@ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 .form-actions - = f.submit 'Create group', class: "btn btn-primary" + = f.submit 'Create group', class: "btn btn-create" %hr .padded @@ -23,5 +23,5 @@ %li Group is kind of directory for several projects %li All created groups are private %li People within a group see only projects they have access to - %li All projects of group will be stored in group directory + %li All projects of group will be stored in a group directory %li You will be able to move existing projects into group diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 63ea78fdd99..0e2e144d326 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -94,7 +94,7 @@ %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} %tr - %td= submit_tag 'Add user to projects in group', class: "btn btn-primary" + %td= submit_tag 'Add user to projects in group', class: "btn btn-create" %td Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" @@ -116,18 +116,5 @@ .input = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' .form-actions - = submit_tag 'Add', class: "btn btn-primary" - -:javascript - $(function(){ - var modal = $('.change-owner-holder'); - $('.change-owner-link').bind("click", function(){ - $(this).hide(); - modal.show(); - }); - $('.change-owner-cancel-link').bind("click", function(){ - modal.hide(); - $('.change-owner-link').show(); - }) - }) + = submit_tag 'Move projects', class: "btn btn-create" diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb index 652ee5aa56f..eeb78b5f0c5 100644 --- a/app/views/admin/hooks/_data_ex.html.erb +++ b/app/views/admin/hooks/_data_ex.html.erb @@ -28,8 +28,8 @@ "project_id": 74, "project_name": "StoreCloud", "project_path": "storecloud", - "owner_email": "johnsmith@gmail.com", - "owner_name": "John Smith", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", } 4. Team Member Removed: @@ -40,8 +40,8 @@ "project_id": 74, "project_name": "StoreCloud", "project_path": "storecloud", - "owner_email": "johnsmith@gmail.com", - "owner_name": "John Smith", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", } 5. User created: diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index acbf7a108b8..316e8235cbe 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -15,7 +15,7 @@ .input = f.text_field :url, class: "text_field xxlarge" - = f.submit "Add System Hook", class: "btn btn-primary" + = f.submit "Add System Hook", class: "btn btn-create" %hr -if @hooks.any? diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml deleted file mode 100644 index 29b90bdd4cb..00000000000 --- a/app/views/admin/projects/_form.html.haml +++ /dev/null @@ -1,86 +0,0 @@ -= form_for [:admin, project] do |f| - -if project.errors.any? - .alert.alert-error - %ul - - project.errors.full_messages.each do |msg| - %li= msg - - .clearfix.project_name_holder - = f.label :name do - Project name is - .input - = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - - - if project.repo_exists? - %fieldset.adv_settings - %legend Advanced settings: - .clearfix - = f.label :path do - Path - .input - = text_field_tag :ppath, @project.repository.path_to_repo, class: "xlarge", disabled: true - - .clearfix - = f.label :default_branch, "Default Branch" - .input= f.select(:default_branch, @project.repository.heads.map(&:name), {}, style: "width:210px;") - - %fieldset.adv_settings - %legend Features: - - .clearfix - = f.label :issues_enabled, "Issues" - .input= f.check_box :issues_enabled - - - if Project.issues_tracker.values.count > 1 - .clearfix - = f.label :issues_tracker, "Issues tracker", class: 'control-label' - .input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled }) - - .clearfix - = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' - .input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id? - - .clearfix - = f.label :merge_requests_enabled, "Merge Requests" - .input= f.check_box :merge_requests_enabled - - .clearfix - = f.label :wall_enabled, "Wall" - .input= f.check_box :wall_enabled - - .clearfix - = f.label :wiki_enabled, "Wiki" - .input= f.check_box :wiki_enabled - - %fieldset.features - %legend Public mode: - .clearfix - = f.label :public do - %span Allow public http clone - .input= f.check_box :public - - %fieldset.features - %legend Transfer: - .control-group - = f.label :namespace_id do - %span Namespace - .controls - = f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'} - %br - %ul.prepend-top-10.cred - %li Be careful. Changing project namespace can have unintended side effects - %li You can transfer project only to namespaces you can manage - %li You will need to update your local repositories to point to the new location. - - - .actions - = f.submit 'Save Project', class: "btn btn-save" - = link_to 'Cancel', admin_projects_path, class: "btn btn-cancel" - - - -:javascript - $(function(){ - new Projects(); - }) - diff --git a/app/views/admin/projects/edit.html.haml b/app/views/admin/projects/edit.html.haml deleted file mode 100644 index 7b59a0cc753..00000000000 --- a/app/views/admin/projects/edit.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -%h3.page_title #{@project.name} → Edit project -%hr -= render 'form', project: @project diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 15b2778252a..59831d83cc2 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -52,10 +52,8 @@ %i.icon-lock.cgreen = link_to project.name_with_namespace, [:admin, project] .pull-right - = link_to 'Edit', edit_admin_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Destroy', [:admin, project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" + = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" + = link_to 'Destroy', [project], confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove" - if @projects.blank? %p.nothing_here_message 0 projects matches - - else - %li.bottom - = paginate @projects, theme: "gitlab" + = paginate @projects, theme: "gitlab" diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 65b921170fd..92b8960151e 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,129 +1,90 @@ %h3.page_title Project: #{@project.name_with_namespace} - = link_to edit_admin_project_path(@project), class: "btn pull-right" do + = link_to edit_project_path(@project), class: "btn pull-right" do %i.icon-edit Edit +%hr +.row + .span6 + .ui-box + %h5.title + Project info: + %ul.well-list + %li + %span.light Name: + %strong= @project.name + %li + %span.light Namespace: + %strong + - if @project.namespace + = link_to @project.namespace.human_name, [:admin, @project.group || @project.owner] + - else + Global + %li + %span.light Owned by: + %strong + - if @project.owner + = link_to @project.owner_name, admin_user_path(@project.owner) + - else + (deleted) + %li + %span.light Created by: + %strong + = @project.creator.try(:name) || '(deleted)' -%br -%table.zebra-striped - %thead - %tr - %th Project - %th - %tr - %td - %b - Name: - %td - = @project.name - %tr - %td - %b - Namespace: - %td - - if @project.namespace - = @project.namespace.human_name - - else - Global - %tr - %td - %b - Owned by: - %td - - if @project.owner - = link_to @project.owner_name, admin_user_path(@project.owner) - - else - (deleted) - %tr - %td - %b - Created by: - %td - = @project.creator.try(:name) || '(deleted)' - %tr - %td - %b - Created at: - %td - = @project.created_at.stamp("March 1, 1999") - %tr - %td - %b - Smart HTTP: - %td - = link_to @project.http_url_to_repo - %tr - %td - %b - SSH: - %td - = link_to @project.ssh_url_to_repo - - if @project.public - %tr.bgred - %td - %b - Public Read-Only Code access: - %td - = check_box_tag 'public', nil, @project.public + %li + %span.light Created at: + %strong + = @project.created_at.stamp("March 1, 1999") -- if @repository - %table.zebra-striped - %thead - %tr - %th Repository - %th - %tr - %td - %b - FS Path: - %td - %code= @repository.path_to_repo - %tr - %td - %b - Last commit at: - %td - = last_commit(@project) + %li + %span.light http: + %strong + = link_to @project.http_url_to_repo + %li + %span.light ssh: + %strong + = link_to @project.ssh_url_to_repo + - if @project.repository.exists? + %li + %span.light fs: + %strong + = @repository.path_to_repo -%br -%h5 - Team - %small - (#{@project.users.count}) -%br -%table.zebra-striped.team_members - %thead - %tr - %th Name - %th Project Access - %th Repository Access - %th + %li + %span.light last commit: + %strong + = last_commit(@project) + - else + %li + %span.light repository: + %strong.cred + does not exist - - @project.users.each do |tm| - %tr - %td - = link_to tm.name, admin_user_path(tm) - %td= @project.project_access_human(tm) - %td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn btn-small" - %td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove small" - -%br -%h5 Add new team member -%br -= form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do - %table.zebra-striped - %thead - %tr - %th Users - %th Project Access: - - %tr - %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' - %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} - - %tr - %td= submit_tag 'Add', class: "btn btn-primary" - %td - Read more about project permissions - %strong= link_to "here", help_permissions_path, class: "vlink" + %li + %span.light access: + %strong + - if @project.public + %span.cblue + %i.icon-share + Public + - else + %span.cgreen + %i.icon-lock + Private + .span6 + .ui-box + %h5.title + Team + %small + (#{@project.users.count}) + = link_to project_team_index_path(@project), class: "btn btn-tiny" do + %i.icon-edit + Edit Team + %ul.well-list.team_members + - @project.users.each do |tm| + %li + %strong + = link_to tm.name, admin_user_path(tm) + %span.pull-right.light= @project.project_access_human(tm) diff --git a/app/views/admin/projects/team.html.haml b/app/views/admin/projects/team.html.haml deleted file mode 100644 index e69de29bb2d..00000000000 --- a/app/views/admin/projects/team.html.haml +++ /dev/null diff --git a/app/views/admin/teams/index.html.haml b/app/views/admin/teams/index.html.haml index 3690d6d9eb4..cf24ed5398f 100644 --- a/app/views/admin/teams/index.html.haml +++ b/app/views/admin/teams/index.html.haml @@ -1,7 +1,7 @@ %h3.page_title Teams %small - simple Teams description + allow you to organize groups of people that have a common focus. Use teams to simplify the process of assigning roles to groups of people. = link_to 'New Team', new_admin_team_path, class: "btn btn-small pull-right" %br @@ -40,4 +40,4 @@ = link_to 'Edit', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small" = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" -= paginate @teams, theme: "admin" += paginate @teams, theme: "gitlab" diff --git a/app/views/admin/teams/new.html.haml b/app/views/admin/teams/new.html.haml index 1c90cb20c10..8bccdacc351 100644 --- a/app/views/admin/teams/new.html.haml +++ b/app/views/admin/teams/new.html.haml @@ -16,7 +16,7 @@ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4 .form-actions - = f.submit 'Create team', class: "btn btn-primary" + = f.submit 'Create team', class: "btn btn-create" %hr .padded diff --git a/app/views/admin/teams/show.html.haml b/app/views/admin/teams/show.html.haml index abdfada8c5e..bd4d90b607f 100644 --- a/app/views/admin/teams/show.html.haml +++ b/app/views/admin/teams/show.html.haml @@ -91,17 +91,3 @@ = link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn btn-small" = link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn btn-remove small", id: "relegate_project_#{project.id}" - -:javascript - $(function(){ - var modal = $('.change-owner-holder'); - $('.change-owner-link').bind("click", function(){ - $(this).hide(); - modal.show(); - }); - $('.change-owner-cancel-link').bind("click", function(){ - modal.hide(); - $('.change-owner-link').show(); - }) - }) - diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 1d1fe341c5b..9bde50f8947 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -80,8 +80,9 @@ .input= f.text_field :twitter .actions - = f.submit 'Save', class: "btn btn-save" - if @admin_user.new_record? + = f.submit 'Create user', class: "btn btn-create" = link_to 'Cancel', admin_users_path, class: "btn btn-cancel" - else + = f.submit 'Save changes', class: "btn btn-save" = link_to 'Cancel', admin_user_path(@admin_user), class: "btn btn-cancel" diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 9da2871e992..3657f660c9b 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -58,5 +58,4 @@ - else = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove" = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-small btn-remove" - %li.bottom - = paginate @admin_users, theme: "gitlab" + = paginate @admin_users, theme: "gitlab" diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index b813f859a0b..6709b8f8a6b 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -71,7 +71,7 @@ .ui-box %h5.title Projects (#{@projects.count}) %ul.well-list - - @projects.each do |project| + - @projects.sort_by(&:name_with_namespace).each do |project| %li = link_to admin_project_path(project), class: dom_class(project) do - if project.namespace @@ -87,7 +87,7 @@ = tm.project_access_human = link_to edit_admin_project_member_path(project, tm.user), class: "btn btn-small" do %i.icon-edit - = link_to admin_project_member_path(project, tm.user), confirm: 'Are you sure?', method: :delete, class: "btn btn-small btn-remove" do + = link_to admin_project_member_path(project, tm.user), confirm: remove_from_project_team_message(project, @admin_user), method: :delete, class: "btn btn-small btn-remove" do %i.icon-remove %p.light %i.icon-wrench diff --git a/app/views/blame/show.html.haml b/app/views/blame/show.html.haml index b2a45ef5303..297e30356a7 100644 --- a/app/views/blame/show.html.haml +++ b/app/views/blame/show.html.haml @@ -6,7 +6,7 @@ %i.icon-angle-right = link_to project_tree_path(@project, @ref) do = @project.name - - @tree.breadcrumbs(6) do |link| + - tree_breadcrumbs(@tree, 6) do |link| \/ %li= link .clear @@ -15,20 +15,20 @@ .file_title %i.icon-file %span.file_name - = @tree.name - %small= number_to_human_size @tree.size - %span.options= render "tree/blob_actions" + = @blob.name + %small= number_to_human_size @blob.size + %span.options= render "blob/actions" .file_content.blame %table - current_line = 1 - @blame.each do |commit, lines| - - commit = CommitDecorator.decorate(Commit.new(commit)) + - commit = Commit.new(commit) %tr %td.blame-commit %span.commit = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" - = commit.author_link avatar: true, size: 16 + = commit_author_link(commit, avatar: true, size: 16) = link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title" %td.lines.blame-numbers @@ -43,6 +43,7 @@ - current_line += 1 %td.lines %pre - - lines.each do |line| - = line - \ + :erb + <% lines.each do |line| %> + <%= line %> + <% end %> diff --git a/app/views/tree/_blob_actions.html.haml b/app/views/blob/_actions.html.haml index 0bde968d0e6..456c7432c94 100644 --- a/app/views/tree/_blob_actions.html.haml +++ b/app/views/blob/_actions.html.haml @@ -1,12 +1,12 @@ .btn-group.tree-btn-group -# only show edit link for text files - - if @tree.text? - = link_to "edit", edit_project_tree_path(@project, @id), class: "btn btn-tiny", disabled: !allowed_tree_edit? - = link_to "raw", project_blob_path(@project, @id), class: "btn btn-tiny", target: "_blank" + - if @blob.text? + = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-tiny", disabled: !allowed_tree_edit? + = link_to "raw", project_raw_path(@project, @id), class: "btn btn-tiny", target: "_blank" -# only show normal/blame view links for text files - - if @tree.text? + - if @blob.text? - if current_page? project_blame_path(@project, @id) - = link_to "normal view", project_tree_path(@project, @id), class: "btn btn-tiny" + = link_to "normal view", project_blob_path(@project, @id), class: "btn btn-tiny" - else - = link_to "blame", project_blame_path(@project, @id), class: "btn btn-tiny" + = link_to "blame", project_blame_path(@project, @id), class: "btn btn-tiny" unless @blob.empty? = link_to "history", project_commits_path(@project, @id), class: "btn btn-tiny" diff --git a/app/views/blob/_blob.html.haml b/app/views/blob/_blob.html.haml new file mode 100644 index 00000000000..68d6c06065c --- /dev/null +++ b/app/views/blob/_blob.html.haml @@ -0,0 +1,32 @@ +%ul.breadcrumb + %li + %i.icon-angle-right + = link_to project_tree_path(@project, @ref) do + = @project.path + - tree_breadcrumbs(@tree, 6) do |title, path| + \/ + %li + - if path + - if path.end_with?(@path) + = link_to project_blob_path(@project, path) do + %span.cblue + = truncate(title, length: 40) + - else + = link_to truncate(title, length: 40), project_tree_path(@project, path) + - else + = link_to title, '#' + +%div#tree-content-holder.tree-content-holder + .file_holder + .file_title + %i.icon-file + %span.file_name + = blob.name + %small= number_to_human_size blob.size + %span.options= render "actions" + - if blob.text? + = render "text", blob: blob + - elsif blob.image? + = render "image", blob: blob + - else + = render "download", blob: blob diff --git a/app/views/tree/blob/_download.html.haml b/app/views/blob/_download.html.haml index 864c209db76..864c209db76 100644 --- a/app/views/tree/blob/_download.html.haml +++ b/app/views/blob/_download.html.haml diff --git a/app/views/tree/blob/_image.html.haml b/app/views/blob/_image.html.haml index 7b23f0c810c..7b23f0c810c 100644 --- a/app/views/tree/blob/_image.html.haml +++ b/app/views/blob/_image.html.haml diff --git a/app/views/tree/blob/_text.html.haml b/app/views/blob/_text.html.haml index 122e275219d..122e275219d 100644 --- a/app/views/tree/blob/_text.html.haml +++ b/app/views/blob/_text.html.haml diff --git a/app/views/blob/show.html.haml b/app/views/blob/show.html.haml new file mode 100644 index 00000000000..d96595bc7f0 --- /dev/null +++ b/app/views/blob/show.html.haml @@ -0,0 +1,4 @@ +%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/commit/_commit_box.html.haml b/app/views/commit/_commit_box.html.haml index 4c80c13ced1..646791773b2 100644 --- a/app/views/commit/_commit_box.html.haml +++ b/app/views/commit/_commit_box.html.haml @@ -24,14 +24,14 @@ .row .span5 .author - = @commit.author_link avatar: true, size: 32 + = commit_author_link(@commit, avatar: true, size: 32) authored %time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")} #{time_ago_in_words(@commit.authored_date)} ago - if @commit.different_committer? .committer → - = @commit.committer_link + = commit_committer_link(@commit) committed %time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")} #{time_ago_in_words(@commit.committed_date)} ago diff --git a/app/views/commit/huge_commit.html.haml b/app/views/commit/huge_commit.html.haml index 7f0bcf38037..5d447d6cee5 100644 --- a/app/views/commit/huge_commit.html.haml +++ b/app/views/commit/huge_commit.html.haml @@ -1,3 +1,3 @@ -= render "commits/commit_box" += render "commit/commit_box" .alert.alert-error %h4 Commit diffs are too big to be displayed diff --git a/app/views/commit/show.html.haml b/app/views/commit/show.html.haml index 48fb44a99d5..6cb1a6905ca 100644 --- a/app/views/commit/show.html.haml +++ b/app/views/commit/show.html.haml @@ -9,10 +9,3 @@ = render "commits/diffs", diffs: @commit.diffs = render "notes/notes_with_form" - -:javascript - $(function(){ - $('.files .file').each(function(){ - new CommitFile(this); - }); - }); diff --git a/app/views/commits/_commit.html.haml b/app/views/commits/_commit.html.haml index 2f5ff130f03..eba6c206c46 100644 --- a/app/views/commits/_commit.html.haml +++ b/app/views/commits/_commit.html.haml @@ -4,7 +4,7 @@ %strong= link_to "Browse Code »", project_tree_path(@project, commit), class: "right" %p = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" - = commit.author_link avatar: true, size: 24 + = commit_author_link(commit, avatar: true, size: 24) = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title" @@ -16,6 +16,6 @@ %span.notes_count - notes = @project.notes.for_commit_id(commit.id) - if notes.any? - %span.btn.disabled.grouped + %span.badge.badge-info %i.icon-comment = notes.count diff --git a/app/views/commits/_commits.html.haml b/app/views/commits/_commits.html.haml index 869d1f9c769..933780e4534 100644 --- a/app/views/commits/_commits.html.haml +++ b/app/views/commits/_commits.html.haml @@ -3,4 +3,7 @@ %h5.title %i.icon-calendar %span= day.stamp("28 Aug, 2010") + + .pull-right + %small= pluralize(commits.count, 'commit') %ul.well-list= render commits diff --git a/app/views/commits/_diffs.html.haml b/app/views/commits/_diffs.html.haml index b2da4796db6..3f4d51f753a 100644 --- a/app/views/commits/_diffs.html.haml +++ b/app/views/commits/_diffs.html.haml @@ -16,16 +16,16 @@ - unless @suppress_diff - diffs.each_with_index do |diff, i| - next if diff.diff.empty? - - file = (@commit.tree / diff.new_path) - - file = (@commit.prev_commit.tree / diff.old_path) unless file - - next unless file + - file = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, diff.new_path) + - file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) unless file.exists? + - next unless file.exists? .file{id: "diff-#{i}"} .header - if diff.deleted_file %span= diff.old_path - - if @commit.prev_commit - = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do + - if @commit.parent_ids.present? + = link_to project_blob_path(@project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do View file @ %span.commit-short-id= @commit.short_id(6) - else @@ -33,7 +33,7 @@ - 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_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do + = link_to project_blob_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do View file @ %span.commit-short-id= @commit.short_id(6) @@ -43,7 +43,7 @@ - if file.text? = render "commits/text_file", diff: diff, index: i - elsif file.image? - - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil? + - old_file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id = render "commits/image", diff: diff, old_file: old_file, file: file, index: i - else %p.nothing_here_message No preview for this file type diff --git a/app/views/commits/_text_file.html.haml b/app/views/commits/_text_file.html.haml index 760fd07ed8b..8f737e43887 100644 --- a/app/views/commits/_text_file.html.haml +++ b/app/views/commits/_text_file.html.haml @@ -4,7 +4,7 @@ %table.text-file{class: "#{'hide' if too_big}"} - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old| - %tr.line_holder{ id: line_code } + %tr.line_holder{ id: line_code, class: "#{type}" } - if type == "match" %td.old_line= "..." %td.new_line= "..." diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml index d180b8ec426..cb9ef820d3e 100644 --- a/app/views/commits/show.html.haml +++ b/app/views/commits/show.html.haml @@ -2,7 +2,7 @@ - if @path.present? %ul.breadcrumb - = breadcrumbs + = commits_breadcrumbs %div{id: dom_id(@project)} #commits-list= render "commits" @@ -11,7 +11,5 @@ - if @commits.count == @limit :javascript - $(function(){ - CommitsList.init("#{@ref}", #{@limit}); - }); + CommitsList.init("#{@ref}", #{@limit}); diff --git a/app/views/compare/show.html.haml b/app/views/compare/show.html.haml index 476be2550af..56c4a113ea0 100644 --- a/app/views/compare/show.html.haml +++ b/app/views/compare/show.html.haml @@ -16,7 +16,7 @@ %div.ui-box %h5.title Commits (#{@commits.count}) - %ul.well-list= render @commits + %ul.well-list= render Commit.decorate(@commits) - unless @diffs.empty? %h4 Diff diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml index 3124d76aa7f..2fedf87a9ba 100644 --- a/app/views/dashboard/_groups.html.haml +++ b/app/views/dashboard/_groups.html.haml @@ -1,11 +1,11 @@ .ui-box %h5.title Groups - %small + %span.light (#{groups.count}) - if current_user.can_create_group? %span.pull-right - = link_to new_group_path, class: "btn btn-tiny info" do + = link_to new_group_path, class: "btn btn-small" do %i.icon-plus New Group %ul.well-list diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 105e23fe12a..a106e83e783 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -1,11 +1,11 @@ .ui-box %h5.title Projects - %small + %span.light (#{@projects_count}) - if current_user.can_create_project? %span.pull-right - = link_to new_project_path, class: "btn btn-tiny info" do + = link_to new_project_path, class: "btn btn-small" do %i.icon-plus New Project diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml index 876a5b61297..748ff9810b5 100644 --- a/app/views/dashboard/_sidebar.html.haml +++ b/app/views/dashboard/_sidebar.html.haml @@ -22,7 +22,4 @@ News Feed %hr -.gitlab-promo - = link_to "Homepage", "http://gitlab.org" - = link_to "Blog", "http://blog.gitlab.org" - = link_to "@gitlabhq", "https://twitter.com/gitlabhq" += render 'shared/promo' diff --git a/app/views/dashboard/_teams.html.haml b/app/views/dashboard/_teams.html.haml index 5c28f964cb6..95d87f508e0 100644 --- a/app/views/dashboard/_teams.html.haml +++ b/app/views/dashboard/_teams.html.haml @@ -1,10 +1,10 @@ .ui-box.teams-box %h5.title Teams - %small + %span.light (#{teams.count}) %span.pull-right - = link_to new_team_path, class: "btn btn-tiny info" do + = link_to new_team_path, class: "btn btn-small" do %i.icon-plus New Team %ul.well-list diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 539c57651f7..b41edb9eb60 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -15,7 +15,12 @@ - project = group[0] %h5.title = link_to_project project - %ul.well-list.issues_table + + %i.icon-angle-right + + = link_to 'issues', project_issues_path(project) + + %ul.well-list.issues-list - group[1].each do |issue| = render issue %hr diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 29a16d61e63..9b16db340b2 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -20,6 +20,15 @@ = nav_tab :scope, 'joined' do = link_to "Joined", projects_dashboard_path(scope: 'joined') + %p.light Filter by label: + %ul.bordered-list + - @labels.each do |label| + %li{ class: (label.name == params[:label]) ? 'active' : 'light' } + = link_to projects_dashboard_path(scope: params[:scope], label: label.name) do + %i.icon-tag + = label.name + + .span9 = form_tag projects_dashboard_path, method: 'get' do %fieldset.dashboard-search-filter @@ -28,7 +37,7 @@ = button_tag type: 'submit', class: 'btn' do %i.icon-search - %ul.well-list + %ul.bordered-list - @projects.each do |project| %li.clearfix .clearfix @@ -36,7 +45,7 @@ = link_to project_path(project), class: dom_class(project) do - if project.namespace = project.namespace.human_name - \/ + %span= "/" %strong = truncate(project.name, length: 45) .pull-right.light @@ -49,6 +58,10 @@ .left - if project.description.present? %span.light= project.description + - project.labels.each do |label| + %span.label.label-info + %i.icon-tag + = label.name .pull-right.light %small.light diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index 29b2e4a26d8..a913df92299 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -8,11 +8,10 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear @events.each do |event| if event.proper? - event = EventDecorator.decorate(event) xml.entry do - event_link = event.feed_url - event_title = event.feed_title - event_summary = event.feed_summary + event_link = event_feed_url(event) + event_title = event_feed_title(event) + event_summary = event_feed_summary(event) xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" xml.link :href => event_link diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index 1a66ba4fb37..2305eae1f71 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -1,5 +1,5 @@ - if @has_authorized_projects - .projects + .dashboard .activities.span8 = render 'activities' .side.span4 @@ -7,6 +7,3 @@ - else = render "zero_authorized_projects" - -:javascript - dashboardPage(); diff --git a/app/views/deploy_keys/_deploy_key.html.haml b/app/views/deploy_keys/_deploy_key.html.haml new file mode 100644 index 00000000000..45f80ecd556 --- /dev/null +++ b/app/views/deploy_keys/_deploy_key.html.haml @@ -0,0 +1,25 @@ +%li + .pull-right + - if @available_keys.include?(deploy_key) + = link_to enable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do + %i.icon-plus + Enable + - else + - if deploy_key.projects.count > 1 + = link_to disable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do + %i.icon-off + Disable + - else + = link_to 'Remove', project_deploy_key_path(@project, deploy_key), confirm: 'You are going to remove deploy key. Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right" + + + = link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do + %i.icon-key + %strong= deploy_key.title + + %p.light.prepend-top-10 + - deploy_key.projects.map(&:name_with_namespace).each do |project_name| + %span.label= project_name + %small.pull-right + Created #{time_ago_in_words(deploy_key.created_at)} ago + diff --git a/app/views/deploy_keys/_form.html.haml b/app/views/deploy_keys/_form.html.haml index 5fb83021dc0..71bf309dd8b 100644 --- a/app/views/deploy_keys/_form.html.haml +++ b/app/views/deploy_keys/_form.html.haml @@ -18,6 +18,6 @@ = link_to "here", help_ssh_path .actions - = f.submit 'Save', class: "btn-save btn" + = f.submit 'Create', class: "btn-create btn" = link_to "Cancel", project_deploy_keys_path(@project), class: "btn btn-cancel" diff --git a/app/views/deploy_keys/_show.html.haml b/app/views/deploy_keys/_show.html.haml deleted file mode 100644 index 635054350ec..00000000000 --- a/app/views/deploy_keys/_show.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%tr - %td - %a{href: project_deploy_key_path(key.project, key)} - %strong= key.title - %td - %span.update-author - Added - = time_ago_in_words(key.created_at) - ago - %td - = link_to 'Remove', project_deploy_key_path(key.project, key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right" - diff --git a/app/views/deploy_keys/index.html.haml b/app/views/deploy_keys/index.html.haml index 80d30e1c2dc..7801302d3f4 100644 --- a/app/views/deploy_keys/index.html.haml +++ b/app/views/deploy_keys/index.html.haml @@ -1,17 +1,32 @@ = render "projects/settings_nav" %p.slead - Deploy keys allow read-only access to repository. It matches perfectly for CI, staging or production servers. + Deploy keys allow read-only access to repository. They can be used for CI, staging or production servers - - if can? current_user, :admin_project, @project - = link_to new_project_deploy_key_path(@project), class: "btn btn-small", title: "New Deploy Key" do - Add Deploy Key -- if @keys.any? - %table - %thead - %tr - %th Keys - %th - %th - - @keys.each do |key| - = render(partial: 'show', locals: {key: key}) +%p + You can create a deploy key or add existing one + = link_to new_project_deploy_key_path(@project), class: "btn btn-primary pull-right", title: "New Deploy Key" do + %i.icon-plus + New Deploy Key + +%hr.clearfix + +.row + .span6.enabled-keys + %h5.cgreen + Enabled deploy keys + %small for this project + %ul.bordered-list + = render @enabled_keys + - if @enabled_keys.blank? + .light-well + %p.nothing_here_message Create #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add existing one + .span6.available-keys + %h5 + Available deploy keys + %small from projects you are able to manage + %ul.bordered-list + = render @available_keys + - if @available_keys.blank? + .light-well + %p.nothing_here_message All deploy keys created in projects you own will be displayed here diff --git a/app/views/deploy_keys/show.html.haml b/app/views/deploy_keys/show.html.haml index 0a9f376d046..5b59d322343 100644 --- a/app/views/deploy_keys/show.html.haml +++ b/app/views/deploy_keys/show.html.haml @@ -12,4 +12,4 @@ %hr %pre= @key.key .pull-right - = link_to 'Remove', project_deploy_key_path(@key.project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key" + = link_to 'Remove', project_deploy_key_path(@project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key" diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb index 5399a961570..adc9b672092 100644 --- a/app/views/devise/confirmations/new.html.erb +++ b/app/views/devise/confirmations/new.html.erb @@ -1,6 +1,6 @@ <h2>Resend confirmation instructions</h2> -<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %> +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> <%= devise_error_messages! %> <div><%= f.label :email %><br /> @@ -9,4 +9,4 @@ <div><%= f.submit "Resend confirmation instructions" %></div> <% end %> -<%= render :partial => "devise/shared/links" %> +<%= render partial: "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index a6ea8ca17e8..7b4fd526964 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -2,4 +2,4 @@ <p>You can confirm your account through the link below:</p> -<p><%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %></p> +<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @resource.confirmation_token) %></p> diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index ae9e888abb9..e1144e943b4 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -2,7 +2,7 @@ <p>Someone has requested a link to change your password, and you can do this through the link below.</p> -<p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %></p> +<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @resource.reset_password_token) %></p> <p>If you didn't request this, please ignore this email.</p> <p>Your password won't change until you access the link above and create a new one.</p> diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb index 2263c219522..0429883f05b 100644 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -4,4 +4,4 @@ <p>Click the link below to unlock your account:</p> -<p><%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %></p> +<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @resource.unlock_token) %></p> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index dd26e8a47b8..139acf28a9f 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,6 +1,6 @@ <h2>Edit <%= resource_name.to_s.humanize %></h2> -<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <div><%= f.label :email %><br /> @@ -18,11 +18,11 @@ <div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> <%= f.password_field :current_password %></div> -<div><%= f.submit "Update", :class => "input_button" %></div> +<div><%= f.submit "Update", class: "input_button" %></div> <% end %> <h3>Cancel my account</h3> -<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p> +<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), confirm: "Are you sure?", method: :delete %>.</p> <%= link_to "Back", :back %> diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml new file mode 100644 index 00000000000..1ca43d5dd08 --- /dev/null +++ b/app/views/devise/sessions/_new_base.html.haml @@ -0,0 +1,13 @@ += form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| + = f.text_field :login, class: "text top", placeholder: "Username or Email", autofocus: "autofocus" + = f.password_field :password, class: "text bottom", placeholder: "Password" + - if devise_mapping.rememberable? + .clearfix.inputs-list + %label.checkbox.remember_me{for: "user_remember_me"} + = f.check_box :remember_me + %span Remember me + = f.submit "Sign in", class: "btn-create btn" + .pull-right + = link_to "Forgot your password?", new_password_path(resource_name), class: "btn" + + diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index eb8c5194607..575d33949b6 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,29 +1,5 @@ -= form_tag(user_omniauth_callback_path(:ldap), :class => "login-box", :id => 'new_ldap_user' ) do - = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo" - = text_field_tag :username, nil, {:class => "text top", :placeholder => "LDAP Login", :autofocus => "autofocus"} - = password_field_tag :password, nil, {:class => "text bottom", :placeholder => "Password"} += form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do + = text_field_tag :username, nil, {class: "text top", placeholder: "LDAP Login", autofocus: "autofocus"} + = password_field_tag :password, nil, {class: "text bottom", placeholder: "Password"} %br/ - = submit_tag "LDAP Sign in", :class => "btn-primary btn" - - if devise_mapping.omniauthable? - - (resource_class.omniauth_providers - [:ldap]).each do |provider| - %hr/ - = link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn btn-primary" - %br/ - %hr/ - %a#other_form_toggle{:href => "#", :onclick => "javascript:$('#new_user').toggle();"} Other Sign in - :javascript - $(function() { - $('#new_user').toggle(); - }); -= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| - = f.text_field :email, :class => "text top", :placeholder => "Email" - = f.password_field :password, :class => "text bottom", :placeholder => "Password" - - if devise_mapping.rememberable? - .clearfix.inputs-list - %label.checkbox.remember_me{:for => "user_remember_me"} - = f.check_box :remember_me - %span Remember me - %br/ - = f.submit "Sign in", :class => "btn-primary btn" - .pull-right - = render :partial => "devise/shared/links" + = submit_tag "LDAP Sign in", class: "btn-create btn" diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml new file mode 100644 index 00000000000..710a5d52514 --- /dev/null +++ b/app/views/devise/sessions/_oauth_providers.html.haml @@ -0,0 +1,10 @@ +- if enabled_oauth_providers.present? + %hr + %div{:'data-no-turbolink' => 'data-no-turbolink'} + %span Sign in with: + - (enabled_oauth_providers - [:ldap]).each do |provider| + %span + - if default_providers.include?(provider) + = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) + - else + = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn" diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index d904e701b8a..8b71ebed5f4 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,28 +1,31 @@ -- if ldap_enable? - = render :partial => 'devise/sessions/new_ldap' -- else - = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| - = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo" - = f.email_field :email, :class => "text top", :placeholder => "Email", :autofocus => "autofocus" - = f.password_field :password, :class => "text bottom", :placeholder => "Password" - - if devise_mapping.rememberable? - .clearfix.inputs-list - %label.checkbox.remember_me{:for => "user_remember_me"} - = f.check_box :remember_me - %span Remember me - %br/ - = f.submit "Sign in", :class => "btn-create btn" - .pull-right - = link_to "Forgot your password?", new_password_path(resource_name), :class => "btn" - %br/ - - if Gitlab.config.gitlab.signup_enabled - %hr/ +.login-box + = image_tag "login-logo.png", width: "304", height: "66", class: "login-logo", alt: "Login Logo" + + - if ldap_enabled? + %ul.nav.nav-tabs + %li.active + = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab' + %li + = link_to 'Ordinary', '#tab-signin', 'data-toggle' => 'tab' + .tab-content + %div#tab-ldap.tab-pane.active + = render partial: 'devise/sessions/new_ldap' + %div#tab-signin.tab-pane + = render partial: 'devise/sessions/new_base' + + - else + = render partial: 'devise/sessions/new_base' + + + = render 'devise/sessions/oauth_providers' if devise_mapping.omniauthable? + + - if Gitlab.config.gitlab.signup_enabled + %hr + %div Don't have an account? - = link_to "Sign up", new_registration_path(resource_name) - - if devise_mapping.omniauthable? && resource_class.omniauth_providers.present? - %hr - %div - %span Sign in with: - - resource_class.omniauth_providers.each do |provider| - %span - = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) + %strong + = link_to "Sign up", new_registration_path(resource_name) + + - if extra_config.has_key?('sign_in_text') + %hr + = markdown(extra_config.sign_in_text) diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb index d7499d14ec5..a47b5ff1ec7 100644 --- a/app/views/devise/shared/_links.erb +++ b/app/views/devise/shared/_links.erb @@ -1,5 +1,5 @@ <%- if controller_name != 'sessions' %> - <%= link_to "Sign in", new_session_path(resource_name), :class => "btn" %><br /> + <%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br /> <% end -%> <%- if devise_mapping.registerable? && controller_name != 'registrations' %> @@ -7,7 +7,7 @@ <% end -%> <%- if devise_mapping.recoverable? && controller_name != 'passwords' %> -<%= link_to "Forgot your password?", new_password_path(resource_name), :class => "btn" %><br /> +<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn" %><br /> <% end -%> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb index b787e648ca2..f9277d1673f 100644 --- a/app/views/devise/unlocks/new.html.erb +++ b/app/views/devise/unlocks/new.html.erb @@ -1,6 +1,6 @@ <h2>Resend unlock instructions</h2> -<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %> +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> <%= devise_error_messages! %> <div><%= f.label :email %><br /> @@ -9,4 +9,4 @@ <div><%= f.submit "Resend unlock instructions" %></div> <% end %> -<%= render :partial => "devise/shared/links" %> +<%= render partial: "devise/shared/links" %> diff --git a/app/views/tree/edit.html.haml b/app/views/edit_tree/show.html.haml index 81918e509b8..17d813ce75e 100644 --- a/app/views/tree/edit.html.haml +++ b/app/views/edit_tree/show.html.haml @@ -1,24 +1,24 @@ .file-editor - = form_tag(project_tree_path(@project, @id), method: :put, class: "form-horizontal") do + = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do .file_holder .file_title %i.icon-file %span.file_name - = @tree.path + = @path %small on %strong= @ref %span.options .btn-group.tree-btn-group - = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: "Are you sure?" + = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: leave_edit_message .file_content.code - %pre#editor= @tree.data + %pre#editor= @blob.data .control-group.commit_message-group = label_tag 'commit_message', class: "control-label" do Commit message .controls - = text_area_tag 'commit_message', '', placeholder: "Update #{@tree.name}", required: true, rows: 3 + = text_area_tag 'commit_message', '', placeholder: "Update #{@blob.name}", required: true, rows: 3 .form-actions = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: :file_content @@ -27,10 +27,11 @@ .message to branch %strong= @ref - = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", confirm: "Are you sure?" + = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message :javascript - var ace_mode = "#{@tree.language.try(:ace_mode)}"; + ace.config.set("modePath", "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") + var ace_mode = "#{@blob.language.try(:ace_mode)}"; var editor = ace.edit("editor"); if (ace_mode) { editor.getSession().setMode('ace/mode/' + ace_mode); diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index ea417aa9f30..2d80fc103f6 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -1,8 +1,7 @@ -- commit = CommitDecorator.decorate(commit) %li.commit %p - = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" - %span= commit.author_name + = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id" + %span= commit[:author][:name] – - = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16 - = gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding" + = image_tag gravatar_icon(commit[:author][:email]), class: "avatar", width: 16 + = gfm escape_once(truncate(commit[:message], length: 50)) rescue "--broken encoding" diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index d09e6e03f01..e44b366040f 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -1,12 +1,12 @@ -%div{:xmlns => "http://www.w3.org/1999/xhtml"} +%div{xmlns: "http://www.w3.org/1999/xhtml"} - event.commits.first(15).each do |commit| %p - %strong= commit.author_name - = link_to "(##{commit.short_id})", project_commit_path(event.project, :id => commit.id) + %strong= commit[:author][:name] + = link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id]) %i at - = commit.committed_date.strftime("%Y-%m-%d %H:%M:%S") - %blockquote= simple_format(escape_once(commit.safe_message)) + = commit[:timestamp].to_time.to_s(:short) + %blockquote= simple_format(escape_once(commit[:message])) - if event.commits_count > 15 %p %i diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index 53cbe1c94ce..05d3832c7f9 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -2,11 +2,15 @@ %span.author_name= link_to_author event %span.event_label{class: event.action_name}= event_action_name(event) - if event.target - %strong= link_to_gfm truncate(event.target_title), [event.project, event.target] + %strong= link_to "##{event.target_id}", [event.project, event.target] - else - %strong= gfm truncate(event.target_title) + %strong= gfm event.target_title at - if event.project = link_to_project event.project - else = event.project_name +.event-body + .event-note + - if event.target.respond_to?(:title) + = event.target.title diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 119b8e828d0..f2b8dc4238c 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -21,5 +21,5 @@ %li.commits-stat - if event.commits_count > 2 %span ... and #{event.commits_count - 2} more commits. - = link_to project_compare_path(event.project, from: event.parent_commit.id, to: event.last_commit.id) do - %strong Compare → #{event.parent_commit.id[0..7]}...#{event.last_commit.id[0..7]} + = link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do + %strong Compare → #{event.commit_from[0..7]}...#{event.commit_to[0..7]} diff --git a/app/views/graph/_head.html.haml b/app/views/graph/_head.html.haml index fba9a958a19..7a5b3c6f43d 100644 --- a/app/views/graph/_head.html.haml +++ b/app/views/graph/_head.html.haml @@ -3,14 +3,24 @@ .clearfix .pull-left - = render partial: 'shared/ref_switcher', locals: {destination: 'graph', path: @path} + = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} + .pull-left + = form_tag project_graph_path(@project, @id), method: :get do |f| + .control-group + = label_tag :filter_ref, "Show only selected ref", class: 'control-label light' + .controls + = check_box_tag :filter_ref, 1, @options[:filter_ref] + - @options.each do |key, value| + = hidden_field_tag(key, value, id: nil) unless key == "filter_ref" .search.pull-right - = form_tag project_graph_path(@project, params[:id]), method: :get do |f| + = form_tag project_graph_path(@project, @id), method: :get do |f| .control-group = label_tag :search , "Looking for commit:", class: 'control-label light' .controls - = text_field_tag :q, @q, placeholder: "Input SHA", class: "search-input xlarge" + = text_field_tag :q, @options[:q], placeholder: "Input SHA", class: "search-input xlarge" = button_tag type: 'submit', class: 'btn vtop' do %i.icon-search + - @options.each do |key, value| + = hidden_field_tag(key, value, id: nil) unless key == "q" diff --git a/app/views/graph/show.html.haml b/app/views/graph/show.html.haml index e45aca1ddcb..0ee6648317c 100644 --- a/app/views/graph/show.html.haml +++ b/app/views/graph/show.html.haml @@ -7,11 +7,12 @@ :javascript var branch_graph; - $(function(){ - branch_graph = new BranchGraph($("#holder"), { - url: '#{project_graph_path(@project, @ref, q: @q, format: :json)}', - commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}', - ref: '#{@ref}', - commit_id: '#{@commit.id}' - }); + $("#filter_ref").click(function() { + $(this).closest('form').submit(); + }); + branch_graph = new BranchGraph($("#holder"), { + url: '#{project_graph_path(@project, @ref, @options.merge(format: :json))}', + commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}', + ref: '#{@ref}', + commit_id: '#{@commit.id}' }); diff --git a/app/views/graph/show.json.erb b/app/views/graph/show.json.erb index d0a0709ac47..9a62cdb3dc9 100644 --- a/app/views/graph/show.json.erb +++ b/app/views/graph/show.json.erb @@ -7,13 +7,13 @@ { parents: parents_zip_spaces(c.parents(@graph.map), c.parent_spaces), author: { - name: c.author.name, - email: c.author.email, - icon: gravatar_icon(c.author.email, 20) + name: c.author_name, + email: c.author_email, + icon: gravatar_icon(c.author_email, 20) }, time: c.time, space: c.spaces.first, - refs: join_with_space(c.refs), + refs: get_refs(c), id: c.sha, date: c.date, message: c.message, diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index eb4f324b358..0f8c140c067 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -26,7 +26,7 @@ .pull-right = link_to 'Team', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Remove', project, confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" + = link_to 'Remove', project, 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 diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 96aa2a1626d..6d642b65cbd 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -14,7 +14,7 @@ - project = group[0] %h5.title = link_to_project project - %ul.well-list.issues_table + %ul.well-list.issues-list - group[1].each do |issue| = render issue %hr diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 36ee4922731..b395a8bc6a3 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -24,5 +24,5 @@ %li Group is kind of directory for several projects %li All created groups are private %li People within a group see only projects they have access to - %li All projects of group will be stored in group directory + %li All projects of group will be stored in a group directory %li You will be able to move existing projects into group diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index 5f2999c3d8e..edf03642d82 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -8,10 +8,9 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear @events.each do |event| if event.proper? - event = EventDecorator.decorate(event) xml.entry do - event_link = event.feed_url - event_title = event.feed_title + event_link = event_feed_url(event) + event_title = event_feed_title(event) xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" xml.link :href => event_link diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index adf249f656a..1ce008f7e85 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,4 +1,4 @@ -.projects +.dashboard .activities.span8 = render "events/event_last_push", event: @last_push = link_to dashboard_path, class: 'btn btn-tiny' do @@ -13,7 +13,7 @@ .loading.hide .side.span4 - if @group.description.present? - .description.well.light + .description.well.well-small.light = @group.description = render "projects", projects: @projects .prepend-top-20 @@ -23,10 +23,4 @@ News Feed %hr - .gitlab-promo - = link_to "Homepage", "http://gitlabhq.com" - = link_to "Blog", "http://blog.gitlabhq.com" - = link_to "@gitlabhq", "https://twitter.com/gitlabhq" - -:javascript - $(function(){ Pager.init(20, true); }); + = render 'shared/promo' diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index ffea654df73..1685c6eec53 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -19,7 +19,7 @@ %ul.well-list %li Email your - = mail_to Gitlab.config.gitlab.support_email, "support contact" + = mail_to gitlab_config.support_email, "support contact" %li Use the = link_to "search bar", '#', onclick: "$('#search').focus();" @@ -27,6 +27,8 @@ %li Ask in our = link_to "support forum", "https://groups.google.com/forum/#!forum/gitlabhq" + or on + = link_to "Stack Overflow", "http://stackoverflow.com/questions/tagged/gitlab" %li Browse our = link_to "issue tracker", "https://github.com/gitlabhq/gitlabhq/issues" diff --git a/app/views/issues/_filter.html.haml b/app/views/issues/_filter.html.haml index b621f11bc5b..8495c323cb8 100644 --- a/app/views/issues/_filter.html.haml +++ b/app/views/issues/_filter.html.haml @@ -1,7 +1,7 @@ = form_tag project_issues_path(@project), method: 'get' do %fieldset %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if !params[:status])} + %li{class: ("active" if !params[:status] || params[:status].blank?)} = link_to project_issues_path(@project, status: nil) do Open %li{class: ("active" if params[:status] == 'assigned-to-me')} diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml index 6d7613a700d..2bb5e6ca5ac 100644 --- a/app/views/issues/_form.html.haml +++ b/app/views/issues/_form.html.haml @@ -19,7 +19,9 @@ = f.label :assignee_id do %i.icon-user Assign to - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) + .input + = f.select(:assignee_id, @project.users.alphabetically.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) + = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' .issue_milestone.pull-left = f.label :milestone_id do %i.icon-time @@ -38,7 +40,7 @@ .clearfix = f.label :description, "Details" .input - = f.text_area :description, maxlength: 2000, class: "xxlarge js-gfm-input", rows: 14 + = f.text_area :description, class: "xxlarge js-gfm-input", rows: 14 %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. @@ -55,31 +57,33 @@ :javascript - $(function(){ - $("#issue_label_list") - .bind( "keydown", function( event ) { - if ( event.keyCode === $.ui.keyCode.TAB && - $( this ).data( "autocomplete" ).menu.active ) { - event.preventDefault(); - } - }) - .autocomplete({ - minLength: 0, - source: function( request, response ) { - response( $.ui.autocomplete.filter( - #{raw labels_autocomplete_source}, extractLast( request.term ) ) ); - }, - focus: function() { - return false; - }, - select: function(event, ui) { - var terms = split( this.value ); - terms.pop(); - terms.push( ui.item.value ); - terms.push( "" ); - this.value = terms.join( ", " ); - return false; - } - }); - }); + $("#issue_label_list") + .bind( "keydown", function( event ) { + if ( event.keyCode === $.ui.keyCode.TAB && + $( this ).data( "autocomplete" ).menu.active ) { + event.preventDefault(); + } + }) + .autocomplete({ + minLength: 0, + source: function( request, response ) { + response( $.ui.autocomplete.filter( + #{raw labels_autocomplete_source}, extractLast( request.term ) ) ); + }, + focus: function() { + return false; + }, + select: function(event, ui) { + var terms = split( this.value ); + terms.pop(); + terms.push( ui.item.value ); + terms.push( "" ); + this.value = terms.join( ", " ); + return false; + } + }); + $('.assign-to-me-link').on('click', function(e){ + $('#issue_assignee_id').val("#{current_user.id}").trigger("liszt:updated"); + e.preventDefault(); + }); diff --git a/app/views/issues/_issue.html.haml b/app/views/issues/_issue.html.haml index 3d1ecd43881..f44c0a6c81f 100644 --- a/app/views/issues/_issue.html.haml +++ b/app/views/issues/_issue.html.haml @@ -1,12 +1,37 @@ %li{ id: dom_id(issue), class: issue_css_classes(issue), url: project_issue_path(issue.project, issue) } - if controller.controller_name == 'issues' - .issue_check + .issue-check = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) - .pull-right + + .issue-title + %span.light= "##{issue.id}" + = link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title" + + .issue-info + - if issue.assignee + assigned to #{link_to_member(@project, issue.assignee)} + - else + unassigned + - if issue.votes_count > 0 + = render 'votes/votes_inline', votable: issue - if issue.notes.any? - %span.btn.btn-small.disabled.grouped - %i.icon-comment + %span + %i.icon-comments = issue.notes.count + - if issue.milestone_id? + %span + %i.icon-time + = issue.milestone.title + .pull-right + %small updated #{time_ago_in_words(issue.updated_at)} ago + + .issue-labels + - issue.labels.each do |label| + %span{class: "label #{label_css_class(label.name)}"} + %i.icon-tag + = label.name + + .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 @@ -16,24 +41,4 @@ %i.icon-edit Edit - - if issue.assignee - = image_tag gravatar_icon(issue.assignee_email), class: "avatar" - - else - = image_tag "no_avatar.png", class: "avatar" - - %p= link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title" - - %span.update-author - %span.cdark= "##{issue.id}" - - if issue.assignee - assigned to #{issue.assignee_name} - - else - - - if issue.votes_count > 0 - = render 'votes/votes_inline', votable: issue - %span - - issue.labels.each do |label| - %span.label - %i.icon-tag - = label.name diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml index dc7db9061ac..cc8d8d9cae2 100644 --- a/app/views/issues/_issues.html.haml +++ b/app/views/issues/_issues.html.haml @@ -1,12 +1,94 @@ -= render @issues +.ui-box + .title + = 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_issues_text Update selected issues with + .left + = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status") + = select_tag('update[assignee_id]', options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee") + = select_tag('update[milestone_id]', options_from_collection_for_select(issues_active_milestones, "id", "title", params[:milestone_id]), 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_issues_with_filter_path(@project, label_name: nil) do + Any + - issue_label_names.each do |label_name| + %li + = link_to project_issues_with_filter_path(@project, 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_issues_with_filter_path(@project, assignee_id: nil) do + Any + = link_to project_issues_with_filter_path(@project, assignee_id: 0) do + Unassigned + - @project.users.sort_by(&:name).each do |user| + %li + = link_to project_issues_with_filter_path(@project, assignee_id: user.id) do + = image_tag gravatar_icon(user.email), class: "avatar s16" + = 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" + Unspecified + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_issues_with_filter_path(@project, milestone_id: nil) do + Any + = link_to project_issues_with_filter_path(@project, milestone_id: 0) do + Unspecified + - issues_active_milestones.each do |milestone| + %li + = link_to project_issues_with_filter_path(@project, milestone_id: milestone.id) do + %strong= milestone.title + %small.light= milestone.expires_at + + + %ul.well-list.issues-list + = render @issues + - if @issues.blank? + %li + %h4.nothing_here_message Nothing to show here - if @issues.present? - %li.bottom - .left= paginate @issues, remote: true, theme: "gitlab" - .pull-right - %span.issue_counter #{@issues.total_count} - issues for this filter -- else - %li - %h4.nothing_here_message Nothing to show here + .pull-right + %span.issue_counter #{@issues.total_count} + issues for this filter + = paginate @issues, remote: true, theme: "gitlab" diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml index 875f29e2600..bf33769349a 100644 --- a/app/views/issues/index.html.haml +++ b/app/views/issues/index.html.haml @@ -4,47 +4,22 @@ Issues %span (<span class=issue_counter>#{@issues.total_count}</span>) .pull-right - .span5 + .span6 - 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-primary pull-right", title: "New Issue", id: "new_issue_link" do %i.icon-plus New Issue - = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: 'pull-right' do - = hidden_field_tag :project_id, @project.id, { id: 'project_id' } - = hidden_field_tag :status, params[:status] - = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 pull-right neib search-text-input' } + = form_tag project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: 'pull-right' do + = hidden_field_tag :status, params[:status], id: 'search_status' + = hidden_field_tag :assignee_id, params[:assignee_id], id: 'search_assignee_id' + = hidden_field_tag :milestone_id, params[:milestone_id], id: 'search_milestone_id' + = hidden_field_tag :label_name, params[:label_name], id: 'search_label_name' + = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search input-xlarge append-right-10 search-text-input' } .clearfix .row .span3 = render 'filter', entity: 'issue' - .span9 - %div#issues-table-holder.ui-box - .title - = 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_issues_text Update selected issues with - .left - = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status") - = select_tag('update[assignee_id]', options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee") - = select_tag('update[milestone_id]', options_from_collection_for_select(issues_active_milestones, "id", "title", params[:milestone_id]), 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 - = form_tag project_issues_path(@project), method: :get do - = select_tag(:label_name, options_for_select(issue_tags, params[:label_name]), prompt: "Labels") - = select_tag(:assignee_id, options_from_collection_for_select([unassigned_filter] + @project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee") - = select_tag(:milestone_id, options_from_collection_for_select([unassigned_filter] + issues_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone") - = hidden_field_tag :status, params[:status] - - %ul#issues-table.well-list.issues_table - = render "issues" - -:javascript - $(function(){ - issuesPage(); - }) + .span9.issues-holder + = render "issues" diff --git a/app/views/issues/index.js.haml b/app/views/issues/index.js.haml index 48d7f582be2..1be6a64f535 100644 --- a/app/views/issues/index.js.haml +++ b/app/views/issues/index.js.haml @@ -1,2 +1,4 @@ :plain - $('#issues-table').html("#{escape_javascript(render('issues'))}"); + $('.issues-holder').html("#{escape_javascript(render('issues'))}"); + History.replaceState({path: "#{request.url}"}, document.title, "#{request.url}"); + Issues.reload(); diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml index 70f94e52942..2e204b8240d 100644 --- a/app/views/issues/show.html.haml +++ b/app/views/issues/show.html.haml @@ -6,6 +6,9 @@ = @issue.created_at.stamp("Aug 21, 2011") %span.pull-right + = link_to new_project_issue_path(@project), class: "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" @@ -44,7 +47,7 @@ .pull-right - @issue.labels.each do |label| - %span.label + %span{class: "label #{label_css_class(label.name)}"} %i.icon-tag = label.name diff --git a/app/views/kaminari/admin/_first_page.html.haml b/app/views/kaminari/admin/_first_page.html.haml deleted file mode 100644 index 41c9c0b3af6..00000000000 --- a/app/views/kaminari/admin/_first_page.html.haml +++ /dev/null @@ -1,9 +0,0 @@ --# Link to the "First" page --# available local variables --# url: url to the first page --# current_page: a page object for the currently displayed page --# num_pages: total number of pages --# per_page: number of items to fetch per page --# remote: data-remote -%span.first - = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote diff --git a/app/views/kaminari/admin/_gap.html.haml b/app/views/kaminari/admin/_gap.html.haml deleted file mode 100644 index 3ffd12f8587..00000000000 --- a/app/views/kaminari/admin/_gap.html.haml +++ /dev/null @@ -1,9 +0,0 @@ --# Non-link tag that stands for skipped pages... --# available local variables --# current_page: a page object for the currently displayed page --# num_pages: total number of pages --# per_page: number of items to fetch per page --# remote: data-remote -%li{class: "page"} - %span.page.gap - = raw(t 'views.pagination.truncate') diff --git a/app/views/kaminari/admin/_last_page.html.haml b/app/views/kaminari/admin/_last_page.html.haml deleted file mode 100644 index b03a206224c..00000000000 --- a/app/views/kaminari/admin/_last_page.html.haml +++ /dev/null @@ -1,9 +0,0 @@ --# Link to the "Last" page --# available local variables --# url: url to the last page --# current_page: a page object for the currently displayed page --# num_pages: total number of pages --# per_page: number of items to fetch per page --# remote: data-remote -%span.last - = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote} diff --git a/app/views/kaminari/admin/_next_page.html.haml b/app/views/kaminari/admin/_next_page.html.haml deleted file mode 100644 index 00c5f0b6f4e..00000000000 --- a/app/views/kaminari/admin/_next_page.html.haml +++ /dev/null @@ -1,9 +0,0 @@ --# Link to the "Next" page --# available local variables --# url: url to the next page --# current_page: a page object for the currently displayed page --# num_pages: total number of pages --# per_page: number of items to fetch per page --# remote: data-remote -%li.next - = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote diff --git a/app/views/kaminari/admin/_page.html.haml b/app/views/kaminari/admin/_page.html.haml deleted file mode 100644 index a52d883b9a8..00000000000 --- a/app/views/kaminari/admin/_page.html.haml +++ /dev/null @@ -1,10 +0,0 @@ --# Link showing page number --# available local variables --# page: a page object for "this" page --# url: url to this page --# current_page: a page object for the currently displayed page --# num_pages: total number of pages --# per_page: number of items to fetch per page --# remote: data-remote -%li{class: "page#{' active' if page.current?}"} - = link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil} diff --git a/app/views/kaminari/admin/_paginator.html.haml b/app/views/kaminari/admin/_paginator.html.haml deleted file mode 100644 index 6f9fb332261..00000000000 --- a/app/views/kaminari/admin/_paginator.html.haml +++ /dev/null @@ -1,17 +0,0 @@ --# The container tag --# available local variables --# current_page: a page object for the currently displayed page --# num_pages: total number of pages --# per_page: number of items to fetch per page --# remote: data-remote --# paginator: the paginator that renders the pagination tags inside -= paginator.render do - %div.pagination - %ul - = prev_page_tag unless current_page.first? - - each_page do |page| - - if page.left_outer? || page.right_outer? || page.inside_window? - = page_tag page - - elsif !page.was_truncated? - = gap_tag - = next_page_tag unless current_page.last? diff --git a/app/views/kaminari/admin/_prev_page.html.haml b/app/views/kaminari/admin/_prev_page.html.haml deleted file mode 100644 index f673abdb3ae..00000000000 --- a/app/views/kaminari/admin/_prev_page.html.haml +++ /dev/null @@ -1,9 +0,0 @@ --# Link to the "Previous" page --# available local variables --# url: url to the previous page --# current_page: a page object for the currently displayed page --# num_pages: total number of pages --# per_page: number of items to fetch per page --# remote: data-remote -%li{class: "prev" } - = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote diff --git a/app/views/kaminari/gitlab/_gap.html.haml b/app/views/kaminari/gitlab/_gap.html.haml index f82f185ac35..3ffd12f8587 100644 --- a/app/views/kaminari/gitlab/_gap.html.haml +++ b/app/views/kaminari/gitlab/_gap.html.haml @@ -4,5 +4,6 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.page.gap - = raw(t 'views.pagination.truncate') +%li{class: "page"} + %span.page.gap + = raw(t 'views.pagination.truncate') diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml index 296cceb080b..00c5f0b6f4e 100644 --- a/app/views/kaminari/gitlab/_next_page.html.haml +++ b/app/views/kaminari/gitlab/_next_page.html.haml @@ -5,5 +5,5 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.next +%li.next = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml index 19456dcc058..a52d883b9a8 100644 --- a/app/views/kaminari/gitlab/_page.html.haml +++ b/app/views/kaminari/gitlab/_page.html.haml @@ -6,5 +6,5 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span{class: "page#{' current' if page.current?}"} - = link_to_unless page.current?, page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil} +%li{class: "page#{' active' if page.current?}"} + = link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil} diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml index 6dd5a5782a2..6f9fb332261 100644 --- a/app/views/kaminari/gitlab/_paginator.html.haml +++ b/app/views/kaminari/gitlab/_paginator.html.haml @@ -6,11 +6,12 @@ -# remote: data-remote -# paginator: the paginator that renders the pagination tags inside = paginator.render do - %nav.gitlab_pagination - = prev_page_tag - - each_page do |page| - - if page.left_outer? || page.right_outer? || page.inside_window? - = page_tag page - - elsif !page.was_truncated? - = gap_tag - = next_page_tag + %div.pagination + %ul + = prev_page_tag unless current_page.first? + - each_page do |page| + - if page.left_outer? || page.right_outer? || page.inside_window? + = page_tag page + - elsif !page.was_truncated? + = gap_tag + = next_page_tag unless current_page.last? diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml index 5c2061690ac..f673abdb3ae 100644 --- a/app/views/kaminari/gitlab/_prev_page.html.haml +++ b/app/views/kaminari/gitlab/_prev_page.html.haml @@ -5,5 +5,5 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.prev +%li{class: "prev" } = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote diff --git a/app/views/labels/_label.html.haml b/app/views/labels/_label.html.haml index 027b041d58e..2b1aafc546b 100644 --- a/app/views/labels/_label.html.haml +++ b/app/views/labels/_label.html.haml @@ -1,9 +1,15 @@ +- frequency = @project.issues.tagged_with(label.name).count %li %strong - %i.icon-tag - = label.name + %span{class: "label #{label_css_class(label.name)}"} + %i.icon-tag + - if frequency.zero? + %span.light= label.name + - else + = label.name .pull-right - = link_to project_issues_path(label_name: label.name) do - %strong - = pluralize(label.count, 'issue') - = "»" + - unless frequency.zero? + = link_to project_issues_path(label_name: label.name) do + %strong + = pluralize(frequency, 'issue') + = "»" diff --git a/app/views/labels/index.html.haml b/app/views/labels/index.html.haml index 6eb2c00e56d..53f411d932c 100644 --- a/app/views/labels/index.html.haml +++ b/app/views/labels/index.html.haml @@ -3,12 +3,12 @@ %h3.page_title Labels %br -%div.ui-box - %ul.well-list.labels-table + +- if @labels.present? + %ul.bordered-list.labels-table - @labels.each do |label| = render 'label', label: label - - unless @labels.present? - %li - %h3.nothing_here_message Nothing to show here - +- 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 diff --git a/app/views/layouts/_google_analytics.html.haml b/app/views/layouts/_google_analytics.html.haml new file mode 100644 index 00000000000..81e03c7eff2 --- /dev/null +++ b/app/views/layouts/_google_analytics.html.haml @@ -0,0 +1,10 @@ +:javascript + var _gaq = _gaq || []; + _gaq.push(['_setAccount', '#{extra_config.google_analytics_id}']); + _gaq.push(['_trackPageview']); + + (function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index eb83fd2fd45..0775abea3dd 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -9,6 +9,8 @@ = csrf_meta_tags = include_gon + = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') + -# Atom feed - if current_user - if controller_name == 'projects' && action_name == 'index' diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 8f6873e1dfc..2ea6c3e46d9 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -9,6 +9,11 @@ %h1.project_name= title %ul.nav %li + %a + %div.hide.turbolink-spinner + %i.icon-refresh.icon-spin + Loading... + %li = render "layouts/search" %li = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do @@ -31,5 +36,3 @@ = link_to current_user, class: "profile-pic" do = image_tag gravatar_icon(current_user.email, 26) - -= render "layouts/init_auto_complete" diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 8f8c7d8885e..3549794b90d 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,17 +1,6 @@ :javascript $(function() { - GitLab.GfmAutoComplete.Members.url = "#{ "/api/v3/projects/#{@project.id}/members" if @project }"; - GitLab.GfmAutoComplete.Members.params.private_token = "#{current_user.private_token}"; - - GitLab.GfmAutoComplete.Emoji.data = #{raw emoji_autocomplete_source}; - // convert the list so that the items have the right format for completion - GitLab.GfmAutoComplete.Emoji.data = $.map(GitLab.GfmAutoComplete.Emoji.data, function(value) { - return { - name: value, - insert: value+':', - image: '#{image_path("emoji")}/'+value+'.png' - } - }); - + GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project)}" + GitLab.GfmAutoComplete.Emoji.assetBase = '#{image_path("emoji")}' GitLab.GfmAutoComplete.setup(); }); diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index c484af04704..a43a46b8726 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -2,12 +2,9 @@ = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = text_field_tag "search", nil, placeholder: "Search", class: "search-input" = hidden_field_tag :group_id, @group.try(:id) - = hidden_field_tag :project_id, @project.try(:id) - -:javascript - $(function(){ - $("#search").autocomplete({ - source: #{raw search_autocomplete_source}, - select: function(event, ui) { location.href = ui.item.url } - }); - }); + - if @project && @project.persisted? + = hidden_field_tag :project_id, @project.id + = 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 } diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 00a08e6131d..3a23cbdb376 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,27 +1,11 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Admin area" - %body{class: "#{app_theme} admin"} + %body{class: "#{app_theme} admin", :'data-page' => body_data_page} = render "layouts/head_panel", title: "Admin area" = render "layouts/flash" - .container - %ul.main_menu - = nav_link(controller: :dashboard, html_options: {class: 'home'}) do - = link_to admin_root_path, title: "Stats" do - %i.icon-home - = nav_link(controller: :projects) do - = link_to "Projects", admin_projects_path - = nav_link(controller: :teams) do - = link_to "Teams", admin_teams_path - = nav_link(controller: :groups) do - = link_to "Groups", admin_groups_path - = nav_link(controller: :users) do - = link_to "Users", admin_users_path - = nav_link(controller: :logs) do - = link_to "Logs", admin_logs_path - = nav_link(controller: :hooks) do - = link_to "Hooks", admin_hooks_path - = nav_link(controller: :resque) do - = link_to "Background Jobs", admin_resque_path + %nav.main-nav + .container= render 'layouts/nav/admin' + .container .content= yield diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 90c2653438d..792fe5e4a28 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,28 +1,11 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Dashboard" - %body{class: "#{app_theme} application"} + %body{class: "#{app_theme} application", :'data-page' => body_data_page } = render "layouts/head_panel", title: "Dashboard" = render "layouts/flash" - .container - %ul.main_menu - = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do - = link_to root_path, title: "Home" do - %i.icon-home - = nav_link(path: 'dashboard#projects') do - = link_to projects_dashboard_path do - Projects - = nav_link(path: 'dashboard#issues') do - = link_to issues_dashboard_path do - Issues - %span.count= current_user.assigned_issues.opened.count - = nav_link(path: 'dashboard#merge_requests') do - = link_to merge_requests_dashboard_path do - Merge Requests - %span.count= current_user.cared_merge_requests.opened.count - = nav_link(path: 'search#show') do - = link_to "Search", search_path - = nav_link(controller: :help) do - = link_to "Help", help_path + %nav.main-nav + .container= render 'layouts/nav/dashboard' + .container .content= yield diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 45528281ed0..0e955d59ff8 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,28 +1,11 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "#{@group.name}" - %body{class: "#{app_theme} application"} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/head_panel", title: "group: #{@group.name}" = render "layouts/flash" - .container - %ul.main_menu - = nav_link(path: 'groups#show', html_options: {class: 'home'}) do - = link_to group_path(@group), title: "Home" do - %i.icon-home - = 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 - = 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 - = nav_link(path: 'groups#people') do - = link_to "People", people_group_path(@group) - - - if can?(current_user, :manage_group, @group) - = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group), class: "tab " do - Settings + %nav.main-nav + .container= render 'layouts/nav/group' + .container .content= yield diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml new file mode 100644 index 00000000000..e9ca29ea3be --- /dev/null +++ b/app/views/layouts/nav/_admin.html.haml @@ -0,0 +1,19 @@ +%ul + = nav_link(controller: :dashboard, html_options: {class: 'home'}) do + = link_to admin_root_path, title: "Stats" do + %i.icon-home + = nav_link(controller: :projects) do + = link_to "Projects", admin_projects_path + = nav_link(controller: :teams) do + = link_to "Teams", admin_teams_path + = nav_link(controller: :groups) do + = link_to "Groups", admin_groups_path + = nav_link(controller: :users) do + = link_to "Users", admin_users_path + = nav_link(controller: :logs) do + = link_to "Logs", admin_logs_path + = nav_link(controller: :hooks) do + = link_to "Hooks", admin_hooks_path + = nav_link(controller: :background_jobs) do + = link_to "Background Jobs", admin_background_jobs_path + diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml new file mode 100644 index 00000000000..2ac35050b64 --- /dev/null +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -0,0 +1,20 @@ +%ul + = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do + = link_to root_path, title: "Home" do + %i.icon-home + = nav_link(path: 'dashboard#projects') do + = link_to projects_dashboard_path do + Projects + = nav_link(path: 'dashboard#issues') do + = link_to issues_dashboard_path do + Issues + %span.count= current_user.assigned_issues.opened.count + = nav_link(path: 'dashboard#merge_requests') do + = link_to merge_requests_dashboard_path do + Merge Requests + %span.count= current_user.cared_merge_requests.opened.count + = nav_link(path: 'search#show') do + = link_to "Search", search_path + = nav_link(controller: :help) do + = link_to "Help", help_path + diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml new file mode 100644 index 00000000000..f3cdb5ac457 --- /dev/null +++ b/app/views/layouts/nav/_group.html.haml @@ -0,0 +1,20 @@ +%ul + = nav_link(path: 'groups#show', html_options: {class: 'home'}) do + = link_to group_path(@group), title: "Home" do + %i.icon-home + = 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 + = 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 + = nav_link(path: 'groups#people') do + = link_to "People", people_group_path(@group) + + - if can?(current_user, :manage_group, @group) + = nav_link(path: 'groups#edit') do + = link_to edit_group_path(@group), class: "tab " do + Settings + diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml new file mode 100644 index 00000000000..e5e4b27c665 --- /dev/null +++ b/app/views/layouts/nav/_profile.html.haml @@ -0,0 +1,17 @@ +%ul + = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do + = link_to profile_path, title: "Profile" do + %i.icon-home + = nav_link(path: 'profiles#account') do + = link_to "Account", account_profile_path + = nav_link(controller: :notifications) do + = link_to "Notifications", profile_notifications_path + = nav_link(controller: :keys) do + = link_to keys_path do + SSH Keys + %span.count= current_user.keys.count + = nav_link(path: 'profiles#design') do + = link_to "Design", design_profile_path + = nav_link(path: 'profiles#history') do + = link_to "History", history_profile_path + diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml new file mode 100644 index 00000000000..ec3da964037 --- /dev/null +++ b/app/views/layouts/nav/_project.html.haml @@ -0,0 +1,43 @@ +%ul + = nav_link(path: 'projects#show', html_options: {class: "home"}) do + = link_to project_path(@project), title: "Project" do + %i.icon-home + + - unless @project.empty_repo? + - if can? current_user, :download_code, @project + = nav_link(controller: %w(tree blob blame)) do + = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref) + = nav_link(controller: %w(commit commits compare repositories protected_branches)) do + = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref) + = nav_link(controller: %w(graph)) do + = link_to "Network", project_graph_path(@project, @ref || @repository.root_ref) + + - if @project.issues_enabled + = nav_link(controller: %w(issues milestones labels)) do + = link_to url_for_project_issues do + Issues + - if @project.used_default_issues_tracker? + %span.count.issue_counter= @project.issues.opened.count + + - if @project.repo_exists? && @project.merge_requests_enabled + = nav_link(controller: :merge_requests) do + = link_to project_merge_requests_path(@project) do + Merge Requests + %span.count.merge_counter= @project.merge_requests.opened.count + + - if @project.wiki_enabled + = nav_link(controller: :wikis) do + = link_to 'Wiki', project_wiki_path(@project, :home) + + - if @project.wall_enabled + = nav_link(controller: :walls) do + = link_to 'Wall', project_wall_path(@project) + + - if @project.snippets_enabled + = nav_link(controller: :snippets) do + = link_to 'Snippets', project_snippets_path(@project) + + - if can? current_user, :admin_project, @project + = nav_link(html_options: {class: "#{project_tab_class}"}) do + = link_to edit_project_path(@project), class: "stat-tab tab " do + Settings diff --git a/app/views/layouts/nav/_team.html.haml b/app/views/layouts/nav/_team.html.haml new file mode 100644 index 00000000000..415e45104df --- /dev/null +++ b/app/views/layouts/nav/_team.html.haml @@ -0,0 +1,25 @@ +%ul + = nav_link(path: 'teams#show', html_options: {class: 'home'}) do + = link_to team_path(@team), title: "Home" do + %i.icon-home + + = nav_link(path: 'teams#issues') do + = link_to issues_team_path(@team) do + Issues + %span.count= Issue.opened.of_user_team(@team).count + + = nav_link(path: 'teams#merge_requests') do + = link_to merge_requests_team_path(@team) do + Merge Requests + %span.count= MergeRequest.opened.of_user_team(@team).count + + = nav_link(controller: [:members]) do + = link_to team_members_path(@team), class: "team-tab tab" do + Members + %span.count= @team.members.count + + - if can? current_user, :admin_user_team, @team + = nav_link(path: 'teams#edit') do + = link_to edit_team_path(@team), class: "stat-tab tab " do + Settings + diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 611063e8c99..30a0532bc2b 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,23 +1,11 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Profile" - %body{class: "#{app_theme} profile"} + %body{class: "#{app_theme} profile", :'data-page' => body_data_page} = render "layouts/head_panel", title: "Profile" = render "layouts/flash" - .container - %ul.main_menu - = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do - = link_to profile_path, title: "Profile" do - %i.icon-home - = nav_link(path: 'profiles#account') do - = link_to "Account", account_profile_path - = nav_link(controller: :keys) do - = link_to keys_path do - SSH Keys - %span.count= current_user.keys.count - = nav_link(path: 'profiles#design') do - = link_to "Design", design_profile_path - = nav_link(path: 'profiles#history') do - = link_to "History", history_profile_path + %nav.main-nav + .container= render 'layouts/nav/profile' + .container .content= yield diff --git a/app/views/layouts/project_resource.html.haml b/app/views/layouts/project_resource.html.haml index ca2f6e9a702..6d8bf9b710b 100644 --- a/app/views/layouts/project_resource.html.haml +++ b/app/views/layouts/project_resource.html.haml @@ -1,55 +1,15 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "#{app_theme} project"} + %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } = render "layouts/head_panel", title: project_title(@project) + = render "layouts/init_auto_complete" = render "layouts/flash" - if can?(current_user, :download_code, @project) = render 'shared/no_ssh' - .container - %ul.main_menu - = nav_link(path: 'projects#show', html_options: {class: "home"}) do - = link_to project_path(@project), title: "Project" do - %i.icon-home - - - if @project.repo_exists? - - if can? current_user, :download_code, @project - = nav_link(controller: %w(tree blob blame)) do - = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref) - = nav_link(controller: %w(commit commits compare repositories protected_branches)) do - = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref) - = nav_link(controller: %w(graph)) do - = link_to "Network", project_graph_path(@project, @ref || @repository.root_ref) - - - if @project.issues_enabled - = nav_link(controller: %w(issues milestones labels)) do - = link_to url_for_project_issues do - Issues - - if @project.used_default_issues_tracker? - %span.count.issue_counter= @project.issues.opened.count - - - if @project.repo_exists? && @project.merge_requests_enabled - = nav_link(controller: :merge_requests) do - = link_to project_merge_requests_path(@project) do - Merge Requests - %span.count.merge_counter= @project.merge_requests.opened.count - - - if @project.wiki_enabled - = nav_link(controller: :wikis) do - = link_to 'Wiki', project_wiki_path(@project, :home) - - - if @project.wall_enabled - = nav_link(controller: :walls) do - = link_to 'Wall', project_wall_path(@project) - - - if @project.snippets_enabled - = nav_link(controller: :snippets) do - = link_to 'Snippets', project_snippets_path(@project) - - - if can? current_user, :admin_project, @project - = nav_link(html_options: {class: "#{project_tab_class}"}) do - = link_to edit_project_path(@project), class: "stat-tab tab " do - Settings + %nav.main-nav + .container= render 'layouts/nav/project' + .container .content= yield diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml index 483bfad66ae..e64e68d2446 100644 --- a/app/views/layouts/user_team.html.haml +++ b/app/views/layouts/user_team.html.haml @@ -1,33 +1,11 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "#{@team.name}" - %body{class: "#{app_theme} application"} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/head_panel", title: "team: #{@team.name}" = render "layouts/flash" - .container - %ul.main_menu - = nav_link(path: 'teams#show', html_options: {class: 'home'}) do - = link_to team_path(@team), title: "Home" do - %i.icon-home - - = nav_link(path: 'teams#issues') do - = link_to issues_team_path(@team) do - Issues - %span.count= Issue.opened.of_user_team(@team).count - - = nav_link(path: 'teams#merge_requests') do - = link_to merge_requests_team_path(@team) do - Merge Requests - %span.count= MergeRequest.opened.of_user_team(@team).count - - = nav_link(controller: [:members]) do - = link_to team_members_path(@team), class: "team-tab tab" do - Members - %span.count= @team.members.count - - - if can? current_user, :admin_user_team, @team - = nav_link(path: 'teams#edit') do - = link_to edit_team_path(@team), class: "stat-tab tab " do - Settings + %nav.main-nav + .container= render 'layouts/nav/team' + .container .content= yield diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml index 816c852d24b..1c3aca176ab 100644 --- a/app/views/merge_requests/_form.html.haml +++ b/app/views/merge_requests/_form.html.haml @@ -13,7 +13,7 @@ .mr_branch_box %h5.cgray From (Head Branch) .body - .padded= f.select(:source_branch, @repository.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span4'}) + .padded= f.select(:source_branch, @repository.branch_names, { include_blank: "Select branch" }, {class: 'chosen span4'}) .mr_source_commit .span2 @@ -22,7 +22,7 @@ .mr_branch_box %h5.cgray To (Base Branch) .body - .padded= f.select(:target_branch, @repository.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span4'}) + .padded= f.select(:target_branch, @repository.branch_names, { include_blank: "Select branch" }, {class: 'chosen span4'}) .mr_target_commit %fieldset @@ -40,7 +40,7 @@ = f.label :assignee_id do %i.icon-user Assign to - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'}) + .input= f.select(:assignee_id, @project.users.alphabetically.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'}) .left = f.label :milestone_id do %i.icon-time diff --git a/app/views/merge_requests/_merge_request.html.haml b/app/views/merge_requests/_merge_request.html.haml index 09c55d98465..ffc6b8fda1e 100644 --- a/app/views/merge_requests/_merge_request.html.haml +++ b/app/views/merge_requests/_merge_request.html.haml @@ -1,32 +1,29 @@ %li{ class: mr_css_classes(merge_request) } - .pull-right - .left - - if merge_request.merged? - %span.btn.btn-small.disabled.grouped - %strong - %i.icon-ok - = "MERGED" - - if merge_request.notes.any? - %span.btn.btn-small.disabled.grouped - %i.icon-comment - = merge_request.mr_and_commit_notes.count - - if merge_request.milestone_id? - %span.btn.btn-small.disabled.grouped - %i.icon-time - = merge_request.milestone.title - %span.btn.btn-small.disabled.grouped - = merge_request.source_branch - → + .merge-request-title + %span.light= "##{merge_request.id}" + = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.project, merge_request), class: "row_title" + - if merge_request.merged? + %small.pull-right + %i.icon-ok + = "MERGED" + - else + %span.pull-right + %i.icon-angle-right = merge_request.target_branch - = image_tag gravatar_icon(merge_request.author_email), class: "avatar" - - %p= link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.project, merge_request), class: "row_title" - - %span.update-author - %small.cdark= "##{merge_request.id}" - authored by #{merge_request.author_name} - = time_ago_in_words(merge_request.created_at) - ago - + .merge-request-info + - if merge_request.author + authored by #{link_to_member(@project, merge_request.author)} - if merge_request.votes_count > 0 = render 'votes/votes_inline', votable: merge_request + - if merge_request.notes.any? + %span + %i.icon-comments + = merge_request.mr_and_commit_notes.count + - if merge_request.milestone_id? + %span + %i.icon-time + = merge_request.milestone.title + + + .pull-right + %small updated #{time_ago_in_words(merge_request.updated_at)} ago diff --git a/app/views/merge_requests/_show.html.haml b/app/views/merge_requests/_show.html.haml index 08b80172645..d6e00ca9b8b 100644 --- a/app/views/merge_requests/_show.html.haml +++ b/app/views/merge_requests/_show.html.haml @@ -26,14 +26,12 @@ :javascript var merge_request; - $(function(){ - merge_request = new MergeRequest({ - url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}", - check_enable: #{@merge_request.unchecked? ? "true" : "false"}, - url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}", - ci_enable: #{@project.gitlab_ci? ? "true" : "false"}, - current_status: "#{@merge_request.merge_status_name}", - action: "#{controller.action_name}" - }); - }); + merge_request = new MergeRequest({ + url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}", + check_enable: #{@merge_request.unchecked? ? "true" : "false"}, + url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}", + ci_enable: #{@project.gitlab_ci? ? "true" : "false"}, + current_status: "#{@merge_request.merge_status_name}", + action: "#{controller.action_name}" + }); diff --git a/app/views/merge_requests/index.html.haml b/app/views/merge_requests/index.html.haml index 3073c8f6bab..b9e9096e3ae 100644 --- a/app/views/merge_requests/index.html.haml +++ b/app/views/merge_requests/index.html.haml @@ -20,16 +20,16 @@ = hidden_field_tag :f, params[:f] .clearfix - %ul.well-list + %ul.well-list.mr-list = render @merge_requests - if @merge_requests.blank? %li %h4.nothing_here_message Nothing to show here - - if @merge_requests.present? - %li.bottom - .left= paginate @merge_requests, theme: "gitlab" - .pull-right - %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter + - if @merge_requests.present? + .pull-right + %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter + + = paginate @merge_requests, theme: "gitlab" :javascript $(merge_requestsPage); diff --git a/app/views/merge_requests/show/_commits.html.haml b/app/views/merge_requests/show/_commits.html.haml index 5e27b6dc25a..eee786d71ef 100644 --- a/app/views/merge_requests/show/_commits.html.haml +++ b/app/views/merge_requests/show/_commits.html.haml @@ -22,9 +22,9 @@ = render "commits/commit", commit: commit - else - %h5 + %h4.nothing_here_message Nothing to merge from - %span.label #{@merge_request.source_branch} + %span.label-branch #{@merge_request.source_branch} to - %span.label #{@merge_request.target_branch} + %span.label-branch #{@merge_request.target_branch} %br diff --git a/app/views/merge_requests/show/_diffs.html.haml b/app/views/merge_requests/show/_diffs.html.haml index 0807454c4b0..033d66a4ad4 100644 --- a/app/views/merge_requests/show/_diffs.html.haml +++ b/app/views/merge_requests/show/_diffs.html.haml @@ -4,7 +4,7 @@ %h4.nothing_here_message Can't load diff. You can - = link_to "download it", project_merge_request_path(@project, @merge_request), format: :diff, class: "vlink" + = link_to "download it", project_merge_request_path(@project, @merge_request, format: :diff), class: "vlink" instead. - else %h4.nothing_here_message Nothing to merge diff --git a/app/views/merge_requests/show/_how_to_merge.html.haml b/app/views/merge_requests/show/_how_to_merge.html.haml index 69881d4352f..7f1e33418de 100644 --- a/app/views/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/merge_requests/show/_how_to_merge.html.haml @@ -3,22 +3,17 @@ %a.close{href: "#"} × %h3 How To Merge .modal-body + %p + %strong Step 1. + Checkout target branch and get recent objects from GitLab %pre.dark - = preserve do + :preserve git checkout #{@merge_request.target_branch} git fetch origin + %p + %strong Step 2. + Merge source branch into target branch and push changes to GitLab + %pre.dark + :preserve git merge origin/#{@merge_request.source_branch} git push origin #{@merge_request.target_branch} - - -:javascript - $(function(){ - var modal = $('#modal_merge_info').modal({modal: true, show:false}); - $('.how_to_merge_link').bind("click", function(){ - modal.show(); - }); - $('.modal-header .close').bind("click", function(){ - modal.hide(); - }) - }) - diff --git a/app/views/merge_requests/show/_mr_box.html.haml b/app/views/merge_requests/show/_mr_box.html.haml index 3b54f613f58..594f4061c23 100644 --- a/app/views/merge_requests/show/_mr_box.html.haml +++ b/app/views/merge_requests/show/_mr_box.html.haml @@ -1,13 +1,13 @@ .ui-box.ui-box-show .ui-box-head %h4.box-title + = gfm escape_once(@merge_request.title) - if @merge_request.merged? - .error.status_info + .success.status_info %i.icon-ok Merged - elsif @merge_request.closed? .error.status_info Closed - = gfm escape_once(@merge_request.title) .ui-box-body %div @@ -22,13 +22,15 @@ - if @merge_request.closed? - .ui-box-bottom + .ui-box-bottom.alert-error %span + %i.icon-remove Closed by #{link_to_member(@project, @merge_request.closed_event.author)} %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. - if @merge_request.merged? - .ui-box-bottom + .ui-box-bottom.alert-success %span + %i.icon-ok Merged by #{link_to_member(@project, @merge_request.merge_event.author)} - %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago. + #{time_ago_in_words(@merge_request.merge_event.created_at)} ago. diff --git a/app/views/merge_requests/show/_mr_ci.html.haml b/app/views/merge_requests/show/_mr_ci.html.haml index a8faa6ba617..9b15c4526e9 100644 --- a/app/views/merge_requests/show/_mr_ci.html.haml +++ b/app/views/merge_requests/show/_mr_ci.html.haml @@ -1,4 +1,4 @@ -- if @merge_request.opened? && @commits.any? +- if @commits.any? .ci_widget.ci-success{style: "display:none"} .alert.alert-success %i.icon-ok diff --git a/app/views/milestones/_milestone.html.haml b/app/views/milestones/_milestone.html.haml index 8a3727c6f6a..894fa6c1133 100644 --- a/app/views/milestones/_milestone.html.haml +++ b/app/views/milestones/_milestone.html.haml @@ -4,6 +4,8 @@ = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do %i.icon-edit Edit + - if milestone.can_be_closed? + = link_to 'Close', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-remove" %h4 = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone) - if milestone.expired? and not milestone.closed? @@ -13,11 +15,8 @@ - if milestone.is_empty? %span.muted Empty - else - .row - .span4 - .progress.progress-info - .bar{style: "width: #{milestone.percent_complete}%;"} - .span6 + %div + %div = link_to project_issues_path(milestone.project, milestone_id: milestone.id) do = pluralize milestone.issues.count, 'Issue' @@ -25,3 +24,5 @@ = pluralize milestone.merge_requests.count, 'Merge Request' %span.light #{milestone.percent_complete}% complete + .progress.progress-info + .bar{style: "width: #{milestone.percent_complete}%;"} diff --git a/app/views/milestones/index.html.haml b/app/views/milestones/index.html.haml index b78f17053fd..fb7cbc41edf 100644 --- a/app/views/milestones/index.html.haml +++ b/app/views/milestones/index.html.haml @@ -3,11 +3,14 @@ %h3.page_title Milestones - if can? current_user, :admin_milestone, @project - = link_to "New Milestone", new_project_milestone_path(@project), class: "pull-right btn btn-small", title: "New Milestone" + = link_to new_project_milestone_path(@project), class: "pull-right btn btn-primary", title: "New Milestone" do + %i.icon-plus + New Milestone %br - %div.ui-box - .title - %ul.nav.nav-pills + + .row + .span3 + %ul.nav.nav-pills.nav-stacked %li{class: ("active" if (params[:f] == "active" || !params[:f]))} = link_to project_milestones_path(@project, f: "active") do Active @@ -17,12 +20,13 @@ %li{class: ("active" if params[:f] == "all")} = link_to project_milestones_path(@project, f: "all") do All + .span9 + .ui-box + %ul.well-list + = render @milestones - %ul.well-list - = render @milestones + - if @milestones.blank? + %li + %h3.nothing_here_message Nothing to show here - - if @milestones.present? - %li.bottom= paginate @milestones, theme: "gitlab" - - else - %li - %h3.nothing_here_message Nothing to show here + = paginate @milestones, theme: "gitlab" diff --git a/app/views/milestones/show.html.haml b/app/views/milestones/show.html.haml index e1808a2003f..034c37852f1 100644 --- a/app/views/milestones/show.html.haml +++ b/app/views/milestones/show.html.haml @@ -1,3 +1,4 @@ += render "issues/head" .row .span6 %h3.page_title @@ -87,8 +88,6 @@ %h6 Participants: %div - @users.each do |user| - = link_to user, class: 'float-link' do - = user.avatar_image - = user.name + = link_to_member(@project, user) .clearfix diff --git a/app/views/milestones/update.js.haml b/app/views/milestones/update.js.haml new file mode 100644 index 00000000000..3ff84915e97 --- /dev/null +++ b/app/views/milestones/update.js.haml @@ -0,0 +1,2 @@ +:plain + $('##{dom_id(@milestone)}').fadeOut(); diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml index c2bdeafb830..7add2921830 100644 --- a/app/views/notes/_form.html.haml +++ b/app/views/notes/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form" } do |f| += form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" } do |f| = note_target_fields = f.hidden_field :commit_id @@ -27,15 +27,6 @@ %a.btn.grouped.js-close-discussion-note-form Cancel .note-form-option - = label_tag :notify do - = check_box_tag :notify, 1, false - %span.light Notify team via email - - .js-notify-commit-author - = label_tag :notify_author do - = check_box_tag :notify_author, 1 , false - %span.light Notify commit author - .note-form-option %a.choose-btn.btn.btn-small.js-choose-note-attachment-button %i.icon-paper-clip %span Choose File ... diff --git a/app/views/notes/_notes_with_form.html.haml b/app/views/notes/_notes_with_form.html.haml index 2566edd81ad..38d1a3c93c0 100644 --- a/app/views/notes/_notes_with_form.html.haml +++ b/app/views/notes/_notes_with_form.html.haml @@ -6,6 +6,4 @@ = render "notes/form" :javascript - $(function(){ - NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}"); - }); + NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}"); diff --git a/app/views/notifications/show.html.haml b/app/views/notifications/show.html.haml new file mode 100644 index 00000000000..b7f39306fd8 --- /dev/null +++ b/app/views/notifications/show.html.haml @@ -0,0 +1,77 @@ +%h3.page_title Setup your notification level + +%br + +%p.light + %strong Disabled + – You will not get any notifications via email +%p.light + %strong Participating + – You will receive only notifications from related resources(ex. from assigned issue or your commit) +%p.light + %strong Watch + – You will receive all notifications from projects in which you participate +%hr + +.row + .span4 + %h5 Global setting + .span7 + = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do + = hidden_field_tag :notification_type, 'global' + + = label_tag do + = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit' + %span Disabled + + = label_tag do + = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' + %span Participating + + = label_tag do + = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit' + %span Watch + +%hr += link_to '#', class: 'js-toggle-visibility-link' do + %h6.btn.btn-tiny + %i.icon-chevron-down + %span Per project notifications setting + +%ul.well-list.js-toggle-visibility-container.hide + - @users_projects.each do |users_project| + - notification = Notification.new(users_project) + %li + .row + .span4 + %span + = link_to_project(users_project.project) + .span7 + = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do + = hidden_field_tag :notification_type, 'project', id: dom_id(users_project, 'notification_type') + = hidden_field_tag :notification_id, users_project.id, id: dom_id(users_project, 'notification_id') + + = label_tag do + = radio_button_tag :notification_level, Notification::N_GLOBAL, notification.global?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' + %span Use global setting + + = label_tag do + = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' + %span Disabled + + = label_tag do + = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' + %span Participating + + = label_tag do + = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' + %span Watch + + +.save-status-fixed + %span.update-success.cgreen.hide + %i.icon-ok + Saved + %span.update-failed.cred.hide + %i.icon-remove + Failed diff --git a/app/views/notifications/update.js.haml b/app/views/notifications/update.js.haml new file mode 100644 index 00000000000..88e74d50671 --- /dev/null +++ b/app/views/notifications/update.js.haml @@ -0,0 +1,6 @@ +- if @saved + :plain + $('.save-status-fixed .update-success').showAndHide(); +- else + :plain + $('.save-status-fixed .update-failed').showAndHide(); diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml new file mode 100644 index 00000000000..23ccc4538de --- /dev/null +++ b/app/views/notify/closed_issue_email.html.haml @@ -0,0 +1,5 @@ +%p + = "Issue was closed by #{@updated_by.name}" +%p + = "Issue ##{@issue.id}" + = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml new file mode 100644 index 00000000000..0cca321552c --- /dev/null +++ b/app/views/notify/closed_issue_email.text.haml @@ -0,0 +1,3 @@ += "Issue was closed by #{@updated_by.name}" + +Issue ##{@issue.id}: #{project_issue_url(@issue.project, @issue)} diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml new file mode 100644 index 00000000000..0c6c79e097a --- /dev/null +++ b/app/views/notify/closed_merge_request_email.html.haml @@ -0,0 +1,9 @@ +%p + = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}" +%p + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) +%p + Branches: #{@merge_request.source_branch} → #{@merge_request.target_branch} +%p + Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} + diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml new file mode 100644 index 00000000000..ee4648e3d09 --- /dev/null +++ b/app/views/notify/closed_merge_request_email.text.haml @@ -0,0 +1,8 @@ += "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}" + +Merge Request url: #{project_merge_request_url(@merge_request.project, @merge_request)} + +Branches: #{@merge_request.source_branch} - #{@merge_request.target_branch} + +Author: #{@merge_request.author_name} +Assignee: #{@merge_request.assignee_name} diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml new file mode 100644 index 00000000000..2b8cc030b23 --- /dev/null +++ b/app/views/notify/merged_merge_request_email.html.haml @@ -0,0 +1,9 @@ +%p + = "Merge Request #{@merge_request.id} was merged" +%p + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) +%p + Branches: #{@merge_request.source_branch} → #{@merge_request.target_branch} +%p + Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} + diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml new file mode 100644 index 00000000000..91c23360195 --- /dev/null +++ b/app/views/notify/merged_merge_request_email.text.haml @@ -0,0 +1,8 @@ += "Merge Request #{@merge_request.id} was merged" + +Merge Request Url: #{project_merge_request_url(@merge_request.project, @merge_request)} + +Branches: #{@merge_request.source_branch} - #{@merge_request.target_branch} + +Author: #{@merge_request.author_name} +Assignee: #{@merge_request.assignee_name} diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml index 0c891748918..f11c850641b 100644 --- a/app/views/notify/new_issue_email.html.haml +++ b/app/views/notify/new_issue_email.html.haml @@ -1,5 +1,9 @@ %p - New Issue was created and assigned to you. + New Issue was created. %p = "Issue ##{@issue.id}" = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title +%p + Author: #{@issue.author_name} +%p + Assignee: #{@issue.assignee_name} diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb index 5ed55c35b23..9907ca83247 100644 --- a/app/views/notify/new_issue_email.text.erb +++ b/app/views/notify/new_issue_email.text.erb @@ -1,4 +1,5 @@ -New Issue was created and assigned to you. +New Issue was created. - Issue <%= @issue.id %>: <%= url_for(project_issue_url(@issue.project, @issue)) %> +Author: <%= @issue.author_name %> +Asignee: <%= @issue.assignee_name %> diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml index 57f4297e6cb..eff197ce0f4 100644 --- a/app/views/notify/new_ssh_key_email.html.haml +++ b/app/views/notify/new_ssh_key_email.html.haml @@ -6,5 +6,5 @@ title: %code= @key.title %p - If this key was added in error, you can remove here: + If this key was added in error, you can remove it here: = link_to "SSH Keys", keys_url diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb index 71974eab975..2b1f8a06858 100644 --- a/app/views/notify/new_ssh_key_email.text.erb +++ b/app/views/notify/new_ssh_key_email.text.erb @@ -4,4 +4,4 @@ A new public key was added to your account: title.................. <%= @key.title %> -If this key was added in error, you can remove here: <%= keys_url %> +If this key was added in error, you can remove it here: <%= keys_url %> diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml index 8606dc6aa76..9804fbdd51e 100644 --- a/app/views/notify/new_user_email.html.haml +++ b/app/views/notify/new_user_email.html.haml @@ -2,9 +2,9 @@ Hi #{@user['name']}! %p - if Gitlab.config.gitlab.signup_enabled - Account has been created successfully. + Your account has been created successfully. - else - Administrator created account for you. Now you are a member of company GitLab application. + The Administrator created an account for you. Now you are a member of company GitLab application. %p login.......................................... %code= @user['email'] @@ -12,5 +12,9 @@ - unless Gitlab.config.gitlab.signup_enabled password.................................. %code= @password + +%p + Please change your password immediately after login. + %p = link_to "Click here to login", root_url diff --git a/app/views/notify/new_user_email.text.erb b/app/views/notify/new_user_email.text.erb index 94072d7fe5c..777930a2803 100644 --- a/app/views/notify/new_user_email.text.erb +++ b/app/views/notify/new_user_email.text.erb @@ -1,10 +1,12 @@ Hi <%= @user.name %>! - -Administrator created account for you. Now you are a member of company GitLab application. - + +The Administrator created an account for you. Now you are a member of company GitLab application. + login.................. <%= @user.email %> <% unless Gitlab.config.gitlab.signup_enabled %> password............... <%= @password %> <% end %> +Please change your password immediately after login. + Click here to login: <%= url_for(root_url) %> diff --git a/app/views/profiles/account.html.haml b/app/views/profiles/account.html.haml index b20d5c7a6a1..09d9ec10e81 100644 --- a/app/views/profiles/account.html.haml +++ b/app/views/profiles/account.html.haml @@ -1,11 +1,35 @@ -- if Gitlab.config.omniauth.enabled - %fieldset - %legend Social Accounts - .oauth_select_holder - %p.hint Tip: Click on icon to activate sigin with one of the following services - - User.omniauth_providers.each do |provider| - %span{class: oauth_active_class(provider) } - = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) +- unless current_user.ldap_user? + - if Gitlab.config.omniauth.enabled + %fieldset + %legend Social Accounts + .oauth_select_holder + %p.hint Tip: Click on icon to activate sigin with one of the following services + - enabled_social_providers.each do |provider| + %span{class: oauth_active_class(provider) } + = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + + + %fieldset.update-password + %legend Password + = form_for @user, url: update_password_profile_path, method: :put do |f| + .padded + %p.slead After successful password update you will be redirected to login page where you should login with new password + -if @user.errors.any? + .alert.alert-error + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + + .clearfix + = f.label :password + .input= f.password_field :password, required: true + .clearfix + = f.label :password_confirmation + .input + = f.password_field :password_confirmation, required: true + .clearfix + .input + = f.submit 'Save password', class: "btn btn-save" @@ -29,29 +53,6 @@ %span You don`t have one yet. Click generate to fix it. = f.submit 'Generate', class: "btn success btn-build-token" -%fieldset.update-password - %legend Password - = form_for @user, url: update_password_profile_path, method: :put do |f| - .padded - %p.slead After successful password update you will be redirected to login page where you should login with new password - -if @user.errors.any? - .alert.alert-error - %ul - - @user.errors.full_messages.each do |msg| - %li= msg - - .clearfix - = f.label :password - .input= f.password_field :password, required: true - .clearfix - = f.label :password_confirmation - .input - = f.password_field :password_confirmation, required: true - .clearfix - .input - = f.submit 'Save password', class: "btn btn-save" - - - if current_user.can_change_username? %fieldset.update-username @@ -78,7 +79,7 @@ .input = f.submit 'Save username', class: "btn btn-save" -- if Gitlab.config.gitlab.signup_enabled +- if gitlab_config.signup_enabled %fieldset.remove-account %legend Remove account diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml index 77068dabb32..878297fe49d 100644 --- a/app/views/profiles/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -55,3 +55,8 @@ = image_tag "solarized_dark.png" = f.radio_button :color_scheme_id, 3 Solarized Dark + = label_tag do + .prev + = image_tag "monokai.png" + = f.radio_button :color_scheme_id, 4 + Monokai diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 9cab3ba5252..d4793da8987 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -51,7 +51,7 @@ %legend Tips: %ul %li - %p You can change your password on Account page + %p You can change your password on the Account page - if Gitlab.config.gravatar.enabled %li %p You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"} @@ -73,16 +73,17 @@ Want to share a team between projects? = link_to new_team_path, class: "btn btn-tiny" do Create a team - %fieldset - %legend - Personal projects: - %small.pull-right - %span= current_user.owned_projects.count - of - %span= current_user.projects_limit - .padded - .progress - .bar{style: "width: #{current_user.projects_limit_percent}%;"} + - unless current_user.projects_limit_left > 100 + %fieldset + %legend + Owned projects: + %small.pull-right + %span= current_user.owned_projects.count + of + %span= current_user.projects_limit + .padded + .progress + .bar{style: "width: #{current_user.projects_limit_percent}%;"} %fieldset %legend diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml index e52df19be96..f8276c3c2b6 100644 --- a/app/views/projects/_clone_panel.html.haml +++ b/app/views/projects/_clone_panel.html.haml @@ -1,17 +1,24 @@ .project_clone_panel .row - .span7 + .span8 .form-horizontal= render "shared/clone_panel" - .span4.pull-right + .span3.pull-right .pull-right - unless @project.empty_repo? + - if can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace + - if current_user.already_forked?(@project) + = link_to project_path(current_user.fork_of(@project)), class: 'btn grouped btn-primary' do + Forked + - else + = link_to fork_project_path(@project), title: "Fork", class: "btn grouped", method: "POST" do + %i.icon-copy + Fork - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project), class: "btn-small btn grouped" do + = link_to archive_project_repository_path(@project), class: "btn grouped" do %i.icon-download-alt Download - - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn-small btn grouped" do - Merge Request - - if @project.issues_enabled && can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), title: "New Issue", class: "btn-small btn grouped" do - Issue + + = link_to tags_project_repository_path(@project), class: "btn grouped only-wide", title: 'Git Tags' do + %i.icon-tags + Tags + diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml new file mode 100644 index 00000000000..bb9759353a3 --- /dev/null +++ b/app/views/projects/_errors.html.haml @@ -0,0 +1,4 @@ +- if @project.errors.any? + .alert.alert-error + %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + = @project.errors.full_messages.first diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml deleted file mode 100644 index 6d547c94e98..00000000000 --- a/app/views/projects/_form.html.haml +++ /dev/null @@ -1,139 +0,0 @@ -.row - .span3 - %ul.nav.nav-pills.nav-stacked - %li.active - = link_to 'Settings', '#tab-settings', 'data-toggle' => 'tab' - %li - = link_to 'Transfer', '#tab-transfer', 'data-toggle' => 'tab' - %li - = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab' - - .span9 - .tab-content - .tab-pane.active#tab-settings - .ui-box.white - %h5.title Settings: - .form-holder - = form_for(@project, remote: true) do |f| - - if @project.errors.any? - .alert.alert-error - %ul - - @project.errors.full_messages.each do |msg| - %li= msg - - %fieldset - .clearfix.project_name_holder - = f.label :name do - Project name is - .input - = f.text_field :name, placeholder: "Example Project", class: "span5" - - - .clearfix - = f.label :description do - Project description - %span.light (optional) - .input - = f.text_area :description, placeholder: "awesome project", class: "span5", rows: 3, maxlength: 250 - - - unless @repository.heads.empty? - .clearfix - = f.label :default_branch, "Default Branch" - .input= f.select(:default_branch, @repository.heads.map(&:name), {}, style: "width:210px;") - - - - if can?(current_user, :change_public_mode, @project) - %fieldset.public-mode - %legend - Public mode: - .control-group - = f.label :public, class: 'control-label' do - %span Public clone access - .controls - = f.check_box :public - %span.descr - If checked, this project can be cloned - %em without any - authentification. - It will also be listed on the #{link_to "public access directory", public_root_path}. - - %fieldset.features - %legend - Features: - .control-group - = f.label :issues_enabled, "Issues", class: 'control-label' - .controls - = f.check_box :issues_enabled - %span.descr Lightweight issue tracking system for this project - - - if Project.issues_tracker.values.count > 1 - .control-group - = f.label :issues_tracker, "Issues tracker", class: 'control-label' - .input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled }) - - .clearfix - = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' - .input= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id? - - .control-group - = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' - .controls - = f.check_box :merge_requests_enabled - %span.descr Submit changes to be merged upstream. - - .control-group - = f.label :wiki_enabled, "Wiki", class: 'control-label' - .controls - = f.check_box :wiki_enabled - %span.descr Pages for project documentation - - .control-group - = f.label :wall_enabled, "Wall", class: 'control-label' - .controls - = f.check_box :wall_enabled - %span.descr Simple chat system for broadcasting inside project - - .control-group - = f.label :snippets_enabled, "Snippets", class: 'control-label' - .controls - = f.check_box :snippets_enabled - %span.descr Share code pastes with others out of git repository - - - .form-actions - = f.submit 'Save', class: "btn btn-save" - - .tab-pane#tab-transfer - - if can?(current_user, :change_namespace, @project) - .ui-box.ui-box-danger - %h5.title Transfer project - .form-holder - = form_for(@project, remote: true, html: { class: 'transfer-project' }) do |f| - .control-group - = f.label :namespace_id do - %span Namespace - .controls - .clearfix - = f.select :namespace_id, namespaces_options(@project.namespace_id || Namespace::global_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'} - %ul - %li Be careful. Changing project namespace can have unintended side effects - %li You can transfer project only to namespaces you can manage - %li You will need to update your local repositories to point to the new location. - .form-actions - = f.submit 'Transfer', class: "btn btn-remove" - - else - %p.nothing_here_message Only project owner can transfer a project - - .tab-pane#tab-remove - - if can?(current_user, :remove_project, @project) - .ui-box.ui-box-danger - %h5.title Remove project - .ui-box-body - %p - Remove of project will cause removing repository and all related resources like issues, merge requests etc. - %p - %strong Removed project can not be restored! - - = link_to 'Remove project', @project, confirm: 'Removed project can not be restored! Are you sure?', method: :delete, class: "btn btn-remove btn-small" - - else - %p.nothing_here_message Only project owner can remove a project diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml deleted file mode 100644 index b6503636890..00000000000 --- a/app/views/projects/_new_form.html.haml +++ /dev/null @@ -1,48 +0,0 @@ -= form_for(@project, remote: true) do |f| - - if @project.errors.any? - .alert.alert-error - %span= @project.errors.full_messages.first - .clearfix.project_name_holder - = f.label :name do - Project name is - .input - = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - = f.submit 'Create project', class: "btn btn-create project-submit" - - - if current_user.can_select_namespace? - .clearfix - = f.label :namespace_id do - %span Namespace - .input - = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'} - - - .clearfix - .input - = link_to "#", class: 'appear-link' do - %i.icon-upload-alt - %span Import existing repository? - .clearfix.appear-data - = f.label :import_url do - %span Import existing repo - .input - = f.text_field :import_url, class: 'xlarge', placeholder: 'https://github.com/randx/six.git' - .light - URL must be clonable - - %p.padded - New projects are private by default. You choose who can see the project and commit to repository. - %hr - - - if current_user.can_create_group? - .clearfix - .input.light - Need a group for several dependent projects? - = link_to new_group_path, class: "btn btn-tiny" do - Create a group - - if current_user.can_create_team? - .clearfix - .input.light - Want to share a project between team? - = link_to new_team_path, class: "btn btn-tiny" do - Create a team diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml index 296c8688f47..52e61f47283 100644 --- a/app/views/projects/create.js.haml +++ b/app/views/projects/create.js.haml @@ -3,8 +3,6 @@ location.href = "#{project_path(@project)}"; - else :plain - $('.project_new_holder').show(); - $("#new_project").replaceWith("#{escape_javascript(render('new_form'))}"); + $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); $('.save-project-loader').hide(); - new Projects(); - $('select.chosen').chosen() + $('.project-edit-container').show(); diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 394522bfd88..bec64bf6c58 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1,13 +1,177 @@ = render "projects/settings_nav" -.project_edit_holder +.project-edit-container %h3.page_title Edit Project %hr - = render "projects/form" -%div.save-project-loader.hide + .project-edit-errors + .project-edit-content + .row + .span3 + %ul.nav.nav-pills.nav-stacked + %li.active + = link_to 'Settings', '#tab-settings', 'data-toggle' => 'tab' + %li + = link_to 'Rename repo', '#tab-rename', 'data-toggle' => 'tab' + %li + = link_to 'Transfer', '#tab-transfer', 'data-toggle' => 'tab' + %li + = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab' + + .span9 + .tab-content + .tab-pane.active#tab-settings + .ui-box.white + %h5.title Settings: + .form-holder + = form_for(@project, remote: true) do |f| + %fieldset + .clearfix.project_name_holder + = f.label :name do + Project name is + .input + = f.text_field :name, placeholder: "Example Project", class: "span5" + + + .clearfix + = f.label :description do + Project description + %span.light (optional) + .input + = f.text_area :description, placeholder: "awesome project", class: "span5", rows: 3, maxlength: 250 + + - unless @project.empty_repo? + .clearfix + = f.label :default_branch, "Default Branch" + .input= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'}) + + + - if can?(current_user, :change_public_mode, @project) + %fieldset.public-mode + %legend + Public mode: + .control-group + = f.label :public, class: 'control-label' do + %span Public access + .controls + = f.check_box :public + %span.descr + If checked, this project can be cloned + %em without any + authentication. + It will also be listed on the #{link_to "public access directory", public_root_path}. + %em Any + user will have #{link_to "Guest", help_permissions_path} permissions on the repository. + + %fieldset.features + %legend + Labels: + .control-group + = f.label :label_list, "Labels", class: 'control-label' + .controls + = f.text_field :label_list, maxlength: 2000, class: "span5" + %p.hint Separate with comma. + + %fieldset.features + %legend + Features: + .control-group + = f.label :issues_enabled, "Issues", class: 'control-label' + .controls + = f.check_box :issues_enabled + %span.descr Lightweight issue tracking system for this project + + - if Project.issues_tracker.values.count > 1 + .control-group + = f.label :issues_tracker, "Issues tracker", class: 'control-label' + .input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled }) + + .clearfix + = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' + .input= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id? + + .control-group + = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' + .controls + = f.check_box :merge_requests_enabled + %span.descr Submit changes to be merged upstream. + + .control-group + = f.label :wiki_enabled, "Wiki", class: 'control-label' + .controls + = f.check_box :wiki_enabled + %span.descr Pages for project documentation + + .control-group + = f.label :wall_enabled, "Wall", class: 'control-label' + .controls + = f.check_box :wall_enabled + %span.descr Simple chat system for broadcasting inside project + + .control-group + = f.label :snippets_enabled, "Snippets", class: 'control-label' + .controls + = f.check_box :snippets_enabled + %span.descr Share code pastes with others out of git repository + + + .form-actions + = f.submit 'Save', class: "btn btn-save" + + .tab-pane#tab-transfer + - if can?(current_user, :change_namespace, @project) + .ui-box.ui-box-danger + %h5.title Transfer project + .errors-holder + .form-holder + = form_for(@project, url: transfer_project_path(@project), remote: true, html: { class: 'transfer-project' }) do |f| + .control-group + = f.label :namespace_id do + %span Namespace + .controls + .clearfix + = f.select :namespace_id, namespaces_options(@project.namespace_id || Namespace::global_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'} + %ul + %li Be careful. Changing project namespace can have unintended side effects + %li You can transfer project only to namespaces you can manage + %li You will need to update your local repositories to point to the new location. + .form-actions + = f.submit 'Transfer', class: "btn btn-remove" + - else + %p.nothing_here_message Only project owner can transfer a project + + .tab-pane#tab-rename + .ui-box.ui-box-danger + %h5.title Rename repository + .errors-holder + .form-holder + = form_for(@project) do |f| + .control-group + = f.label :path do + %span Path + .controls + .clearfix + = f.text_field :path + %ul + %li Be careful. Rename of project repo can have unintended side effects + %li You will need to update your local repositories to point to the new location. + .form-actions + = f.submit 'Rename', class: "btn btn-remove" + + .tab-pane#tab-remove + - if can?(current_user, :remove_project, @project) + .ui-box.ui-box-danger + %h5.title Remove project + .ui-box-body + %p + Remove of project will cause removing repository and all related resources like issues, merge requests etc. + %p + %strong Removed project can not be restored! + + = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove btn-small" + - else + %p.nothing_here_message Only project owner can remove a project + +.save-project-loader.hide %center = image_tag "ajax_loader.gif" - %h3 Saving project. Please wait a few minutes - -:javascript - $(function(){ new Projects(); }); + %h3 Saving project. Please wait a moment, this page will automatically refresh when ready. diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index b1795b301b0..56dbbf0755e 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -31,4 +31,4 @@ - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn btn-remove pull-right" + = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove pull-right" diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 933cb671142..e9099f264bc 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,12 +1,55 @@ -.project_new_holder - %h3.page_title - New Project +.project-edit-container + %h3.page_title New Project %hr - = render 'new_form' -%div.save-project-loader.hide + .project-edit-errors + = render 'projects/errors' + .project-edit-content + = form_for @project, remote: true do |f| + .clearfix.project_name_holder + = f.label :name do + Project name is + .input + = f.text_field :name, placeholder: "Example Project", class: "xxlarge", tabindex: 1 + = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 3 + + - if current_user.can_select_namespace? + .clearfix + = f.label :namespace_id do + %span Namespace + .input + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen', tabindex: 2} + + .clearfix + .input + = link_to "#", class: 'appear-link' do + %i.icon-upload-alt + %span Import existing repository? + .clearfix.appear-data.import-url-data + = f.label :import_url do + %span Import existing repo + .input + = f.text_field :import_url, class: 'xlarge', placeholder: 'https://github.com/randx/six.git' + .light + URL must be clonable + + %p.padded + New projects are private by default. You choose who can see the project and commit to repository. + %hr + + - if current_user.can_create_group? + .clearfix + .input.light + Need a group for several dependent projects? + = link_to new_group_path, class: "btn btn-tiny" do + Create a group + - if current_user.can_create_team? + .clearfix + .input.light + Want to share a project between team? + = link_to new_team_path, class: "btn btn-tiny" do + Create a team + +.save-project-loader.hide %center = image_tag "ajax_loader.gif" - %h3 Creating project & repository. Please wait a few minutes - -:javascript - $(function(){ new Projects(); }); + %h3 Creating project & repository. Please wait a moment, this page will automatically refresh when ready. diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 824d4daf698..f72ef0a99ae 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,32 +1,51 @@ = render 'clone_panel' -= render "events/event_last_push", event: @last_push .row .span9 - .content_list= render @events + = render "events/event_last_push", event: @last_push + .content_list .loading.hide .span3 - .ui-box.white - .padded - %h3.page_title - = @project.name - - if @project.description.present? - %p.light= @project.description + .light-well + .dropdown.pull-right + %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} + %i.icon-plus-sign-alt + %b.caret + %ul.dropdown-menu + - if @project.issues_enabled && can?(current_user, :write_issue, @project) + %li + = link_to url_for_new_issue, title: "New Issue" do + Issue + - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) + %li + = link_to new_project_merge_request_path(@project), title: "New Merge Request" do + Merge Request + - if @project.snippets_enabled && can?(current_user, :write_snippet, @project) + %li + = link_to new_project_snippet_path(@project), title: "New Snippet" do + Snippet + - if can?(current_user, :admin_team_member, @project) + %li.divider + %li + = link_to new_project_team_member_path(@project), title: "New Team member" do + Team member - %hr + %h3.page_title + = @project.name + - if @project.description.present? + %p.light= @project.description + + %hr + %p + %p Repo Size: #{@project.repository.size} MB + %p Created on: #{@project.created_at.stamp('Aug 22, 2013')} + %p Owner: #{link_to @project.owner_name, @project.owner} + - if @project.forked_from_project %p - Access level: - - if @project.public - %span.cblue - %i.icon-share - Public - - else - %span.cgreen - %i.icon-lock - Private + Forked from: + = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) - %p Repo Size: #{@project.repository.size} MB - %p Created at: #{@project.created_at.stamp('Aug 22, 2013')} - %p Owner: #{link_to @project.owner_name, @project.owner} -:javascript - $(function(){ Pager.init(20); }); + - if @project.gitlab_ci? + %hr + = link_to @project.gitlab_ci_service.builds_path do + = image_tag @project.gitlab_ci_service.status_img_path, alt: "build status" diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml new file mode 100644 index 00000000000..10b0de98c04 --- /dev/null +++ b/app/views/projects/transfer.js.haml @@ -0,0 +1,7 @@ +- if @project.errors[:namespace_id].present? + :plain + $("#tab-transfer .errors-holder").replaceWith(errorMessage('#{escape_javascript(@project.errors[:namespace_id].first)}')); + $("#tab-transfer .form-actions input").removeAttr('disabled').removeClass('disabled'); +- else + :plain + location.href = "#{edit_project_path(@project)}"; diff --git a/app/views/projects/tree.js.haml b/app/views/projects/tree.js.haml deleted file mode 100644 index ba5d53c16a2..00000000000 --- a/app/views/projects/tree.js.haml +++ /dev/null @@ -1,5 +0,0 @@ -:plain - $("#tree-holder table").hide("slide", { direction: "left" }, 150, function(){ - $("#tree-holder").html("#{escape_javascript(render(partial: "tree", locals: {repo: @repo, commit: @commit, tree: @tree}))}"); - $("#tree-holder table").show("slide", { direction: "right" }, 150); - }); diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index f44ed529182..cbb21f2b9fb 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -3,6 +3,7 @@ location.href = "#{edit_project_path(@project)}"; - else :plain - $('.project_edit_holder').show(); - $(".edit_project").replaceWith("#{escape_javascript(render('form'))}"); + $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); $('.save-project-loader').hide(); + $('.project-edit-container').show(); + $('.project-edit-content .btn-save').enableButton(); diff --git a/app/views/projects/update_failed.js.haml b/app/views/projects/update_failed.js.haml deleted file mode 100644 index a3ac5f4088f..00000000000 --- a/app/views/projects/update_failed.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - $(".save-project-loader").replaceWith(errorMessage('#{escape_javascript(@error.message)}')); diff --git a/app/views/protected_branches/index.html.haml b/app/views/protected_branches/index.html.haml index 15644de552f..a338344c52d 100644 --- a/app/views/protected_branches/index.html.haml +++ b/app/views/protected_branches/index.html.haml @@ -51,4 +51,4 @@ (branch was removed from repository) %td - if can? current_user, :admin_project, @project - = link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small" + = link_to 'Unprotect', [@project, branch], confirm: 'Branch will be writable for developers. Are you sure?', method: :delete, class: "btn btn-remove btn-small" diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index b50484f6354..e2b19d0d824 100644 --- a/app/views/public/projects/index.html.haml +++ b/app/views/public/projects/index.html.haml @@ -1,6 +1,16 @@ -%h3.page_title - Projects (#{@projects.total_count}) - %small with read-only access +.row + .span6 + %h3.page_title + Projects (#{@projects.total_count}) + %small with read-only access + .span6 + .pull-right + = form_tag public_projects_path, method: :get, class: 'form-inline' do |f| + .search-holder + .input + = search_field_tag :search, params[:search], placeholder: "gitlab-ci", class: "span3 search-text-input", id: "projects_search" + = submit_tag 'Search', class: "btn btn-primary wide" + %hr .public-projects @@ -9,7 +19,10 @@ %li.clearfix %h5 %i.icon-share - = project.name_with_namespace + - if current_user + = link_to_project project + - else + = project.name_with_namespace .pull-right %pre.dark.tiny git clone #{project.http_url_to_repo} %p.description @@ -17,4 +30,4 @@ - unless @projects.present? %h3.nothing_here_message No public projects - = paginate @projects, theme: "admin" + = paginate @projects, theme: "gitlab" diff --git a/app/views/refs/logs_tree.js.haml b/app/views/refs/logs_tree.js.haml index 23a6dae7810..0b517327139 100644 --- a/app/views/refs/logs_tree.js.haml +++ b/app/views/refs/logs_tree.js.haml @@ -1,4 +1,4 @@ -- @logs.each do |content_data| +- @logs.each do |content_data| - file_name = content_data[:file_name] - commit = content_data[:commit] diff --git a/app/views/repositories/_branch.html.haml b/app/views/repositories/_branch.html.haml index a6faa5fd633..dd91e14b66b 100644 --- a/app/views/repositories/_branch.html.haml +++ b/app/views/repositories/_branch.html.haml @@ -1,5 +1,4 @@ -- commit = Commit.new(branch.commit) -- commit = CommitDecorator.decorate(commit) +- commit = Commit.new(Gitlab::Git::Commit.new(branch.commit)) %tr %td = link_to project_commits_path(@project, branch.name) do diff --git a/app/views/repositories/_feed.html.haml b/app/views/repositories/_feed.html.haml index eaf15ca77d6..6bb75265ffb 100644 --- a/app/views/repositories/_feed.html.haml +++ b/app/views/repositories/_feed.html.haml @@ -1,5 +1,4 @@ - commit = update -- commit = CommitDecorator.new(commit) %tr %td = link_to project_commits_path(@project, commit.head.name) do diff --git a/app/views/repositories/stats.html.haml b/app/views/repositories/stats.html.haml index dde35ea38aa..6d1fb4686ea 100644 --- a/app/views/repositories/stats.html.haml +++ b/app/views/repositories/stats.html.haml @@ -1,8 +1,8 @@ = render "commits/head" .row - .span5 - %h4 - Stats: + .span6 + %div#activity-chart.chart + %hr %p %b Total commits: %span= @stats.commits_count @@ -13,9 +13,8 @@ %b Authors: %span= @stats.authors_count - %br - %div#activity-chart - .span7 + + .span6 %h4 Top 50 Committers: %ol.styled - @stats.authors[0...50].each do |author| @@ -28,14 +27,7 @@ :javascript - $(function(){ - var labels = [#{@graph.labels.to_json}]; - var commits = [#{@graph.commits.join(', ')}]; - var r = Raphael('activity-chart'); - r.text(160, 10, "Commit activity for last #{@graph.weeks} weeks").attr({ font: "13px sans-serif" }); - r.barchart( - 10, 10, 400, 160, - [commits], - {colors:["#456"]} - ).label(labels, true); - }) + var labels = [#{@graph.labels.to_json}]; + var commits = [#{@graph.commits.join(', ')}]; + var title = "Commit activity for last #{@graph.weeks} weeks"; + Chart.init(labels, commits, title); diff --git a/app/views/repositories/tags.html.haml b/app/views/repositories/tags.html.haml index d4b8bbe10d4..bef5cd0841b 100644 --- a/app/views/repositories/tags.html.haml +++ b/app/views/repositories/tags.html.haml @@ -1,33 +1,30 @@ = render "commits/head" - unless @tags.empty? - %table - %thead - %tr - %th Name - %th Last commit - %th + %ul.bordered-list - @tags.each do |tag| - - commit = Commit.new(tag.commit) - - commit = CommitDecorator.decorate(commit) - %tr - %td - %strong - = link_to project_commits_path(@project, tag.name), class: "" do - %i.icon-tag - = tag.name - %small.light= truncate(tag.message || '', length: 70) - %td - = image_tag gravatar_icon(commit.author_email), class: "avatar s16" - = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do - = commit.short_id - %span.light - = time_ago_in_words(commit.committed_date) - ago - %td + - commit = Commit.new(Gitlab::Git::Commit.new(tag.commit)) + %li + %h5 + = link_to project_commits_path(@project, tag.name), class: "" do + %i.icon-tag + = tag.name + %small + = truncate(tag.message || '', length: 70) + .pull-right + %span.light + = time_ago_in_words(commit.committed_date) + ago + %div.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" + - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project, ref: tag.name) do - %i.icon-download-alt - Download + .pull-right + = link_to archive_project_repository_path(@project, ref: tag.name) do + %i.icon-download-alt + Download + - else %h3.nothing_here_message diff --git a/app/views/search/_blob.html.haml b/app/views/search/_blob.html.haml new file mode 100644 index 00000000000..39e71170706 --- /dev/null +++ b/app/views/search/_blob.html.haml @@ -0,0 +1,10 @@ +%li + .file_holder + .file_title + = link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do + %i.icon-file + %strong + = blob.filename + .file_content.code.term + %div{class: user_color_scheme_class} + = raw blob.colorize( formatter: :gitlab, options: { first_line_number: blob.startline } ) diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index a523fa254df..f7a00b23480 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -1,16 +1,35 @@ -%fieldset - %legend Groups: - .clearfix - = select_tag 'group_id', options_from_collection_for_select(current_user.authorized_groups, :id, :name, params[:group_id]), prompt: 'All', include_blank: true, class: 'trigger-submit chosen' - - -%fieldset - %legend Teams: - .clearfix - = select_tag 'team_id', options_from_collection_for_select(current_user.authorized_teams, :id, :name, params[:team_id]), prompt: 'All', include_blank: true, class: 'trigger-submit chosen' - -%fieldset - %legend Projects: - .clearfix - = select_tag 'project_id', options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace, params[:project_id]), prompt: 'All', include_blank: true, class: 'trigger-submit chosen' +.dropdown.inline + %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} + %i.icon-tags + %span.light Group: + - if @group.present? + %strong= @group.name + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to search_path(group_id: nil) do + Any + - current_user.authorized_groups.sort_by(&:name).each do |group| + %li + = link_to search_path(group_id: group.id, search: params[:search]) do + = group.name +.dropdown.inline.prepend-left-10 + %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} + %i.icon-tags + %span.light Project: + - if @project.present? + %strong= @project.name_with_namespace + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to search_path(project_id: nil) do + Any + - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project| + %li + = link_to search_path(project_id: project.id, search: params[:search]) do + = project.name_with_namespace diff --git a/app/views/search/_result.html.haml b/app/views/search/_result.html.haml index bfa46075baa..4d8caa5e70c 100644 --- a/app/views/search/_result.html.haml +++ b/app/views/search/_result.html.haml @@ -1,9 +1,19 @@ %fieldset %legend Search results - %span.cgray (#{@projects.count + @merge_requests.count + @issues.count + @wiki_pages.count}) + %span.cgray (#{@total_results}) + +- if @project + %ul.nav.nav-pills + %li{class: ("active" if params[:search_code].present?)} + = link_to search_path(params.merge(search_code: true)) do + Repository Code + %li{class: ("active" if params[:search_code].blank?)} + = link_to search_path(params.merge(search_code: nil)) do + Everything else + .search_results - %ul.well-list + %ul.bordered-list - @projects.each do |project| %li project: @@ -33,6 +43,11 @@ = truncate wiki_page.title, length: 50 %span.light (#{wiki_page.project.name_with_namespace}) + - @blobs.each do |blob| + = render 'blob', blob: blob + + = paginate @blobs, theme: 'gitlab' + :javascript $(function() { $(".search_results .term").highlight("#{escape_javascript(params[:search])}"); diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 5914c22df6e..f9647377961 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -4,12 +4,13 @@ %span Looking for .input = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" + = hidden_field_tag :project_id, params[:project_id] + = hidden_field_tag :group_id, params[:group_id] + = hidden_field_tag :search_code, params[:search_code] = submit_tag 'Search', class: "btn btn-primary wide" - .clearfix - .row - .span3 - = render 'filter', f: f - .span9 - .results - - if params[:search].present? - = render 'search/result' + .prepend-top-10 + = render 'filter', f: f + + .results.prepend-top-10 + - if params[:search].present? + = render 'search/result' diff --git a/app/views/services/_form.html.haml b/app/views/services/_form.html.haml new file mode 100644 index 00000000000..ff6769531c4 --- /dev/null +++ b/app/views/services/_form.html.haml @@ -0,0 +1,48 @@ +%h3.page_title + - if @service.activated? + %span.cgreen + %i.icon-circle + - else + %span.cgray + %i.icon-circle-blank + = @service.title + +%p= @service.description + +.back_link + = link_to project_services_path(@project) do + ← to services + +%hr + += form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put) do |f| + - if @service.errors.any? + .alert.alert-error + %ul + - @service.errors.full_messages.each do |msg| + %li= msg + + + .control-group + = f.label :active, "Active", class: "control-label" + .controls + = f.check_box :active + + - @service.fields.each do |field| + - name = field[:name] + - type = field[:type] + - placeholder = field[:placeholder] + + .control-group + = f.label name, class: "control-label" + .controls + - if type == 'text' + = f.text_field name, class: "input-xlarge", placeholder: placeholder + - elsif type == 'checkbox' + = f.check_box name + + .form-actions + = f.submit 'Save', class: 'btn btn-save' + + - if @service.valid? && @service.activated? + = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn btn-small' diff --git a/app/views/services/_gitlab_ci.html.haml b/app/views/services/_gitlab_ci.html.haml deleted file mode 100644 index dfde643849e..00000000000 --- a/app/views/services/_gitlab_ci.html.haml +++ /dev/null @@ -1,46 +0,0 @@ -%h3.page_title - GitLab CI - %small Continuous integration server from GitLab - .pull-right - - if @service.active - %small.cgreen Enabled - - else - %small.cgray Disabled - - - -.back_link - = link_to project_services_path(@project) do - ← to services - -%hr -= form_for(@service, :as => :service, :url => project_service_path(@project, :gitlab_ci), :method => :put) do |f| - - if @service.errors.any? - .alert.alert-error - %ul - - @service.errors.full_messages.each do |msg| - %li= msg - - - .control-group - = f.label :active, "Active", class: "control-label" - .controls - = f.check_box :active - - .control-group - = f.label :project_url, "Project URL", class: "control-label" - .controls - = f.text_field :project_url, class: "input-xlarge", placeholder: "http://ci.gitlabhq.com/projects/3" - - .control-group - = f.label :token, class: "control-label" do - CI Project token - .controls - = f.text_field :token, class: "input-xlarge", placeholder: "GitLab CI project specific token" - - - .form-actions - = f.submit 'Save', class: 'btn btn-save' - - - if @service.valid? && @service.active - = link_to 'Test settings', test_project_service_path(@project), class: 'btn btn-small' diff --git a/app/views/services/edit.html.haml b/app/views/services/edit.html.haml index 0c63a7ed58c..d4bc9e41c27 100644 --- a/app/views/services/edit.html.haml +++ b/app/views/services/edit.html.haml @@ -1,3 +1,3 @@ = render "projects/settings_nav" -= render 'gitlab_ci' += render 'form' diff --git a/app/views/services/index.html.haml b/app/views/services/index.html.haml index eb2f8d0ca1c..bd52948a6fd 100644 --- a/app/views/services/index.html.haml +++ b/app/views/services/index.html.haml @@ -3,30 +3,16 @@ %h3.page_title Services %br -%ul.ui-box.well-list - %li - %h4.cgreen - = link_to edit_project_service_path(@project, :gitlab_ci) do - GitLab CI - %small Continuous integration server from GitLab - .pull-right - - if @gitlab_ci_service.try(:active) - %small.cgreen - %i.icon-ok - Enabled +%ul.bordered-list + - @services.each do |service| + %li + %h4 + - if service.activated? + %span.cgreen + %i.icon-circle - else - %small.cgray - %i.icon-off - Disabled - %li.disabled - %h4 - Jenkins CI - %small An extendable open source continuous integration server - .pull-right - %small Not implemented yet - %li.disabled - %h4 - Campfire - %small Web-based group chat tool - .pull-right - %small Not implemented yet + %span.cgray + %i.icon-circle-blank + = link_to edit_project_service_path(@project, service.to_param) do + = service.title + %p= service.description diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index bd9ca729352..62e0fa25898 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,4 +1,13 @@ -.input-prepend.project_clone_holder +.input-prepend.input-append.project_clone_holder %button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH - %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge", readonly: true + %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= gitlab_config.protocol.upcase + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span7", readonly: true + %span.add-on + - if @project.public + .cblue + %i.icon-share + public + - else + .cgreen + %i.icon-lock + private diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index 85391a34316..a2800b11d8d 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -4,7 +4,7 @@ - project = group[0] %h5.title = link_to_project project - %ul.well-list + %ul.well-list.mr-list - group[1].each do |merge_request| = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) %hr diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml new file mode 100644 index 00000000000..c97f8ba0f0e --- /dev/null +++ b/app/views/shared/_promo.html.haml @@ -0,0 +1,4 @@ +.gitlab-promo + = link_to "Homepage", "http://gitlab.org" + = link_to "Blog", "http://blog.gitlab.org" + = link_to "@gitlabhq", "https://twitter.com/gitlabhq" diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index 8b44cf1944e..dc8c656e12e 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -3,3 +3,5 @@ = hidden_field_tag :destination, destination - if defined?(path) = hidden_field_tag :path, path + - @options && @options.each do |key, value| + = hidden_field_tag key, value, id: nil diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index d9514890b20..95e9e0357bc 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -33,7 +33,7 @@ = f.submit 'Save', class: "btn-save btn" = link_to "Cancel", snippets_path(@project), class: " btn" - unless @snippet.new_record? - .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" + .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" :javascript diff --git a/app/views/team_members/_assigned_team.html.haml b/app/views/team_members/_assigned_team.html.haml index 1d512c44b26..51a31a6456d 100644 --- a/app/views/team_members/_assigned_team.html.haml +++ b/app/views/team_members/_assigned_team.html.haml @@ -1,7 +1,7 @@ %li{id: dom_id(team), class: "user_team_row team_#{team.id}"} .pull-right - if can?(current_user, :admin_team_member, @project) - = link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn btn-remove btn-tiny" do + = link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you sure?", class: "btn btn-remove btn-tiny" do %i.icon-minus.icon-white %strong= link_to team.name, team_path(team), title: team.name, class: "dark" diff --git a/app/views/team_members/_team.html.haml b/app/views/team_members/_team.html.haml index 2ec8c1a8451..4b49b308edc 100644 --- a/app/views/team_members/_team.html.haml +++ b/app/views/team_members/_team.html.haml @@ -1,8 +1,9 @@ - team.each do |access, members| - .ui-box + - role = Project.access_options.key(access).pluralize + .ui-box{class: role.downcase} %h5.title - = Project.access_options.key(access).pluralize - %small= members.size + = role + %span.light (#{members.size}) %ul.well-list - members.sort_by(&:user_name).each do |team_member| = render 'team_members/team_member', member: team_member diff --git a/app/views/team_members/_team_member.html.haml b/app/views/team_members/_team_member.html.haml index 2b0709beb92..5fd8d2465d1 100644 --- a/app/views/team_members/_team_member.html.haml +++ b/app/views/team_members/_team_member.html.haml @@ -16,11 +16,11 @@ = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit" .pull-right - if current_user == user - %span.label This is you! + %span.label.label-success This is you! - if @project.namespace_owner == user - %span.label Owner + %span.label.label-info Owner - elsif user.blocked? - %span.label Blocked + %span.label.label-error Blocked - elsif allow_admin = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do %i.icon-minus.icon-white diff --git a/app/views/team_members/create.js.haml b/app/views/team_members/create.js.haml deleted file mode 100644 index b7dff35a269..00000000000 --- a/app/views/team_members/create.js.haml +++ /dev/null @@ -1,13 +0,0 @@ -- if @user_project_relation.valid? - :plain - $("#new_team_member").hide("slide", { direction: "right" }, 150, function(){ - $("#team-table").show("slide", { direction: "left" }, 150, function() { - $("#new_team_member").remove(); - $("#team-table").replaceWith("#{escape_javascript(render('projects/team'))}"); - $(".add_new").show(); - }); - }); -- else - :plain - $("#new_team_member").replaceWith("#{escape_javascript(render('form'))}"); - $('select#team_member_user_id').chosen(); diff --git a/app/views/team_members/index.html.haml b/app/views/team_members/index.html.haml index 50d44bcd8d8..3132fd5ca8a 100644 --- a/app/views/team_members/index.html.haml +++ b/app/views/team_members/index.html.haml @@ -22,22 +22,22 @@ .span3 %ul.nav.nav-pills.nav-stacked %li{class: ("active" if !params[:type])} - = link_to project_team_members_path(type: nil) do + = link_to project_team_index_path(type: nil) do All %li{class: ("active" if params[:type] == 'masters')} - = link_to project_team_members_path(type: 'masters') do + = link_to project_team_index_path(type: 'masters') do Masters %span.pull-right= @project.users_projects.masters.count %li{class: ("active" if params[:type] == 'developers')} - = link_to project_team_members_path(type: 'developers') do + = link_to project_team_index_path(type: 'developers') do Developers %span.pull-right= @project.users_projects.developers.count %li{class: ("active" if params[:type] == 'reporters')} - = link_to project_team_members_path(type: 'reporters') do + = link_to project_team_index_path(type: 'reporters') do Reporters %span.pull-right= @project.users_projects.reporters.count %li{class: ("active" if params[:type] == 'guests')} - = link_to project_team_members_path(type: 'guests') do + = link_to project_team_index_path(type: 'guests') do Guests %span.pull-right= @project.users_projects.guests.count diff --git a/app/views/teams/issues.html.haml b/app/views/teams/issues.html.haml index 5b17c5d4f0b..94818ccd7f4 100644 --- a/app/views/teams/issues.html.haml +++ b/app/views/teams/issues.html.haml @@ -14,7 +14,7 @@ - @project = group[0] %h5.title = link_to_project @project - %ul.well-list.issues_table + %ul.well-list.issues-list - group[1].each do |issue| = render issue %hr diff --git a/app/views/teams/members/_member.html.haml b/app/views/teams/members/_member.html.haml new file mode 100644 index 00000000000..17096d2f1cd --- /dev/null +++ b/app/views/teams/members/_member.html.haml @@ -0,0 +1,31 @@ +- user = member.user +- allow_admin = can? current_user, :manage_user_team, @team +%li{id: dom_id(member), class: "team_member_row user_#{user.id}"} + .row + .span3 + = link_to user_path(user.username), title: user.name, class: "dark" do + = image_tag gravatar_icon(user.email, 40), class: "avatar s32" + = link_to user_path(user.username), title: user.name, class: "dark" do + %strong= truncate(user.name, lenght: 40) + %br + %small.cgray= user.username + + .span5.pull-right + - if allow_admin + .pull-left + = form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f| + = label_tag :group_admin do + = f.check_box :group_admin, class: 'trigger-submit' + %span Admin access + + = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "span2 trigger-submit" + .pull-right + - if current_user == user + %span.label.label-success This is you! + - if @team.owner == user + %span.label.label-info Owner + - elsif user.blocked? + %span.label.label-error Blocked + - elsif allow_admin + = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove", title: "Remove from team" do + %i.icon-minus.icon-white diff --git a/app/views/teams/members/_show.html.haml b/app/views/teams/members/_show.html.haml deleted file mode 100644 index 1a323043d09..00000000000 --- a/app/views/teams/members/_show.html.haml +++ /dev/null @@ -1,30 +0,0 @@ -- user = member.user -- allow_admin = can? current_user, :manage_user_team, @team -%li{id: dom_id(member), class: "team_member_row user_#{user.id}"} - .row - .span5 - = link_to user_path(user.username), title: user.name, class: "dark" do - = image_tag gravatar_icon(user.email, 40), class: "avatar s32" - = link_to user_path(user.username), title: user.name, class: "dark" do - %strong= truncate(user.name, lenght: 40) - %br - %small.cgray= user.username - - .span4 - - if allow_admin - = form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f| - = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium trigger-submit" - %br - = label_tag do - = f.check_box :group_admin, class: 'trigger-submit' - %span Admin access - .pull-right - - if current_user == user - %span.btn.disabled This is you! - - if @team.owner == user - %span.btn.disabled Owner - - elsif user.blocked? - %span.btn.disabled.blocked Blocked - - elsif allow_admin - = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove", title: "Remove from team" do - %i.icon-minus.icon-white diff --git a/app/views/teams/members/_team.html.haml b/app/views/teams/members/_team.html.haml index d8afc1fa371..52bb597cd80 100644 --- a/app/views/teams/members/_team.html.haml +++ b/app/views/teams/members/_team.html.haml @@ -1,16 +1,10 @@ -- grouped_user_team_members(@team).each do |access, members| +- grouped_user_team_members(team).each do |access, members| + - access_key = Project.access_options.key(access) + - next if params[:type].present? && params[:type] != access_key.tableize .ui-box %h5.title - = Project.access_options.key(access).pluralize + = access_key.pluralize %small= members.size - %ul.well-list - - members.sort_by(&:user_name).each do |up| - = render(partial: 'teams/members/show', locals: {member: up}) - - -:javascript - $(function(){ - $('.repo-access-select, .project-access-select').live("change", function() { - $(this.form).submit(); - }); - }) + %ul.well-list.team-members + - members.sort_by(&:user_name).each do |member| + = render 'teams/members/member', member: member diff --git a/app/views/teams/members/index.html.haml b/app/views/teams/members/index.html.haml index 87438266cfb..02700e9fb71 100644 --- a/app/views/teams/members/index.html.haml +++ b/app/views/teams/members/index.html.haml @@ -12,6 +12,26 @@ %hr -.clearfix -%div.team-table - = render partial: "teams/members/team", locals: {project: @team} +.row + .span3 + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if !params[:type])} + = link_to team_members_path(@team, type: nil) do + All + %li{class: ("active" if params[:type] == 'masters')} + = link_to team_members_path(@team, type: 'masters') do + Masters + %li{class: ("active" if params[:type] == 'developers')} + = link_to team_members_path(@team, type: 'developers') do + Developers + %li{class: ("active" if params[:type] == 'reporters')} + = link_to team_members_path(@team, type: 'reporters') do + Reporters + %li{class: ("active" if params[:type] == 'guests')} + = link_to team_members_path(@team, type: 'guests') do + Guests + + .span9 + .clearfix + %div.team-table + = render "teams/members/team", team: @team diff --git a/app/views/teams/members/new.html.haml b/app/views/teams/members/new.html.haml index 9b9b3cef59b..99530ebb7f0 100644 --- a/app/views/teams/members/new.html.haml +++ b/app/views/teams/members/new.html.haml @@ -1,29 +1,25 @@ %h3.page_title Team: #{@team.name} -%fieldset - %legend Members (#{@team.members.count}) - = form_tag team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do - %table#members_list - %thead - %tr - %th User name - %th Default project access - %th Team access - %th - - @team.members.each do |member| - %tr.member - %td - = member.name - %small= "(#{member.username})" - %td= @team.human_default_projects_access(member) - %td= @team.admin?(member) ? "Admin" : "Member" - %td - %tr - %td - = users_select_tag(:user_ids, multiple: true) - %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" } - %td - %span= check_box_tag :group_admin - %span Admin? - %td= submit_tag 'Add User', class: "btn btn-create", id: :add_members_to_team +%hr + += form_tag team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do + %h6 1. Choose people you want in the team + .clearfix + = label_tag :user_ids, "People" + .input + = users_select_tag(:user_ids, multiple: true) + + %h6 2. Set access level for them + .clearfix + = label_tag :project_access, "Project Access" + .input= select_tag :default_project_access, options_for_select(Project.access_options), class: "project-access-select chosen" + + .clearfix + = label_tag :group_admin do + %span Team Admin? + .input= check_box_tag :group_admin + + .actions + = submit_tag 'Add users', class: "btn btn-create", id: :add_members_to_team + = link_to "Cancel", team_members_path(@team), class: "btn btn-cancel" diff --git a/app/views/teams/show.atom.builder b/app/views/teams/show.atom.builder index bb0f666e860..fffb78d53e8 100644 --- a/app/views/teams/show.atom.builder +++ b/app/views/teams/show.atom.builder @@ -8,10 +8,9 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear @events.each do |event| if event.proper? - event = EventDecorator.decorate(event) xml.entry do - event_link = event.feed_url - event_title = event.feed_title + event_link = event_feed_url(event) + event_title = event_feed_title(event) xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" xml.link :href => event_link diff --git a/app/views/teams/show.html.haml b/app/views/teams/show.html.haml index 2eb0283e0ab..2ad7f743010 100644 --- a/app/views/teams/show.html.haml +++ b/app/views/teams/show.html.haml @@ -1,4 +1,4 @@ -.projects +.dashboard .activities.span8 = link_to dashboard_path, class: 'btn btn-tiny' do ← To dashboard @@ -12,7 +12,7 @@ .loading.hide .side.span4 - if @team.description.present? - .description.well.light + .description.well.well-small.light = @team.description = render "projects", projects: @projects .prepend-top-20 @@ -22,10 +22,4 @@ News Feed %hr - .gitlab-promo - = link_to "Homepage", "http://gitlabhq.com" - = link_to "Blog", "http://blog.gitlabhq.com" - = link_to "@gitlabhq", "https://twitter.com/gitlabhq" - -:javascript - $(function(){ Pager.init(20, true); }); + = render 'shared/promo' diff --git a/app/views/tree/_blob.html.haml b/app/views/tree/_blob.html.haml deleted file mode 100644 index ebf1ee2c678..00000000000 --- a/app/views/tree/_blob.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -.file_holder - .file_title - %i.icon-file - %span.file_name - = blob.name - %small= number_to_human_size blob.size - %span.options= render "tree/blob_actions" - - if blob.text? - = render "tree/blob/text", blob: blob - - elsif blob.image? - = render "tree/blob/image", blob: blob - - else - = render "tree/blob/download", blob: blob diff --git a/app/views/tree/_blob_item.html.haml b/app/views/tree/_blob_item.html.haml new file mode 100644 index 00000000000..ec15b608f85 --- /dev/null +++ b/app/views/tree/_blob_item.html.haml @@ -0,0 +1,9 @@ +%tr{ class: "tree-item #{tree_hex_class(blob_item)}" } + %td.tree-item-file-name + = tree_icon(type) + %strong= link_to truncate(blob_item.name, length: 40), 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 + %td.tree_commit{ colspan: 2 } diff --git a/app/views/tree/_tree.html.haml b/app/views/tree/_tree.html.haml index 24a57ae7a29..cc45faa1459 100644 --- a/app/views/tree/_tree.html.haml +++ b/app/views/tree/_tree.html.haml @@ -3,7 +3,7 @@ %i.icon-angle-right = link_to project_tree_path(@project, @ref) do = @project.path - - tree.breadcrumbs(6) do |title, path| + - tree_breadcrumbs(tree, 6) do |title, path| \/ %li - if path @@ -12,36 +12,40 @@ = link_to title, '#' %div#tree-content-holder.tree-content-holder - - if tree.is_blob? - = render "tree/blob", blob: tree - - else - %table#tree-slider{class: "table_#{@hex_path} tree-table" } - %thead - %tr - %th Name - %th Last Update - %th Last Commit - %th= link_to "history", project_commits_path(@project, @id), class: "btn btn-tiny pull-right" + %table#tree-slider{class: "table_#{@hex_path} tree-table" } + %thead + %tr + %th Name + %th Last Update + %th + Last Commit + + %i.icon-angle-right + + %small.light + = link_to @commit.short_id, project_commit_path(@project, @commit) + – + = truncate(@commit.title, length: 50) + %th= link_to "history", project_commits_path(@project, @id), class: "btn btn-tiny pull-right" - - if tree.up_dir? - %tr.tree-item - %td.tree-item-file-name - = image_tag "file_empty.png", size: '16x16' - = link_to "..", project_tree_path(@project, tree.up_dir_path) - %td - %td - %td + - if tree.up_dir? + %tr.tree-item + %td.tree-item-file-name + = image_tag "file_empty.png", size: '16x16' + = link_to "..", project_tree_path(@project, up_dir_path(tree)) + %td + %td + %td - = render_tree(tree.contents) + = render_tree(tree) - - if tree.readme - = render "tree/readme", readme: tree.readme + - if tree.readme + = render "tree/readme", readme: tree.readme %div.tree_progress -- unless tree.is_blob? - :javascript - // Load last commit log for each file in tree - $(window).load(function(){ - ajaxGet('#{@logs_path}'); - }); +:javascript + // Load last commit log for each file in tree + $('#tree-slider').waitForImages(function() { + ajaxGet('#{@logs_path}'); + }); diff --git a/app/views/tree/_tree_commit_column.html.haml b/app/views/tree/_tree_commit_column.html.haml index 9d02132b0f4..7ae2582c130 100644 --- a/app/views/tree/_tree_commit_column.html.haml +++ b/app/views/tree/_tree_commit_column.html.haml @@ -1,2 +1,2 @@ -%span.tree_author= commit.author_link avatar: true +%span.tree_author= commit_author_link(commit, avatar: true) = link_to_gfm truncate(commit.title, length: 80), project_commit_path(@project, commit.id), class: "tree-commit-link" diff --git a/app/views/tree/show.js.haml b/app/views/tree/show.js.haml deleted file mode 100644 index fadd5e2251f..00000000000 --- a/app/views/tree/show.js.haml +++ /dev/null @@ -1,10 +0,0 @@ -:plain - // Load Files list - $("#tree-holder").html("#{escape_javascript(render(partial: "tree", locals: {tree: @tree}))}"); - $("#tree-content-holder").show("slide", { direction: "right" }, 150); - $('.project-refs-form #path').val("#{@path}"); - - // Load last commit log for each file in tree - $('#tree-slider').waitForImages(function() { - ajaxGet('#{@logs_path}'); - }); diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml index 91bd200df44..ee805474830 100644 --- a/app/views/votes/_votes_inline.html.haml +++ b/app/views/votes/_votes_inline.html.haml @@ -1,6 +1,9 @@ .votes.votes-inline - .upvotes= votable.upvotes - .progress - .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"} - .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} - .downvotes= votable.downvotes + - unless votable.upvotes.zero? + .upvotes + + #{votable.upvotes} + - unless votable.downvotes.zero? + \/ + - unless votable.downvotes.zero? + .downvotes + \- #{votable.downvotes} diff --git a/app/views/walls/show.html.haml b/app/views/walls/show.html.haml index 0cd29486d93..88aecee0815 100644 --- a/app/views/walls/show.html.haml +++ b/app/views/walls/show.html.haml @@ -1,5 +1,5 @@ %div.wall-page - %ul.well-list.notes + %ul.notes - if can? current_user, :write_note, @project .note-form-holder @@ -12,11 +12,6 @@ = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button" .note-form-option - = label_tag :notify do - = check_box_tag :notify, 1, false - %span.light Notify team via email - - .note-form-option %a.choose-btn.btn.btn-small.js-choose-note-attachment-button %i.icon-paper-clip %span Choose File ... @@ -26,8 +21,3 @@ .hint.pull-right CTRL + Enter to send message .clearfix - -:javascript - $(function(){ - Wall.init(#{@project.id}); - }); diff --git a/app/views/wikis/_nav.html.haml b/app/views/wikis/_nav.html.haml index 0dffdd8fc14..09a1986e105 100644 --- a/app/views/wikis/_nav.html.haml +++ b/app/views/wikis/_nav.html.haml @@ -11,8 +11,8 @@ Git Access - if can?(current_user, :write_wiki, @project) - %li.pull-right - = link_to '#', class: "add-new-wiki" do + .pull-right + = link_to '#', class: "add-new-wiki btn btn-small btn-primary" do %i.icon-plus New Page diff --git a/app/views/wikis/_new.html.haml b/app/views/wikis/_new.html.haml index 50b40bff41c..ca8e7c1b4b4 100644 --- a/app/views/wikis/_new.html.haml +++ b/app/views/wikis/_new.html.haml @@ -1,25 +1,12 @@ %div#modal-new-wiki.modal.hide .modal-header %a.close{href: "#"} × - %h3 New Wiki Page + %h3.page_title New Wiki Page .modal-body = label_tag :new_wiki_path do %span Page slug - = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'input-xlarge' + = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'input-xlarge', required: true, :'data-wikis-path' => project_wikis_path(@project) + %p.hint + Please dont use spaces and slashes .modal-footer = link_to 'Build', '#', class: 'build-new-wiki btn btn-create' - -:javascript - $(function(){ - var modal = $('#modal-new-wiki').modal({modal: true, show:false}); - $('.add-new-wiki').bind("click", function(){ - modal.show(); - }); - $('.build-new-wiki').bind("click", function(){ - location.href = "#{project_wikis_path(@project)}/" + $('#new_wiki_path').val(); - }); - $('.modal-header .close').bind("click", function(){ - modal.hide(); - }) - }) - diff --git a/app/views/wikis/git_access.html.haml b/app/views/wikis/git_access.html.haml index 58c8aa06aca..462d483fd85 100644 --- a/app/views/wikis/git_access.html.haml +++ b/app/views/wikis/git_access.html.haml @@ -24,8 +24,8 @@ %legend Clone Your Wiki: %pre.dark :preserve - git clone #{@gollum_wiki.path_with_namespace}.git - cd #{@gollum_wiki.path_with_namespace} + git clone #{@gollum_wiki.ssh_url_to_repo} + cd #{@gollum_wiki.path} %legend Start Gollum And Edit Locally: %pre.dark diff --git a/app/views/wikis/history.html.haml b/app/views/wikis/history.html.haml index 599e9cf6793..f4946ed000d 100644 --- a/app/views/wikis/history.html.haml +++ b/app/views/wikis/history.html.haml @@ -14,12 +14,13 @@ %th Format %tbody - @wiki.versions.each do |version| - - commit = CommitDecorator.new(version) + - commit = version %tr %td = link_to project_wiki_path(@project, @wiki, version_id: commit.id) do = commit.short_id - %td= commit.author_link avatar: true, size: 24 + %td + = commit_author_link(commit, avatar: true, size: 24) %td = commit.title %td diff --git a/app/views/wikis/pages.html.haml b/app/views/wikis/pages.html.haml index eb65599d087..95d5eef16f5 100644 --- a/app/views/wikis/pages.html.haml +++ b/app/views/wikis/pages.html.haml @@ -21,5 +21,5 @@ = wiki_page.created_at.to_s(:short) do (#{time_ago_in_words(wiki_page.created_at)} ago) - - commit = CommitDecorator.decorate(wiki_page.version) - %td= commit.author_link avatar: true, size: 24 + %td + = commit_author_link(wiki_page.version, avatar: true, size: 24) diff --git a/app/views/wikis/show.html.haml b/app/views/wikis/show.html.haml index b660a15ee32..b237bc524ed 100644 --- a/app/views/wikis/show.html.haml +++ b/app/views/wikis/show.html.haml @@ -13,5 +13,4 @@ = preserve do = render_wiki_content(@wiki) -- commit = CommitDecorator.new(@wiki.version) -%p.time Last edited by #{commit.author_link(avatar: true, size: 16)} #{time_ago_in_words @wiki.created_at} ago +%p.time Last edited by #{commit_author_link(@wiki.version, avatar: true, size: 16)} #{time_ago_in_words @wiki.created_at} ago diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 72cef0fd295..6416aa608ec 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -1,5 +1,6 @@ class PostReceive include Sidekiq::Worker + include Gitlab::Identifier sidekiq_options queue: :post_receive @@ -8,7 +9,7 @@ class PostReceive if repo_path.start_with?(Gitlab.config.gitlab_shell.repos_path.to_s) repo_path.gsub!(Gitlab.config.gitlab_shell.repos_path.to_s, "") else - Gitlab::GitLogger.error("POST-RECEIVE: Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"") + log("Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"") end repo_path.gsub!(/.git$/, "") @@ -17,31 +18,21 @@ class PostReceive project = Project.find_with_namespace(repo_path) if project.nil? - Gitlab::GitLogger.error("POST-RECEIVE: Triggered hook for non-existing project with full path \"#{repo_path} \"") + log("Triggered hook for non-existing project with full path \"#{repo_path} \"") return false end - user = if identifier.blank? - # Local push from gitlab - email = project.repository.commit(newrev).author.email rescue nil - User.find_by_email(email) if email - - elsif identifier =~ /\Auser-\d+\Z/ - # git push over http - user_id = identifier.gsub("user-", "") - User.find_by_id(user_id) - - elsif identifier =~ /\Akey-\d+\Z/ - # git push over ssh - key_id = identifier.gsub("key-", "") - Key.find_by_id(key_id).try(:user) - end + user = identify(identifier, project, newrev) unless user - Gitlab::GitLogger.error("POST-RECEIVE: Triggered hook for non-existing user \"#{identifier} \"") + log("Triggered hook for non-existing user \"#{identifier} \"") return false end GitPushService.new.execute(project, user, oldrev, newrev, ref) end + + def log(message) + Gitlab::GitLogger.error("POST-RECEIVE: #{message}") + end end |
