Detect markdown line width, resolve relative URLs (#332)
~~this is semi-blocked by https://github.com/charmbracelet/glamour/pull/96, but behaviour isn't really worse than the previous behaviour (most links work, some are still broken)~~ #### testcase for link resolver ``` tea pr 332 tea checkout 332 && make install && tea pr 332 ``` - [rel](./332) - [abs](/gitea/tea/pulls/332) - [full](https://gitea.com/gitea/tea/pulls/332) Co-authored-by: Norwin Roosen <git@nroo.de> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/332 Reviewed-by: 6543 <6543@obermui.de> Reviewed-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Norwin <noerw@noreply.gitea.io> Co-committed-by: Norwin <noerw@noreply.gitea.io>
This commit is contained in:
360
vendor/github.com/yuin/goldmark-emoji/emoji.go
generated
vendored
Normal file
360
vendor/github.com/yuin/goldmark-emoji/emoji.go
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
// package emoji is a extension for the goldmark(http://github.com/yuin/goldmark).
|
||||
package emoji
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
east "github.com/yuin/goldmark-emoji/ast"
|
||||
"github.com/yuin/goldmark-emoji/definition"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Option interface sets options for this extension.
|
||||
type Option interface {
|
||||
emojiOption()
|
||||
}
|
||||
|
||||
// ParserConfig struct is a data structure that holds configuration of
|
||||
// the Emoji extension.
|
||||
type ParserConfig struct {
|
||||
Emojis definition.Emojis
|
||||
}
|
||||
|
||||
const optEmojis parser.OptionName = "EmojiEmojis"
|
||||
|
||||
// SetOption implements parser.SetOptioner
|
||||
func (c *ParserConfig) SetOption(name parser.OptionName, value interface{}) {
|
||||
switch name {
|
||||
case optEmojis:
|
||||
c.Emojis = value.(definition.Emojis)
|
||||
}
|
||||
}
|
||||
|
||||
// A ParserOption interface sets options for the emoji parser.
|
||||
type ParserOption interface {
|
||||
Option
|
||||
parser.Option
|
||||
|
||||
SetEmojiOption(*ParserConfig)
|
||||
}
|
||||
|
||||
var _ ParserOption = &withEmojis{}
|
||||
|
||||
type withEmojis struct {
|
||||
value definition.Emojis
|
||||
}
|
||||
|
||||
func (o *withEmojis) emojiOption() {}
|
||||
|
||||
func (o *withEmojis) SetParserOption(c *parser.Config) {
|
||||
c.Options[optEmojis] = o.value
|
||||
}
|
||||
|
||||
func (o *withEmojis) SetEmojiOption(c *ParserConfig) {
|
||||
c.Emojis = o.value
|
||||
}
|
||||
|
||||
// WithMaping is a functional option that defines links names to unicode emojis.
|
||||
func WithEmojis(value definition.Emojis) Option {
|
||||
return &withEmojis{
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// RenderingMethod indicates how emojis are rendered.
|
||||
type RenderingMethod int
|
||||
|
||||
// RendererFunc will be used for rendering emojis.
|
||||
type RendererFunc func(w util.BufWriter, source []byte, n *east.Emoji, config *RendererConfig)
|
||||
|
||||
const (
|
||||
// Entity renders an emoji as an html entity.
|
||||
Entity RenderingMethod = iota
|
||||
|
||||
// Unicode renders an emoji as unicode character.
|
||||
Unicode
|
||||
|
||||
// Twemoji renders an emoji as an img tag with [twemoji](https://github.com/twitter/twemoji).
|
||||
Twemoji
|
||||
|
||||
// Func renders an emoji using RendererFunc.
|
||||
Func
|
||||
)
|
||||
|
||||
// RendererConfig struct holds options for the emoji renderer.
|
||||
type RendererConfig struct {
|
||||
html.Config
|
||||
|
||||
// Method indicates how emojis are rendered.
|
||||
Method RenderingMethod
|
||||
|
||||
// TwemojiTemplate is a printf template for twemoji. This value is valid only when Method is set to Twemoji.
|
||||
// `printf` arguments are:
|
||||
//
|
||||
// 1: name (e.g. "face with tears of joy")
|
||||
// 2: file name without an extension (e.g. 1f646-2642)
|
||||
// 3: '/' if XHTML, otherwise ''
|
||||
//
|
||||
TwemojiTemplate string
|
||||
|
||||
// RendererFunc is a RendererFunc that renders emojis. This value is valid only when Method is set to Func.
|
||||
RendererFunc RendererFunc
|
||||
}
|
||||
|
||||
// DefaultTwemojiTemplate is a default value for RendererConfig.TwemojiTemplate.
|
||||
const DefaultTwemojiTemplate = `<img class="emoji" draggable="false" alt="%[1]s" src="https://twemoji.maxcdn.com/v/latest/72x72/%[2]s.png"%[3]s>`
|
||||
|
||||
// SetOption implements renderer.SetOptioner.
|
||||
func (c *RendererConfig) SetOption(name renderer.OptionName, value interface{}) {
|
||||
switch name {
|
||||
case optRenderingMethod:
|
||||
c.Method = value.(RenderingMethod)
|
||||
case optTwemojiTemplate:
|
||||
c.TwemojiTemplate = value.(string)
|
||||
case optRendererFunc:
|
||||
c.RendererFunc = value.(RendererFunc)
|
||||
default:
|
||||
c.Config.SetOption(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
// A RendererOption interface sets options for the emoji renderer.
|
||||
type RendererOption interface {
|
||||
Option
|
||||
renderer.Option
|
||||
|
||||
SetEmojiOption(*RendererConfig)
|
||||
}
|
||||
|
||||
var _ RendererOption = &withRenderingMethod{}
|
||||
|
||||
type withRenderingMethod struct {
|
||||
value RenderingMethod
|
||||
}
|
||||
|
||||
func (o *withRenderingMethod) emojiOption() {
|
||||
}
|
||||
|
||||
// SetConfig implements renderer.Option#SetConfig.
|
||||
func (o *withRenderingMethod) SetConfig(c *renderer.Config) {
|
||||
c.Options[optRenderingMethod] = o.value
|
||||
}
|
||||
|
||||
// SetEmojiOption implements RendererOption#SetEmojiOption
|
||||
func (o *withRenderingMethod) SetEmojiOption(c *RendererConfig) {
|
||||
c.Method = o.value
|
||||
}
|
||||
|
||||
const optRenderingMethod renderer.OptionName = "EmojiRenderingMethod"
|
||||
|
||||
// WithRenderingMethod is a functional option that indicates how emojis are rendered.
|
||||
func WithRenderingMethod(a RenderingMethod) Option {
|
||||
return &withRenderingMethod{a}
|
||||
}
|
||||
|
||||
type withTwemojiTemplate struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (o *withTwemojiTemplate) emojiOption() {
|
||||
}
|
||||
|
||||
// SetConfig implements renderer.Option#SetConfig.
|
||||
func (o *withTwemojiTemplate) SetConfig(c *renderer.Config) {
|
||||
c.Options[optTwemojiTemplate] = o.value
|
||||
}
|
||||
|
||||
// SetEmojiOption implements RendererOption#SetEmojiOption
|
||||
func (o *withTwemojiTemplate) SetEmojiOption(c *RendererConfig) {
|
||||
c.TwemojiTemplate = o.value
|
||||
}
|
||||
|
||||
const optTwemojiTemplate renderer.OptionName = "EmojiTwemojiTemplate"
|
||||
|
||||
// WithTwemojiTemplate is a functional option that changes a twemoji img tag.
|
||||
func WithTwemojiTemplate(s string) Option {
|
||||
return &withTwemojiTemplate{s}
|
||||
}
|
||||
|
||||
var _ RendererOption = &withRendererFunc{}
|
||||
|
||||
type withRendererFunc struct {
|
||||
value RendererFunc
|
||||
}
|
||||
|
||||
func (o *withRendererFunc) emojiOption() {
|
||||
}
|
||||
|
||||
// SetConfig implements renderer.Option#SetConfig.
|
||||
func (o *withRendererFunc) SetConfig(c *renderer.Config) {
|
||||
c.Options[optRendererFunc] = o.value
|
||||
}
|
||||
|
||||
// SetEmojiOption implements RendererOption#SetEmojiOption
|
||||
func (o *withRendererFunc) SetEmojiOption(c *RendererConfig) {
|
||||
c.RendererFunc = o.value
|
||||
}
|
||||
|
||||
const optRendererFunc renderer.OptionName = "EmojiRendererFunc"
|
||||
|
||||
// WithRendererFunc is a functional option that changes a renderer func.
|
||||
func WithRendererFunc(f RendererFunc) Option {
|
||||
return &withRendererFunc{f}
|
||||
}
|
||||
|
||||
type emojiParser struct {
|
||||
ParserConfig
|
||||
}
|
||||
|
||||
// NewParser returns a new parser.InlineParser that can parse emoji expressions.
|
||||
func NewParser(opts ...ParserOption) parser.InlineParser {
|
||||
p := &emojiParser{
|
||||
ParserConfig: ParserConfig{
|
||||
Emojis: definition.Github(),
|
||||
},
|
||||
}
|
||||
for _, o := range opts {
|
||||
o.SetEmojiOption(&p.ParserConfig)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *emojiParser) Trigger() []byte {
|
||||
return []byte{':'}
|
||||
}
|
||||
|
||||
func (s *emojiParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||
line, _ := block.PeekLine()
|
||||
if len(line) < 1 {
|
||||
return nil
|
||||
}
|
||||
i := 1
|
||||
for ; i < len(line); i++ {
|
||||
c := line[i]
|
||||
if !(util.IsAlphaNumeric(c) || c == '_' || c == '-' || c == '+') {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i >= len(line) || line[i] != ':' {
|
||||
return nil
|
||||
}
|
||||
block.Advance(i + 1)
|
||||
shortName := line[1:i]
|
||||
emoji, ok := s.Emojis.Get(util.BytesToReadOnlyString(shortName))
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return east.NewEmoji(shortName, emoji)
|
||||
}
|
||||
|
||||
type emojiHTMLRenderer struct {
|
||||
RendererConfig
|
||||
}
|
||||
|
||||
// NewHTMLRenderer returns a new HTMLRenderer.
|
||||
func NewHTMLRenderer(opts ...RendererOption) renderer.NodeRenderer {
|
||||
r := &emojiHTMLRenderer{
|
||||
RendererConfig: RendererConfig{
|
||||
Config: html.NewConfig(),
|
||||
Method: Entity,
|
||||
TwemojiTemplate: DefaultTwemojiTemplate,
|
||||
RendererFunc: nil,
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.SetEmojiOption(&r.RendererConfig)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *emojiHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(east.KindEmoji, r.renderEmoji)
|
||||
}
|
||||
|
||||
const slash = " /"
|
||||
const empty = ""
|
||||
|
||||
func (r *emojiHTMLRenderer) renderEmoji(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
node := n.(*east.Emoji)
|
||||
if !node.Value.IsUnicode() && r.Method != Func {
|
||||
fmt.Fprintf(w, `<span title="%s">:%s:</span>`, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), node.ShortName)
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case Entity:
|
||||
for _, r := range node.Value.Unicode {
|
||||
if r == 0x200D {
|
||||
_, _ = w.WriteString("‍")
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "&#x%x;", r)
|
||||
}
|
||||
case Unicode:
|
||||
fmt.Fprintf(w, "%s", string(node.Value.Unicode))
|
||||
case Twemoji:
|
||||
s := slash
|
||||
if !r.XHTML {
|
||||
s = empty
|
||||
}
|
||||
values := []string{}
|
||||
for _, r := range node.Value.Unicode {
|
||||
values = append(values, fmt.Sprintf("%x", r))
|
||||
}
|
||||
fmt.Fprintf(w, r.TwemojiTemplate, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), strings.Join(values, "-"), s)
|
||||
case Func:
|
||||
r.RendererFunc(w, source, node, &r.RendererConfig)
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type emoji struct {
|
||||
options []Option
|
||||
}
|
||||
|
||||
// Emoji is a goldmark.Extender implementation.
|
||||
var Emoji = &emoji{
|
||||
options: []Option{},
|
||||
}
|
||||
|
||||
// New returns a new extension with given options.
|
||||
func New(opts ...Option) goldmark.Extender {
|
||||
return &emoji{
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// Extend implements goldmark.Extender.
|
||||
func (e *emoji) Extend(m goldmark.Markdown) {
|
||||
pOpts := []ParserOption{}
|
||||
rOpts := []RendererOption{}
|
||||
for _, o := range e.options {
|
||||
if po, ok := o.(ParserOption); ok {
|
||||
pOpts = append(pOpts, po)
|
||||
continue
|
||||
}
|
||||
if ro, ok := o.(RendererOption); ok {
|
||||
rOpts = append(rOpts, ro)
|
||||
}
|
||||
}
|
||||
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewHTMLRenderer(rOpts...), 200),
|
||||
))
|
||||
|
||||
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||
util.Prioritized(NewParser(pOpts...), 999),
|
||||
))
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user