BigW Consortium Gitlab

awards_handler.coffee 9.73 KB
class @AwardsHandler

  constructor: ->

    @aliases = gl.emojiAliases()

    $(document)
      .off 'click', '.js-add-award'
      .on  'click', '.js-add-award', (e) =>
        e.stopPropagation()
        e.preventDefault()

        @showEmojiMenu $(e.currentTarget)

    $('html').on 'click', (e) ->
      $target = $ e.target

      unless $target.closest('.emoji-menu-content').length
        $('.js-awards-block.current').removeClass 'current'

      unless $target.closest('.emoji-menu').length
        if $('.emoji-menu').is(':visible')
          $('.js-add-award.is-active').removeClass 'is-active'
          $('.emoji-menu').removeClass 'is-visible'

    $(document)
      .off 'click', '.js-emoji-btn'
      .on  'click', '.js-emoji-btn', (e) =>
        e.preventDefault()

        $target = $ e.currentTarget
        emoji   = $target.find('.icon').data 'emoji'

        $target.closest('.js-awards-block').addClass 'current'
        @addAward @getVotesBlock(), @getAwardUrl(), emoji


  showEmojiMenu: ($addBtn) ->

    $menu = $ '.emoji-menu'

    if $addBtn.hasClass 'js-note-emoji'
      $addBtn.parents('.note').find('.js-awards-block').addClass 'current'
    else
      $addBtn.closest('.js-awards-block').addClass 'current'

    if $menu.length
      $holder = $addBtn.closest('.js-award-holder')

      if $menu.is '.is-visible'
        $addBtn.removeClass 'is-active'
        $menu.removeClass 'is-visible'
        $('#emoji_search').blur()
      else
        $addBtn.addClass 'is-active'
        @positionMenu($menu, $addBtn)

        $menu.addClass 'is-visible'
        $('#emoji_search').focus()
    else
      $addBtn.addClass 'is-loading is-active'
      url = @getAwardMenuUrl()

      @createEmojiMenu url, =>
        $addBtn.removeClass 'is-loading'
        $menu = $('.emoji-menu')
        @positionMenu($menu, $addBtn)
        @renderFrequentlyUsedBlock()

        setTimeout =>
          $menu.addClass 'is-visible'
          $('#emoji_search').focus()
          @setupSearch()
        , 200


  createEmojiMenu: (awardMenuUrl, callback) ->

    $.get awardMenuUrl, (response) ->
      $('body').append response
      callback()


  positionMenu: ($menu, $addBtn) ->

    position = $addBtn.data('position')

    # The menu could potentially be off-screen or in a hidden overflow element
    # So we position the element absolute in the body
    css =
      top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px"

    if position? and position is 'right'
      css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px"
      $menu.addClass 'is-aligned-right'
    else
      css.left = "#{$addBtn.offset().left}px"
      $menu.removeClass 'is-aligned-right'

    $menu.css(css)


  addAward: (votesBlock, awardUrl, emoji, checkMutuality = yes, callback) ->

    emoji = @normilizeEmojiName emoji

    @postEmoji awardUrl, emoji, =>
      @addAwardToEmojiBar votesBlock, emoji, checkMutuality
      callback?()

    $('.emoji-menu').removeClass 'is-visible'


  addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = yes) ->

    @checkMutuality votesBlock, emoji  if checkForMutuality
    @addEmojiToFrequentlyUsedList emoji

    emoji        = @normilizeEmojiName emoji
    $emojiButton = @findEmojiIcon(votesBlock, emoji).parent()

    if $emojiButton.length > 0
      if @isActive $emojiButton
        @decrementCounter $emojiButton, emoji
      else
        counter = $emojiButton.find '.js-counter'
        counter.text parseInt(counter.text()) + 1
        $emojiButton.addClass 'active'
        @addMeToUserList votesBlock, emoji
        @animateEmoji $emojiButton
    else
      votesBlock.removeClass 'hidden'
      @createEmoji votesBlock, emoji


  getVotesBlock: ->

    currentBlock = $ '.js-awards-block.current'
    return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0


  getAwardUrl: -> return @getVotesBlock().data 'award-url'


  checkMutuality: (votesBlock, emoji) ->

    awardUrl = @getAwardUrl()

    if emoji in [ 'thumbsup', 'thumbsdown' ]
      mutualVote     = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
      $emojiButton   = votesBlock.find("[data-emoji=#{mutualVote}]").parent()
      isAlreadyVoted = $emojiButton.hasClass 'active'

      if isAlreadyVoted
        @showEmojiLoader $emojiButton
        @addAward votesBlock, awardUrl, mutualVote, no, ->
          $emojiButton.removeClass 'is-loading'


  showEmojiLoader: ($emojiButton) ->

    $loader = $emojiButton.find '.fa-spinner'

    unless $loader.length
      $emojiButton.append '<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>'

    $emojiButton.addClass 'is-loading'


  isActive: ($emojiButton) -> $emojiButton.hasClass 'active'


  decrementCounter: ($emojiButton, emoji) ->

    counter       = $ '.js-counter', $emojiButton
    counterNumber = parseInt counter.text(), 10

    if counterNumber > 1
      counter.text counterNumber - 1
      @removeMeFromUserList $emojiButton, emoji
    else if emoji is 'thumbsup' or emoji is 'thumbsdown'
      $emojiButton.tooltip 'destroy'
      counter.text '0'
      @removeMeFromUserList $emojiButton, emoji
      @removeEmoji $emojiButton if $emojiButton.parents('.note').length
    else
      @removeEmoji $emojiButton

    $emojiButton.removeClass 'active'


  removeEmoji: ($emojiButton) ->

    $emojiButton.tooltip('destroy')
    $emojiButton.remove()

    $votesBlock = @getVotesBlock()

    if $votesBlock.find('.js-emoji-btn').length is 0
      $votesBlock.addClass 'hidden'


  getAwardTooltip: ($awardBlock) ->

    return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''


  removeMeFromUserList: ($emojiButton, emoji) ->

    awardBlock    = $emojiButton
    originalTitle = @getAwardTooltip awardBlock

    authors = originalTitle.split ', '
    authors.splice authors.indexOf('me'), 1

    newAuthors = authors.join ', '

    awardBlock
      .closest '.js-emoji-btn'
      .removeData 'original-title'
      .attr 'data-original-title', newAuthors

    @resetTooltip awardBlock


  addMeToUserList: (votesBlock, emoji) ->

    awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
    origTitle  = @getAwardTooltip awardBlock
    users      = []

    if origTitle
      users = origTitle.trim().split ', '

    users.push 'me'
    awardBlock.attr 'title', users.join ', '

    @resetTooltip awardBlock


  resetTooltip: (award) ->

    award.tooltip 'destroy'

    # 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
    cb = -> award.tooltip()
    setTimeout cb, 200


  createEmoji_: (votesBlock, emoji) ->

    emojiCssClass = @resolveNameToCssClass emoji
    buttonHtml    = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
      <div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>
      <span class='award-control-text js-counter'>1</span>
    </button>"

    $emojiButton = $ buttonHtml
    $emojiButton
      .insertBefore votesBlock.find '.js-award-holder'
      .find '.emoji-icon'
      .data 'emoji', emoji

    @animateEmoji $emojiButton
    $('.award-control').tooltip()
    votesBlock.removeClass 'current'


  animateEmoji: ($emoji) ->

    className = 'pulse animated'

    $emoji.addClass className
    setTimeout (-> $emoji.removeClass className), 321


  createEmoji: (votesBlock, emoji) ->

    if $('.emoji-menu').length
      return @createEmoji_ votesBlock, emoji

    @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji


  getAwardMenuUrl: -> return gl.awardMenuUrl


  resolveNameToCssClass: (emoji) ->

    emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"

    if emojiIcon.length > 0
      unicodeName = emojiIcon.data 'unicode-name'
    else
      # Find by alias
      unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data 'unicode-name'

    return "emoji-#{unicodeName}"


  postEmoji: (awardUrl, emoji, callback) ->

    $.post awardUrl, { name: emoji }, (data) ->
      callback() if data.ok


  findEmojiIcon: (votesBlock, emoji) ->

    return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"


  scrollToAwards: ->

    options = scrollTop: $('.awards').offset().top - 110
    $('body, html').animate options, 200


  normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji


  addEmojiToFrequentlyUsedList: (emoji) ->

    frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
    frequentlyUsedEmojis.push emoji
    $.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }


  getFrequentlyUsedEmojis: ->

    frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
    return _.compact _.uniq frequentlyUsedEmojis


  renderFrequentlyUsedBlock: ->

    if $.cookie 'frequently_used_emojis'
      frequentlyUsedEmojis = @getFrequentlyUsedEmojis()

      ul = $("<ul class='clearfix emoji-menu-list'>")

      for emoji in frequentlyUsedEmojis
        $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)

      $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))


  setupSearch: ->

    $('input.emoji-search').on 'keyup', (ev) =>
      term = $(ev.target).val()

      # Clean previous search results
      $('ul.emoji-menu-search, h5.emoji-search').remove()

      if term
        # Generate a search result block
        h5 = $('<h5>').text('Search results').addClass('emoji-search')
        found_emojis = @searchEmojis(term).show()
        ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis)
        $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
        $('.emoji-menu-content').append(h5).append(ul)
      else
        $('.emoji-menu-content').children().show()


  searchEmojis: (term) ->

    $(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone()