diff options
author | Robert Speicher <rspeicher@gmail.com> | 2015-04-16 17:38:01 -0400 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2015-04-21 12:53:42 -0400 |
commit | 4ced630fed7cc057e66f2c795ebca17a5d6a22e6 (patch) | |
tree | c31a363e286130b18be335366a927f13c3aca782 | |
parent | 837dc328bf1632e80de4c288a13eb22661177ca5 (diff) | |
download | gitlab-ce-4ced630fed7cc057e66f2c795ebca17a5d6a22e6.tar.gz |
Add Gitlab::Markdown::EmojiFilter
-rw-r--r-- | lib/gitlab/markdown/emoji_filter.rb | 79 | ||||
-rw-r--r-- | spec/lib/gitlab/markdown/emoji_filter_spec.rb | 97 |
2 files changed, 176 insertions, 0 deletions
diff --git a/lib/gitlab/markdown/emoji_filter.rb b/lib/gitlab/markdown/emoji_filter.rb new file mode 100644 index 00000000000..e239f766844 --- /dev/null +++ b/lib/gitlab/markdown/emoji_filter.rb @@ -0,0 +1,79 @@ +require 'gitlab_emoji' +require 'html/pipeline/filter' +require 'action_controller' + +module Gitlab + module Markdown + # HTML filter that replaces :emoji: with images. + # + # Based on HTML::Pipeline::EmojiFilter + # + # Context options: + # :asset_root + # :asset_host + class EmojiFilter < HTML::Pipeline::Filter + IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set + + def call + doc.search('text()').each do |node| + content = node.to_html + next unless content.include?(':') + next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) + + html = emoji_image_filter(content) + + next if html == content + + node.replace(html) + end + + doc + end + + # Replace :emoji: with corresponding images. + # + # text - String text to replace :emoji: in. + # + # Returns a String with :emoji: replaced with images. + def emoji_image_filter(text) + text.gsub(emoji_pattern) do |match| + name = $1 + "<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{emoji_url(name)}' height='20' width='20' align='absmiddle' />" + end + end + + private + + def emoji_url(name) + emoji_path = "emoji/#{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 url_to_image(image) + ActionController::Base.helpers.url_to_image(image) + end + + # Build a regexp that matches all valid :emoji: names. + def self.emoji_pattern + @emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ + end + + def emoji_pattern + self.class.emoji_pattern + end + + def emoji_filename(name) + "#{Emoji.emoji_filename(name)}.png" + end + end + end +end diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb new file mode 100644 index 00000000000..18d55c4818f --- /dev/null +++ b/spec/lib/gitlab/markdown/emoji_filter_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +module Gitlab::Markdown + describe EmojiFilter do + def filter(html, contexts = {}) + described_class.call(html, contexts) + end + + before do + ActionController::Base.asset_host = 'https://foo.com' + end + + it 'replaces supported emoji' do + doc = filter('<p>:heart:</p>') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png' + end + + it 'ignores unsupported emoji' do + exp = act = '<p>:foo:</p>' + doc = filter(act) + expect(doc.to_html).to match Regexp.escape(exp) + end + + it 'correctly encodes the URL' do + doc = filter('<p>:+1:</p>') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png' + end + + it 'matches at the start of a string' do + doc = filter(':+1:') + expect(doc.css('img').size).to eq 1 + end + + it 'matches at the end of a string' do + doc = filter('This gets a :-1:') + expect(doc.css('img').size).to eq 1 + end + + it 'matches with adjacent text' do + doc = filter('+1 (:+1:)') + expect(doc.css('img').size).to eq 1 + end + + it 'matches multiple emoji in a row' do + doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') + expect(doc.css('img').size).to eq 3 + end + + it 'has a title attribute' do + doc = filter(':-1:') + expect(doc.css('img').first.attr('title')).to eq ':-1:' + end + + it 'has an alt attribute' do + doc = filter(':-1:') + expect(doc.css('img').first.attr('alt')).to eq ':-1:' + end + + it 'has an align attribute' do + doc = filter(':8ball:') + expect(doc.css('img').first.attr('align')).to eq 'absmiddle' + end + + it 'has an emoji class' do + doc = filter(':cat:') + expect(doc.css('img').first.attr('class')).to eq 'emoji' + end + + it 'has height and width attributes' do + doc = filter(':dog:') + img = doc.css('img').first + + expect(img.attr('width')).to eq '20' + expect(img.attr('height')).to eq '20' + end + + it 'keeps whitespace intact' do + doc = filter('This deserves a :+1:, big time.') + + expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/) + end + + it 'uses a custom asset_root context' do + root = Gitlab.config.gitlab.url + 'gitlab/root' + + doc = filter(':smile:', asset_root: root) + expect(doc.css('img').first.attr('src')).to start_with(root) + end + + it 'uses a custom asset_host context' do + ActionController::Base.asset_host = 'https://cdn.example.com' + + doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?') + expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') + end + end +end |