1// package emoji is a extension for the goldmark(http://github.com/yuin/goldmark).
2package emoji
3
4import (
5 "fmt"
6 "strings"
7
8 "github.com/yuin/goldmark"
9 east "github.com/yuin/goldmark-emoji/ast"
10 "github.com/yuin/goldmark-emoji/definition"
11 "github.com/yuin/goldmark/ast"
12 "github.com/yuin/goldmark/parser"
13 "github.com/yuin/goldmark/renderer"
14 "github.com/yuin/goldmark/renderer/html"
15 "github.com/yuin/goldmark/text"
16 "github.com/yuin/goldmark/util"
17)
18
19// Option interface sets options for this extension.
20type Option interface {
21 emojiOption()
22}
23
24// ParserConfig struct is a data structure that holds configuration of
25// the Emoji extension.
26type ParserConfig struct {
27 Emojis definition.Emojis
28}
29
30const optEmojis parser.OptionName = "EmojiEmojis"
31
32// SetOption implements parser.SetOptioner
33func (c *ParserConfig) SetOption(name parser.OptionName, value interface{}) {
34 switch name {
35 case optEmojis:
36 c.Emojis = value.(definition.Emojis)
37 }
38}
39
40// A ParserOption interface sets options for the emoji parser.
41type ParserOption interface {
42 Option
43 parser.Option
44
45 SetEmojiOption(*ParserConfig)
46}
47
48var _ ParserOption = &withEmojis{}
49
50type withEmojis struct {
51 value definition.Emojis
52}
53
54func (o *withEmojis) emojiOption() {}
55
56func (o *withEmojis) SetParserOption(c *parser.Config) {
57 c.Options[optEmojis] = o.value
58}
59
60func (o *withEmojis) SetEmojiOption(c *ParserConfig) {
61 c.Emojis = o.value
62}
63
64// WithMaping is a functional option that defines links names to unicode emojis.
65func WithEmojis(value definition.Emojis) Option {
66 return &withEmojis{
67 value: value,
68 }
69}
70
71// RenderingMethod indicates how emojis are rendered.
72type RenderingMethod int
73
74// RendererFunc will be used for rendering emojis.
75type RendererFunc func(w util.BufWriter, source []byte, n *east.Emoji, config *RendererConfig)
76
77const (
78 // Entity renders an emoji as an html entity.
79 Entity RenderingMethod = iota
80
81 // Unicode renders an emoji as unicode character.
82 Unicode
83
84 // Twemoji renders an emoji as an img tag with [twemoji](https://github.com/twitter/twemoji).
85 Twemoji
86
87 // Func renders an emoji using RendererFunc.
88 Func
89)
90
91// RendererConfig struct holds options for the emoji renderer.
92type RendererConfig struct {
93 html.Config
94
95 // Method indicates how emojis are rendered.
96 Method RenderingMethod
97
98 // TwemojiTemplate is a printf template for twemoji. This value is valid only when Method is set to Twemoji.
99 // `printf` arguments are:
100 //
101 // 1: name (e.g. "face with tears of joy")
102 // 2: file name without an extension (e.g. 1f646-2642)
103 // 3: '/' if XHTML, otherwise ''
104 //
105 TwemojiTemplate string
106
107 // RendererFunc is a RendererFunc that renders emojis. This value is valid only when Method is set to Func.
108 RendererFunc RendererFunc
109}
110
111// DefaultTwemojiTemplate is a default value for RendererConfig.TwemojiTemplate.
112const DefaultTwemojiTemplate = `<img class="emoji" draggable="false" alt="%[1]s" src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/72x72/%[2]s.png"%[3]s>`
113
114// SetOption implements renderer.SetOptioner.
115func (c *RendererConfig) SetOption(name renderer.OptionName, value interface{}) {
116 switch name {
117 case optRenderingMethod:
118 c.Method = value.(RenderingMethod)
119 case optTwemojiTemplate:
120 c.TwemojiTemplate = value.(string)
121 case optRendererFunc:
122 c.RendererFunc = value.(RendererFunc)
123 default:
124 c.Config.SetOption(name, value)
125 }
126}
127
128// A RendererOption interface sets options for the emoji renderer.
129type RendererOption interface {
130 Option
131 renderer.Option
132
133 SetEmojiOption(*RendererConfig)
134}
135
136var _ RendererOption = &withRenderingMethod{}
137
138type withRenderingMethod struct {
139 value RenderingMethod
140}
141
142func (o *withRenderingMethod) emojiOption() {
143}
144
145// SetConfig implements renderer.Option#SetConfig.
146func (o *withRenderingMethod) SetConfig(c *renderer.Config) {
147 c.Options[optRenderingMethod] = o.value
148}
149
150// SetEmojiOption implements RendererOption#SetEmojiOption
151func (o *withRenderingMethod) SetEmojiOption(c *RendererConfig) {
152 c.Method = o.value
153}
154
155const optRenderingMethod renderer.OptionName = "EmojiRenderingMethod"
156
157// WithRenderingMethod is a functional option that indicates how emojis are rendered.
158func WithRenderingMethod(a RenderingMethod) Option {
159 return &withRenderingMethod{a}
160}
161
162type withTwemojiTemplate struct {
163 value string
164}
165
166func (o *withTwemojiTemplate) emojiOption() {
167}
168
169// SetConfig implements renderer.Option#SetConfig.
170func (o *withTwemojiTemplate) SetConfig(c *renderer.Config) {
171 c.Options[optTwemojiTemplate] = o.value
172}
173
174// SetEmojiOption implements RendererOption#SetEmojiOption
175func (o *withTwemojiTemplate) SetEmojiOption(c *RendererConfig) {
176 c.TwemojiTemplate = o.value
177}
178
179const optTwemojiTemplate renderer.OptionName = "EmojiTwemojiTemplate"
180
181// WithTwemojiTemplate is a functional option that changes a twemoji img tag.
182func WithTwemojiTemplate(s string) Option {
183 return &withTwemojiTemplate{s}
184}
185
186var _ RendererOption = &withRendererFunc{}
187
188type withRendererFunc struct {
189 value RendererFunc
190}
191
192func (o *withRendererFunc) emojiOption() {
193}
194
195// SetConfig implements renderer.Option#SetConfig.
196func (o *withRendererFunc) SetConfig(c *renderer.Config) {
197 c.Options[optRendererFunc] = o.value
198}
199
200// SetEmojiOption implements RendererOption#SetEmojiOption
201func (o *withRendererFunc) SetEmojiOption(c *RendererConfig) {
202 c.RendererFunc = o.value
203}
204
205const optRendererFunc renderer.OptionName = "EmojiRendererFunc"
206
207// WithRendererFunc is a functional option that changes a renderer func.
208func WithRendererFunc(f RendererFunc) Option {
209 return &withRendererFunc{f}
210}
211
212type emojiParser struct {
213 ParserConfig
214}
215
216// NewParser returns a new parser.InlineParser that can parse emoji expressions.
217func NewParser(opts ...ParserOption) parser.InlineParser {
218 p := &emojiParser{
219 ParserConfig: ParserConfig{
220 Emojis: definition.Github(),
221 },
222 }
223 for _, o := range opts {
224 o.SetEmojiOption(&p.ParserConfig)
225 }
226 return p
227}
228
229func (s *emojiParser) Trigger() []byte {
230 return []byte{':'}
231}
232
233func (s *emojiParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
234 line, _ := block.PeekLine()
235 if len(line) < 1 {
236 return nil
237 }
238 i := 1
239 for ; i < len(line); i++ {
240 c := line[i]
241 if !(util.IsAlphaNumeric(c) || c == '_' || c == '-' || c == '+') {
242 break
243 }
244 }
245 if i >= len(line) || line[i] != ':' {
246 return nil
247 }
248 block.Advance(i + 1)
249 shortName := line[1:i]
250 emoji, ok := s.Emojis.Get(util.BytesToReadOnlyString(shortName))
251 if !ok {
252 return nil
253 }
254 return east.NewEmoji(shortName, emoji)
255}
256
257type emojiHTMLRenderer struct {
258 RendererConfig
259}
260
261// NewHTMLRenderer returns a new HTMLRenderer.
262func NewHTMLRenderer(opts ...RendererOption) renderer.NodeRenderer {
263 r := &emojiHTMLRenderer{
264 RendererConfig: RendererConfig{
265 Config: html.NewConfig(),
266 Method: Entity,
267 TwemojiTemplate: DefaultTwemojiTemplate,
268 RendererFunc: nil,
269 },
270 }
271 for _, opt := range opts {
272 opt.SetEmojiOption(&r.RendererConfig)
273 }
274 return r
275}
276
277// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
278func (r *emojiHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
279 reg.Register(east.KindEmoji, r.renderEmoji)
280}
281
282const slash = " /"
283const empty = ""
284
285func (r *emojiHTMLRenderer) renderEmoji(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
286 if !entering {
287 return ast.WalkContinue, nil
288 }
289 node := n.(*east.Emoji)
290 if !node.Value.IsUnicode() && r.Method != Func {
291 fmt.Fprintf(w, `<span title="%s">:%s:</span>`, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), node.ShortName)
292 return ast.WalkContinue, nil
293 }
294
295 switch r.Method {
296 case Entity:
297 for _, r := range node.Value.Unicode {
298 if r == 0x200D {
299 _, _ = w.WriteString("‍")
300 continue
301 }
302 fmt.Fprintf(w, "&#x%x;", r)
303 }
304 case Unicode:
305 fmt.Fprintf(w, "%s", string(node.Value.Unicode))
306 case Twemoji:
307 s := slash
308 if !r.XHTML {
309 s = empty
310 }
311 values := []string{}
312 for _, r := range node.Value.Unicode {
313 values = append(values, fmt.Sprintf("%x", r))
314 }
315 fmt.Fprintf(w, r.TwemojiTemplate, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), strings.Join(values, "-"), s)
316 case Func:
317 r.RendererFunc(w, source, node, &r.RendererConfig)
318 }
319 return ast.WalkContinue, nil
320}
321
322type emoji struct {
323 options []Option
324}
325
326// Emoji is a goldmark.Extender implementation.
327var Emoji = &emoji{
328 options: []Option{},
329}
330
331// New returns a new extension with given options.
332func New(opts ...Option) goldmark.Extender {
333 return &emoji{
334 options: opts,
335 }
336}
337
338// Extend implements goldmark.Extender.
339func (e *emoji) Extend(m goldmark.Markdown) {
340 pOpts := []ParserOption{}
341 rOpts := []RendererOption{}
342 for _, o := range e.options {
343 if po, ok := o.(ParserOption); ok {
344 pOpts = append(pOpts, po)
345 continue
346 }
347 if ro, ok := o.(RendererOption); ok {
348 rOpts = append(rOpts, ro)
349 }
350 }
351
352 m.Renderer().AddOptions(renderer.WithNodeRenderers(
353 util.Prioritized(NewHTMLRenderer(rOpts...), 200),
354 ))
355
356 m.Parser().AddOptions(parser.WithInlineParsers(
357 util.Prioritized(NewParser(pOpts...), 999),
358 ))
359
360}