Commit 37120872 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'improve_emoji_picker' into 'master'

Improve emoji picker

This request uses an emoji image sprite to show big set of emojis. The sprite has been made based one gemojione gem images. The categories are retrieved from https://github.com/jonathanwiesel/gemojione/blob/master/config/index.json as well.

Some categories are pretty poor, it can be extended in the future.

It's a first step on the way to improve picker.

The first implementation was using https://github.com/iamcal/emoji-data project, but there was huge incompatibility with gemojione emojis set and I had to drop that solution.

![Screenshot_2015-12-22_23.26.27](/uploads/7d323eb0586204ac92915b41233b97ec/Screenshot_2015-12-22_23.26.27.png)


See merge request !2172
parents e68ae9c6 44d8714f
class @AwardsHandler class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
$(".add-award").click (event)->
event.stopPropagation()
event.preventDefault()
$(".emoji-menu").show()
$("html").click ->
if !$(event.target).closest(".emoji-menu").length
if $(".emoji-menu").is(":visible")
$(".emoji-menu").hide()
addAward: (emoji) -> addAward: (emoji) ->
emoji = @normilizeEmojiName(emoji) emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, => @postEmoji emoji, =>
@addAwardToEmojiBar(emoji) @addAwardToEmojiBar(emoji)
$(".emoji-menu").hide()
addAwardToEmojiBar: (emoji, custom_path = '') -> addAwardToEmojiBar: (emoji) ->
emoji = @normilizeEmojiName(emoji) emoji = @normilizeEmojiName(emoji)
if @exist(emoji) if @exist(emoji)
if @isActive(emoji) if @isActive(emoji)
...@@ -17,7 +28,7 @@ class @AwardsHandler ...@@ -17,7 +28,7 @@ class @AwardsHandler
counter.parent().addClass("active") counter.parent().addClass("active")
@addMeToAuthorList(emoji) @addMeToAuthorList(emoji)
else else
@createEmoji(emoji, custom_path) @createEmoji(emoji)
exist: (emoji) -> exist: (emoji) ->
@findEmojiIcon(emoji).length > 0 @findEmojiIcon(emoji).length > 0
...@@ -54,31 +65,29 @@ class @AwardsHandler ...@@ -54,31 +65,29 @@ class @AwardsHandler
resetTooltip: (award) -> resetTooltip: (award) ->
award.tooltip("destroy") award.tooltip("destroy")
# "destroy" call is asynchronous, this is why we need to set timeout. # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (-> setTimeout (->
award.tooltip() award.tooltip()
), 200 ), 200
createEmoji: (emoji, custom_path) -> createEmoji: (emoji) ->
emojiCssClass = @resolveNameToCssClass(emoji)
nodes = [] nodes = []
nodes.push("<div class='award active' title='me'>") nodes.push("<div class='award active' title='me'>")
nodes.push("<div class='icon' data-emoji='" + emoji + "'>") nodes.push("<div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div>")
nodes.push(@getImage(emoji, custom_path)) nodes.push("<div class='counter'>1</div>")
nodes.push("</div>") nodes.push("</div>")
nodes.push("<div class='counter'>1")
nodes.push("</div></div>")
$(".awards-controls").before(nodes.join("\n")) emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji)
$(".award").tooltip() $(".award").tooltip()
getImage: (emoji, custom_path) -> resolveNameToCssClass: (emoji) ->
if custom_path unicodeName = $(".emoji-menu-content [data-emoji='?']".replace("?", emoji)).data("unicode-name")
$("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
else
$("li[data-emoji='" + emoji + "']").html()
"emoji-" + unicodeName
postEmoji: (emoji, callback) -> postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: { $.post @post_emoji_url, { note: {
...@@ -90,7 +99,7 @@ class @AwardsHandler ...@@ -90,7 +99,7 @@ class @AwardsHandler
callback.call() callback.call()
findEmojiIcon: (emoji) -> findEmojiIcon: (emoji) ->
$(".icon[data-emoji='" + emoji + "']") $(".award [data-emoji='" + emoji + "']")
scrollToAwards: -> scrollToAwards: ->
$('body, html').animate({ $('body, html').animate({
......
...@@ -127,7 +127,7 @@ class @Notes ...@@ -127,7 +127,7 @@ class @Notes
@initTaskList() @initTaskList()
if note.award if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path) awards_handler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards() awards_handler.scrollToAwards()
### ###
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
@include clearfix; @include clearfix;
line-height: 34px; line-height: 34px;
.emoji-icon {
width: 20px;
height: 20px;
margin: 7px 0 0 5px;
}
.award { .award {
@include border-radius(5px); @include border-radius(5px);
...@@ -40,6 +46,7 @@ ...@@ -40,6 +46,7 @@
} }
.awards-controls { .awards-controls {
position: relative;
margin-left: 10px; margin-left: 10px;
float: left; float: left;
...@@ -55,32 +62,60 @@ ...@@ -55,32 +62,60 @@
} }
} }
.awards-menu { .emoji-menu{
padding: $gl-padding; position: absolute;
min-width: 214px; top: 100%;
left: 0;
> li { z-index: 1000;
cursor: pointer; display: none;
width: 30px; float: left;
height: 30px; min-width: 160px;
text-align: center; padding: 5px 0;
@include border-radius(5px); margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
box-shadow: 0 6px 12px rgba(0,0,0,.175);
.emoji-menu-content {
padding: $gl-padding;
width: 300px;
height: 300px;
overflow-y: scroll;
h5 {
clear: left;
}
img { ul {
margin-bottom: 2px; list-style-type: none;
margin-left: -20px;
margin-bottom: 20px;
overflow: auto;
} }
&:hover { li {
background-color: #ccc; cursor: pointer;
width: 30px;
height: 30px;
text-align: center;
float: left;
margin: 3px;
list-decorate: none;
@include border-radius(5px);
&:hover {
background-color: #ccc;
}
} }
} }
} }
} }
.awards-menu{
li {
float: left;
margin: 3px;
}
}
} }
This diff is collapsed.
...@@ -139,7 +139,6 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -139,7 +139,6 @@ class Projects::NotesController < Projects::ApplicationController
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
html: note_to_html(note), html: note_to_html(note),
award: note.is_award, award: note.is_award,
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
note: note.note, note: note.note,
discussion_html: note_to_discussion_html(note), discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note) discussion_with_diff_html: note_to_discussion_with_diff_html(note)
......
...@@ -94,11 +94,13 @@ module IssuesHelper ...@@ -94,11 +94,13 @@ module IssuesHelper
end.sort.to_sentence(last_word_connector: ', or ') end.sort.to_sentence(last_word_connector: ', or ')
end end
def url_to_emoji(name) def emoji_icon(name, unicode = nil)
emoji_path = ::AwardEmoji.path_to_emoji_image(name) unicode ||= Emoji.emoji_filename(name)
url_to_image(emoji_path)
rescue StandardError content_tag :div, "",
"" class: "icon emoji-icon emoji-#{unicode}",
"data-emoji" => name,
"data-unicode-name" => unicode
end end
def emoji_author_list(notes, current_user) def emoji_author_list(notes, current_user)
...@@ -109,10 +111,6 @@ module IssuesHelper ...@@ -109,10 +111,6 @@ module IssuesHelper
list.join(", ") list.join(", ")
end end
def emoji_list
::AwardEmoji::EMOJI_LIST
end
def note_active_class(notes, current_user) def note_active_class(notes, current_user)
if current_user && notes.pluck(:author_id).include?(current_user.id) if current_user && notes.pluck(:author_id).include?(current_user.id)
"active" "active"
......
.awards.votes-block .awards.votes-block
- votable.notes.awards.grouped_awards.each do |emoji, notes| - votable.notes.awards.grouped_awards.each do |emoji, notes|
.award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)} .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)}
.icon{"data-emoji" => "#{emoji}"} = emoji_icon(emoji)
= image_tag url_to_emoji(emoji), height: "20px", width: "20px"
.counter .counter
= notes.count = notes.count
- if current_user - if current_user
.dropdown.awards-controls .awards-controls
%a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
= icon('smile-o') = icon('smile-o')
%ul.dropdown-menu.awards-menu .emoji-menu
- emoji_list.each do |emoji| .emoji-menu-content
%li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" - AwardEmoji.emoji_by_category.each do |category, emojis|
%h5= AwardEmoji::CATEGORIES[category]
%ul
- emojis.each do |emoji|
%li
= emoji_icon(emoji["name"], emoji["unicode"])
- if current_user - if current_user
:coffeescript :coffeescript
...@@ -20,10 +24,16 @@ ...@@ -20,10 +24,16 @@
noteable_type = "#{votable.class.name.underscore}" noteable_type = "#{votable.class.name.underscore}"
noteable_id = "#{votable.id}" noteable_id = "#{votable.id}"
aliases = #{AwardEmoji::ALIASES.to_json} aliases = #{AwardEmoji::ALIASES.to_json}
window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id, aliases)
$(".awards-menu li").click (e)-> window.awards_handler = new AwardsHandler(
emoji = $(this).data("emoji") post_emoji_url,
noteable_type,
noteable_id,
aliases
)
$(".emoji-menu-content li").click (e)->
emoji = $(this).find(".emoji-icon").data("emoji")
awards_handler.addAward(emoji) awards_handler.addAward(emoji)
$(".awards").on "click", ".award", (e)-> $(".awards").on "click", ".award", (e)->
...@@ -31,3 +41,5 @@ ...@@ -31,3 +41,5 @@
awards_handler.addAward(emoji) awards_handler.addAward(emoji)
$(".award").tooltip() $(".award").tooltip()
$(".emoji-menu-content").niceScroll({cursorwidth: "7px", autohidemode: false})
...@@ -13,6 +13,11 @@ Feature: Award Emoji ...@@ -13,6 +13,11 @@ Feature: Award Emoji
Then I have award added Then I have award added
And I can remove it by clicking to icon And I can remove it by clicking to icon
@javascript
Scenario: I can see the list of emoji categories
Given I click to emoji-picker
Then I can see the activity and food categories
@javascript @javascript
Scenario: I add award emoji using regular comment Scenario: I add award emoji using regular comment
Given I leave comment with a single emoji Given I leave comment with a single emoji
......
...@@ -15,8 +15,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -15,8 +15,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end end
step 'I click to emoji in the picker' do step 'I click to emoji in the picker' do
page.within '.awards-menu' do page.within '.emoji-menu' do
page.first('img').click page.first('.emoji-icon').click
end end
end end
...@@ -27,6 +27,13 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -27,6 +27,13 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end end
end end
step 'I can see the activity and food categories' do
page.within '.emoji-menu' do
expect(page).to_not have_selector 'Activity'
expect(page).to_not have_selector 'Food'
end
end
step 'I have award added' do step 'I have award added' do
page.within '.awards' do page.within '.awards' do
expect(page).to have_selector '.award' expect(page).to have_selector '.award'
......
class AwardEmoji class AwardEmoji
EMOJI_LIST = [
"+1", "-1", "100", "blush", "heart", "smile", "rage",
"beers", "disappointed", "ok_hand",
"helicopter", "shit", "airplane", "alarm_clock",
"ambulance", "anguished", "two_hearts", "wink"
]
ALIASES = { ALIASES = {
pout: "rage", pout: "rage",
satisfied: "laughing", satisfied: "laughing",
...@@ -37,11 +30,41 @@ class AwardEmoji ...@@ -37,11 +30,41 @@ class AwardEmoji
squirrel: "shipit" squirrel: "shipit"
}.with_indifferent_access }.with_indifferent_access
def self.path_to_emoji_image(name) CATEGORIES = {
"emoji/#{Emoji.emoji_filename(name)}.png" other: "Other",
end objects: "Objects",
places: "Places",
travel_places: "Travel",
emoticons: "Emoticons",
objects_symbols: "Symbols",
nature: "Nature",
celebration: "Celebration",
people: "People",
activity: "Activity",
flags: "Flags",
food_drink: "Food"
}.with_indifferent_access
def self.normilize_emoji_name(name) def self.normilize_emoji_name(name)
ALIASES[name] || name ALIASES[name] || name
end end
def self.emoji_by_category
unless @emoji_by_category
@emoji_by_category = {}
emojis_added = []
Emoji.emojis.each do |emoji_name, data|
next if emojis_added.include?(data["name"])
emojis_added << data["name"]
@emoji_by_category[data["category"]] ||= []
@emoji_by_category[data["category"]] << data
end
@emoji_by_category = @emoji_by_category.sort.to_h
end
@emoji_by_category
end
end end
...@@ -127,18 +127,6 @@ describe IssuesHelper do ...@@ -127,18 +127,6 @@ describe IssuesHelper do
it { is_expected.to eq("!1, !2, or !3") } it { is_expected.to eq("!1, !2, or !3") }
end end
describe "#url_to_emoji" do
it "returns url" do
expect(url_to_emoji("smile")).to include("emoji/1F604.png")
end
end
describe "#emoji_list" do
it "returns url" do
expect(emoji_list).to be_kind_of(Array)
end
end
describe "#note_active_class" do describe "#note_active_class" do
before do before do
@note = create :note @note = create :note
......
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