diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-06-22 18:00:11 +0200 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-06-22 18:00:11 +0200 |
commit | 73e003013f1f6f81f3ca3518784d41d608490403 (patch) | |
tree | 44ea8d05e155938f265fd577ec3d57a4c7e45feb /app | |
parent | 3fe3cbf222a036d4487b9630e2abfc58ec7515cf (diff) | |
parent | cc9b5c49d1de2a9e1e90895700376793b9d614f6 (diff) | |
download | gitlab-ce-73e003013f1f6f81f3ca3518784d41d608490403.tar.gz |
Merge branch 'master' into admin-edit-identities
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Conflicts:
app/views/admin/users/show.html.haml
Diffstat (limited to 'app')
56 files changed, 539 insertions, 301 deletions
diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico Binary files differindex bfb74960c48..3479cbbb46f 100644 --- a/app/assets/images/favicon.ico +++ b/app/assets/images/favicon.ico diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png Binary files differdeleted file mode 100644 index 917bcfcb7e7..00000000000 --- a/app/assets/images/logo-white.png +++ /dev/null diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg new file mode 100644 index 00000000000..c09785cb96f --- /dev/null +++ b/app/assets/images/logo.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> + <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch --> + <title>Slice 1</title> + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> + <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)"> + <g id="Page-1" sketch:type="MSShapeGroup"> + <g id="Fill-1-+-Group-24"> + <g id="Group-24"> + <g id="Group"> + <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path> + <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path> + <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path> + <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path> + <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path> + <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path> + <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path> + </g> + </g> + </g> + </g> + </g> + </g> +</svg>
\ No newline at end of file diff --git a/app/assets/images/logo_wordmark.svg b/app/assets/images/logo_wordmark.svg new file mode 100644 index 00000000000..a37fe1235cb --- /dev/null +++ b/app/assets/images/logo_wordmark.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="546px" height="194px" viewBox="0 0 546 194" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> + <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch --> + <title>Fill 1 + Group 24</title> + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> + <g id="Fill-1-+-Group-24" sketch:type="MSLayerGroup"> + <g id="Group-24" sketch:type="MSShapeGroup"> + <path d="M316.7906,65.3001 C301.5016,65.3001 292.0046,77.4461 292.0046,97.0001 C292.0046,116.5541 301.5016,128.7001 316.7906,128.7001 C322.5346,128.7001 327.8716,127.0711 332.2226,123.9881 L332.4336,123.8391 L332.4336,101.8711 L310.4336,101.8711 L310.4336,94.0711 L341.4336,94.0711 L341.4336,126.8061 C334.8706,133.1501 326.3546,136.5001 316.7906,136.5001 C296.2666,136.5001 283.0046,120.9951 283.0046,97.0001 C283.0046,73.0051 296.2666,57.5001 316.7906,57.5001 C326.7826,57.5001 335.2176,61.1481 341.2206,68.0561 L335.2246,73.0381 C330.6986,67.9041 324.4986,65.3001 316.7906,65.3001 L316.7906,65.3001 Z M489.8836,135.2501 L482.9356,135.2501 L480.6016,128.8021 L480.0486,129.2991 C479.9716,129.3681 472.2196,136.2501 462.4606,136.2501 C452.6096,136.2501 445.4606,129.6961 445.4606,120.6671 C445.4606,107.5951 456.7446,104.8511 466.2096,104.8511 C473.5836,104.8511 480.1886,106.5111 480.2546,106.5281 L480.8776,106.6871 L480.8776,105.1011 C480.8776,97.9861 476.4356,94.3781 467.6726,94.3781 C462.3646,94.3781 456.7556,95.6891 451.4236,98.1701 L447.8206,91.9581 C452.5266,88.8961 459.6726,85.3781 467.6726,85.3781 C481.5806,85.3781 489.8836,92.9341 489.8836,105.5891 L489.8836,135.2501 Z M470.6886,111.7771 C460.0716,111.7771 454.4606,114.8511 454.4606,120.6671 C454.4606,124.7281 457.5256,127.2501 462.4606,127.2501 C470.5906,127.2501 477.7276,123.9181 480.6626,121.9481 L480.8836,121.8001 L480.8836,112.6201 L480.4676,112.5491 C480.4226,112.5411 475.8766,111.7771 470.6886,111.7771 L470.6886,111.7771 Z M440.4576,127.4501 L440.4576,135.2501 L410.4606,135.2501 L410.4606,61.2501 L419.4606,61.2501 L419.4606,127.4501 L440.4576,127.4501 Z M520.9416,136.5001 C515.0966,136.5001 508.6886,135.6961 501.8926,134.1091 L501.8926,61.2501 L510.8926,61.2501 L510.8926,89.3131 L511.6656,88.8111 C511.7146,88.7791 516.7346,85.5711 523.6536,85.5711 C525.0336,85.5711 526.4146,85.7001 527.7486,85.9521 C539.0936,88.2761 545.8666,97.4301 545.8666,110.4391 C545.8666,125.7831 535.6176,136.5001 520.9416,136.5001 L520.9416,136.5001 Z M521.9426,94.3781 C518.3636,94.3781 514.6196,95.6031 511.1166,97.9191 L510.8926,98.0681 L510.8926,127.9021 L511.3196,127.9651 C514.6986,128.4601 517.9356,128.7121 520.9416,128.7121 C530.3176,128.7121 536.8666,121.1971 536.8666,110.4391 C536.8666,100.2321 531.4266,94.3781 521.9426,94.3781 L521.9426,94.3781 Z M398.4516,86.2501 L398.4516,94.0501 L383.4516,94.0501 L383.4516,116.9501 C383.4516,119.7551 384.5436,122.3921 386.5276,124.3741 C388.5096,126.3581 391.1466,127.4501 393.9516,127.4501 L398.4516,127.4501 L398.4516,135.2501 L393.9516,135.2501 C383.1996,135.2501 374.4516,126.5021 374.4516,115.7501 L374.4516,61.2501 L383.4516,61.2501 L383.4516,86.2501 L398.4516,86.2501 Z M353.4426,66.2501 L362.4426,66.2501 L362.4426,75.2501 L353.4426,75.2501 L353.4426,66.2501 Z M353.4426,86.2501 L362.4426,86.2501 L362.4426,135.2501 L353.4426,135.2501 L353.4426,86.2501 Z" id="Fill-2" fill="#8C929D"></path> + <g id="Group"> + <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path> + <path id="Fill-6" fill="#FC6D26"></path> + <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path> + <path id="Fill-10" fill="#FC6D26"></path> + <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path> + <path id="Fill-14" fill="#FC6D26"></path> + <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path> + <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path> + <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path> + <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path> + </g> + </g> + </g> + </g> +</svg>
\ No newline at end of file diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 6a3f7386d5b..c18ea929506 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -141,8 +141,7 @@ $ -> $('.trigger-submit').on 'change', -> $(@).parents('form').submit() - $("abbr.timeago").timeago() - $('.js-timeago').timeago() + $('abbr.timeago, .js-timeago').timeago() # Flash if (flash = $(".flash-container")).length > 0 diff --git a/app/assets/javascripts/blob/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee deleted file mode 100644 index 37a175fdbc7..00000000000 --- a/app/assets/javascripts/blob/blob.js.coffee +++ /dev/null @@ -1,73 +0,0 @@ -class @BlobView - constructor: -> - # handle multi-line select - handleMultiSelect = (e) -> - [ first_line, last_line ] = parseSelectedLines() - [ line_number ] = parseSelectedLines($(this).attr("id")) - hash = "L#{line_number}" - - if e.shiftKey and not isNaN(first_line) and not isNaN(line_number) - if line_number < first_line - last_line = first_line - first_line = line_number - else - last_line = line_number - - hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}" - - setHash(hash) - e.preventDefault() - - # See if there are lines selected - # "#L12" and "#L34-56" supported - highlightBlobLines = (e) -> - [ first_line, last_line ] = parseSelectedLines() - - unless isNaN first_line - $("#tree-content-holder .highlight .line").removeClass("hll") - $("#LC#{line}").addClass("hll") for line in [first_line..last_line] - $.scrollTo("#L#{first_line}", offset: -50) unless e? - - # parse selected lines from hash - # always return first and last line (initialized to NaN) - parseSelectedLines = (str) -> - first_line = NaN - last_line = NaN - hash = str || window.location.hash - - if hash isnt "" - matches = hash.match(/\#?L(\d+)(\-(\d+))?/) - first_line = parseInt(matches?[1]) - last_line = parseInt(matches?[3]) - last_line = first_line if isNaN(last_line) - - [ first_line, last_line ] - - setHash = (hash) -> - hash = hash.replace(/^\#/, "") - nodes = $("#" + hash) - # if any nodes are using this id, they must be temporarily changed - # also, add a temporary div at the top of the screen to prevent scrolling - if nodes.length > 0 - scroll_top = $(document).scrollTop() - nodes.attr("id", "") - tmp = $("<div></div>") - .css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" }) - .attr("id", hash) - .appendTo(document.body) - - window.location.hash = hash - - # restore the nodes - if nodes.length > 0 - tmp.remove() - nodes.attr("id", hash) - - # initialize multi-line select - $("#tree-content-holder .line-numbers a[id^=L]").on("click", handleMultiSelect) - - # Highlight the correct lines on load - highlightBlobLines() - - # Highlight the correct lines when the hash part of the URL changes - $(window).on("hashchange", highlightBlobLines) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index b7ebe6a5c89..84873e389ea 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -87,7 +87,7 @@ class Dispatcher new TreeView() shortcut_handler = new ShortcutsNavigation() when 'projects:blob:show' - new BlobView() + new LineHighlighter() shortcut_handler = new ShortcutsNavigation() when 'projects:labels:new', 'projects:labels:edit' new Labels() diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee new file mode 100644 index 00000000000..a8b3c1fa33e --- /dev/null +++ b/app/assets/javascripts/line_highlighter.js.coffee @@ -0,0 +1,148 @@ +# LineHighlighter +# +# Handles single- and multi-line selection and highlight for blob views. +# +#= require jquery.scrollTo +# +# ### Example Markup +# +# <div id="tree-content-holder"> +# <div class="file-content"> +# <div class="line-numbers"> +# <a href="#L1" id="L1" data-line-number="1">1</a> +# <a href="#L2" id="L2" data-line-number="2">2</a> +# <a href="#L3" id="L3" data-line-number="3">3</a> +# <a href="#L4" id="L4" data-line-number="4">4</a> +# <a href="#L5" id="L5" data-line-number="5">5</a> +# </div> +# <pre class="code highlight"> +# <code> +# <span id="LC1" class="line">...</span> +# <span id="LC2" class="line">...</span> +# <span id="LC3" class="line">...</span> +# <span id="LC4" class="line">...</span> +# <span id="LC5" class="line">...</span> +# </code> +# </pre> +# </div> +# </div> +# +class @LineHighlighter + # CSS class applied to highlighted lines + highlightClass: 'hll' + + # Internal copy of location.hash so we're not dependent on `location` in tests + _hash: '' + + # Initialize a LineHighlighter object + # + # hash - String URL hash for dependency injection in tests + constructor: (hash = location.hash) -> + @_hash = hash + + @bindEvents() + + unless hash == '' + range = @hashToRange(hash) + + if range[0] + @highlightRange(range) + + # Scroll to the first highlighted line on initial load + # Offset -50 for the sticky top bar, and another -100 for some context + $.scrollTo("#L#{range[0]}", offset: -150) + + bindEvents: -> + $('#tree-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler + + # While it may seem odd to bind to the mousedown event and then throw away + # the click event, there is a method to our madness. + # + # If not done this way, the line number anchor will sometimes keep its + # active state even when the event is cancelled, resulting in an ugly border + # around the link and/or a persisted underline text decoration. + + $('#tree-content-holder').on 'click', 'a[data-line-number]', (event) -> + event.preventDefault() + + clickHandler: (event) => + event.preventDefault() + + @clearHighlight() + + lineNumber = $(event.target).data('line-number') + current = @hashToRange(@_hash) + + unless current[0] && event.shiftKey + # If there's no current selection, or there is but Shift wasn't held, + # treat this like a single-line selection. + @setHash(lineNumber) + @highlightLine(lineNumber) + else if event.shiftKey + if lineNumber < current[0] + range = [lineNumber, current[0]] + else + range = [current[0], lineNumber] + + @setHash(range[0], range[1]) + @highlightRange(range) + + # Unhighlight previously highlighted lines + clearHighlight: -> + $(".#{@highlightClass}").removeClass(@highlightClass) + + # Convert a URL hash String into line numbers + # + # hash - Hash String + # + # Examples: + # + # hashToRange('#L5') # => [5, null] + # hashToRange('#L5-15') # => [5, 15] + # hashToRange('#foo') # => [null, null] + # + # Returns an Array + hashToRange: (hash) -> + matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/) + + if matches && matches.length + first = parseInt(matches[1]) + last = if matches[2] then parseInt(matches[2]) else null + + [first, last] + else + [null, null] + + # Highlight a single line + # + # lineNumber - Line number to highlight + highlightLine: (lineNumber) => + $("#LC#{lineNumber}").addClass(@highlightClass) + + # Highlight all lines within a range + # + # range - Array containing the starting and ending line numbers + highlightRange: (range) -> + if range[1] + for lineNumber in [range[0]..range[1]] + @highlightLine(lineNumber) + else + @highlightLine(range[0]) + + # Set the URL hash string + setHash: (firstLineNumber, lastLineNumber) => + if lastLineNumber + hash = "#L#{firstLineNumber}-#{lastLineNumber}" + else + hash = "#L#{firstLineNumber}" + + @_hash = hash + @__setLocationHash__(hash) + + # Make the actual hash change in the browser + # + # This method is stubbed in tests. + __setLocationHash__: (value) -> + # We're using pushState instead of assigning location.hash directly to + # prevent the page from scrolling on the hashchange event + history.pushState({turbolinks: false, url: value}, document.title, value) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 25a7815dba2..5c0bc686111 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -1,29 +1,25 @@ #= require jquery.waitforimages #= require task_list +#= require merge_request_tabs + class @MergeRequest # Initialize MergeRequest behavior # # Options: - # action - String, current controller action - # diffs_loaded - Boolean, have diffs been pre-rendered server-side? - # (default: true if `action` is 'diffs', otherwise false) - # commits_loaded - Boolean, have commits been pre-rendered server-side? - # (default: false) + # action - String, current controller action # constructor: (@opts) -> @initContextWidget() this.$el = $('.merge-request') - @diffs_loaded = @opts.diffs_loaded or @opts.action == 'diffs' - @commits_loaded = @opts.commits_loaded or false - - this.bindEvents() - this.activateTabFromPath() - this.$('.show-all-commits').on 'click', => this.showAllCommits() + # `MergeRequests#new` has no tab-persisting or lazy-loading behavior + unless @opts.action == 'new' + new MergeRequestTabs(@opts) + # Prevent duplicate event bindings @disableTaskList() @@ -52,83 +48,6 @@ class @MergeRequest $(".context .inline-update").on "change", "#merge_request_assignee_id", -> $(this).submit() - - bindEvents: -> - this.$('.merge-request-tabs a[data-toggle="tab"]').on 'shown.bs.tab', (e) => - $target = $(e.target) - tab_action = $target.data('action') - - # Lazy-load diffs - if tab_action == 'diffs' - this.loadDiff() unless @diffs_loaded - $('.diff-header').trigger('sticky_kit:recalc') - - # Skip tab-persisting behavior on MergeRequests#new - unless @opts.action == 'new' - @setCurrentAction(tab_action) - - # Activate a tab based on the current URL path - # - # If the current action is 'show' or 'new' (i.e., initial page load), - # activates the first tab, otherwise activates the tab corresponding to the - # current action (diffs, commits). - activateTabFromPath: -> - if @opts.action == 'show' || @opts.action == 'new' - this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show') - else - this.$(".merge-request-tabs a[data-action='#{@opts.action}']").tab('show') - - # Replaces the current Merge Request-specific action in the URL with a new one - # - # If the action is "notes", the URL is reset to the standard - # `MergeRequests#show` route. - # - # Examples: - # - # location.pathname # => "/namespace/project/merge_requests/1" - # setCurrentAction('diffs') - # location.pathname # => "/namespace/project/merge_requests/1/diffs" - # - # location.pathname # => "/namespace/project/merge_requests/1/diffs" - # setCurrentAction('notes') - # location.pathname # => "/namespace/project/merge_requests/1" - # - # location.pathname # => "/namespace/project/merge_requests/1/diffs" - # setCurrentAction('commits') - # location.pathname # => "/namespace/project/merge_requests/1/commits" - setCurrentAction: (action) -> - # Normalize action, just to be safe - action = 'notes' if action == 'show' - - # Remove a trailing '/commits' or '/diffs' - new_state = location.pathname.replace(/\/(commits|diffs)\/?$/, '') - - # Append the new action if we're on a tab other than 'notes' - unless action == 'notes' - new_state += "/#{action}" - - # Ensure parameters and hash come along for the ride - new_state += location.search + location.hash - - # Replace the current history state with the new one without breaking - # Turbolinks' history. - # - # See https://github.com/rails/turbolinks/issues/363 - history.replaceState {turbolinks: true, url: new_state}, '', new_state - - loadDiff: (event) -> - $.ajax - type: 'GET' - url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json" - beforeSend: => - this.$('.mr-loading-status .loading').show() - complete: => - @diffs_loaded = true - this.$('.mr-loading-status .loading').hide() - success: (data) => - this.$(".diffs").html(data.html) - dataType: 'json' - showAllCommits: -> this.$('.first-commits').remove() this.$('.all-commits').removeClass 'hide' diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee new file mode 100644 index 00000000000..de9a4c2cc2f --- /dev/null +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -0,0 +1,153 @@ +# MergeRequestTabs +# +# Handles persisting and restoring the current tab selection and lazily-loading +# content on the MergeRequests#show page. +# +# ### Example Markup +# +# <ul class="nav nav-tabs merge-request-tabs"> +# <li class="notes-tab active"> +# <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1"> +# Discussion +# </a> +# </li> +# <li class="commits-tab"> +# <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits"> +# Commits +# </a> +# </li> +# <li class="diffs-tab"> +# <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs"> +# Diffs +# </a> +# </li> +# </ul> +# +# <div class="tab-content"> +# <div class="notes tab-pane active" id="notes"> +# Notes Content +# </div> +# <div class="commits tab-pane" id="commits"> +# Commits Content +# </div> +# <div class="diffs tab-pane" id="diffs"> +# Diffs Content +# </div> +# </div> +# +# <div class="mr-loading-status"> +# <div class="loading"> +# Loading Animation +# </div> +# </div> +# +class @MergeRequestTabs + diffsLoaded: false + commitsLoaded: false + + constructor: (@opts = {}) -> + # Store the `location` object, allowing for easier stubbing in tests + @_location = location + + @bindEvents() + @activateTab(@opts.action) + + switch @opts.action + when 'commits' then @commitsLoaded = true + when 'diffs' then @diffsLoaded = true + + bindEvents: -> + $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown + + tabShown: (event) => + $target = $(event.target) + action = $target.data('action') + + if action == 'commits' + @loadCommits($target.attr('href')) + else if action == 'diffs' + @loadDiff($target.attr('href')) + + @setCurrentAction(action) + + # Activate a tab based on the current action + activateTab: (action) -> + action = 'notes' if action == 'show' + $(".merge-request-tabs a[data-action='#{action}']").tab('show') + + # Replaces the current Merge Request-specific action in the URL with a new one + # + # If the action is "notes", the URL is reset to the standard + # `MergeRequests#show` route. + # + # Examples: + # + # location.pathname # => "/namespace/project/merge_requests/1" + # setCurrentAction('diffs') + # location.pathname # => "/namespace/project/merge_requests/1/diffs" + # + # location.pathname # => "/namespace/project/merge_requests/1/diffs" + # setCurrentAction('notes') + # location.pathname # => "/namespace/project/merge_requests/1" + # + # location.pathname # => "/namespace/project/merge_requests/1/diffs" + # setCurrentAction('commits') + # location.pathname # => "/namespace/project/merge_requests/1/commits" + # + # Returns the new URL String + setCurrentAction: (action) => + # Normalize action, just to be safe + action = 'notes' if action == 'show' + + # Remove a trailing '/commits' or '/diffs' + new_state = @_location.pathname.replace(/\/(commits|diffs)\/?$/, '') + + # Append the new action if we're on a tab other than 'notes' + unless action == 'notes' + new_state += "/#{action}" + + # Ensure parameters and hash come along for the ride + new_state += @_location.search + @_location.hash + + # Replace the current history state with the new one without breaking + # Turbolinks' history. + # + # See https://github.com/rails/turbolinks/issues/363 + history.replaceState {turbolinks: true, url: new_state}, document.title, new_state + + new_state + + loadCommits: (source) -> + return if @commitsLoaded + + @_get + url: "#{source}.json" + success: (data) => + document.getElementById('commits').innerHTML = data.html + $('.js-timeago').timeago() + @commitsLoaded = true + + loadDiff: (source) -> + return if @diffsLoaded + + @_get + url: "#{source}.json" + success: (data) => + document.getElementById('diffs').innerHTML = data.html + $('.diff-header').trigger('sticky_kit:recalc') + @diffsLoaded = true + + toggleLoading: -> + $('.mr-loading-status .loading').toggle() + + _get: (options) -> + defaults = { + beforeSend: @toggleLoading + complete: @toggleLoading + dataType: 'json' + type: 'GET' + } + + options = $.extend({}, defaults, options) + + $.ajax(options) diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 8f17232592e..26eb7ab1a12 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -10,6 +10,10 @@ header { .center-logo { margin: 8px 0; text-align: center; + + img { + height: 32px; + } } } diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 17ddde68f93..d2f0c43929f 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,7 +1,7 @@ class DashboardController < Dashboard::ApplicationController - before_action :load_projects, except: [:projects] + before_action :load_projects before_action :event_filter, only: :show - + respond_to :html def show diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 145f27b67dd..8450ba31021 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -24,7 +24,7 @@ class PasswordsController < Devise::PasswordsController super do |resource| # TODO (rspeicher): In Devise master (> 3.4.1), we can set # `Devise.sign_in_after_reset_password = false` and avoid this mess. - if resource.errors.empty? && resource.try(:otp_required_for_login?) + if resource.errors.empty? && resource.try(:two_factor_enabled?) resource.unlock_access! if unlockable?(resource) # Since we are not signing this user in, we use the :updated_not_active diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index e7579c652fb..03845f1e1ec 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController def create if current_user.valid_otp?(params[:pin_code]) - current_user.otp_required_for_login = true + current_user.two_factor_enabled = true @codes = current_user.generate_otp_backup_codes! current_user.save! @@ -30,7 +30,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController def destroy current_user.update_attributes({ - otp_required_for_login: false, + two_factor_enabled: false, encrypted_otp_secret: nil, encrypted_otp_secret_iv: nil, encrypted_otp_secret_salt: nil, diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 14069bafe71..51ecbfd561a 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -71,7 +71,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def commits - render 'show' + respond_to do |format| + format.html { render 'show' } + format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } } + end end def new diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 4d976fe6630..7577fc96d6d 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -57,7 +57,7 @@ class SessionsController < Devise::SessionsController def authenticate_with_two_factor user = self.resource = find_user - return unless user && user.otp_required_for_login + return unless user && user.two_factor_enabled? if user_params[:otp_attempt].present? && session[:otp_user_id] if valid_otp_attempt?(user) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 0bed2115dc7..2eccc0ee31f 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -45,10 +45,10 @@ class IssuableFinder def group return @group if defined?(@group) - @group = + @group = if params[:group_id].present? Group.find(params[:group_id]) - else + else nil end end @@ -56,10 +56,10 @@ class IssuableFinder def project return @project if defined?(@project) - @project = + @project = if params[:project_id].present? Project.find(params[:project_id]) - else + else nil end end @@ -76,7 +76,7 @@ class IssuableFinder return @milestones if defined?(@milestones) @milestones = - if milestones? && params[:milestone_title] != NONE + if milestones? && params[:milestone_title] != NONE Milestone.where(title: params[:milestone_title]) else nil @@ -90,7 +90,7 @@ class IssuableFinder def assignee return @assignee if defined?(@assignee) - @assignee = + @assignee = if assignee? && params[:assignee_id] != NONE User.find(params[:assignee_id]) else @@ -105,7 +105,7 @@ class IssuableFinder def author return @author if defined?(@author) - @author = + @author = if author? && params[:author_id] != NONE User.find(params[:author_id]) else @@ -148,8 +148,6 @@ class IssuableFinder case params[:state] when 'closed' items.closed - when 'rejected' - items.respond_to?(:rejected) ? items.rejected : items.closed when 'merged' items.respond_to?(:merged) ? items.merged : items.closed when 'all' diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index bb8d5683807..14df8d4cbd7 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -16,6 +16,6 @@ module AppearancesHelper end def brand_header_logo - image_tag 'logo-white.png' + image_tag 'logo.svg' end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 10d7aa11209..0b46db4b1c3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -179,14 +179,33 @@ module ApplicationHelper BroadcastMessage.current end - def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago') - capture_haml do - haml_tag :time, date.to_s, - class: html_class, datetime: date.getutc.iso8601, title: date.in_time_zone.stamp('Aug 21, 2011 9:23pm'), - data: { toggle: 'tooltip', placement: placement } - - haml_tag :script, "$('." + html_class + "').timeago().tooltip()" - end.html_safe + # Render a `time` element with Javascript-based relative date and tooltip + # + # time - Time object + # placement - Tooltip placement String (default: "top") + # html_class - Custom class for `time` element (default: "time_ago") + # skip_js - When true, exclude the `script` tag (default: false) + # + # By default also includes a `script` element with Javascript necessary to + # initialize the `timeago` jQuery extension. If this method is called many + # times, for example rendering hundreds of commits, it's advisable to disable + # this behavior using the `skip_js` argument and re-initializing `timeago` + # manually once all of the elements have been rendered. + # + # A `js-timeago` class is always added to the element, even when a custom + # `html_class` argument is provided. + # + # Returns an HTML-safe String + def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) + element = content_tag :time, time.to_s, + class: "#{html_class} js-timeago", + datetime: time.getutc.iso8601, + title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'), + data: { toggle: 'tooltip', placement: placement } + + element += javascript_tag "$('.js-timeago').timeago()" unless skip_js + + element end def render_markup(file_name, file_content) @@ -214,39 +233,6 @@ module ApplicationHelper Gitlab::MarkupHelper.asciidoc?(filename) end - # Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to - # external links - def link_to(name = nil, options = nil, html_options = {}) - if options.kind_of?(String) - if !options.start_with?('#', '/') - html_options = add_nofollow(options, html_options) - end - end - - super - end - - # Add `"rel=nofollow"` to external links - # - # link - String link to check - # html_options - Hash of `html_options` passed to `link_to` - # - # Returns `html_options`, adding `rel: nofollow` for external links - def add_nofollow(link, html_options = {}) - begin - uri = URI(link) - - if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host - rel = html_options.fetch(:rel, '') - html_options[:rel] = (rel + ' nofollow').strip - end - rescue URI::Error - # noop - end - - html_options - end - def promo_host 'about.gitlab.com' end @@ -295,10 +281,9 @@ module ApplicationHelper def state_filters_text_for(entity, project) titles = { - opened: "Open", - merged: "Accepted" + opened: "Open" } - + entity_title = titles[entity] || entity.to_s.humanize count = diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb index 29ff47663da..6484dca6b55 100644 --- a/app/helpers/broadcast_messages_helper.rb +++ b/app/helpers/broadcast_messages_helper.rb @@ -1,9 +1,16 @@ module BroadcastMessagesHelper def broadcast_styling(broadcast_message) - if(broadcast_message.color || broadcast_message.font) - "background-color:#{broadcast_message.color};color:#{broadcast_message.font}" - else - "" + styling = '' + + if broadcast_message.color.present? + styling << "background-color: #{broadcast_message.color}" + styling << '; ' if broadcast_message.font.present? end + + if broadcast_message.font.present? + styling << "color: #{broadcast_message.font}" + end + + styling end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index a730684f8f3..30b17a736a7 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -1,4 +1,6 @@ module IconsHelper + include FontAwesome::Rails::IconHelper + # Creates an icon tag given icon name(s) and possible icon modifiers. # # Right now this method simply delegates directly to `fa_icon` from the diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 36d3f371c1b..d4c345fe431 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -45,13 +45,13 @@ module IssuesHelper def issue_timestamp(issue) # Shows the created at time and the updated at time if different - ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}" + ts = time_ago_with_tooltip(issue.created_at, placement: 'bottom', html_class: 'note_created_ago') if issue.updated_at != issue.created_at ts << capture_haml do haml_tag :span do haml_concat '·' haml_concat icon('edit', title: 'edited') - haml_concat time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago') + haml_concat time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago') end end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index a7c1fa0b071..dda9b17d61d 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -25,13 +25,13 @@ module NotesHelper def note_timestamp(note) # Shows the created at time and the updated at time if different - ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}" + ts = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago') if note.updated_at != note.created_at ts << capture_haml do haml_tag :span do haml_concat '·' haml_concat icon('edit', title: 'edited') - haml_concat time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago') + haml_concat time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago') end end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index f771fe761ef..2f8e64c375f 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -1,4 +1,6 @@ module NotificationsHelper + include IconsHelper + def notification_icon(notification) if notification.disabled? icon('volume-off', class: 'ns-mute') diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 94ce6646634..ec65e473919 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -211,7 +211,7 @@ module ProjectsHelper def project_last_activity(project) if project.last_activity_at - time_ago_with_tooltip(project.last_activity_at, 'bottom', 'last_activity_time_ago') + time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago') else "Never" end diff --git a/app/models/ability.rb b/app/models/ability.rb index bcd2adee00b..a5db22040e0 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -263,7 +263,7 @@ class Ability :"modify_#{name}", ] else - if subject.respond_to?(:project) + if subject.respond_to?(:project) && subject.project project_abilities(user, subject.project) else [] diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 10c39cb1ece..56849f28ff0 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -75,7 +75,7 @@ module Mentionable refs.reject! { |ref| without.include?(ref) } refs.each do |ref| - Note.create_cross_reference_note(ref, local_reference, a) + SystemNoteService.cross_reference(ref, local_reference, a) end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 487d62e65b6..7ecdaf6b2e0 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -125,16 +125,14 @@ class MergeRequest < ActiveRecord::Base validate :validate_fork scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) } - scope :merged, -> { with_state(:merged) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) } - # Closed scope for merge request should return - # both merged and closed mr's - scope :closed, -> { with_states(:closed, :merged) } - scope :rejected, -> { with_states(:closed) } + scope :merged, -> { with_state(:merged) } + scope :closed, -> { with_state(:closed) } + scope :closed_and_merged, -> { with_states(:closed, :merged) } def self.reference_prefix '!' @@ -417,4 +415,14 @@ class MergeRequest < ActiveRecord::Base def can_be_merged_by?(user) ::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch) end + + def state_human_name + if merged? + "Merged" + elsif closed? + "Closed" + else + "Open" + end + end end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 9c543b37023..e0c5fec97b7 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -56,7 +56,7 @@ class Milestone < ActiveRecord::Base end def closed_items_count - self.issues.closed.count + self.merge_requests.closed.count + self.issues.closed.count + self.merge_requests.closed_and_merged.count end def total_items_count diff --git a/app/models/note.rb b/app/models/note.rb index 6a74d62b715..68b9d433ae0 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -63,11 +63,6 @@ class Note < ActiveRecord::Base after_update :set_references class << self - # TODO (rspeicher): Update usages - def create_cross_reference_note(*args) - SystemNoteService.cross_reference(*args) - end - def discussions_from_notes(notes) discussion_ids = [] discussions = [] diff --git a/app/models/repository.rb b/app/models/repository.rb index 2c6347222aa..b32e8847bb5 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -5,8 +5,13 @@ class Repository def initialize(path_with_namespace, default_branch = nil, project = nil) @path_with_namespace = path_with_namespace - @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace @project = project + + if path_with_namespace + @raw_repository = Gitlab::Git::Repository.new(path_to_repo) + @raw_repository.autocrlf = :input + end + rescue Gitlab::Git::Repository::NoRepository nil end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 3ab9e834c63..b0831982aa7 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -34,7 +34,6 @@ class Snippet < ActiveRecord::Base validates :author, presence: true validates :title, presence: true, length: { within: 0..255 } validates :file_name, - presence: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.file_name_regex, message: Gitlab::Regex.file_name_regex_message } diff --git a/app/models/user.rb b/app/models/user.rb index 982c05212ce..29f43051464 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -172,6 +172,9 @@ class User < ActiveRecord::Base after_create :post_create_hook after_destroy :post_destroy_hook + # User's Dashboard preference + # Note: When adding an option, it MUST go on the end of the array. + enum dashboard: [:projects, :stars] alias_attribute :private_token, :authentication_token @@ -220,10 +223,26 @@ class User < ActiveRecord::Base end def find_for_commit(email, name) - # Prefer email match over name match - User.where(email: email).first || - User.joins(:emails).where(emails: { email: email }).first || - User.where(name: name).first + user_table = arel_table + email_table = Email.arel_table + + # Use ARel to build a query: + query = user_table. + # SELECT "users".* FROM "users" + project(user_table[Arel.star]). + # LEFT OUTER JOIN "emails" + join(email_table, Arel::Nodes::OuterJoin). + # ON "users"."id" = "emails"."user_id" + on(user_table[:id].eq(email_table[:user_id])). + # WHERE ("user"."email" = '<email>' OR "user"."name" = '<name>') + # OR "emails"."email" = '<email>' + where( + user_table[:email].eq(email). + or(user_table[:name].eq(name)). + or(email_table[:email].eq(email)) + ) + + find_by_sql(query.to_sql).first end def filter(filter_name) @@ -297,6 +316,18 @@ class User < ActiveRecord::Base @reset_token end + # Check if the user has enabled Two-factor Authentication + def two_factor_enabled? + otp_required_for_login + end + + # Set whether or not Two-factor Authentication is enabled for the current user + # + # setting - Boolean + def two_factor_enabled=(setting) + self.otp_required_for_login = setting + end + def namespace_uniq namespace_name = self.username existing_namespace = Namespace.by_path(namespace_name) @@ -704,8 +735,4 @@ class User < ActiveRecord::Base def can_be_removed? !solo_owned_groups.present? end - - # User's Dashboard preference - # Note: When adding an option, it MUST go on the end of the array. - enum dashboard: [:projects, :stars] end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 68d3b915fc9..6135ae65007 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -105,7 +105,7 @@ class GitPushService author ||= commit_user(commit) refs.each do |r| - Note.create_cross_reference_note(r, commit, author) + SystemNoteService.cross_reference(r, commit, author) end end end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 0ff37c41743..482c0444049 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -15,7 +15,7 @@ module Notes # Create a cross-reference note if this Note contains GFM that names an # issue, merge request, or commit. note.references.each do |mentioned| - Note.create_cross_reference_note(mentioned, note.noteable, note.author) + SystemNoteService.cross_reference(mentioned, note.noteable, note.author) end execute_hooks(note) diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 45a0db761ec..b5611d46257 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -13,7 +13,7 @@ module Notes # Create a cross-reference note if this Note contains GFM that # names an issue, merge request, or commit. note.references.each do |mentioned| - Note.create_cross_reference_note(mentioned, note.noteable, note.author) + SystemNoteService.cross_reference(mentioned, note.noteable, note.author) end end end diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 69918193e8a..0b8260964fe 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -38,6 +38,14 @@ = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do %i.fa.fa-times + %li.two-factor-status + %span.light Two-factor Authentication: + %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'} + - if @user.two_factor_enabled? + Enabled + - else + Disabled + %li %span.light Can create groups: %strong diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml index a52a3c8f0ef..2ed4edb1136 100644 --- a/app/views/layouts/header/_empty.html.haml +++ b/app/views/layouts/header/_empty.html.haml @@ -1,4 +1,4 @@ %header.navbar.navbar-fixed-top.navbar-empty .container .center-logo - = image_tag 'logo-white.png', width: 32, height: 32 + = brand_header_logo diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index ed009c86568..378dfa2dce0 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -36,7 +36,7 @@ .panel-heading Two-factor Authentication .panel-body - - if current_user.otp_required_for_login + - if current_user.two_factor_enabled? .pull-right = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', data: { confirm: 'Are you sure?' } diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 4d93c89c93a..496fad34dc2 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -15,10 +15,10 @@ - if issuable.is_a?(MergeRequest) %p.help-block - if issuable.work_in_progress? - Remove the <code>WIP</code> prefix from the title to allow this + Remove the <code>WIP</code> prefix from the title to allow this <strong>Work In Progress</strong> merge request to be accepted when it's ready. - else - Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a + Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a <strong>Work In Progress</strong> merge request from being accepted before it's ready. .form-group.issuable-description = f.label :description, 'Description', class: 'control-label' @@ -81,21 +81,22 @@ - if issuable.is_a?(MergeRequest) %hr - - unless @merge_request.persisted? + - if @merge_request.new_record? .form-group = f.label :source_branch, class: 'control-label' do %i.fa.fa-code-fork Source Branch .col-sm-10 = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) - %p.help-block - = link_to 'Change source branch', mr_change_branches_path(@merge_request) .form-group = f.label :target_branch, class: 'control-label' do %i.fa.fa-code-fork Target Branch .col-sm-10 - = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2' }) + = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? }) + - if @merge_request.new_record? + %p.help-block + = link_to 'Change branches', mr_change_branches_path(@merge_request) .form-actions - if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted? diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 083fca9b658..f9106564a27 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -29,5 +29,5 @@ = commit_author_link(commit, avatar: true, size: 24) authored .committed_ago - #{time_ago_with_tooltip(commit.committed_date)} + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} = link_to_browse_code(project, commit) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 64d62b45657..2c296cab977 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -28,7 +28,7 @@ = 0 .issue-info - = "##{issue.iid} opened #{time_ago_with_tooltip(issue.created_at, 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe + = "##{issue.iid} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe - if issue.votes_count > 0 = render 'votes/votes_inline', votable: issue - if issue.milestone @@ -41,4 +41,4 @@ = issue.task_status .pull-right.issue-updated-at - %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} + %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index c16df27ee8f..0bcd543fee7 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -9,11 +9,11 @@ - if merge_request.merged? %span %i.fa.fa-check - ACCEPTED + MERGED - elsif merge_request.closed? %span %i.fa.fa-ban - REJECTED + CLOSED - else %span.hidden-xs.hidden-sm %span.label-branch< @@ -35,7 +35,7 @@ = 0 .merge-request-info - = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe + = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe - if merge_request.votes_count > 0 = render 'votes/votes_inline', votable: merge_request - if merge_request.milestone_id? @@ -48,4 +48,4 @@ = merge_request.task_status .pull-right.hidden-xs - %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} + %small updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 5d7e73f2b28..9dc4a47258e 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -56,7 +56,8 @@ #notes.notes.tab-pane.voting_notes = render "projects/merge_requests/discussion" #commits.commits.tab-pane - = render "projects/merge_requests/show/commits" + - if current_page?(action: 'commits') + = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - if current_page?(action: 'diffs') = render "projects/merge_requests/show/diffs" @@ -64,7 +65,6 @@ .mr-loading-status = spinner - :javascript var merge_request; diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 0690fdb769f..83baf157a92 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,11 +1,6 @@ %h4.page-title .issue-box{ class: issue_box_class(@merge_request) } - - if @merge_request.merged? - Accepted - - elsif @merge_request.closed? - Rejected - - else - Open + = @merge_request.state_human_name = "Merge Request ##{@merge_request.iid}" %small.creator · diff --git a/app/views/projects/merge_requests/widget/_closed.html.haml b/app/views/projects/merge_requests/widget/_closed.html.haml index 18164ba771f..b5704c502c8 100644 --- a/app/views/projects/merge_requests/widget/_closed.html.haml +++ b/app/views/projects/merge_requests/widget/_closed.html.haml @@ -2,7 +2,7 @@ = render 'projects/merge_requests/widget/heading' .mr-widget-body %h4 - Rejected + Closed - if @merge_request.closed_event by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)} #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml index 17c3fdacda8..a3b13140810 100644 --- a/app/views/projects/merge_requests/widget/_merged.html.haml +++ b/app/views/projects/merge_requests/widget/_merged.html.haml @@ -2,7 +2,7 @@ = render 'projects/merge_requests/widget/heading' .mr-widget-body %h4 - Accepted + Merged - if @merge_request.merge_event by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)} #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 417eaa1b09d..5c85092a045 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -86,10 +86,10 @@ .col-md-3 = render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing') .col-md-3 - = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.rejected, id: 'closed') + = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed') .col-md-3 .panel.panel-primary - .panel-heading Accepted + .panel-heading Merged %ul.well-list - @merge_requests.merged.each do |merge_request| = render 'merge_request', merge_request: merge_request diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml index e7a3854701c..4f15a99d061 100644 --- a/app/views/projects/notes/discussions/_active.html.haml +++ b/app/views/projects/notes/discussions/_active.html.haml @@ -16,7 +16,7 @@ = link_to_member(@project, last_note.author, avatar: false) %span.discussion-last-update - #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} - + #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')} + .discussion-body.js-toggle-content = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml index 62609cfc1c8..6903fad4a0a 100644 --- a/app/views/projects/notes/discussions/_commit.html.haml +++ b/app/views/projects/notes/discussions/_commit.html.haml @@ -14,7 +14,7 @@ last updated by = link_to_member(@project, last_note.author, avatar: false) %span.discussion-last-update - #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} + #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')} .discussion-body.js-toggle-content - if note.for_diff_line? = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml index 52a1d342f55..218b0da3977 100644 --- a/app/views/projects/notes/discussions/_outdated.html.haml +++ b/app/views/projects/notes/discussions/_outdated.html.haml @@ -14,6 +14,6 @@ last updated by = link_to_member(@project, last_note.author, avatar: false) %span.discussion-last-update - #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} + #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')} .discussion-body.js-toggle-content.hide = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index adfdd1c7506..2efa616d664 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -11,6 +11,6 @@ #{merge_request.project.name_with_namespace} .pull-right - if merge_request.merged? - %span.label.label-primary Accepted + %span.label.label-primary Merged - elsif merge_request.closed? - %span.label.label-danger Rejected + %span.label.label-danger Closed diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index 86921f0a777..d6a2e177da1 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,11 +1,11 @@ .file-content.code{class: user_color_scheme_class} .line-numbers - if blob.data.present? - - blob.data.lines.to_a.size.times do |index| + - blob.data.lines.each_index do |index| - offset = defined?(first_line_number) ? first_line_number : 1 - i = index + offset - / We're not using `link_to` because it is too slow once we get to thousands of lines. - %a{href: "#L#{i}", id: "L#{i}", rel: "#L#{i}"} + -# We're not using `link_to` because it is too slow once we get to thousands of lines. + %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} %i.fa.fa-link = i :preserve diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml index a5187fa4ea7..a355eb62813 100644 --- a/app/views/shared/_issuable_filter.html.haml +++ b/app/views/shared/_issuable_filter.html.haml @@ -12,10 +12,10 @@ = icon('check-circle') #{state_filters_text_for(:merged, @project)} - %li{class: ("active" if params[:state] == 'rejected')} - = link_to page_filter_path(state: 'rejected') do + %li{class: ("active" if params[:state] == 'closed')} + = link_to page_filter_path(state: 'closed') do = icon('ban') - #{state_filters_text_for(:rejected, @project)} + #{state_filters_text_for(:closed, @project)} - else %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed') do diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index fe25133abb0..913b6744844 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -18,7 +18,7 @@ .col-sm-10 .file-holder.snippet .file-title - = f.text_field :file_name, placeholder: "example.rb", class: 'form-control snippet-file-name', required: true + = f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name' .file-content.code %pre#editor= @snippet.content = f.hidden_field :content, class: 'snippet-file-content' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 1694818aef6..15d53499e03 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -9,7 +9,8 @@ .row %section.col-md-8 .header-with-avatar - = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' + = link_to avatar_icon(@user.email), target: '_blank' do + = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' %h3 = @user.name - if @user == current_user |