Commit 1311f8c2 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge branch 'rename-ci-commit-phase-2' into rename-ci-commit-phase-3

parents ef35ca23 4e38d88d
......@@ -44,6 +44,7 @@ v 8.8.4 (unreleased)
- Fix issue with arrow keys not working in search autocomplete dropdown
- Fix todos page throwing errors when you have a project pending deletion
- Reduce number of SQL queries when rendering user references
- Upgrade to jQuery 2
v 8.8.3
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
......
......@@ -4,7 +4,7 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require jquery
#= require jquery2
#= require jquery-ui/autocomplete
#= require jquery-ui/datepicker
#= require jquery-ui/draggable
......
......@@ -2,39 +2,47 @@ class @AwardsHandler
constructor: ->
@aliases = emojiAliases()
@aliases = gl.emojiAliases()
$(document)
.off 'click', '.js-add-award'
.on 'click', '.js-add-award', (event) =>
event.stopPropagation()
event.preventDefault()
.on 'click', '.js-add-award', (e) =>
e.stopPropagation()
e.preventDefault()
@showEmojiMenu $(event.currentTarget)
@showEmojiMenu $(e.currentTarget)
$('html').on 'click', (event) ->
unless $(event.target).closest('.emoji-menu').length
$('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', @handleClick
.on 'click', '.js-emoji-btn', (e) =>
e.preventDefault()
handleClick: (e) =>
$target = $ e.currentTarget
emoji = $target.find('.icon').data 'emoji'
e.preventDefault()
emoji = $(e.currentTarget).find('.icon').data 'emoji'
@getVotesBlock().addClass 'js-awards-block'
@addAward @getAwardUrl(), emoji
$target.closest('.js-awards-block').addClass 'current'
@addAward @getVotesBlock(), @getAwardUrl(), emoji
showEmojiMenu: ($addBtn) ->
$menu = $('.emoji-menu')
$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')
......@@ -51,7 +59,7 @@ class @AwardsHandler
$('#emoji_search').focus()
else
$addBtn.addClass 'is-loading is-active'
url = $addBtn.data 'award-menu-url'
url = @getAwardMenuUrl()
@createEmojiMenu url, =>
$addBtn.removeClass 'is-loading'
......@@ -68,12 +76,13 @@ class @AwardsHandler
createEmojiMenu: (awardMenuUrl, callback) ->
$.get awardMenuUrl, (response) =>
$.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
......@@ -91,88 +100,114 @@ class @AwardsHandler
$menu.css(css)
addAward: (awardUrl, emoji, checkMutuality = yes) ->
addAward: (votesBlock, awardUrl, emoji, checkMutuality = yes, callback) ->
emoji = @normilizeEmojiName(emoji)
@postEmoji awardUrl, emoji, =>
@addAwardToEmojiBar(emoji, checkMutuality)
emoji = @normilizeEmojiName emoji
$('.js-awards-block-current').removeClass 'js-awards-block-current'
@postEmoji awardUrl, emoji, =>
@addAwardToEmojiBar votesBlock, emoji, checkMutuality
callback?()
$('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji, checkForMutuality = yes) ->
addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = yes) ->
@checkMutuality emoji if checkForMutuality
@addEmojiToFrequentlyUsedList(emoji)
@checkMutuality votesBlock, emoji if checkForMutuality
@addEmojiToFrequentlyUsedList emoji
emoji = @normilizeEmojiName(emoji)
$emojiBtn = @findEmojiIcon(emoji).parent()
emoji = @normilizeEmojiName emoji
$emojiButton = @findEmojiIcon(votesBlock, emoji).parent()
if $emojiBtn.length > 0
if @isActive($emojiBtn)
@decrementCounter($emojiBtn, emoji)
if $emojiButton.length > 0
if @isActive $emojiButton
@decrementCounter $emojiButton, emoji
else
counter = $emojiBtn.find('.js-counter')
counter.text(parseInt(counter.text()) + 1)
$emojiBtn.addClass('active')
@addMeToUserList(emoji)
counter = $emojiButton.find '.js-counter'
counter.text parseInt(counter.text()) + 1
$emojiButton.addClass 'active'
@addMeToUserList votesBlock, emoji
@animateEmoji $emojiButton
else
@createEmoji(emoji)
votesBlock.removeClass 'hidden'
@createEmoji votesBlock, emoji
getVotesBlock: -> return $ '.awards.js-awards-block'
getVotesBlock: ->
currentBlock = $ '.js-awards-block.current'
return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0
getAwardUrl: -> @getVotesBlock().data 'award-url'
getAwardUrl: -> return @getVotesBlock().data 'award-url'
checkMutuality: (emoji) ->
checkMutuality: (votesBlock, emoji) ->
awardUrl = @getAwardUrl()
if emoji in [ 'thumbsup', 'thumbsdown' ]
mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
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'
isAlreadyVoted = $("[data-emoji=#{mutualVote}]").parent().hasClass 'active'
@addAward awardUrl, mutualVote, no if isAlreadyVoted
showEmojiLoader: ($emojiButton) ->
isActive: ($emojiBtn) -> $emojiBtn.hasClass 'active'
$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>'
decrementCounter: ($emojiBtn, emoji) ->
isntNoteBody = $emojiBtn.closest('.note-body').length is 0
counter = $('.js-counter', $emojiBtn)
counterNumber = parseInt(counter.text())
$emojiButton.addClass 'is-loading'
if !isntNoteBody
# If this is a note body, we just hide the award emoji row like the initial state
$emojiBtn.closest('.js-awards-block').addClass 'hidden'
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($emojiBtn, emoji)
else if (emoji == 'thumbsup' || emoji == 'thumbsdown') && isntNoteBody
$emojiBtn.tooltip('destroy')
counter.text('0')
@removeMeFromUserList($emojiBtn, emoji)
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
$emojiBtn.tooltip('destroy')
$emojiBtn.remove()
@removeEmoji $emojiButton
$emojiButton.removeClass 'active'
$emojiBtn.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')
return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''
removeMeFromUserList: ($emojiBtn, emoji) ->
removeMeFromUserList: ($emojiButton, emoji) ->
awardBlock = $emojiBtn
awardBlock = $emojiButton
originalTitle = @getAwardTooltip awardBlock
authors = originalTitle.split ', '
......@@ -183,117 +218,134 @@ class @AwardsHandler
awardBlock
.closest '.js-emoji-btn'
.removeData 'original-title'
.removeData 'title'
.attr 'data-original-title', newAuthors
.attr 'data-title', newAuthors
@resetTooltip(awardBlock)
@resetTooltip awardBlock
addMeToUserList: (emoji) ->
addMeToUserList: (votesBlock, emoji) ->
awardBlock = @findEmojiIcon(emoji).parent()
awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
origTitle = @getAwardTooltip awardBlock
users = []
if origTitle
users = origTitle.trim().split(', ')
users = origTitle.trim().split ', '
users.push('me')
awardBlock.attr('title', users.join(', '))
users.push 'me'
awardBlock.attr 'title', users.join ', '
@resetTooltip(awardBlock)
@resetTooltip awardBlock
resetTooltip: (award) ->
award.tooltip('destroy')
award.tooltip 'destroy'
# 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (->
award.tooltip()
), 200
cb = -> award.tooltip()
setTimeout cb, 200
createEmoji_: (emoji) ->
createEmoji_: (votesBlock, emoji) ->
emojiCssClass = @resolveNameToCssClass emoji
buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
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>"
emoji_node = $(buttonHtml)
.insertBefore '.js-awards-block .js-award-holder:not(.js-award-action-btn)'
$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) ->
$currentBlock = $ '.js-awards-block'
className = 'pulse animated'
if $currentBlock.is '.hidden'
$currentBlock.removeClass 'hidden'
$emoji.addClass className
setTimeout (-> $emoji.removeClass className), 321
createEmoji: (emoji) ->
createEmoji: (votesBlock, emoji) ->
return @createEmoji_ emoji if $('.emoji-menu').length
if $('.emoji-menu').length
return @createEmoji_ votesBlock, emoji
awardMenuUrl = gl.awardMenuUrl or '/emojis'
@createEmojiMenu awardMenuUrl, => @createEmoji emoji
@createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
getAwardMenuUrl: -> return gl.awardMenuUrl
resolveNameToCssClass: (emoji) ->
emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"
if emoji_icon.length > 0
unicodeName = emoji_icon.data('unicode-name')
if emojiIcon.length > 0
unicodeName = emojiIcon.data 'unicode-name'
else
# Find by alias
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data 'unicode-name'
return "emoji-#{unicodeName}"
postEmoji: (awardUrl, emoji, callback) ->
$.post awardUrl, { name: emoji }, (data) ->
if data.ok
callback.call()
callback() if data.ok
findEmojiIcon: (votesBlock, emoji) ->
return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"
findEmojiIcon: (emoji) ->
$(".js-awards-block.awards > .js-emoji-btn [data-emoji='#{emoji}']")
scrollToAwards: ->
$('body, html').animate({
scrollTop: $('.awards').offset().top - 80
}, 200)
normilizeEmojiName: (emoji) ->
@aliases[emoji] || emoji
options = scrollTop: $('.awards').offset().top - 110
$('body, html').animate options, 200
normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji
addEmojiToFrequentlyUsedList: (emoji) ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
frequently_used_emojis.push(emoji)
$.cookie('frequently_used_emojis', frequently_used_emojis.join(','), { expires: 365 })
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
frequentlyUsedEmojis.push emoji
$.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }
getFrequentlyUsedEmojis: ->
frequently_used_emojis = ($.cookie('frequently_used_emojis') || '').split(',')
_.compact(_.uniq(frequently_used_emojis))
frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
return _.compact _.uniq frequentlyUsedEmojis
renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis')
frequently_used_emojis = @getFrequentlyUsedEmojis()
if $.cookie 'frequently_used_emojis'
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul class='clearfix emoji-menu-list'>")
for emoji in frequently_used_emojis
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()
......@@ -310,5 +362,7 @@ class @AwardsHandler
else
$('.emoji-menu-content').children().show()
searchEmojis: (term)->
searchEmojis: (term) ->
$(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone()
......@@ -23,7 +23,7 @@ class Dispatcher
new Issue()
shortcut_handler = new ShortcutsIssuable()
new ZenMode()
window.awardsHandler = new AwardsHandler()
gl.awardsHandler = new AwardsHandler()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone()
when 'dashboard:todos:index'
......@@ -54,7 +54,7 @@ class Dispatcher
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
new ZenMode()
window.awardsHandler = new AwardsHandler()
gl.awardsHandler = new AwardsHandler()
when "projects:merge_requests:diffs"
new Diff()
new ZenMode()
......
......@@ -21,7 +21,7 @@ class @DueDateSelect
$dropdown.glDropdown(
hidden: ->
$selectbox.hide()
$value.removeAttr('style')
$value.css('display', '')
)
addDueDate = (isDropdown) ->
......@@ -42,12 +42,13 @@ class @DueDateSelect
type: 'PUT'
url: issueUpdateURL
data: data
dataType: 'json'
beforeSend: ->
$loading.fadeIn()
if isDropdown
$dropdown.trigger('loading.gl.dropdown')
$selectbox.hide()
$value.removeAttr('style')
$value.css('display', '')
$valueContent.html(mediumDate)
$sidebarValue.html(mediumDate)
......
window.emojiAliases = ->
gl.emojiAliases = ->
JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
......@@ -83,7 +83,7 @@ class @MilestoneSelect
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
$value.css('display', '')
clicked: (selected) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
......@@ -118,7 +118,7 @@ class @MilestoneSelect
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
$value.removeAttr('style')
$value.css('display', '')
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
......
......@@ -162,13 +162,14 @@ class @Notes
renderNote: (note) ->
unless note.valid
if note.award
flash = new Flash('You have already used this award emoji!', 'alert')
flash = new Flash('You have already awarded this emoji!', 'alert')
flash.pinTo('.header-content')
return
if note.award
awardsHandler.addAwardToEmojiBar(note.name)
awardsHandler.scrollToAwards()
votesBlock = $('.js-awards-block').eq 0
gl.awardsHandler.addAwardToEmojiBar votesBlock, note.name
gl.awardsHandler.scrollToAwards()
# render note if it not present in loaded list
# or skip if rendered
......
......@@ -149,7 +149,7 @@ class @UsersSelect
hidden: (e) ->
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
$value.css('display', '')
clicked: (user) ->
page = $('body').data 'page'
......
......@@ -5,7 +5,7 @@
padding: 0;
.timeline-entry {
padding: $gl-padding $gl-btn-padding;
padding: $gl-padding $gl-btn-padding 11px;
border-color: $table-border-color;
color: $gl-gray;
border-bottom: 1px solid $border-white-light;
......
......@@ -95,6 +95,7 @@
.award-control {
margin-right: 5px;
margin-bottom: 5px;
padding-left: 5px;
padding-right: 5px;
line-height: 20px;
......@@ -108,7 +109,8 @@
}
&.is-loading {
.award-control-icon-normal {
.award-control-icon-normal,
.emoji-icon {
display: none;
}
......
......@@ -69,6 +69,10 @@ ul.notes {
.note-edit-form {
display: block;
&.current-note-edit-form + .note-awards {
display: none;
}
}
}
......@@ -116,10 +120,38 @@ ul.notes {
}
}
.note-awards {
.js-awards-block {
padding: 2px;
margin-top: 10px;
}
.award-control {
font-size: 13px;
padding: 2px 5px;
}
}
.note-header {
padding-bottom: 3px;
}
.note-emoji-button {
.fa-spinner {
display: none;
}
&.is-loading {
.fa-smile-o {
display: none;
}
.fa-spinner {
display: inline-block;
}
}
}
}
}
......
......@@ -9,13 +9,22 @@ module ToggleAwardEmoji
name = params.require(:name)
awardable.toggle_award_emoji(name, current_user)
TodoService.new.new_award_emoji(awardable, current_user)
TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
render json: { ok: true }
end
private
def to_todoable(awardable)
case awardable
when Note
awardable.noteable
else
awardable
end
end
def awardable
raise NotImplementedError
end
......
class Projects::NotesController < Projects::ApplicationController
include ToggleAwardEmoji
# Authorize
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
......@@ -61,6 +63,7 @@ class Projects::NotesController < Projects::ApplicationController
def note
@note ||= @project.notes.find(params[:id])
end
alias_method :awardable, :note
def note_to_html(note)
render_to_string(
......
......@@ -3,6 +3,7 @@ class Note < ActiveRecord::Base
include Gitlab::CurrentSettings
include Participable
include Mentionable
include Awardable
default_value_for :system, false
......
......@@ -11,7 +11,7 @@
gl.awardMenuUrl = "#{emojis_path}"
.award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{ type: "button", data: { award_menu_url: emojis_path } }
%button.btn.award-control.js-add-award{ type: "button" }
= icon('smile-o', class: "award-control-icon award-control-icon-normal")
= icon('spinner spin', class: "award-control-icon award-control-icon-loading")
%span.award-control-text
......
......@@ -68,9 +68,9 @@
#related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript.
.content-block.content-block-small
= render 'new_branch'
= render 'award_emoji/awards_block', awardable: @issue, inline: true
.content-block.content-block-small
= render 'new_branch'
= render 'award_emoji/awards_block', awardable: @issue, inline: true
%section.issuable-discussion
= render 'projects/issues/discussion'
......
......@@ -22,6 +22,9 @@
%span.note-role
= access
- if note_editable
= link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
= icon('spinner spin')
= icon('smile-o')
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
......@@ -30,9 +33,11 @@
.note-text
= preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author)
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable
= render 'projects/notes/edit_form', note: note
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
.note-awards
= render 'award_emoji/awards_block', awardable: note, inline: false
- if note.attachment.url
.note-attachment
......
......@@ -758,6 +758,7 @@ Rails.application.routes.draw do
resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
member do
post :toggle_award_emoji
delete :delete_attachment
end
end
......
require('spec_helper')
describe Projects::NotesController do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:note) { create(:note, noteable: issue, project: project) }
describe 'POST #toggle_award_emoji' do
before do
sign_in(user)
project.team << [user, :developer]
end
it "toggles the award emoji" do
expect do
post(:toggle_award_emoji, namespace_id: project.namespace.path,
project_id: project.path, id: note.id, name: "thumbsup")
end.to change { note.award_emoji.count }.by(1)
expect(response.status).to eq(200)
end
it "removes the already awarded emoji" do
post(:toggle_award_emoji, namespace_id: project.namespace.path,
project_id: project.path, id: note.id, name: "thumbsup")
expect do
post(:toggle_award_emoji, namespace_id: project.namespace.path,
project_id: project.path, id: note.id, name: "thumbsup")
end.to change { AwardEmoji.count }.by(-1)
expect(response.status).to eq(200)
end
end
end
......@@ -365,13 +365,9 @@ describe 'Issues', feature: true do
page.within('.assignee') do
expect(page).to have_content "#{@user.name}"
end
find('.block.assignee .edit-link').click
sleep 2 # wait for ajax stuff to complete
first('.dropdown-menu-user-link').click
sleep 2
page.within('.assignee') do
click_link 'Edit'
click_link 'Unassigned'
expect(page).to have_content 'No assignee'
end
......
#= require awards_handler
#= require jquery
#= require jquery.cookie
#= require ./fixtures/emoji_menu
awardsHandler = null
window.gl or= {}
gl.emojiAliases = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' }
gl.awardMenuUrl = '/emojis'
lazyAssert = (done, assertFn) ->
setTimeout -> # Maybe jasmine.clock here?
assertFn()
done()
, 333
describe 'AwardsHandler', ->
fixture.preload 'awards_handler.html'
beforeEach ->
fixture.load 'awards_handler.html'
awardsHandler = new AwardsHandler
spyOn(awardsHandler, 'postEmoji').and.callFake (url, emoji, cb) => cb()
spyOn(jQuery, 'get').and.callFake (req, cb) ->
expect(req).toBe '/emojis'
cb window.emojiMenu
describe '::showEmojiMenu', ->
it 'should show emoji menu when Add emoji button clicked', (done) ->
$('.js-add-award').eq(0).click()
lazyAssert done, ->
$emojiMenu = $ '.emoji-menu'
expect($emojiMenu.length).toBe 1
expect($emojiMenu.hasClass('is-visible')).toBe yes
expect($emojiMenu.find('#emoji_search').length).toBe 1
expect($('.js-awards-block.current').length).toBe 1
it 'should also show emoji menu for the smiley icon in notes', (done) ->
$('.note-action-button').click()
lazyAssert done, ->
$emojiMenu = $ '.emoji-menu'
expect($emojiMenu.length).toBe 1
it 'should remove emoji menu when body is clicked', (done) ->
$('.js-add-award').eq(0).click()
lazyAssert done, ->
$emojiMenu = $('.emoji-menu')
$('body').click()
expect($emojiMenu.length).toBe 1
expect($emojiMenu.hasClass('is-visible')).toBe no
expect($('.js-awards-block.current').length).toBe 0
describe '::addAwardToEmojiBar', ->
it 'should add emoji to votes block', ->
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
$emojiButton = $votesBlock.find '[data-emoji=heart]'
expect($emojiButton.length).toBe 1
expect($emojiButton.next('.js-counter').text()).toBe '1'
expect($votesBlock.hasClass('hidden')).toBe no
it 'should remove the emoji when we click again', ->
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
$emojiButton = $votesBlock.find '[data-emoji=heart]'
expect($emojiButton.length).toBe 0
it 'should decrement the emoji counter', ->
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
$emojiButton = $votesBlock.find '[data-emoji=heart]'
$emojiButton.next('.js-counter').text 5
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
expect($emojiButton.length).toBe 1
expect($emojiButton.next('.js-counter').text()).toBe '4'
describe '::getAwardUrl', ->
it 'should return the url for request', ->
expect(awardsHandler.getAwardUrl()).toBe '/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'
describe '::addAward and ::checkMutuality', ->
it 'should handle :+1: and :-1: mutuality', ->
awardUrl = awardsHandler.getAwardUrl()
$votesBlock = $('.js-awards-block').eq 0
$thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent()
$thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent()
awardsHandler.addAward $votesBlock, awardUrl, 'thumbsup', no
expect($thumbsUpEmoji.hasClass('active')).toBe yes
expect($thumbsDownEmoji.hasClass('active')).toBe no
$thumbsUpEmoji.tooltip()
$thumbsDownEmoji.tooltip()
awardsHandler.addAward $votesBlock, awardUrl, 'thumbsdown', yes
expect($thumbsUpEmoji.hasClass('active')).toBe no
expect($thumbsDownEmoji.hasClass('active')).toBe yes
describe '::removeEmoji', ->
it 'should remove emoji', ->
awardUrl = awardsHandler.getAwardUrl()
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAward $votesBlock, awardUrl, 'fire', no
expect($votesBlock.find('[data-emoji=fire]').length).toBe 1
awardsHandler.removeEmoji $votesBlock.find('[data-emoji=fire]').closest('button')
expect($votesBlock.find('[data-emoji=fire]').length).toBe 0
describe 'search', ->
it 'should filter the emoji', ->
$('.js-add-award').eq(0).click()
expect($('[data-emoji=angel]').is(':visible')).toBe yes
expect($('[data-emoji=anger]').is(':visible')).toBe yes
$('#emoji_search').val('ali').trigger 'keyup'
expect($('[data-emoji=angel]').is(':visible')).toBe no
expect($('[data-emoji=anger]').is(':visible')).toBe no
expect($('[data-emoji=alien]').is(':visible')).toBe yes
expect($('h5.emoji-search').is(':visible')).toBe yes
describe 'emoji menu', ->
selector = '[data-emoji=sunglasses]'
openEmojiMenuAndAddEmoji = ->
$('.js-add-award').eq(0).click()
$menu = $ '.emoji-menu'
$block = $ '.js-awards-block'
$emoji = $menu.find ".emoji-menu-list-item #{selector}"
expect($emoji.length).toBe 1
expect($block.find(selector).length).toBe 0
$emoji.click()
expect($menu.hasClass('.is-visible')).toBe no
expect($block.find(selector).length).toBe 1
it 'should add selected emoji to awards block', ->
openEmojiMenuAndAddEmoji()
it 'should remove already selected emoji', ->
openEmojiMenuAndAddEmoji()
$('.js-add-award').eq(0).click()
$block = $ '.js-awards-block'
$emoji = $('.emoji-menu').find ".emoji-menu-list-item #{selector}"
$emoji.click()
expect($block.find(selector).length).toBe 0
......@@ -14,17 +14,17 @@ describe 'Quick Submit behavior', ->
}
it 'does not respond to other keyCodes', ->
$('input').trigger(keydownEvent(keyCode: 32))
$('input.quick-submit-input').trigger(keydownEvent(keyCode: 32))
expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to Enter alone', ->
$('input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
$('input.quick-submit-input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to repeated events', ->
$('input').trigger(keydownEvent(repeat: true))
$('input.quick-submit-input').trigger(keydownEvent(repeat: true))
expect(@spies.submit).not.toHaveBeenTriggered()
......@@ -38,26 +38,26 @@ describe 'Quick Submit behavior', ->
# only run the tests that apply to the current platform
if navigator.userAgent.match(/Macintosh/)
it 'responds to Meta+Enter', ->
$('input').trigger(keydownEvent())
$('input.quick-submit-input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', ->
$('input').trigger(keydownEvent(altKey: true))
$('input').trigger(keydownEvent(ctrlKey: true))
$('input').trigger(keydownEvent(shiftKey: true))
$('input.quick-submit-input').trigger(keydownEvent(altKey: true))
$('input.quick-submit-input').trigger(keydownEvent(ctrlKey: true))
$('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered()
else
it 'responds to Ctrl+Enter', ->
$('input').trigger(keydownEvent())
$('input.quick-submit-input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', ->
$('input').trigger(keydownEvent(altKey: true))
$('input').trigger(keydownEvent(metaKey: true))
$('input').trigger(keydownEvent(shiftKey: true))
$('input.quick-submit-input').trigger(keydownEvent(altKey: true))
$('input.quick-submit-input').trigger(keydownEvent(metaKey: true))
$('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered()
......
.issue-details.issuable-details
.detail-page-description.content-block
%h2.title Quibusdam sint officiis earum molestiae ipsa autem voluptatem nisi rem.
.description.js-task-list-container.is-task-list-enabled
.wiki
%p Qui exercitationem magnam optio quae fuga earum odio.
%textarea.hidden.js-task-list-field Qui exercitationem magnam optio quae fuga earum odio.
%small.edited-text
.content-block.content-block-small
.awards.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/issues/8/toggle_award_emoji"}
%button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
.icon.emoji-icon.emoji-1F44D{"data-aliases" => "", "data-emoji" => "thumbsup", "data-unicode-name" => "1F44D", :title => "thumbsup"}
%span.award-control-text.js-counter 0
%button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
.icon.emoji-icon.emoji-1F44E{"data-aliases" => "", "data-emoji" => "thumbsdown", "data-unicode-name" => "1F44E", :title => "thumbsdown"}
%span.award-control-text.js-counter 0
.award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{:type => "button"}
%i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
%i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
%span.award-control-text Add
%section.issuable-discussion
#notes
%ul#notes-list.notes.main-notes-list.timeline
%li#note_348.note.note-row-348.timeline-entry{"data-author-id" => "18", "data-editable" => ""}
.timeline-entry-inner
.timeline-icon
%a{:href => "/u/agustin"}
%img.avatar.s40{:alt => "", :src => "#"}/
.timeline-content
.note-header
%a.author_link{:href => "/u/agustin"}
%span.author Brenna Stokes
.inline.note-headline-light
@agustin commented
%a{:href => "#note_348"}
%time 11 days ago
.note-actions
%span.note-role Reporter
%a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"}
%i.fa.fa-spinner.fa-spin
%i.fa.fa-smile-o
.js-task-list-container.note-body.is-task-list-enabled
.note-text
%p Suscipit sunt quia quisquam sed eveniet ipsam.
.note-awards
.awards.hidden.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/notes/348/toggle_award_emoji"}
.award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{:type => "button"}
%i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
%i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
%span.award-control-text Add
%form.js-quick-submit{ action: '/foo' }
%input{ type: 'text' }
%input{ type: 'text', class: 'quick-submit-input'}
%textarea
%input{ type: 'submit'} Submit
......
window.emojiMenu = """
<div class='emoji-menu'>
<div class='emoji-menu-content'>
<input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" />
<h5 class='emoji-menu-title'>
Emoticons
</h5>
<ul class='clearfix emoji-menu-list'>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47D" title="alien" data-aliases="" data-emoji="alien" data-unicode-name="1F47D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47C" title="angel" data-aliases="" data-emoji="angel" data-unicode-name="1F47C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A2" title="anger" data-aliases="" data-emoji="anger" data-unicode-name="1F4A2"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F620" title="angry" data-aliases="" data-emoji="angry" data-unicode-name="1F620"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F627" title="anguished" data-aliases="" data-emoji="anguished" data-unicode-name="1F627"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F632" title="astonished" data-aliases="" data-emoji="astonished" data-unicode-name="1F632"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45F" title="athletic_shoe" data-aliases="" data-emoji="athletic_shoe" data-unicode-name="1F45F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F476" title="baby" data-aliases="" data-emoji="baby" data-unicode-name="1F476"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F459" title="bikini" data-aliases="" data-emoji="bikini" data-unicode-name="1F459"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F499" title="blue_heart" data-aliases="" data-emoji="blue_heart" data-unicode-name="1F499"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60A" title="blush" data-aliases="" data-emoji="blush" data-unicode-name="1F60A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A5" title="boom" data-aliases="" data-emoji="boom" data-unicode-name="1F4A5"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F462" title="boot" data-aliases="" data-emoji="boot" data-unicode-name="1F462"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F647" title="bow" data-aliases="" data-emoji="bow" data-unicode-name="1F647"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F466" title="boy" data-aliases="" data-emoji="boy" data-unicode-name="1F466"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F470" title="bride_with_veil" data-aliases="" data-emoji="bride_with_veil" data-unicode-name="1F470"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4BC" title="briefcase" data-aliases="" data-emoji="briefcase" data-unicode-name="1F4BC"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F494" title="broken_heart" data-aliases="" data-emoji="broken_heart" data-unicode-name="1F494"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F464" title="bust_in_silhouette" data-aliases="" data-emoji="bust_in_silhouette" data-unicode-name="1F464"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F465" title="busts_in_silhouette" data-aliases="" data-emoji="busts_in_silhouette" data-unicode-name="1F465"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44F" title="clap" data-aliases="" data-emoji="clap" data-unicode-name="1F44F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F302" title="closed_umbrella" data-aliases="" data-emoji="closed_umbrella" data-unicode-name="1F302"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F630" title="cold_sweat" data-aliases="" data-emoji="cold_sweat" data-unicode-name="1F630"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F616" title="confounded" data-aliases="" data-emoji="confounded" data-unicode-name="1F616"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F615" title="confused" data-aliases="" data-emoji="confused" data-unicode-name="1F615"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F477" title="construction_worker" data-aliases="" data-emoji="construction_worker" data-unicode-name="1F477"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46E" title="cop" data-aliases="" data-emoji="cop" data-unicode-name="1F46E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46B" title="couple" data-aliases="" data-emoji="couple" data-unicode-name="1F46B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F491" title="couple_with_heart" data-aliases="" data-emoji="couple_with_heart" data-unicode-name="1F491"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48F" title="couplekiss" data-aliases="" data-emoji="couplekiss" data-unicode-name="1F48F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F451" title="crown" data-aliases="" data-emoji="crown" data-unicode-name="1F451"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F622" title="cry" data-aliases="" data-emoji="cry" data-unicode-name="1F622"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63F" title="crying_cat_face" data-aliases="" data-emoji="crying_cat_face" data-unicode-name="1F63F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F498" title="cupid" data-aliases="" data-emoji="cupid" data-unicode-name="1F498"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F483" title="dancer" data-aliases="" data-emoji="dancer" data-unicode-name="1F483"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46F" title="dancers" data-aliases="" data-emoji="dancers" data-unicode-name="1F46F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A8" title="dash" data-aliases="" data-emoji="dash" data-unicode-name="1F4A8"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61E" title="disappointed" data-aliases="" data-emoji="disappointed" data-unicode-name="1F61E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F625" title="disappointed_relieved" data-aliases="" data-emoji="disappointed_relieved" data-unicode-name="1F625"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AB" title="dizzy" data-aliases="" data-emoji="dizzy" data-unicode-name="1F4AB"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F635" title="dizzy_face" data-aliases="" data-emoji="dizzy_face" data-unicode-name="1F635"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F457" title="dress" data-aliases="" data-emoji="dress" data-unicode-name="1F457"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A7" title="droplet" data-aliases="" data-emoji="droplet" data-unicode-name="1F4A7"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F442" title="ear" data-aliases="" data-emoji="ear" data-unicode-name="1F442"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F611" title="expressionless" data-aliases="" data-emoji="expressionless" data-unicode-name="1F611"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F453" title="eyeglasses" data-aliases="" data-emoji="eyeglasses" data-unicode-name="1F453"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F440" title="eyes" data-aliases="" data-emoji="eyes" data-unicode-name="1F440"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46A" title="family" data-aliases="" data-emoji="family" data-unicode-name="1F46A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F628" title="fearful" data-aliases="" data-emoji="fearful" data-unicode-name="1F628"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F525" title="fire" data-aliases=":flame:" data-emoji="fire" data-unicode-name="1F525"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-270A" title="fist" data-aliases="" data-emoji="fist" data-unicode-name="270A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F633" title="flushed" data-aliases="" data-emoji="flushed" data-unicode-name="1F633"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F463" title="footprints" data-aliases="" data-emoji="footprints" data-unicode-name="1F463"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F626" title="frowning" data-aliases=":anguished:" data-emoji="frowning" data-unicode-name="1F626"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48E" title="gem" data-aliases="" data-emoji="gem" data-unicode-name="1F48E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F467" title="girl" data-aliases="" data-emoji="girl" data-unicode-name="1F467"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49A" title="green_heart" data-aliases="" data-emoji="green_heart" data-unicode-name="1F49A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62C" title="grimacing" data-aliases="" data-emoji="grimacing" data-unicode-name="1F62C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F601" title="grin" data-aliases="" data-emoji="grin" data-unicode-name="1F601"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F600" title="grinning" data-aliases="" data-emoji="grinning" data-unicode-name="1F600"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F482" title="guardsman" data-aliases="" data-emoji="guardsman" data-unicode-name="1F482"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F487" title="haircut" data-aliases="" data-emoji="haircut" data-unicode-name="1F487"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45C" title="handbag" data-aliases="" data-emoji="handbag" data-unicode-name="1F45C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F649" title="hear_no_evil" data-aliases="" data-emoji="hear_no_evil" data-unicode-name="1F649"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-2764" title="heart" data-aliases="" data-emoji="heart" data-unicode-name="2764"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60D" title="heart_eyes" data-aliases="" data-emoji="heart_eyes" data-unicode-name="1F60D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63B" title="heart_eyes_cat" data-aliases="" data-emoji="heart_eyes_cat" data-unicode-name="1F63B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F493" title="heartbeat" data-aliases="" data-emoji="heartbeat" data-unicode-name="1F493"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F497" title="heartpulse" data-aliases="" data-emoji="heartpulse" data-unicode-name="1F497"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F460" title="high_heel" data-aliases="" data-emoji="high_heel" data-unicode-name="1F460"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62F" title="hushed" data-aliases="" data-emoji="hushed" data-unicode-name="1F62F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47F" title="imp" data-aliases="" data-emoji="imp" data-unicode-name="1F47F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F481" title="information_desk_person" data-aliases="" data-emoji="information_desk_person" data-unicode-name="1F481"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F607" title="innocent" data-aliases="" data-emoji="innocent" data-unicode-name="1F607"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47A" title="japanese_goblin" data-aliases="" data-emoji="japanese_goblin" data-unicode-name="1F47A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F479" title="japanese_ogre" data-aliases="" data-emoji="japanese_ogre" data-unicode-name="1F479"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F456" title="jeans" data-aliases="" data-emoji="jeans" data-unicode-name="1F456"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F602" title="joy" data-aliases="" data-emoji="joy" data-unicode-name="1F602"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F639" title="joy_cat" data-aliases="" data-emoji="joy_cat" data-unicode-name="1F639"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F458" title="kimono" data-aliases="" data-emoji="kimono" data-unicode-name="1F458"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48B" title="kiss" data-aliases="" data-emoji="kiss" data-unicode-name="1F48B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F617" title="kissing" data-aliases="" data-emoji="kissing" data-unicode-name="1F617"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63D" title="kissing_cat" data-aliases="" data-emoji="kissing_cat" data-unicode-name="1F63D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61A" title="kissing_closed_eyes" data-aliases="" data-emoji="kissing_closed_eyes" data-unicode-name="1F61A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F618" title="kissing_heart" data-aliases="" data-emoji="kissing_heart" data-unicode-name="1F618"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F619" title="kissing_smiling_eyes" data-aliases="" data-emoji="kissing_smiling_eyes" data-unicode-name="1F619"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F606" title="laughing" data-aliases=":satisfied:" data-emoji="laughing" data-unicode-name="1F606"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F444" title="lips" data-aliases="" data-emoji="lips" data-unicode-name="1F444"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F484" title="lipstick" data-aliases="" data-emoji="lipstick" data-unicode-name="1F484"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48C" title="love_letter" data-aliases="" data-emoji="love_letter" data-unicode-name="1F48C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F468" title="man" data-aliases="" data-emoji="man" data-unicode-name="1F468"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F472" title="man_with_gua_pi_mao" data-aliases="" data-emoji="man_with_gua_pi_mao" data-unicode-name="1F472"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F473" title="man_with_turban" data-aliases="" data-emoji="man_with_turban" data-unicode-name="1F473"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45E" title="mans_shoe" data-aliases="" data-emoji="mans_shoe" data-unicode-name="1F45E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F637" title="mask" data-aliases="" data-emoji="mask" data-unicode-name="1F637"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F486" title="massage" data-aliases="" data-emoji="massage" data-unicode-name="1F486"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AA" title="muscle" data-aliases="" data-emoji="muscle" data-unicode-name="1F4AA"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F485" title="nail_care" data-aliases="" data-emoji="nail_care" data-unicode-name="1F485"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F454" title="necktie" data-aliases="" data-emoji="necktie" data-unicode-name="1F454"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F610" title="neutral_face" data-aliases="" data-emoji="neutral_face" data-unicode-name="1F610"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F645" title="no_good" data-aliases="" data-emoji="no_good" data-unicode-name="1F645"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F636" title="no_mouth" data-aliases="" data-emoji="no_mouth" data-unicode-name="1F636"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F443" title="nose" data-aliases="" data-emoji="nose" data-unicode-name="1F443"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44C" title="ok_hand" data-aliases="" data-emoji="ok_hand" data-unicode-name="1F44C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F646" title="ok_woman" data-aliases="" data-emoji="ok_woman" data-unicode-name="1F646"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F474" title="older_man" data-aliases="" data-emoji="older_man" data-unicode-name="1F474"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F475" title="older_woman" data-aliases=":grandma:" data-emoji="older_woman" data-unicode-name="1F475"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F450" title="open_hands" data-aliases="" data-emoji="open_hands" data-unicode-name="1F450"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62E" title="open_mouth" data-aliases="" data-emoji="open_mouth" data-unicode-name="1F62E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F614" title="pensive" data-aliases="" data-emoji="pensive" data-unicode-name="1F614"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F623" title="persevere" data-aliases="" data-emoji="persevere" data-unicode-name="1F623"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64D" title="person_frowning" data-aliases="" data-emoji="person_frowning" data-unicode-name="1F64D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F471" title="person_with_blond_hair" data-aliases="" data-emoji="person_with_blond_hair" data-unicode-name="1F471"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64E" title="person_with_pouting_face" data-aliases="" data-emoji="person_with_pouting_face" data-unicode-name="1F64E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F447" title="point_down" data-aliases="" data-emoji="point_down" data-unicode-name="1F447"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F448" title="point_left" data-aliases="" data-emoji="point_left" data-unicode-name="1F448"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F449" title="point_right" data-aliases="" data-emoji="point_right" data-unicode-name="1F449"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-261D" title="point_up" data-aliases="" data-emoji="point_up" data-unicode-name="261D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F446" title="point_up_2" data-aliases="" data-emoji="point_up_2" data-unicode-name="1F446"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A9" title="poop" data-aliases=":shit: :hankey: :poo:" data-emoji="poop" data-unicode-name="1F4A9"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45D" title="pouch" data-aliases="" data-emoji="pouch" data-unicode-name="1F45D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63E" title="pouting_cat" data-aliases="" data-emoji="pouting_cat" data-unicode-name="1F63E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64F" title="pray" data-aliases="" data-emoji="pray" data-unicode-name="1F64F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F478" title="princess" data-aliases="" data-emoji="princess" data-unicode-name="1F478"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44A" title="punch" data-aliases="" data-emoji="punch" data-unicode-name="1F44A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49C" title="purple_heart" data-aliases="" data-emoji="purple_heart" data-unicode-name="1F49C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45B" title="purse" data-aliases="" data-emoji="purse" data-unicode-name="1F45B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F621" title="rage" data-aliases="" data-emoji="rage" data-unicode-name="1F621"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-270B" title="raised_hand" data-aliases="" data-emoji="raised_hand" data-unicode-name="270B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64C" title="raised_hands" data-aliases="" data-emoji="raised_hands" data-unicode-name="1F64C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64B" title="raising_hand" data-aliases="" data-emoji="raising_hand" data-unicode-name="1F64B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-263A" title="relaxed" data-aliases="" data-emoji="relaxed" data-unicode-name="263A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60C" title="relieved" data-aliases="" data-emoji="relieved" data-unicode-name="1F60C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49E" title="revolving_hearts" data-aliases="" data-emoji="revolving_hearts" data-unicode-name="1F49E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F380" title="ribbon" data-aliases="" data-emoji="ribbon" data-unicode-name="1F380"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48D" title="ring" data-aliases="" data-emoji="ring" data-unicode-name="1F48D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F3C3" title="runner" data-aliases="" data-emoji="runner" data-unicode-name="1F3C3"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F3BD" title="running_shirt_with_sash" data-aliases="" data-emoji="running_shirt_with_sash" data-unicode-name="1F3BD"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F461" title="sandal" data-aliases="" data-emoji="sandal" data-unicode-name="1F461"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F631" title="scream" data-aliases="" data-emoji="scream" data-unicode-name="1F631"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F640" title="scream_cat" data-aliases="" data-emoji="scream_cat" data-unicode-name="1F640"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F648" title="see_no_evil" data-aliases="" data-emoji="see_no_evil" data-unicode-name="1F648"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F455" title="shirt" data-aliases="" data-emoji="shirt" data-unicode-name="1F455"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F480" title="skull" data-aliases=":skeleton:" data-emoji="skull" data-unicode-name="1F480"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F634" title="sleeping" data-aliases="" data-emoji="sleeping" data-unicode-name="1F634"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62A" title="sleepy" data-aliases="" data-emoji="sleepy" data-unicode-name="1F62A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F604" title="smile" data-aliases="" data-emoji="smile" data-unicode-name="1F604"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F638" title="smile_cat" data-aliases="" data-emoji="smile_cat" data-unicode-name="1F638"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F603" title="smiley" data-aliases="" data-emoji="smiley" data-unicode-name="1F603"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63A" title="smiley_cat" data-aliases="" data-emoji="smiley_cat" data-unicode-name="1F63A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F608" title="smiling_imp" data-aliases="" data-emoji="smiling_imp" data-unicode-name="1F608"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60F" title="smirk" data-aliases="" data-emoji="smirk" data-unicode-name="1F60F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63C" title="smirk_cat" data-aliases="" data-emoji="smirk_cat" data-unicode-name="1F63C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62D" title="sob" data-aliases="" data-emoji="sob" data-unicode-name="1F62D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-2728" title="sparkles" data-aliases="" data-emoji="sparkles" data-unicode-name="2728"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F496" title="sparkling_heart" data-aliases="" data-emoji="sparkling_heart" data-unicode-name="1F496"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64A" title="speak_no_evil" data-aliases="" data-emoji="speak_no_evil" data-unicode-name="1F64A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AC" title="speech_balloon" data-aliases="" data-emoji="speech_balloon" data-unicode-name="1F4AC"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F31F" title="star2" data-aliases="" data-emoji="star2" data-unicode-name="1F31F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61B" title="stuck_out_tongue" data-aliases="" data-emoji="stuck_out_tongue" data-unicode-name="1F61B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61D" title="stuck_out_tongue_closed_eyes" data-aliases="" data-emoji="stuck_out_tongue_closed_eyes" data-unicode-name="1F61D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61C" title="stuck_out_tongue_winking_eye" data-aliases="" data-emoji="stuck_out_tongue_winking_eye" data-unicode-name="1F61C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60E" title="sunglasses" data-aliases="" data-emoji="sunglasses" data-unicode-name="1F60E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F613" title="sweat" data-aliases="" data-emoji="sweat" data-unicode-name="1F613"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A6" title="sweat_drops" data-aliases="" data-emoji="sweat_drops" data-unicode-name="1F4A6"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F605" title="sweat_smile" data-aliases="" data-emoji="sweat_smile" data-unicode-name="1F605"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AD" title="thought_balloon" data-aliases="" data-emoji="thought_balloon" data-unicode-name="1F4AD"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44E" title="thumbsdown" data-aliases=":-1:" data-emoji="thumbsdown" data-unicode-name="1F44E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44D" title="thumbsup" data-aliases=":+1:" data-emoji="thumbsup" data-unicode-name="1F44D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62B" title="tired_face" data-aliases="" data-emoji="tired_face" data-unicode-name="1F62B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F445" title="tongue" data-aliases="" data-emoji="tongue" data-unicode-name="1F445"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F3A9" title="tophat" data-aliases="" data-emoji="tophat" data-unicode-name="1F3A9"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F624" title="triumph" data-aliases="" data-emoji="triumph" data-unicode-name="1F624"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F495" title="two_hearts" data-aliases="" data-emoji="two_hearts" data-unicode-name="1F495"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46C" title="two_men_holding_hands" data-aliases="" data-emoji="two_men_holding_hands" data-unicode-name="1F46C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46D" title="two_women_holding_hands" data-aliases="" data-emoji="two_women_holding_hands" data-unicode-name="1F46D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F612" title="unamused" data-aliases="" data-emoji="unamused" data-unicode-name="1F612"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-270C" title="v" data-aliases="" data-emoji="v" data-unicode-name="270C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F6B6" title="walking" data-aliases="" data-emoji="walking" data-unicode-name="1F6B6"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44B" title="wave" data-aliases="" data-emoji="wave" data-unicode-name="1F44B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F629" title="weary" data-aliases="" data-emoji="weary" data-unicode-name="1F629"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F609" title="wink" data-aliases="" data-emoji="wink" data-unicode-name="1F609"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F469" title="woman" data-aliases="" data-emoji="woman" data-unicode-name="1F469"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45A" title="womans_clothes" data-aliases="" data-emoji="womans_clothes" data-unicode-name="1F45A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F452" title="womans_hat" data-aliases="" data-emoji="womans_hat" data-unicode-name="1F452"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61F" title="worried" data-aliases="" data-emoji="worried" data-unicode-name="1F61F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49B" title="yellow_heart" data-aliases="" data-emoji="yellow_heart" data-unicode-name="1F49B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60B" title="yum" data-aliases="" data-emoji="yum" data-unicode-name="1F60B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A4" title="zzz" data-aliases="" data-emoji="zzz" data-unicode-name="1F4A4"></div>
</button>
</li>
</ul>
</div>
</div>
"""
......@@ -9,14 +9,14 @@ describe("ContributorsStatGraphUtil", function () {
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}]
var correct_parsed_log = {
total: [
{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:
[
{
{
author_name: "Karlo Soriano", author_email: "karlo@email.com",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
......@@ -132,8 +132,8 @@ describe("ContributorsStatGraphUtil", function () {
total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:[
{
author: "Karlo Soriano",
{
author: "Karlo Soriano",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
{
......@@ -161,11 +161,11 @@ describe("ContributorsStatGraphUtil", function () {
it("returns the log by author sorted by specified field", function () {
var fake_parsed_log = {
total: [
{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
],
by_author: [
{
{
author_name: "Karlo Soriano", author_email: "karlo@email.com",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
......
#= require jquery-ui
#= require jquery-ui/autocomplete
#= require new_branch_form
describe 'Branch', ->
......
......@@ -42,8 +42,7 @@ describe Gitlab::Badge::Build do
end
context 'build exists' do
let(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: branch) }
let!(:build) { create(:ci_build, pipeline: pipeline) }
let!(:build) { create_build(project, sha, branch) }
context 'build success' do
before { build.success! }
......@@ -95,6 +94,28 @@ describe Gitlab::Badge::Build do
end
end
context 'when outdated pipeline for given ref exists' do
before do
build = create_build(project, sha, branch)
build.success!
old_build = create_build(project, '11eeffdd', branch)
old_build.drop!
end
it 'does not take outdated pipeline into account' do
expect(badge.to_s).to eq 'build-success'
end
end
def create_build(project, sha, branch)
pipeline = create(:ci_pipeline, project: project,
sha: sha,
ref: branch)
create(:ci_build, pipeline: pipeline)
end
def status_node(data, status)
xml = Nokogiri::XML.parse(data)
xml.at(%Q{text:contains("#{status}")})
......
......@@ -9,6 +9,16 @@ describe Note, models: true do
it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Mentionable) }
it { is_expected.to include_module(Awardable) }
it { is_expected.to include_module(Gitlab::CurrentSettings) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) }
......
# The MIT License (MIT)
#
# Copyright (c) 2014 GitHub, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# TaskList Behavior
#
#= provides tasklist:enabled
#= provides tasklist:disabled
#= provides tasklist:change
#= provides tasklist:changed
#
#
# Enables Task List update behavior.
#
# ### Example Markup
#
# <div class="js-task-list-container">
# <ul class="task-list">
# <li class="task-list-item">
# <input type="checkbox" class="js-task-list-item-checkbox" disabled />
# text
# </li>
# </ul>
# <form>
# <textarea class="js-task-list-field">- [ ] text</textarea>
# </form>
# </div>
#
# ### Specification
#
# TaskLists MUST be contained in a `(div).js-task-list-container`.
#
# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
#
# Task list items MUST match `(input).task-list-item-checkbox` and MUST be
# `disabled` by default.
#
# TaskLists MUST have a `(textarea).js-task-list-field` form element whose
# `value` attribute is the source (Markdown) to be udpated. The source MUST
# follow the syntax guidelines.
#
# TaskList updates trigger `tasklist:change` events. If the change is
# successful, `tasklist:changed` is fired. The change can be canceled.
#
# jQuery is required.
#
# ### Methods
#
# `.taskList('enable')` or `.taskList()`
#
# Enables TaskList updates for the container.
#
# `.taskList('disable')`
#
# Disables TaskList updates for the container.
#
## ### Events
#
# `tasklist:enabled`
#
# Fired when the TaskList is enabled.
#
# * **Synchronicity** Sync
# * **Bubbles** Yes
# * **Cancelable** No
# * **Target** `.js-task-list-container`
#
# `tasklist:disabled`
#
# Fired when the TaskList is disabled.
#
# * **Synchronicity** Sync
# * **Bubbles** Yes
# * **Cancelable** No
# * **Target** `.js-task-list-container`
#
# `tasklist:change`
#
# Fired before the TaskList item change takes affect.
#
# * **Synchronicity** Sync
# * **Bubbles** Yes
# * **Cancelable** Yes
# * **Target** `.js-task-list-field`
#
# `tasklist:changed`
#
# Fired once the TaskList item change has taken affect.
#
# * **Synchronicity** Sync
# * **Bubbles** Yes
# * **Cancelable** No
# * **Target** `.js-task-list-field`
#
# ### NOTE
#
# Task list checkboxes are rendered as disabled by default because rendered
# user content is cached without regard for the viewer.
incomplete = "[ ]"
complete = "[x]"
# Escapes the String for regular expression matching.
escapePattern = (str) ->
str.
replace(/([\[\]])/g, "\\$1"). # escape square brackets
replace(/\s/, "\\s"). # match all white space
replace("x", "[xX]") # match all cases
incompletePattern = ///
#{escapePattern(incomplete)}
///
completePattern = ///
#{escapePattern(complete)}
///
# Pattern used to identify all task list items.
# Useful when you need iterate over all items.
itemPattern = ///
^
(?: # prefix, consisting of
\s* # optional leading whitespace
(?:>\s*)* # zero or more blockquotes
(?:[-+*]|(?:\d+\.)) # list item indicator
)
\s* # optional whitespace prefix
( # checkbox
#{escapePattern(complete)}|
#{escapePattern(incomplete)}
)
\s+ # is followed by whitespace
(?!
\(.*?\) # is not part of a [foo](url) link
)
(?= # and is followed by zero or more links
(?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
(?:[^\[]|$) # and either a non-link or the end of the string
)
///
# Used to filter out code fences from the source for comparison only.
# http://rubular.com/r/x5EwZVrloI
# Modified slightly due to issues with JS
codeFencesPattern = ///
^`{3} # ```
(?:\s*\w+)? # followed by optional language
[\S\s] # whitespace
.* # code
[\S\s] # whitespace
^`{3}$ # ```
///mg
# Used to filter out potential mismatches (items not in lists).
# http://rubular.com/r/OInl6CiePy
itemsInParasPattern = ///
^
(
#{escapePattern(complete)}|
#{escapePattern(incomplete)}
)
.+
$
///g
# Given the source text, updates the appropriate task list item to match the
# given checked value.
#
# Returns the updated String text.
updateTaskListItem = (source, itemIndex, checked) ->
clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
replace(itemsInParasPattern, '').split("\n")
index = 0
result = for line in source.split("\n")
if line in clean && line.match(itemPattern)
index += 1
if index == itemIndex
line =
if checked
line.replace(incompletePattern, complete)
else
line.replace(completePattern, incomplete)
line
result.join("\n")
# Updates the $field value to reflect the state of $item.
# Triggers the `tasklist:change` event before the value has changed, and fires
# a `tasklist:changed` event once the value has changed.
updateTaskList = ($item) ->
$container = $item.closest '.js-task-list-container'
$field = $container.find '.js-task-list-field'
index = 1 + $container.find('.task-list-item-checkbox').index($item)
checked = $item.prop 'checked'
event = $.Event 'tasklist:change'
$field.trigger event, [index, checked]
unless event.isDefaultPrevented()
$field.val updateTaskListItem($field.val(), index, checked)
$field.trigger 'change'
$field.trigger 'tasklist:changed', [index, checked]
# When the task list item checkbox is updated, submit the change
$(document).on 'change', '.task-list-item-checkbox', ->
updateTaskList $(this)
# Enables TaskList item changes.
enableTaskList = ($container) ->
if $container.find('.js-task-list-field').length > 0
$container.
find('.task-list-item').addClass('enabled').
find('.task-list-item-checkbox').attr('disabled', null)
$container.addClass('is-task-list-enabled').
trigger 'tasklist:enabled'
# Enables a collection of TaskList containers.
enableTaskLists = ($containers) ->
for container in $containers
enableTaskList $(container)
# Disable TaskList item changes.
disableTaskList = ($container) ->
$container.
find('.task-list-item').removeClass('enabled').
find('.task-list-item-checkbox').attr('disabled', 'disabled')
$container.removeClass('is-task-list-enabled').
trigger 'tasklist:disabled'
# Disables a collection of TaskList containers.
disableTaskLists = ($containers) ->
for container in $containers
disableTaskList $(container)
$.fn.taskList = (method) ->
$container = $(this).closest('.js-task-list-container')
methods =
enable: enableTaskLists
disable: disableTaskLists
methods[method || 'enable']($container)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment