merge_request_tabs.js.coffee 7.19 KB
Newer Older
1 2 3 4 5
# MergeRequestTabs
#
# Handles persisting and restoring the current tab selection and lazily-loading
# content on the MergeRequests#show page.
#
6 7
#= require jquery.cookie
#
8 9
# ### Example Markup
#
10
#   <ul class="nav-links merge-request-tabs">
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#     <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>
#
46 47
class @MergeRequestTabs
  diffsLoaded: false
48
  buildsLoaded: false
49 50
  commitsLoaded: false

51 52 53 54
  constructor: (@opts = {}) ->
    # Store the `location` object, allowing for easier stubbing in tests
    @_location = location

55 56 57
    @bindEvents()
    @activateTab(@opts.action)

58
  bindEvents: ->
59
    $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown
60 61 62 63 64 65
    $(document).on 'click', '.js-show-tab', @showTab

  showTab: (event) =>
    event.preventDefault()

    @activateTab $(event.target).data('action')
66

67
  tabShown: (event) =>
68 69 70
    $target = $(event.target)
    action = $target.data('action')

71 72
    if action == 'commits'
      @loadCommits($target.attr('href'))
73
      @expandView()
74 75
    else if action == 'diffs'
      @loadDiff($target.attr('href'))
76
      if bp? and bp.getBreakpointSize() isnt 'lg'
77
        @shrinkView()
Douwe Maan's avatar
Douwe Maan committed
78 79
    else if action == 'builds'
      @loadBuilds($target.attr('href'))
80 81 82
      @expandView()
    else
      @expandView()
83 84 85

    @setCurrentAction(action)

86 87
  scrollToElement: (container) ->
    if window.location.hash
88 89 90 91
      navBarHeight = $('.navbar-gitlab').outerHeight()

      $el = $("#{container} #{window.location.hash}")
      $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
92

93 94 95 96
  # Activate a tab based on the current action
  activateTab: (action) ->
    action = 'notes' if action == 'show'
    $(".merge-request-tabs a[data-action='#{action}']").tab('show')
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

  # 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"
116 117 118
  #
  # Returns the new URL String
  setCurrentAction: (action) =>
119 120 121 122
    # Normalize action, just to be safe
    action = 'notes' if action == 'show'

    # Remove a trailing '/commits' or '/diffs'
Douwe Maan's avatar
Douwe Maan committed
123
    new_state = @_location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '')
124 125 126 127 128 129

    # 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
130
    new_state += @_location.search + @_location.hash
131 132 133 134 135 136 137

    # 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

138 139 140 141 142 143 144
    new_state

  loadCommits: (source) ->
    return if @commitsLoaded

    @_get
      url: "#{source}.json"
145
      success: (data) =>
146
        document.querySelector("div#commits").innerHTML = data.html
147
        gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
148
        @commitsLoaded = true
149
        @scrollToElement("#commits")
150

Douwe Maan's avatar
Douwe Maan committed
151 152 153 154 155 156
  loadDiff: (source) ->
    return if @diffsLoaded

    @_get
      url: "#{source}.json" + @_location.search
      success: (data) =>
157
        $('#diffs').html data.html
158
        gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
159
        $('#diffs .js-syntax-highlight').syntaxHighlight()
160
        @expandViewContainer() if @diffViewType() is 'parallel'
Douwe Maan's avatar
Douwe Maan committed
161 162
        @diffsLoaded = true
        @scrollToElement("#diffs")
163 164
        @highlighSelectedLine()

165
        $(document)
166 167
          .off 'click', '.diff-line-num a'
          .on 'click', '.diff-line-num a', (e) =>
168 169 170 171 172
            e.preventDefault()
            window.location.hash = $(e.currentTarget).attr 'href'
            @highlighSelectedLine()
            @scrollToElement("#diffs")

173
  highlighSelectedLine: ->
174 175
    $('.hll').removeClass 'hll'
    locationHash = window.location.hash
176 177 178 179 180 181 182 183 184 185 186 187 188

    if locationHash isnt ''
      hashClassString = ".#{locationHash.replace('#', '')}"
      $diffLine = $(locationHash)

      if $diffLine.is ':not(tr)'
        $diffLine = $("td#{locationHash}, td#{hashClassString}")
      else
        $diffLine = $('td', $diffLine)

      $diffLine.addClass 'hll'
      diffLineTop = $diffLine.offset().top
      navBarHeight = $('.navbar-gitlab').outerHeight()
Douwe Maan's avatar
Douwe Maan committed
189

190 191 192 193 194 195
  loadBuilds: (source) ->
    return if @buildsLoaded

    @_get
      url: "#{source}.json"
      success: (data) =>
196
        document.querySelector("div#builds").innerHTML = data.html
197
        gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
198 199 200
        @buildsLoaded = true
        @scrollToElement("#builds")

201 202 203 204 205
  # Show or hide the loading spinner
  #
  # status - Boolean, true to show, false to hide
  toggleLoading: (status) ->
    $('.mr-loading-status .loading').toggle(status)
206 207 208

  _get: (options) ->
    defaults = {
209 210
      beforeSend: => @toggleLoading(true)
      complete:   => @toggleLoading(false)
211 212 213 214 215 216 217
      dataType: 'json'
      type: 'GET'
    }

    options = $.extend({}, defaults, options)

    $.ajax(options)
218

219
  # Returns diff view type
220
  diffViewType: ->
Alfredo Sumaran's avatar
Alfredo Sumaran committed
221
    $('.inline-parallel-buttons a.active').data('view-type')
222 223 224

  expandViewContainer: ->
    $('.container-fluid').removeClass('container-limited')
225 226

  shrinkView: ->
227
    $gutterIcon = $('.js-sidebar-toggle i:visible')
228

229 230
    # Wait until listeners are set
    setTimeout( ->
231
      # Only when sidebar is expanded
232
      if $gutterIcon.is('.fa-angle-double-right')
233 234 235 236 237 238 239 240 241 242 243 244 245 246
        $gutterIcon.closest('a').trigger('click', [true])
    , 0)

  # Expand the issuable sidebar unless the user explicitly collapsed it
  expandView: ->
    return if $.cookie('collapsed_gutter') == 'true'

    $gutterIcon = $('.js-sidebar-toggle i:visible')

    # Wait until listeners are set
    setTimeout( ->
      # Only when sidebar is collapsed
      if $gutterIcon.is('.fa-angle-double-left')
        $gutterIcon.closest('a').trigger('click', [true])
247
    , 0)