diff options
author | Eric Eastwood <contact@ericeastwood.com> | 2017-02-28 07:44:34 +0300 |
---|---|---|
committer | Eric Eastwood <contact@ericeastwood.com> | 2017-03-06 21:54:46 +0300 |
commit | e6fc0207cb37666cdf606c03641f2afbb5646213 (patch) | |
tree | 8a596255b77da1b3e8a5a6349a80fb72aa8ac678 /lib | |
parent | f911b948e9b376e65f5d5bf7e6d09b32e3c995c8 (diff) |
Use native unicode emojis
- gl_emoji for falling back to image/css-sprite when the browser
doesn't support an emoji
- Markdown rendering (Banzai filter)
- Autocomplete
- Award emoji menu
- Perceived perf
- Immediate response because we now build client-side
- Update `digests.json` generation in gemojione rake task to be more
useful and include `unicodeVersion`
MR: !9437
See issues
- #26371
- #27250
- #22474
Diffstat (limited to 'lib')
-rw-r--r-- | lib/banzai/filter/emoji_filter.rb | 64 | ||||
-rw-r--r-- | lib/gitlab/award_emoji.rb | 73 | ||||
-rw-r--r-- | lib/gitlab/emoji.rb | 25 | ||||
-rw-r--r-- | lib/gitlab/gon_helper.rb | 1 | ||||
-rw-r--r-- | lib/tasks/gemojione.rake | 90 |
5 files changed, 102 insertions, 151 deletions
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index a8c1ca0c60a..d6138816e70 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -17,8 +17,8 @@ module Banzai next unless content.include?(':') || node.text.match(emoji_unicode_pattern) - html = emoji_name_image_filter(content) - html = emoji_unicode_image_filter(html) + html = emoji_unicode_element_unicode_filter(content) + html = emoji_name_element_unicode_filter(html) next if html == content @@ -27,33 +27,30 @@ module Banzai doc end - # Replace :emoji: with corresponding images. + # Replace :emoji: with corresponding gl-emoji unicode. # # text - String text to replace :emoji: in. # - # Returns a String with :emoji: replaced with images. - def emoji_name_image_filter(text) + # Returns a String with :emoji: replaced with gl-emoji unicode. + def emoji_name_element_unicode_filter(text) text.gsub(emoji_pattern) do |match| name = $1 - emoji_image_tag(name, emoji_url(name)) + Gitlab::Emoji.gl_emoji_tag(name) end end - # Replace unicode emoji with corresponding images if they exist. + # Replace unicode emoji with corresponding gl-emoji unicode. # # text - String text to replace unicode emoji in. # - # Returns a String with unicode emoji replaced with images. - def emoji_unicode_image_filter(text) + # Returns a String with unicode emoji replaced with gl-emoji unicode. + def emoji_unicode_element_unicode_filter(text) text.gsub(emoji_unicode_pattern) do |moji| - emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji)) + emoji_info = Gitlab::Emoji.emojis_by_moji[moji] + Gitlab::Emoji.gl_emoji_tag(emoji_info['name']) end end - def emoji_image_tag(emoji_name, emoji_url) - "<img class='emoji' title=':#{emoji_name}:' alt=':#{emoji_name}:' src='#{emoji_url}' height='20' width='20' align='absmiddle' />" - end - # Build a regexp that matches all valid :emoji: names. def self.emoji_pattern @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ @@ -66,52 +63,13 @@ module Banzai private - def emoji_url(name) - emoji_path = emoji_filename(name) - - if context[:asset_host] - # Asset host is specified. - url_to_image(emoji_path) - elsif context[:asset_root] - # Gitlab url is specified - File.join(context[:asset_root], url_to_image(emoji_path)) - else - # All other cases - url_to_image(emoji_path) - end - end - - def emoji_unicode_url(moji) - emoji_unicode_path = emoji_unicode_filename(moji) - - if context[:asset_host] - url_to_image(emoji_unicode_path) - elsif context[:asset_root] - File.join(context[:asset_root], url_to_image(emoji_unicode_path)) - else - url_to_image(emoji_unicode_path) - end - end - - def url_to_image(image) - ActionController::Base.helpers.url_to_image(image) - end - def emoji_pattern self.class.emoji_pattern end - def emoji_filename(name) - "#{Gitlab::Emoji.emoji_filename(name)}.png" - end - def emoji_unicode_pattern self.class.emoji_unicode_pattern end - - def emoji_unicode_filename(name) - "#{Gitlab::Emoji.emoji_unicode_filename(name)}.png" - end end end end diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb index 7555326d384..6f104850d34 100644 --- a/lib/gitlab/award_emoji.rb +++ b/lib/gitlab/award_emoji.rb @@ -1,84 +1,15 @@ module Gitlab class AwardEmoji - CATEGORIES = { - objects: "Objects", - travel: "Travel", - symbols: "Symbols", - nature: "Nature", - people: "People", - activity: "Activity", - flags: "Flags", - food: "Food" - }.with_indifferent_access - def self.normalize_emoji_name(name) aliases[name] || name end - def self.emoji_by_category - unless @emoji_by_category - @emoji_by_category = Hash.new { |h, key| h[key] = [] } - - emojis.each do |emoji_name, data| - data["name"] = emoji_name - - # Skip Fitzpatrick(tone) modifiers - next if data["category"] == "modifier" - - category = data["category"] - - @emoji_by_category[category] << data - end - - @emoji_by_category = @emoji_by_category.sort.to_h - end - - @emoji_by_category - end - def self.emojis - @emojis ||= - begin - json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' ) - JSON.parse(File.read(json_path)) - end + Gitlab::Emoji.emojis end def self.aliases - @aliases ||= - begin - json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json') - JSON.parse(File.read(json_path)) - end - end - - # Returns an Array of Emoji names and their asset URLs. - def self.urls - @urls ||= begin - path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') - # Construct the full asset path ourselves because - # ActionView::Helpers::AssetUrlHelper.asset_url is slow for hundreds - # of entries since it has to do a lot of extra work (e.g. regexps). - prefix = Gitlab::Application.config.assets.prefix - digest = Gitlab::Application.config.assets.digest - base = - if defined?(Gitlab::Application.config.relative_url_root) && Gitlab::Application.config.relative_url_root - Gitlab::Application.config.relative_url_root - else - '' - end - - JSON.parse(File.read(path)).map do |hash| - fname = - if digest - "#{hash['unicode']}-#{hash['digest']}" - else - hash['unicode'] - end - - { name: hash['name'], path: File.join(base, prefix, "#{fname}.png") } - end - end + Gitlab::Emoji.emojis_aliases end end end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index bbbca8acc40..35712c214fc 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -1,7 +1,9 @@ module Gitlab module Emoji extend self - + @emoji_unicode_version = JSON.parse(File.read(File.absolute_path(File.dirname(__FILE__) + '/../../node_modules/emoji-unicode-version/emoji-unicode-version-map.json'))) + @emoji_aliases = JSON.parse(File.read(File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json'))) + def emojis Gemojione.index.instance_variable_get(:@emoji_by_name) end @@ -18,6 +20,10 @@ module Gitlab emojis.keys end + def emojis_aliases + @emoji_aliases + end + def emoji_filename(name) emojis[name]["unicode"] end @@ -25,5 +31,22 @@ module Gitlab def emoji_unicode_filename(moji) emojis_by_moji[moji]["unicode"] end + + def emoji_unicode_version(name) + @emoji_unicode_version[name] + end + + def emoji_image_tag(name, src) + "<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{src}' height='20' width='20' align='absmiddle' />" + end + + # CSS sprite fallback takes precedence over image fallback + def gl_emoji_tag(name, sprite: false, force_fallback: false) + emoji_name = emojis_aliases[name] || name + emoji_info = emojis[emoji_name] + emoji_fallback_image_source = ActionController::Base.helpers.asset_url("emoji/#{emoji_info['name']}.png") + emoji_fallback_sprite_class = "emoji-#{emoji_name}" + "<gl-emoji #{force_fallback && sprite ? "class='emoji-icon #{emoji_fallback_sprite_class}'" : ""} data-name='#{emoji_name}' data-fallback-src='#{emoji_fallback_image_source}' #{sprite ? "data-fallback-sprite-class='#{emoji_fallback_sprite_class}'" : ""} data-unicode-version='#{emoji_unicode_version(emoji_name)}'>#{force_fallback && sprite === false ? emoji_image_tag(emoji_name, emoji_fallback_image_source) : emoji_info['moji']}</gl-emoji>" + end end end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 9c384069661..57fc4eb7c18 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -7,7 +7,6 @@ module Gitlab gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.shortcuts_path = help_page_path('shortcuts') gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - gon.award_menu_url = emojis_path gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css') gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js') diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake index 993112aee3b..b8a6ba7e77c 100644 --- a/lib/tasks/gemojione.rake +++ b/lib/tasks/gemojione.rake @@ -5,29 +5,29 @@ namespace :gemojione do require 'json' dir = Gemojione.images_path - digests = [] - aliases = Hash.new { |hash, key| hash[key] = [] } - aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json') - - JSON.parse(File.read(aliases_path)).each do |alias_name, real_name| - aliases[real_name] << alias_name - end - - Gitlab::AwardEmoji.emojis.map do |name, emoji_hash| - fpath = File.join(dir, "#{emoji_hash['unicode']}.png") - digest = Digest::SHA256.file(fpath).hexdigest - - digests << { name: name, unicode: emoji_hash['unicode'], digest: digest } + resultant_emoji_map = {} + + Gitlab::Emoji.emojis.map do |name, emoji_hash| + # Ignore aliases + unless Gitlab::Emoji.emojis_aliases.key?(name) + fpath = File.join(dir, "#{emoji_hash['unicode']}.png") + hash_digest = Digest::SHA256.file(fpath).hexdigest + + entry = { + category: emoji_hash['category'], + moji: emoji_hash['moji'], + unicodeVersion: Gitlab::Emoji.emoji_unicode_version(name), + digest: hash_digest, + } - aliases[name].each do |alias_name| - digests << { name: alias_name, unicode: emoji_hash['unicode'], digest: digest } + resultant_emoji_map[name] = entry end end out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') File.open(out, 'w') do |handle| - handle.write(JSON.pretty_generate(digests)) + handle.write(JSON.pretty_generate(resultant_emoji_map)) end end @@ -55,21 +55,42 @@ namespace :gemojione do SPRITESHEET_WIDTH = 860 SPRITESHEET_HEIGHT = 840 + # Setup a map to rename image files + emoji_uncicode_string_to_name_map = {} + Gitlab::Emoji.emojis.map do |name, emoji_hash| + # Ignore aliases + unless Gitlab::Emoji.emojis_aliases.key?(name) + emoji_uncicode_string_to_name_map[emoji_hash['unicode']] = name + end + end + + # Copy the Gemojione assets to the temporary folder for renaming + emoji_dir = "app/assets/images/emoji" + FileUtils.rm_rf(emoji_dir) + FileUtils.mkdir_p(emoji_dir, mode: 0700) + FileUtils.cp_r(File.join(Gemojione.images_path, '.'), emoji_dir) + Dir.chdir(emoji_dir) do + Dir["**/*.png"].each do |png| + image_path = File.join(Dir.pwd, png) + rename_to_named_emoji_image!(emoji_uncicode_string_to_name_map, image_path) + end + end + Dir.mktmpdir do |tmpdir| - # Copy the Gemojione assets to the temporary folder for resizing - FileUtils.cp_r(Gemojione.images_path, tmpdir) + FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir) Dir.chdir(tmpdir) do Dir["**/*.png"].each do |png| - resize!(File.join(tmpdir, png), SIZE) + tmp_image_path = File.join(tmpdir, png) + resize!(tmp_image_path, SIZE) end end - style_path = Rails.root.join(*%w(app assets stylesheets pages emojis.scss)) + style_path = Rails.root.join(*%w(app assets stylesheets framework emoji-sprites.scss)) # Combine the resized assets into a packed sprite and re-generate the SCSS SpriteFactory.cssurl = "image-url('$IMAGE')" - SpriteFactory.run!(File.join(tmpdir, 'png'), { + SpriteFactory.run!(tmpdir, { output_style: style_path, output_image: "app/assets/images/emoji.png", selector: '.emoji-', @@ -83,7 +104,7 @@ namespace :gemojione do # let's simplify it system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path})) system(%Q(sed -i '' "s/ no-repeat//" #{style_path})) - system(%Q(sed -i '' "s/ 0px/ 0/" #{style_path})) + system(%Q(sed -i '' "s/ 0px/ 0/g" #{style_path})) # Append a generic rule that applies to all Emojis File.open(style_path, 'a') do |f| @@ -92,6 +113,8 @@ namespace :gemojione do .emoji-icon { background-image: image-url('emoji.png'); background-repeat: no-repeat; + color: transparent; + text-indent: -99em; height: #{SIZE}px; width: #{SIZE}px; @@ -112,16 +135,17 @@ namespace :gemojione do # Now do it again but for Retina Dir.mktmpdir do |tmpdir| # Copy the Gemojione assets to the temporary folder for resizing - FileUtils.cp_r(Gemojione.images_path, tmpdir) + FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir) Dir.chdir(tmpdir) do Dir["**/*.png"].each do |png| - resize!(File.join(tmpdir, png), RETINA) + tmp_image_path = File.join(tmpdir, png) + resize!(tmp_image_path, RETINA) end end # Combine the resized assets into a packed sprite and re-generate the SCSS - SpriteFactory.run!(File.join(tmpdir), { + SpriteFactory.run!(tmpdir, { output_image: "app/assets/images/emoji@2x.png", style: false, nocomments: true, @@ -155,4 +179,20 @@ namespace :gemojione do image.write(image_path) { self.quality = 100 } image.destroy! end + + EMOJI_IMAGE_PATH_RE = /(.*?)(([0-9a-f]-?)+)\.png$/i + def rename_to_named_emoji_image!(emoji_uncicode_string_to_name_map, image_path) + # Rename file from unicode to emoji name + matches = EMOJI_IMAGE_PATH_RE.match(image_path) + preceding_path = matches[1] + unicode_string = matches[2] + name = emoji_uncicode_string_to_name_map[unicode_string] + if name + new_png_path = File.join(preceding_path, "#{name}.png") + FileUtils.mv(image_path, new_png_path) + new_png_path + else + puts "Warning: emoji_uncicode_string_to_name_map missing entry for #{unicode_string}. Full path: #{image_path}" + end + end end |