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:
Norwin
2021-03-12 20:28:46 +08:00
committed by Andrew Thornton
parent cb404b53b5
commit 222d0501df
82 changed files with 7709 additions and 316 deletions

15
vendor/github.com/yuin/goldmark-emoji/.gitignore generated vendored Normal file
View File

@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
*.pprof
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.DS_Store

21
vendor/github.com/yuin/goldmark-emoji/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Yusuke Inuzuka
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

71
vendor/github.com/yuin/goldmark-emoji/README.md generated vendored Normal file
View File

@ -0,0 +1,71 @@
goldmark-emoji
=========================
[![GoDev][godev-image]][godev-url]
[godev-image]: https://pkg.go.dev/badge/github.com/yuin/goldmark-emoji
[godev-url]: https://pkg.go.dev/github.com/yuin/goldmark-emoji
goldmark-emoji is an extension for the [goldmark](http://github.com/yuin/goldmark)
that parses `:joy:` style emojis.
Installation
--------------------
```
go get github.com/yuin/goldmark-emoji
```
Usage
--------------------
```go
import (
"bytes"
"fmt"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark-emoji"
"github.com/yuin/goldmark-emoji/definition"
)
func main() {
markdown := goldmark.New(
goldmark.WithExtensions(
emoji.Emoji,
),
)
source := `
Joy :joy:
`
var buf bytes.Buffer
if err := markdown.Convert([]byte(source), &buf); err != nil {
panic(err)
}
fmt.Print(buf.String())
}
```
See `emoji_test.go` for detailed usage.
### Options
Options for the extension
| Option | Description |
| ------ | ----------- |
| `WithEmojis` | Definition of emojis. This defaults to github emoji set |
| `WithRenderingMethod` | `Entity` : renders as HTML entities, `Twemoji` : renders as an img tag that uses [twemoji](https://github.com/twitter/twemoji), `Func` : renders using a go function |
| `WithTwemojiTemplate` | Twemoji img tag printf template |
| `WithRendererFunc` | renders by a go function |
License
--------------------
MIT
Author
--------------------
Yusuke Inuzuka

42
vendor/github.com/yuin/goldmark-emoji/ast/emoji.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
// Package ast defines AST nodes that represetns emoji extension's elements.
package ast
import (
"fmt"
"github.com/yuin/goldmark-emoji/definition"
gast "github.com/yuin/goldmark/ast"
)
// Emoji represents an inline emoji.
type Emoji struct {
gast.BaseInline
ShortName []byte
Value *definition.Emoji
}
// Dump implements Node.Dump.
func (n *Emoji) Dump(source []byte, level int) {
m := map[string]string{
"ShortName": string(n.ShortName),
"Value": fmt.Sprintf("%#v", n.Value),
}
gast.DumpHelper(n, source, level, m, nil)
}
// KindEmoji is a NodeKind of the emoji node.
var KindEmoji = gast.NewNodeKind("Emoji")
// Kind implements Node.Kind.
func (n *Emoji) Kind() gast.NodeKind {
return KindEmoji
}
// NewEmoji returns a new Emoji node.
func NewEmoji(shortName []byte, value *definition.Emoji) *Emoji {
return &Emoji{
ShortName: shortName,
Value: value,
}
}

View File

@ -0,0 +1,106 @@
package definition
// Emoji is a data structure that holds a single emoji.
type Emoji struct {
// Name is a name of this emoji.
Name string
// ShortNames is a shorter representation of this emoji.
ShortNames []string
// Unicode is an unicode representation of this emoji.
Unicode []rune
}
// NewEmoji returns a new Emoji.
func NewEmoji(name string, unicode []rune, shortNames ...string) Emoji {
if len(shortNames) == 0 {
panic("Emoji must have at leat 1 short name.")
}
if unicode == nil || len(unicode) == 0 {
unicode = []rune{0xFFFD}
}
return Emoji{
Name: name,
ShortNames: shortNames,
Unicode: unicode,
}
}
// IsUnicode returns true if this emoji is defined in unicode, otherwise false.
func (em *Emoji) IsUnicode() bool {
return !(len(em.Unicode) == 1 && em.Unicode[0] == 0xFFFD)
}
// Emojis is a collection of emojis.
type Emojis interface {
// Get returns (*Emoji, true) if found mapping associated with given short name, otherwise (nil, false).
Get(shortName string) (*Emoji, bool)
// Add adds new emojis to this collection.
Add(Emojis)
// Clone clones this collection.
Clone() Emojis
}
type emojis struct {
list []Emoji
m map[string]*Emoji
children []Emojis
}
// NewEmojis returns a new Emojis.
func NewEmojis(es ...Emoji) Emojis {
m := &emojis{
list: es,
m: map[string]*Emoji{},
children: []Emojis{},
}
for i, _ := range es {
emoji := &m.list[i]
for _, s := range emoji.ShortNames {
m.m[s] = emoji
}
}
return m
}
func (m *emojis) Add(emojis Emojis) {
m.children = append(m.children, emojis)
}
func (m *emojis) Clone() Emojis {
es := &emojis{
list: m.list,
m: m.m,
children: make([]Emojis, len(m.children)),
}
copy(es.children, m.children)
return es
}
func (m *emojis) Get(shortName string) (*Emoji, bool) {
v, ok := m.m[shortName]
if ok {
return v, ok
}
for _, es := range m.children {
v, ok := es.Get(shortName)
if ok {
return v, ok
}
}
return nil, false
}
// EmojisOption sets options for Emojis.
type EmojisOption func(Emojis)
// WithEmojis is an EmojisOption that adds emojis to the Emojis.
func WithEmojis(emojis ...Emoji) EmojisOption {
return func(m Emojis) {
m.Add(NewEmojis(emojis...))
}
}

File diff suppressed because it is too large Load Diff

360
vendor/github.com/yuin/goldmark-emoji/emoji.go generated vendored Normal file
View 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("&zwj;")
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),
))
}

5
vendor/github.com/yuin/goldmark-emoji/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/yuin/goldmark-emoji
go 1.15
require github.com/yuin/goldmark v1.2.1

2
vendor/github.com/yuin/goldmark-emoji/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=