Commit 4f8a4aa44c90f84480f5cb541d2d79ce11c7f960
Merge pull request #1384 from tsigo/emoji
Emoji!
5.01 KB
4.95 KB
3.22 KB
3.61 KB
4.64 KB
4.04 KB
3.08 KB
3.77 KB
4.15 KB
4.37 KB
4.62 KB
3.41 KB
4.63 KB
6.9 KB
5.33 KB
3.62 KB
4.37 KB
6.52 KB
3.01 KB
4.92 KB
2.78 KB
5.5 KB
4.98 KB
4.24 KB
3.11 KB
3.1 KB
3.53 KB
2.94 KB
2.87 KB
3.13 KB
3.44 KB
3.44 KB
2.97 KB
3.26 KB
3.26 KB
2.95 KB
3.63 KB
3 KB
3.46 KB
3.12 KB
3.15 KB
3.16 KB
1.37 KB
4.71 KB
6.59 KB
2.87 KB
5.9 KB
3.98 KB
2.95 KB
5.78 KB
4.36 KB
3.87 KB
2.9 KB
3.42 KB
2.25 KB
1.79 KB
4.56 KB
3.82 KB
1.35 KB
5.45 KB
2.4 KB
4.15 KB
5.89 KB
6.24 KB
3.13 KB
2.72 KB
3.72 KB
5.43 KB
5.95 KB
6.44 KB
5.13 KB
2.7 KB
4.75 KB
5.6 KB
6.33 KB
4.61 KB
3.8 KB
4.76 KB
5.28 KB
2.31 KB
3.79 KB
2.3 KB
1.3 KB
4.13 KB
3.66 KB
4.97 KB
3.99 KB
4 KB
5.07 KB
4.73 KB
3.74 KB
5.09 KB
5.91 KB
4.59 KB
3.09 KB
6.39 KB
3.25 KB
6.75 KB
5.02 KB
4.09 KB
6.33 KB
5.81 KB
6.07 KB
8.32 KB
5.02 KB
2.63 KB
4.02 KB
5.81 KB
4.38 KB
4.88 KB
3.75 KB
3.97 KB
1.64 KB
1.96 KB
2.95 KB
4.4 KB
5.99 KB
2.85 KB
3.94 KB
4.38 KB
4.55 KB
5.26 KB
4.4 KB
5.02 KB
4.56 KB
4.18 KB
3.66 KB
5.75 KB
5.85 KB
5.59 KB
6.56 KB
4.23 KB
2.84 KB
2.87 KB
1.64 KB
5.47 KB
7.01 KB
5.74 KB
3.89 KB
3.38 KB
5.13 KB
4.61 KB
4.54 KB
3.49 KB
4.57 KB
4.21 KB
3.75 KB
3.41 KB
6.94 KB
4.09 KB
4.55 KB
2.53 KB
2.53 KB
2.8 KB
2.53 KB
2.79 KB
2.45 KB
2.73 KB
2.78 KB
2.53 KB
2.79 KB
2.43 KB
2.67 KB
2.56 KB
2.78 KB
2.56 KB
2.77 KB
2.52 KB
2.67 KB
2.55 KB
2.75 KB
2.54 KB
2.78 KB
2.43 KB
2.68 KB
4.73 KB
5.57 KB
3.78 KB
3.77 KB
1.65 KB
3.55 KB
2.88 KB
4.21 KB
5.83 KB
3.69 KB
1.67 KB
5.39 KB
5.72 KB
4.77 KB
3.61 KB
6.05 KB
3.98 KB
7.96 KB
4.08 KB
6.97 KB
1.54 KB
6.54 KB
7.44 KB
7.2 KB
7.05 KB
5.61 KB
5.18 KB
2.59 KB
5.98 KB
3.92 KB
5.52 KB
5.57 KB
6.53 KB
6.09 KB
5.29 KB
1.23 KB
1.91 KB
5.21 KB
5.67 KB
3.81 KB
4.78 KB
3.65 KB
7.73 KB
4.34 KB
5.33 KB
5.32 KB
2.91 KB
2.58 KB
7.2 KB
5.04 KB
5.56 KB
2.72 KB
4.65 KB
2.97 KB
6.13 KB
5.15 KB
5.81 KB
5.79 KB
4.51 KB
6.97 KB
4.24 KB
3.23 KB
5.09 KB
7.64 KB
6.58 KB
3.55 KB
5.02 KB
3.2 KB
6.83 KB
2.08 KB
4.23 KB
4.65 KB
7 KB
6.87 KB
7.13 KB
5.09 KB
4.56 KB
3.75 KB
3.21 KB
3.92 KB
2.75 KB
4.97 KB
2.63 KB
1.44 KB
1.62 KB
4.2 KB
3.85 KB
5.3 KB
4.7 KB
4.81 KB
1.15 KB
4.81 KB
4.29 KB
4.76 KB
5.43 KB
4.78 KB
7.04 KB
3.03 KB
4.54 KB
5.47 KB
1.12 KB
1.49 KB
6.07 KB
3.92 KB
1.16 KB
3.79 KB
4.75 KB
6.12 KB
5.83 KB
4.18 KB
4.61 KB
5.68 KB
4.37 KB
5.74 KB
3.51 KB
5.98 KB
4.91 KB
3.14 KB
3.35 KB
5.71 KB
4.51 KB
6.55 KB
3.52 KB
4.97 KB
3.1 KB
5.85 KB
3.32 KB
3.52 KB
7.37 KB
6.25 KB
4.71 KB
4.2 KB
6.31 KB
7.05 KB
2.89 KB
5.76 KB
4.74 KB
4.2 KB
4.41 KB
6.55 KB
5.87 KB
6.17 KB
5.7 KB
4.77 KB
1.3 KB
1.02 KB
3.46 KB
5.3 KB
6.06 KB
4.97 KB
4.33 KB
1.12 KB
1.03 KB
5.59 KB
3.5 KB
4.28 KB
3.09 KB
6.93 KB
5.57 KB
3.58 KB
7.05 KB
4.06 KB
5.32 KB
4.64 KB
3.65 KB
5.51 KB
5.79 KB
1.87 KB
6.4 KB
3.22 KB
3.42 KB
5.62 KB
6.03 KB
3.96 KB
6.12 KB
2.86 KB
1.08 KB
340 Bytes
1.38 KB
1.28 KB
197 Bytes
591 Bytes
315 Bytes
4 KB
5.75 KB
8.13 KB
3.96 KB
4.45 KB
2.45 KB
5.69 KB
5.71 KB
4.47 KB
5.77 KB
4.77 KB
5 KB
3.46 KB
4.39 KB
3.43 KB
1.42 KB
5.34 KB
4.5 KB
3.81 KB
3.02 KB
6.48 KB
3.61 KB
2.15 KB
6.45 KB
3.58 KB
6.84 KB
2.81 KB
3.42 KB
3.41 KB
3.97 KB
5.5 KB
3.99 KB
4.82 KB
5.04 KB
6.98 KB
3.39 KB
6.19 KB
7.02 KB
2.76 KB
3.37 KB
4 KB
4.82 KB
6.13 KB
6.64 KB
5.43 KB
5.63 KB
5.55 KB
2.79 KB
4.99 KB
4.53 KB
3.7 KB
3.81 KB
6.03 KB
4.23 KB
6.2 KB
5.44 KB
5.78 KB
3.93 KB
3.33 KB
3.69 KB
5.91 KB
4.8 KB
5.26 KB
4.13 KB
3.7 KB
2.59 KB
3.65 KB
3.3 KB
3.59 KB
4.85 KB
5.64 KB
3.34 KB
5.86 KB
5.8 KB
2.41 KB
2.44 KB
4.63 KB
2.97 KB
3.54 KB
3.23 KB
4.1 KB
4.26 KB
4.47 KB
3.03 KB
5.88 KB
5.2 KB
6.38 KB
4.64 KB
4.35 KB
5.11 KB
5.89 KB
5.4 KB
4.57 KB
8.04 KB
4.83 KB
3.29 KB
3.03 KB
3.32 KB
3.59 KB
4.04 KB
5.74 KB
3.04 KB
5.46 KB
3.44 KB
7.41 KB
5.37 KB
4.86 KB
5.22 KB
4.21 KB
3.46 KB
4.07 KB
4.89 KB
9.29 KB
4.3 KB
7.27 KB
6.47 KB
3.99 KB
3.99 KB
2.12 KB
4.56 KB
4.77 KB
1.9 KB
3.11 KB
1.54 KB
6.48 KB
5.68 KB
3.89 KB
6.27 KB
5.97 KB
3.76 KB
4.73 KB
3.83 KB
5.25 KB
6.59 KB
5.08 KB
4.1 KB
3.69 KB
5.8 KB
5.53 KB
3.43 KB
3.21 KB
6.87 KB
4.96 KB
4.62 KB
5.36 KB
4.11 KB
5.08 KB
3.62 KB
5.89 KB
5.2 KB
1.5 KB
2.12 KB
2.48 KB
3.42 KB
5.64 KB
3.84 KB
5.64 KB
5.41 KB
5.04 KB
4.06 KB
4.49 KB
7.35 KB
6.58 KB
5.84 KB
1.76 KB
7.29 KB
5.18 KB
5.55 KB
6.14 KB
2.76 KB
4.19 KB
4.83 KB
4.33 KB
4.97 KB
3.6 KB
5.94 KB
2.13 KB
3.64 KB
3.93 KB
3.58 KB
4.7 KB
2.5 KB
3.01 KB
2.62 KB
5.06 KB
3.92 KB
2.41 KB
5.78 KB
6.77 KB
4.83 KB
4.35 KB
4.63 KB
4.94 KB
6.14 KB
5.39 KB
4.71 KB
6.47 KB
5.3 KB
5.37 KB
5.86 KB
4.68 KB
4.65 KB
4.9 KB
5.5 KB
4.35 KB
5.15 KB
4.96 KB
3.15 KB
3.01 KB
3.01 KB
3.35 KB
3.11 KB
3.27 KB
6.69 KB
4.64 KB
5.02 KB
4.71 KB
3.31 KB
3.84 KB
4.58 KB
4.1 KB
4.14 KB
4.8 KB
6.06 KB
7.73 KB
4.72 KB
4.19 KB
4.92 KB
3.7 KB
4 KB
1.67 KB
5.54 KB
4.41 KB
4.62 KB
6.01 KB
2.15 KB
5.28 KB
1.06 KB
1.07 KB
1.09 KB
1.24 KB
3.56 KB
5.19 KB
6.03 KB
5.25 KB
6.38 KB
6.42 KB
5.31 KB
3.62 KB
4.18 KB
3.85 KB
1.58 KB
5.33 KB
5.52 KB
3.92 KB
4.19 KB
4.04 KB
5.34 KB
2.98 KB
5.45 KB
4.54 KB
5.28 KB
7.6 KB
6.11 KB
5.11 KB
5.26 KB
5.03 KB
6.02 KB
4.1 KB
6.46 KB
1.89 KB
5.31 KB
3.83 KB
7.6 KB
3.06 KB
3.14 KB
5.57 KB
3.47 KB
4.4 KB
3.74 KB
4.95 KB
3.88 KB
6.12 KB
4.75 KB
5.24 KB
4.15 KB
5.32 KB
5.61 KB
3.75 KB
4.46 KB
6.33 KB
6.68 KB
6.59 KB
5.92 KB
5.24 KB
6.67 KB
2.21 KB
2.98 KB
5.77 KB
4.62 KB
5 KB
4.13 KB
9.13 KB
4.57 KB
4.64 KB
4.69 KB
7.41 KB
3.16 KB
3.7 KB
4.75 KB
4.07 KB
2.37 KB
5.7 KB
4.5 KB
1.85 KB
1.9 KB
2.01 KB
2.11 KB
5.75 KB
5.97 KB
5.66 KB
5.94 KB
7.02 KB
5.18 KB
5.92 KB
2.81 KB
6.5 KB
3.97 KB
5.23 KB
5.5 KB
4.55 KB
5.58 KB
4.76 KB
1.87 KB
4.16 KB
4.91 KB
4.25 KB
1.68 KB
6.79 KB
5.56 KB
2.16 KB
5.84 KB
5.05 KB
2.08 KB
3.43 KB
9.13 KB
3.54 KB
3.97 KB
4.26 KB
4.72 KB
5.93 KB
5.04 KB
5.24 KB
3.74 KB
5.35 KB
7.77 KB
6.41 KB
5.61 KB
3.71 KB
3.82 KB
6.44 KB
6.11 KB
5.13 KB
1016 Bytes
3.84 KB
5.45 KB
4.67 KB
6.37 KB
5.55 KB
4.28 KB
5.31 KB
2.97 KB
5.81 KB
4.31 KB
6.49 KB
4.62 KB
3.66 KB
5.81 KB
5.37 KB
1.95 KB
3.24 KB
5.84 KB
4.38 KB
2.52 KB
3.67 KB
4.95 KB
4.96 KB
3.02 KB
5.91 KB
5.61 KB
6.03 KB
842 Bytes
1.69 KB
4.69 KB
5.61 KB
5.65 KB
3.58 KB
3.7 KB
2.94 KB
5.54 KB
3.46 KB
3.81 KB
4.7 KB
4.75 KB
1.37 KB
2.64 KB
4.72 KB
6.02 KB
4.33 KB
4.79 KB
5.39 KB
4.09 KB
5.71 KB
3.63 KB
4.27 KB
4.57 KB
5.92 KB
5.21 KB
5.12 KB
4.21 KB
3.44 KB
3.48 KB
6.83 KB
7.45 KB
4.43 KB
3.8 KB
3.33 KB
4.01 KB
2.94 KB
3.12 KB
4.32 KB
3.85 KB
2.98 KB
5.05 KB
4.08 KB
5.76 KB
4.63 KB
5.19 KB
5.59 KB
3.47 KB
3.63 KB
6.14 KB
4.56 KB
3.34 KB
3.07 KB
3.81 KB
4.97 KB
4.84 KB
4.9 KB
4.75 KB
6.02 KB
3.34 KB
2.41 KB
5.75 KB
6.36 KB
3.1 KB
5.07 KB
4.66 KB
5.37 KB
4.93 KB
872 Bytes
6.05 KB
6.3 KB
3.99 KB
6.13 KB
5.71 KB
4.82 KB
5.89 KB
4.13 KB
2.45 KB
4.29 KB
1.38 KB
3.41 KB
3.08 KB
5.13 KB
5.87 KB
4.73 KB
6.73 KB
3.98 KB
7.91 KB
3.8 KB
2.71 KB
2 KB
4.31 KB
4.87 KB
5.75 KB
2.18 KB
3.51 KB
1.98 KB
1 | 1 | module GitlabMarkdownHelper |
2 | - # Replaces references (i.e. @abc, #123, !456, ...) in the text with links to | |
3 | - # the appropriate items in GitLab. | |
4 | - # | |
5 | - # text - the source text | |
6 | - # html_options - extra options for the reference links as given to link_to | |
7 | - # | |
8 | - # note: reference links will only be generated if @project is set | |
9 | - # | |
10 | - # see Gitlab::Markdown for details on the supported syntax | |
11 | - def gfm(text, html_options = {}) | |
12 | - return text if text.nil? | |
13 | - return text if @project.nil? | |
14 | - | |
15 | - # Extract pre blocks so they are not altered | |
16 | - # from http://github.github.com/github-flavored-markdown/ | |
17 | - extractions = {} | |
18 | - text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match| | |
19 | - md5 = Digest::MD5.hexdigest(match) | |
20 | - extractions[md5] = match | |
21 | - "{gfm-extraction-#{md5}}" | |
22 | - end | |
23 | - | |
24 | - # TODO: add popups with additional information | |
25 | - | |
26 | - parser = Gitlab::Markdown.new(@project, html_options) | |
27 | - text = parser.parse(text) | |
28 | - | |
29 | - # Insert pre block extractions | |
30 | - text.gsub!(/\{gfm-extraction-(\h{32})\}/) do | |
31 | - extractions[$1] | |
32 | - end | |
33 | - | |
34 | - sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class ) | |
35 | - end | |
2 | + include Gitlab::Markdown | |
36 | 3 | |
37 | 4 | # Use this in places where you would normally use link_to(gfm(...), ...). |
38 | 5 | # | ... | ... |
... | ... | @@ -103,7 +103,7 @@ class Note < ActiveRecord::Base |
103 | 103 | # Returns true if this is an upvote note, |
104 | 104 | # otherwise false is returned |
105 | 105 | def upvote? |
106 | - note =~ /^\+1/ ? true : false | |
106 | + note.start_with?('+1') || note.start_with?(':+1:') | |
107 | 107 | end |
108 | 108 | end |
109 | 109 | # == Schema Information | ... | ... |
1 | 1 | module Gitlab |
2 | 2 | # Custom parser for GitLab-flavored Markdown |
3 | 3 | # |
4 | - # It replaces references in the text with links to the appropriate items in Gitlab. | |
4 | + # It replaces references in the text with links to the appropriate items in | |
5 | + # GitLab. | |
5 | 6 | # |
6 | 7 | # Supported reference formats are: |
7 | 8 | # * @foo for team members |
... | ... | @@ -10,19 +11,20 @@ module Gitlab |
10 | 11 | # * $123 for snippets |
11 | 12 | # * 123456 for commits |
12 | 13 | # |
13 | - # Examples | |
14 | + # It also parses Emoji codes to insert images. See | |
15 | + # http://www.emoji-cheat-sheet.com/ for a list of the supported icons. | |
14 | 16 | # |
15 | - # >> m = Markdown.new(...) | |
17 | + # Examples | |
16 | 18 | # |
17 | - # >> m.parse("Hey @david, can you fix this?") | |
19 | + # >> gfm("Hey @david, can you fix this?") | |
18 | 20 | # => "Hey <a href="/gitlab/team_members/1">@david</a>, can you fix this?" |
19 | 21 | # |
20 | - # >> m.parse("Commit 35d5f7c closes #1234") | |
22 | + # >> gfm("Commit 35d5f7c closes #1234") | |
21 | 23 | # => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>" |
22 | - class Markdown | |
23 | - include Rails.application.routes.url_helpers | |
24 | - include ActionView::Helpers | |
25 | - | |
24 | + # | |
25 | + # >> gfm(":trollface:") | |
26 | + # => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" /> | |
27 | + module Markdown | |
26 | 28 | REFERENCE_PATTERN = %r{ |
27 | 29 | ([^\w&;])? # Prefix (1) |
28 | 30 | ( # Reference (2) |
... | ... | @@ -33,15 +35,52 @@ module Gitlab |
33 | 35 | ([^\w&;])? # Suffix (6) |
34 | 36 | }x.freeze |
35 | 37 | |
38 | + EMOJI_PATTERN = %r{(:(\S+):)}.freeze | |
39 | + | |
36 | 40 | attr_reader :html_options |
37 | 41 | |
38 | - def initialize(project, html_options = {}) | |
39 | - @project = project | |
42 | + # Public: Parse the provided text with GitLab-Flavored Markdown | |
43 | + # | |
44 | + # text - the source text | |
45 | + # html_options - extra options for the reference links as given to link_to | |
46 | + # | |
47 | + # Note: reference links will only be generated if @project is set | |
48 | + def gfm(text, html_options = {}) | |
49 | + return text if text.nil? | |
50 | + return text if @project.nil? | |
51 | + | |
40 | 52 | @html_options = html_options |
53 | + | |
54 | + # Extract pre blocks so they are not altered | |
55 | + # from http://github.github.com/github-flavored-markdown/ | |
56 | + extractions = {} | |
57 | + text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match| | |
58 | + md5 = Digest::MD5.hexdigest(match) | |
59 | + extractions[md5] = match | |
60 | + "{gfm-extraction-#{md5}}" | |
61 | + end | |
62 | + | |
63 | + # TODO: add popups with additional information | |
64 | + | |
65 | + text = parse(text) | |
66 | + | |
67 | + # Insert pre block extractions | |
68 | + text.gsub!(/\{gfm-extraction-(\h{32})\}/) do | |
69 | + extractions[$1] | |
70 | + end | |
71 | + | |
72 | + sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class) | |
41 | 73 | end |
42 | 74 | |
75 | + private | |
76 | + | |
77 | + # Private: Parses text for references and emoji | |
78 | + # | |
79 | + # text - Text to parse | |
80 | + # | |
81 | + # Returns parsed text | |
43 | 82 | def parse(text) |
44 | - text.gsub(REFERENCE_PATTERN) do |match| | |
83 | + text = text.gsub(REFERENCE_PATTERN) do |match| | |
45 | 84 | prefix = $1 || '' |
46 | 85 | reference = $2 |
47 | 86 | identifier = $3 || $4 || $5 |
... | ... | @@ -53,9 +92,26 @@ module Gitlab |
53 | 92 | match |
54 | 93 | end |
55 | 94 | end |
95 | + | |
96 | + text = text.gsub(EMOJI_PATTERN) do |match| | |
97 | + if valid_emoji?($2) | |
98 | + helper.image_tag("#{$2}.png", size: "20x20", class: 'emoji', title: $1, alt: $1) | |
99 | + else | |
100 | + match | |
101 | + end | |
102 | + end | |
103 | + | |
104 | + text | |
56 | 105 | end |
57 | 106 | |
58 | - private | |
107 | + # Private: Checks if an emoji icon exists in the image asset directory | |
108 | + # | |
109 | + # emoji - Identifier of the emoji as a string (e.g., "+1", "heart") | |
110 | + # | |
111 | + # Returns boolean | |
112 | + def valid_emoji?(emoji) | |
113 | + File.exists?(Rails.root.join('app', 'assets', 'images', 'emoji', "#{emoji}.png")) | |
114 | + end | |
59 | 115 | |
60 | 116 | # Private: Dispatches to a dedicated processing method based on reference |
61 | 117 | # | ... | ... |
... | ... | @@ -208,6 +208,46 @@ describe GitlabMarkdownHelper do |
208 | 208 | gfm(actual).should match(expected) |
209 | 209 | end |
210 | 210 | end |
211 | + | |
212 | + describe "emoji" do | |
213 | + it "matches at the start of a string" do | |
214 | + gfm(":+1:").should match(/<img/) | |
215 | + end | |
216 | + | |
217 | + it "matches at the end of a string" do | |
218 | + gfm("This gets a :-1:").should match(/<img/) | |
219 | + end | |
220 | + | |
221 | + it "matches with adjacent text" do | |
222 | + gfm("+1 (:+1:)").should match(/<img/) | |
223 | + end | |
224 | + | |
225 | + it "has a title attribute" do | |
226 | + gfm(":-1:").should match(/title=":-1:"/) | |
227 | + end | |
228 | + | |
229 | + it "has an alt attribute" do | |
230 | + gfm(":-1:").should match(/alt=":-1:"/) | |
231 | + end | |
232 | + | |
233 | + it "has an emoji class" do | |
234 | + gfm(":+1:").should match('class="emoji"') | |
235 | + end | |
236 | + | |
237 | + it "sets height and width" do | |
238 | + actual = gfm(":+1:") | |
239 | + actual.should match(/width="20"/) | |
240 | + actual.should match(/height="20"/) | |
241 | + end | |
242 | + | |
243 | + it "keeps whitespace intact" do | |
244 | + gfm("This deserves a :+1: big time.").should match(/deserves a <img.+\/> big time/) | |
245 | + end | |
246 | + | |
247 | + it "ignores invalid emoji" do | |
248 | + gfm(":invalid-emoji:").should_not match(/<img/) | |
249 | + end | |
250 | + end | |
211 | 251 | end |
212 | 252 | |
213 | 253 | describe "#link_to_gfm" do | ... | ... |
... | ... | @@ -35,6 +35,16 @@ describe Note do |
35 | 35 | note = Factory(:note, note: "-1 for this") |
36 | 36 | note.should_not be_upvote |
37 | 37 | end |
38 | + | |
39 | + it "recognizes a +1 emoji as a vote" do | |
40 | + note = build(:note, note: ":+1: for this") | |
41 | + note.should be_upvote | |
42 | + end | |
43 | + | |
44 | + it "recognizes a neutral emoji note" do | |
45 | + note = build(:note, note: "I would :+1: this, but I don't want to") | |
46 | + note.should_not be_upvote | |
47 | + end | |
38 | 48 | end |
39 | 49 | |
40 | 50 | let(:project) { create(:project) } | ... | ... |