0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-10-10 19:04:45 -04:00

Merge branch 'master' into graceful-queues

This commit is contained in:
Lauris BH
2020-01-05 01:20:52 +02:00
committed by GitHub
161 changed files with 17393 additions and 6702 deletions

View File

@@ -171,6 +171,7 @@ type ProtectBranchForm struct {
EnableApprovalsWhitelist bool
ApprovalsWhitelistUsers string
ApprovalsWhitelistTeams string
BlockOnRejectedReviews bool
}
// Validate validates the fields

View File

@@ -315,7 +315,28 @@ func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (in
// CommitsBetween returns a list that contains commits between [last, before).
func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
stdout, err := NewCommand("rev-list", before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
var stdout []byte
var err error
if before == nil {
stdout, err = NewCommand("rev-list", before.ID.String()).RunInDirBytes(repo.Path)
} else {
stdout, err = NewCommand("rev-list", before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
}
if err != nil {
return nil, err
}
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
}
// CommitsBetweenLimit returns a list that contains at most limit commits skipping the first skip commits between [last, before)
func (repo *Repository) CommitsBetweenLimit(last *Commit, before *Commit, limit, skip int) (*list.List, error) {
var stdout []byte
var err error
if before == nil {
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunInDirBytes(repo.Path)
} else {
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
}
if err != nil {
return nil, err
}
@@ -328,6 +349,9 @@ func (repo *Repository) CommitsBetweenIDs(last, before string) (*list.List, erro
if err != nil {
return nil, err
}
if before == "" {
return repo.CommitsBetween(lastCommit, nil)
}
beforeCommit, err := repo.GetCommit(before)
if err != nil {
return nil, err

View File

@@ -0,0 +1,507 @@
// Copyright 2019 Yusuke Inuzuka
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Most of what follows is a subtly changed version of github.com/yuin/goldmark/extension/footnote.go
package common
import (
"bytes"
"fmt"
"os"
"strconv"
"unicode"
"github.com/yuin/goldmark"
"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"
)
// CleanValue will clean a value to make it safe to be an id
// This function is quite different from the original goldmark function
// and more closely matches the output from the shurcooL sanitizer
// In particular Unicode letters and numbers are a lot more than a-zA-Z0-9...
func CleanValue(value []byte) []byte {
value = bytes.TrimSpace(value)
rs := bytes.Runes(value)
result := make([]rune, 0, len(rs))
needsDash := false
for _, r := range rs {
switch {
case unicode.IsLetter(r) || unicode.IsNumber(r):
if needsDash && len(result) > 0 {
result = append(result, '-')
}
needsDash = false
result = append(result, unicode.ToLower(r))
default:
needsDash = true
}
}
return []byte(string(result))
}
// Most of what follows is a subtly changed version of github.com/yuin/goldmark/extension/footnote.go
// A FootnoteLink struct represents a link to a footnote of Markdown
// (PHP Markdown Extra) text.
type FootnoteLink struct {
ast.BaseInline
Index int
Name []byte
}
// Dump implements Node.Dump.
func (n *FootnoteLink) Dump(source []byte, level int) {
m := map[string]string{}
m["Index"] = fmt.Sprintf("%v", n.Index)
m["Name"] = fmt.Sprintf("%v", n.Name)
ast.DumpHelper(n, source, level, m, nil)
}
// KindFootnoteLink is a NodeKind of the FootnoteLink node.
var KindFootnoteLink = ast.NewNodeKind("GiteaFootnoteLink")
// Kind implements Node.Kind.
func (n *FootnoteLink) Kind() ast.NodeKind {
return KindFootnoteLink
}
// NewFootnoteLink returns a new FootnoteLink node.
func NewFootnoteLink(index int, name []byte) *FootnoteLink {
return &FootnoteLink{
Index: index,
Name: name,
}
}
// A FootnoteBackLink struct represents a link to a footnote of Markdown
// (PHP Markdown Extra) text.
type FootnoteBackLink struct {
ast.BaseInline
Index int
Name []byte
}
// Dump implements Node.Dump.
func (n *FootnoteBackLink) Dump(source []byte, level int) {
m := map[string]string{}
m["Index"] = fmt.Sprintf("%v", n.Index)
m["Name"] = fmt.Sprintf("%v", n.Name)
ast.DumpHelper(n, source, level, m, nil)
}
// KindFootnoteBackLink is a NodeKind of the FootnoteBackLink node.
var KindFootnoteBackLink = ast.NewNodeKind("GiteaFootnoteBackLink")
// Kind implements Node.Kind.
func (n *FootnoteBackLink) Kind() ast.NodeKind {
return KindFootnoteBackLink
}
// NewFootnoteBackLink returns a new FootnoteBackLink node.
func NewFootnoteBackLink(index int, name []byte) *FootnoteBackLink {
return &FootnoteBackLink{
Index: index,
Name: name,
}
}
// A Footnote struct represents a footnote of Markdown
// (PHP Markdown Extra) text.
type Footnote struct {
ast.BaseBlock
Ref []byte
Index int
Name []byte
}
// Dump implements Node.Dump.
func (n *Footnote) Dump(source []byte, level int) {
m := map[string]string{}
m["Index"] = fmt.Sprintf("%v", n.Index)
m["Ref"] = fmt.Sprintf("%s", n.Ref)
m["Name"] = fmt.Sprintf("%v", n.Name)
ast.DumpHelper(n, source, level, m, nil)
}
// KindFootnote is a NodeKind of the Footnote node.
var KindFootnote = ast.NewNodeKind("GiteaFootnote")
// Kind implements Node.Kind.
func (n *Footnote) Kind() ast.NodeKind {
return KindFootnote
}
// NewFootnote returns a new Footnote node.
func NewFootnote(ref []byte) *Footnote {
return &Footnote{
Ref: ref,
Index: -1,
Name: ref,
}
}
// A FootnoteList struct represents footnotes of Markdown
// (PHP Markdown Extra) text.
type FootnoteList struct {
ast.BaseBlock
Count int
}
// Dump implements Node.Dump.
func (n *FootnoteList) Dump(source []byte, level int) {
m := map[string]string{}
m["Count"] = fmt.Sprintf("%v", n.Count)
ast.DumpHelper(n, source, level, m, nil)
}
// KindFootnoteList is a NodeKind of the FootnoteList node.
var KindFootnoteList = ast.NewNodeKind("GiteaFootnoteList")
// Kind implements Node.Kind.
func (n *FootnoteList) Kind() ast.NodeKind {
return KindFootnoteList
}
// NewFootnoteList returns a new FootnoteList node.
func NewFootnoteList() *FootnoteList {
return &FootnoteList{
Count: 0,
}
}
var footnoteListKey = parser.NewContextKey()
type footnoteBlockParser struct {
}
var defaultFootnoteBlockParser = &footnoteBlockParser{}
// NewFootnoteBlockParser returns a new parser.BlockParser that can parse
// footnotes of the Markdown(PHP Markdown Extra) text.
func NewFootnoteBlockParser() parser.BlockParser {
return defaultFootnoteBlockParser
}
func (b *footnoteBlockParser) Trigger() []byte {
return []byte{'['}
}
func (b *footnoteBlockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
line, segment := reader.PeekLine()
pos := pc.BlockOffset()
if pos < 0 || line[pos] != '[' {
return nil, parser.NoChildren
}
pos++
if pos > len(line)-1 || line[pos] != '^' {
return nil, parser.NoChildren
}
open := pos + 1
closes := 0
closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
closes = pos + 1 + closure
next := closes + 1
if closure > -1 {
if next >= len(line) || line[next] != ':' {
return nil, parser.NoChildren
}
} else {
return nil, parser.NoChildren
}
padding := segment.Padding
label := reader.Value(text.NewSegment(segment.Start+open-padding, segment.Start+closes-padding))
if util.IsBlank(label) {
return nil, parser.NoChildren
}
item := NewFootnote(label)
pos = next + 1 - padding
if pos >= len(line) {
reader.Advance(pos)
return item, parser.NoChildren
}
reader.AdvanceAndSetPadding(pos, padding)
return item, parser.HasChildren
}
func (b *footnoteBlockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
line, _ := reader.PeekLine()
if util.IsBlank(line) {
return parser.Continue | parser.HasChildren
}
childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
if childpos < 0 {
return parser.Close
}
reader.AdvanceAndSetPadding(childpos, padding)
return parser.Continue | parser.HasChildren
}
func (b *footnoteBlockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
var list *FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
list = tlist.(*FootnoteList)
} else {
list = NewFootnoteList()
pc.Set(footnoteListKey, list)
node.Parent().InsertBefore(node.Parent(), node, list)
}
node.Parent().RemoveChild(node.Parent(), node)
list.AppendChild(list, node)
}
func (b *footnoteBlockParser) CanInterruptParagraph() bool {
return true
}
func (b *footnoteBlockParser) CanAcceptIndentedLine() bool {
return false
}
type footnoteParser struct {
}
var defaultFootnoteParser = &footnoteParser{}
// NewFootnoteParser returns a new parser.InlineParser that can parse
// footnote links of the Markdown(PHP Markdown Extra) text.
func NewFootnoteParser() parser.InlineParser {
return defaultFootnoteParser
}
func (s *footnoteParser) Trigger() []byte {
// footnote syntax probably conflict with the image syntax.
// So we need trigger this parser with '!'.
return []byte{'!', '['}
}
func (s *footnoteParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, segment := block.PeekLine()
pos := 1
if len(line) > 0 && line[0] == '!' {
pos++
}
if pos >= len(line) || line[pos] != '^' {
return nil
}
pos++
if pos >= len(line) {
return nil
}
open := pos
closure := util.FindClosure(line[pos:], '[', ']', false, false)
if closure < 0 {
return nil
}
closes := pos + closure
value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
block.Advance(closes + 1)
var list *FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
list = tlist.(*FootnoteList)
}
if list == nil {
return nil
}
index := 0
name := []byte{}
for def := list.FirstChild(); def != nil; def = def.NextSibling() {
d := def.(*Footnote)
if bytes.Equal(d.Ref, value) {
if d.Index < 0 {
list.Count++
d.Index = list.Count
val := CleanValue(d.Name)
if len(val) == 0 {
val = []byte(strconv.Itoa(d.Index))
}
d.Name = pc.IDs().Generate(val, KindFootnote)
}
index = d.Index
name = d.Name
break
}
}
if index == 0 {
return nil
}
return NewFootnoteLink(index, name)
}
type footnoteASTTransformer struct {
}
var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
// NewFootnoteASTTransformer returns a new parser.ASTTransformer that
// insert a footnote list to the last of the document.
func NewFootnoteASTTransformer() parser.ASTTransformer {
return defaultFootnoteASTTransformer
}
func (a *footnoteASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
var list *FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
list = tlist.(*FootnoteList)
} else {
return
}
pc.Set(footnoteListKey, nil)
for footnote := list.FirstChild(); footnote != nil; {
var container ast.Node = footnote
next := footnote.NextSibling()
if fc := container.LastChild(); fc != nil && ast.IsParagraph(fc) {
container = fc
}
footnoteNode := footnote.(*Footnote)
index := footnoteNode.Index
name := footnoteNode.Name
if index < 0 {
list.RemoveChild(list, footnote)
} else {
container.AppendChild(container, NewFootnoteBackLink(index, name))
}
footnote = next
}
list.SortChildren(func(n1, n2 ast.Node) int {
if n1.(*Footnote).Index < n2.(*Footnote).Index {
return -1
}
return 1
})
if list.Count <= 0 {
list.Parent().RemoveChild(list.Parent(), list)
return
}
node.AppendChild(node, list)
}
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
// renders FootnoteLink nodes.
type FootnoteHTMLRenderer struct {
html.Config
}
// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
r := &FootnoteHTMLRenderer{
Config: html.NewConfig(),
}
for _, opt := range opts {
opt.SetHTMLOption(&r.Config)
}
return r
}
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindFootnoteLink, r.renderFootnoteLink)
reg.Register(KindFootnoteBackLink, r.renderFootnoteBackLink)
reg.Register(KindFootnote, r.renderFootnote)
reg.Register(KindFootnoteList, r.renderFootnoteList)
}
func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
n := node.(*FootnoteLink)
n.Dump(source, 0)
is := strconv.Itoa(n.Index)
_, _ = w.WriteString(`<sup id="fnref:`)
_, _ = w.Write(n.Name)
_, _ = w.WriteString(`"><a href="#fn:`)
_, _ = w.Write(n.Name)
_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
_, _ = w.WriteString(is)
_, _ = w.WriteString(`</a></sup>`)
}
return ast.WalkContinue, nil
}
func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
n := node.(*FootnoteBackLink)
fmt.Fprintf(os.Stdout, "source:\n%s\n", string(n.Text(source)))
_, _ = w.WriteString(` <a href="#fnref:`)
_, _ = w.Write(n.Name)
_, _ = w.WriteString(`" class="footnote-backref" role="doc-backlink">`)
_, _ = w.WriteString("&#x21a9;&#xfe0e;")
_, _ = w.WriteString(`</a>`)
}
return ast.WalkContinue, nil
}
func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*Footnote)
if entering {
fmt.Fprintf(os.Stdout, "source:\n%s\n", string(n.Text(source)))
_, _ = w.WriteString(`<li id="fn:`)
_, _ = w.Write(n.Name)
_, _ = w.WriteString(`" role="doc-endnote"`)
if node.Attributes() != nil {
html.RenderAttributes(w, node, html.ListItemAttributeFilter)
}
_, _ = w.WriteString(">\n")
} else {
_, _ = w.WriteString("</li>\n")
}
return ast.WalkContinue, nil
}
func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
tag := "div"
if entering {
_, _ = w.WriteString("<")
_, _ = w.WriteString(tag)
_, _ = w.WriteString(` class="footnotes" role="doc-endnotes"`)
if node.Attributes() != nil {
html.RenderAttributes(w, node, html.GlobalAttributeFilter)
}
_ = w.WriteByte('>')
if r.Config.XHTML {
_, _ = w.WriteString("\n<hr />\n")
} else {
_, _ = w.WriteString("\n<hr>\n")
}
_, _ = w.WriteString("<ol>\n")
} else {
_, _ = w.WriteString("</ol>\n")
_, _ = w.WriteString("</")
_, _ = w.WriteString(tag)
_, _ = w.WriteString(">\n")
}
return ast.WalkContinue, nil
}
type footnoteExtension struct{}
// FootnoteExtension represents the Gitea Footnote
var FootnoteExtension = &footnoteExtension{}
// Extend extends the markdown converter with the Gitea Footnote parser
func (e *footnoteExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithBlockParsers(
util.Prioritized(NewFootnoteBlockParser(), 999),
),
parser.WithInlineParsers(
util.Prioritized(NewFootnoteParser(), 101),
),
parser.WithASTTransformers(
util.Prioritized(NewFootnoteASTTransformer(), 999),
),
)
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewFootnoteHTMLRenderer(), 500),
))
}

View File

@@ -0,0 +1,19 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package common
import (
"mvdan.cc/xurls/v2"
)
var (
// NOTE: All below regex matching do not perform any extra validation.
// Thus a link is produced even if the linked entity does not exist.
// While fast, this is also incorrect and lead to false positives.
// TODO: fix invalid linking issue
// LinkRegex is a regexp matching a valid link
LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
)

View File

@@ -0,0 +1,156 @@
// Copyright 2019 Yusuke Inuzuka
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Most of this file is a subtly changed version of github.com/yuin/goldmark/extension/linkify.go
package common
import (
"bytes"
"regexp"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
type linkifyParser struct {
}
var defaultLinkifyParser = &linkifyParser{}
// NewLinkifyParser return a new InlineParser can parse
// text that seems like a URL.
func NewLinkifyParser() parser.InlineParser {
return defaultLinkifyParser
}
func (s *linkifyParser) Trigger() []byte {
// ' ' indicates any white spaces and a line head
return []byte{' ', '*', '_', '~', '('}
}
var protoHTTP = []byte("http:")
var protoHTTPS = []byte("https:")
var protoFTP = []byte("ftp:")
var domainWWW = []byte("www.")
func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
if pc.IsInLinkLabel() {
return nil
}
line, segment := block.PeekLine()
consumes := 0
start := segment.Start
c := line[0]
// advance if current position is not a line head.
if c == ' ' || c == '*' || c == '_' || c == '~' || c == '(' {
consumes++
start++
line = line[1:]
}
var m []int
var protocol []byte
var typ ast.AutoLinkType = ast.AutoLinkURL
if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
m = LinkRegex.FindSubmatchIndex(line)
}
if m == nil && bytes.HasPrefix(line, domainWWW) {
m = wwwURLRegxp.FindSubmatchIndex(line)
protocol = []byte("http")
}
if m != nil {
lastChar := line[m[1]-1]
if lastChar == '.' {
m[1]--
} else if lastChar == ')' {
closing := 0
for i := m[1] - 1; i >= m[0]; i-- {
if line[i] == ')' {
closing++
} else if line[i] == '(' {
closing--
}
}
if closing > 0 {
m[1] -= closing
}
} else if lastChar == ';' {
i := m[1] - 2
for ; i >= m[0]; i-- {
if util.IsAlphaNumeric(line[i]) {
continue
}
break
}
if i != m[1]-2 {
if line[i] == '&' {
m[1] -= m[1] - i
}
}
}
}
if m == nil {
if len(line) > 0 && util.IsPunct(line[0]) {
return nil
}
typ = ast.AutoLinkEmail
stop := util.FindEmailIndex(line)
if stop < 0 {
return nil
}
at := bytes.IndexByte(line, '@')
m = []int{0, stop, at, stop - 1}
if m == nil || bytes.IndexByte(line[m[2]:m[3]], '.') < 0 {
return nil
}
lastChar := line[m[1]-1]
if lastChar == '.' {
m[1]--
}
if m[1] < len(line) {
nextChar := line[m[1]]
if nextChar == '-' || nextChar == '_' {
return nil
}
}
}
if m == nil {
return nil
}
if consumes != 0 {
s := segment.WithStop(segment.Start + 1)
ast.MergeOrAppendTextSegment(parent, s)
}
consumes += m[1]
block.Advance(consumes)
n := ast.NewTextSegment(text.NewSegment(start, start+m[1]))
link := ast.NewAutoLink(typ, n)
link.Protocol = protocol
return link
}
func (s *linkifyParser) CloseBlock(parent ast.Node, pc parser.Context) {
// nothing to do
}
type linkify struct {
}
// Linkify is an extension that allow you to parse text that seems like a URL.
var Linkify = &linkify{}
func (e *linkify) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithInlineParsers(
util.Prioritized(NewLinkifyParser(), 999),
),
)
}

View File

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/common"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -57,8 +58,6 @@ var (
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|\\.(\\s|$))")
linkRegex, _ = xurls.StrictMatchingScheme("https?://")
// blackfriday extensions create IDs like fn:user-content-footnote
blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)
)
@@ -118,7 +117,7 @@ func CustomLinkURLSchemes(schemes []string) {
}
withAuth = append(withAuth, s)
}
linkRegex, _ = xurls.StrictMatchingScheme(strings.Join(withAuth, "|"))
common.LinkRegex, _ = xurls.StrictMatchingScheme(strings.Join(withAuth, "|"))
}
// IsSameDomain checks if given url string has the same hostname as current Gitea instance
@@ -509,6 +508,12 @@ func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) {
(strings.HasPrefix(val, "") && strings.HasSuffix(val, "")) {
const lenQuote = len("")
val = val[lenQuote : len(val)-lenQuote]
} else if (strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"")) ||
(strings.HasPrefix(val, "'") && strings.HasSuffix(val, "'")) {
val = val[1 : len(val)-1]
} else if strings.HasPrefix(val, "'") && strings.HasSuffix(val, "") {
const lenQuote = len("")
val = val[1 : len(val)-lenQuote]
}
props[key] = val
}
@@ -803,7 +808,7 @@ func emailAddressProcessor(ctx *postProcessCtx, node *html.Node) {
// linkProcessor creates links for any HTTP or HTTPS URL not captured by
// markdown.
func linkProcessor(ctx *postProcessCtx, node *html.Node) {
m := linkRegex.FindStringIndex(node.Data)
m := common.LinkRegex.FindStringIndex(node.Data)
if m == nil {
return
}
@@ -832,7 +837,7 @@ func genDefaultLinkProcessor(defaultLink string) processor {
// descriptionLinkProcessor creates links for DescriptionHTML
func descriptionLinkProcessor(ctx *postProcessCtx, node *html.Node) {
m := linkRegex.FindStringIndex(node.Data)
m := common.LinkRegex.FindStringIndex(node.Data)
if m == nil {
return
}

View File

@@ -323,6 +323,6 @@ func TestRender_ShortLinks(t *testing.T) {
`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`"/></a></p>`)
test(
"<p><a href=\"https://example.org\">[[foobar]]</a></p>",
`<p></p><p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p><p></p>`,
`<p></p><p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p><p></p>`)
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
}

View File

@@ -0,0 +1,178 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package markdown
import (
"bytes"
"fmt"
"strings"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/common"
giteautil "code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/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"
)
var byteMailto = []byte("mailto:")
// GiteaASTTransformer is a default transformer of the goldmark tree.
type GiteaASTTransformer struct{}
// Transform transforms the given AST tree.
func (g *GiteaASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
switch v := n.(type) {
case *ast.Image:
// Images need two things:
//
// 1. Their src needs to munged to be a real value
// 2. If they're not wrapped with a link they need a link wrapper
// Check if the destination is a real link
link := v.Destination
if len(link) > 0 && !markup.IsLink(link) {
prefix := pc.Get(urlPrefixKey).(string)
if pc.Get(isWikiKey).(bool) {
prefix = giteautil.URLJoin(prefix, "wiki", "raw")
}
prefix = strings.Replace(prefix, "/src/", "/media/", 1)
lnk := string(link)
lnk = giteautil.URLJoin(prefix, lnk)
lnk = strings.Replace(lnk, " ", "+", -1)
link = []byte(lnk)
}
v.Destination = link
parent := n.Parent()
// Create a link around image only if parent is not already a link
if _, ok := parent.(*ast.Link); !ok && parent != nil {
wrap := ast.NewLink()
wrap.Destination = link
wrap.Title = v.Title
parent.ReplaceChild(parent, n, wrap)
wrap.AppendChild(wrap, n)
}
case *ast.Link:
// Links need their href to munged to be a real value
link := v.Destination
if len(link) > 0 && !markup.IsLink(link) &&
link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
// special case: this is not a link, a hash link or a mailto:, so it's a
// relative URL
lnk := string(link)
if pc.Get(isWikiKey).(bool) {
lnk = giteautil.URLJoin("wiki", lnk)
}
link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk))
}
v.Destination = link
}
return ast.WalkContinue, nil
})
}
type prefixedIDs struct {
values map[string]bool
}
// Generate generates a new element id.
func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte {
dft := []byte("id")
if kind == ast.KindHeading {
dft = []byte("heading")
}
return p.GenerateWithDefault(value, dft)
}
// Generate generates a new element id.
func (p *prefixedIDs) GenerateWithDefault(value []byte, dft []byte) []byte {
result := common.CleanValue(value)
if len(result) == 0 {
result = dft
}
if !bytes.HasPrefix(result, []byte("user-content-")) {
result = append([]byte("user-content-"), result...)
}
if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok {
p.values[util.BytesToReadOnlyString(result)] = true
return result
}
for i := 1; ; i++ {
newResult := fmt.Sprintf("%s-%d", result, i)
if _, ok := p.values[newResult]; !ok {
p.values[newResult] = true
return []byte(newResult)
}
}
}
// Put puts a given element id to the used ids table.
func (p *prefixedIDs) Put(value []byte) {
p.values[util.BytesToReadOnlyString(value)] = true
}
func newPrefixedIDs() *prefixedIDs {
return &prefixedIDs{
values: map[string]bool{},
}
}
// NewTaskCheckBoxHTMLRenderer creates a TaskCheckBoxHTMLRenderer to render tasklists
// in the gitea form.
func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
r := &TaskCheckBoxHTMLRenderer{
Config: html.NewConfig(),
}
for _, opt := range opts {
opt.SetHTMLOption(&r.Config)
}
return r
}
// TaskCheckBoxHTMLRenderer is a renderer.NodeRenderer implementation that
// renders checkboxes in list items.
// Overrides the default goldmark one to present the gitea format
type TaskCheckBoxHTMLRenderer struct {
html.Config
}
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
}
func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
n := node.(*east.TaskCheckBox)
end := ">"
if r.XHTML {
end = " />"
}
var err error
if n.IsChecked {
_, err = w.WriteString(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled"` + end + `<label` + end + `</span>`)
} else {
_, err = w.WriteString(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled"` + end + `<label` + end + `</span>`)
}
if err != nil {
return ast.WalkStop, err
}
return ast.WalkContinue, nil
}

View File

@@ -7,161 +7,83 @@ package markdown
import (
"bytes"
"io"
"strings"
"sync"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/common"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
giteautil "code.gitea.io/gitea/modules/util"
"github.com/russross/blackfriday/v2"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util"
)
// Renderer is a extended version of underlying render object.
type Renderer struct {
blackfriday.Renderer
URLPrefix string
IsWiki bool
var converter goldmark.Markdown
var once = sync.Once{}
var urlPrefixKey = parser.NewContextKey()
var isWikiKey = parser.NewContextKey()
// NewGiteaParseContext creates a parser.Context with the gitea context set
func NewGiteaParseContext(urlPrefix string, isWiki bool) parser.Context {
pc := parser.NewContext(parser.WithIDs(newPrefixedIDs()))
pc.Set(urlPrefixKey, urlPrefix)
pc.Set(isWikiKey, isWiki)
return pc
}
var byteMailto = []byte("mailto:")
var htmlEscaper = [256][]byte{
'&': []byte("&amp;"),
'<': []byte("&lt;"),
'>': []byte("&gt;"),
'"': []byte("&quot;"),
}
func escapeHTML(w io.Writer, s []byte) {
var start, end int
for end < len(s) {
escSeq := htmlEscaper[s[end]]
if escSeq != nil {
_, _ = w.Write(s[start:end])
_, _ = w.Write(escSeq)
start = end + 1
}
end++
}
if start < len(s) && end <= len(s) {
_, _ = w.Write(s[start:end])
}
}
// RenderNode is a default renderer of a single node of a syntax tree. For
// block nodes it will be called twice: first time with entering=true, second
// time with entering=false, so that it could know when it's working on an open
// tag and when on close. It writes the result to w.
//
// The return value is a way to tell the calling walker to adjust its walk
// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
// can ask the walker to skip a subtree of this node by returning SkipChildren.
// The typical behavior is to return GoToNext, which asks for the usual
// traversal to the next node.
func (r *Renderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
switch node.Type {
case blackfriday.Image:
prefix := r.URLPrefix
if r.IsWiki {
prefix = util.URLJoin(prefix, "wiki", "raw")
}
prefix = strings.Replace(prefix, "/src/", "/media/", 1)
link := node.LinkData.Destination
if len(link) > 0 && !markup.IsLink(link) {
lnk := string(link)
lnk = util.URLJoin(prefix, lnk)
lnk = strings.Replace(lnk, " ", "+", -1)
link = []byte(lnk)
}
node.LinkData.Destination = link
// Render link around image only if parent is not link already
if node.Parent != nil && node.Parent.Type != blackfriday.Link {
if entering {
_, _ = w.Write([]byte(`<a href="`))
escapeHTML(w, link)
_, _ = w.Write([]byte(`">`))
return r.Renderer.RenderNode(w, node, entering)
}
s := r.Renderer.RenderNode(w, node, entering)
_, _ = w.Write([]byte(`</a>`))
return s
}
return r.Renderer.RenderNode(w, node, entering)
case blackfriday.Link:
// special case: this is not a link, a hash link or a mailto:, so it's a
// relative URL
link := node.LinkData.Destination
if len(link) > 0 && !markup.IsLink(link) &&
link[0] != '#' && !bytes.HasPrefix(link, byteMailto) &&
node.LinkData.Footnote == nil {
lnk := string(link)
if r.IsWiki {
lnk = util.URLJoin("wiki", lnk)
}
link = []byte(util.URLJoin(r.URLPrefix, lnk))
}
node.LinkData.Destination = link
return r.Renderer.RenderNode(w, node, entering)
case blackfriday.Text:
isListItem := false
for n := node.Parent; n != nil; n = n.Parent {
if n.Type == blackfriday.Item {
isListItem = true
break
}
}
if isListItem {
text := node.Literal
switch {
case bytes.HasPrefix(text, []byte("[ ] ")):
_, _ = w.Write([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`))
text = text[3:]
case bytes.HasPrefix(text, []byte("[x] ")):
_, _ = w.Write([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`))
text = text[3:]
}
node.Literal = text
}
}
return r.Renderer.RenderNode(w, node, entering)
}
const (
blackfridayExtensions = 0 |
blackfriday.NoIntraEmphasis |
blackfriday.Tables |
blackfriday.FencedCode |
blackfriday.Strikethrough |
blackfriday.NoEmptyLineBeforeBlock |
blackfriday.DefinitionLists |
blackfriday.Footnotes |
blackfriday.HeadingIDs |
blackfriday.AutoHeadingIDs
blackfridayHTMLFlags = 0 |
blackfriday.Smartypants
)
// RenderRaw renders Markdown to HTML without handling special links.
func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
renderer := &Renderer{
Renderer: blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
Flags: blackfridayHTMLFlags,
FootnoteAnchorPrefix: "user-content-",
HeadingIDPrefix: "user-content-",
}),
URLPrefix: urlPrefix,
IsWiki: wikiMarkdown,
once.Do(func() {
converter = goldmark.New(
goldmark.WithExtensions(extension.Table,
extension.Strikethrough,
extension.TaskList,
extension.DefinitionList,
common.FootnoteExtension,
extension.NewTypographer(
extension.WithTypographicSubstitutions(extension.TypographicSubstitutions{
extension.EnDash: nil,
extension.EmDash: nil,
}),
),
),
goldmark.WithParserOptions(
parser.WithAttribute(),
parser.WithAutoHeadingID(),
parser.WithASTTransformers(
util.Prioritized(&GiteaASTTransformer{}, 10000),
),
),
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
)
// Override the original Tasklist renderer!
converter.Renderer().AddOptions(
renderer.WithNodeRenderers(
util.Prioritized(NewTaskCheckBoxHTMLRenderer(), 1000),
),
)
if setting.Markdown.EnableHardLineBreak {
converter.Renderer().AddOptions(html.WithHardWraps())
}
})
pc := NewGiteaParseContext(urlPrefix, wikiMarkdown)
var buf bytes.Buffer
if err := converter.Convert(giteautil.NormalizeEOL(body), &buf, parser.WithContext(pc)); err != nil {
log.Error("Unable to render: %v", err)
}
exts := blackfridayExtensions
if setting.Markdown.EnableHardLineBreak {
exts |= blackfriday.HardLineBreak
}
// Need to normalize EOL to UNIX LF to have consistent results in rendering
body = blackfriday.Run(util.NormalizeEOL(body), blackfriday.WithRenderer(renderer), blackfriday.WithExtensions(exts))
return markup.SanitizeBytes(body)
return markup.SanitizeReader(&buf).Bytes()
}
var (
@@ -174,8 +96,7 @@ func init() {
}
// Parser implements markup.Parser
type Parser struct {
}
type Parser struct{}
// Name implements markup.Parser
func (Parser) Name() string {

View File

@@ -98,16 +98,12 @@ func TestRender_Images(t *testing.T) {
func testAnswers(baseURLContent, baseURLImages string) []string {
return []string{
`<p>Wiki! Enjoy :)</p>
<ul>
<li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
<li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li>
</ul>
<p>See commit <a href="http://localhost:3000/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
<p>Ideas and codes</p>
<ul>
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" rel="nofollow">ocornut/imgui#786</a></li>
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" rel="nofollow">#786</a></li>
@@ -117,13 +113,9 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
</ul>
`,
`<h2 id="user-content-what-is-wine-staging">What is Wine Staging?</h2>
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
<h2 id="user-content-quick-links">Quick Links</h2>
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
<table>
<thead>
<tr>
@@ -131,7 +123,6 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
<th><a href="` + baseURLContent + `/Installation" rel="nofollow">Installation</a></th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="` + baseURLImages + `/images/icon-usage.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-usage.png" title="icon-usage.png" alt="images/icon-usage.png"/></a></td>
@@ -141,20 +132,15 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
</table>
`,
`<p><a href="http://www.excelsiorjet.com/" rel="nofollow">Excelsior JET</a> allows you to create native executables for Windows, Linux and Mac OS X.</p>
<ol>
<li><a href="https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop" rel="nofollow">Package your libGDX application</a>
<a href="` + baseURLImages + `/images/1.png" rel="nofollow"><img src="` + baseURLImages + `/images/1.png" title="1.png" alt="images/1.png"/></a></li>
<li>Perform a test run by hitting the Run! button.
<a href="` + baseURLImages + `/images/2.png" rel="nofollow"><img src="` + baseURLImages + `/images/2.png" title="2.png" alt="images/2.png"/></a></li>
</ol>
<h2 id="user-content-custom-id">More tests</h2>
<p>(from <a href="https://www.markdownguide.org/extended-syntax/" rel="nofollow">https://www.markdownguide.org/extended-syntax/</a>)</p>
<h3 id="user-content-definition-list">Definition list</h3>
<dl>
<dt>First Term</dt>
<dd>This is the definition of the first term.</dd>
@@ -162,27 +148,21 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
<dd>This is one definition of the second term.</dd>
<dd>This is another definition of the second term.</dd>
</dl>
<h3 id="user-content-footnotes">Footnotes</h3>
<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2</a></sup></p>
<div>
<hr/>
<ol>
<li id="fn:user-content-1">This is the first footnote.</li>
<li id="fn:user-content-bignote"><p>Here is one with multiple paragraphs and code.</p>
<li id="fn:user-content-1">
<p>This is the first footnote. <a href="#fnref:user-content-1" rel="nofollow">↩︎</a></p>
</li>
<li id="fn:user-content-bignote">
<p>Here is one with multiple paragraphs and code.</p>
<p>Indent paragraphs to include them in the footnote.</p>
<p><code>{ my code }</code></p>
<p>Add as many paragraphs as you like.</p></li>
<p>Add as many paragraphs as you like. <a href="#fnref:user-content-bignote" rel="nofollow">↩︎</a></p>
</li>
</ol>
</div>
`,
}
@@ -299,15 +279,15 @@ func TestRender_RenderParagraphs(t *testing.T) {
test := func(t *testing.T, str string, cnt int) {
unix := []byte(str)
res := string(RenderRaw(unix, "", false))
assert.Equal(t, strings.Count(res, "<p"), cnt)
assert.Equal(t, strings.Count(res, "<p"), cnt, "Rendered result for unix should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
mac := []byte(strings.ReplaceAll(str, "\n", "\r"))
res = string(RenderRaw(mac, "", false))
assert.Equal(t, strings.Count(res, "<p"), cnt)
assert.Equal(t, strings.Count(res, "<p"), cnt, "Rendered result for mac should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
dos := []byte(strings.ReplaceAll(str, "\n", "\r\n"))
res = string(RenderRaw(dos, "", false))
assert.Equal(t, strings.Count(res, "<p"), cnt)
assert.Equal(t, strings.Count(res, "<p"), cnt, "Rendered result for windows should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
}
test(t, "\nOne\nTwo\nThree", 1)

View File

@@ -6,33 +6,86 @@ package mdstripper
import (
"bytes"
"sync"
"io"
"github.com/russross/blackfriday/v2"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/common"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
)
// MarkdownStripper extends blackfriday.Renderer
type MarkdownStripper struct {
links []string
coallesce bool
empty bool
type stripRenderer struct {
links []string
empty bool
}
const (
blackfridayExtensions = 0 |
blackfriday.NoIntraEmphasis |
blackfriday.Tables |
blackfriday.FencedCode |
blackfriday.Strikethrough |
blackfriday.NoEmptyLineBeforeBlock |
blackfriday.DefinitionLists |
blackfriday.Footnotes |
blackfriday.HeadingIDs |
blackfriday.AutoHeadingIDs |
// Not included in modules/markup/markdown/markdown.go;
// required here to process inline links
blackfriday.Autolink
)
func (r *stripRenderer) Render(w io.Writer, source []byte, doc ast.Node) error {
return ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
switch v := n.(type) {
case *ast.Text:
if !v.IsRaw() {
_, prevSibIsText := n.PreviousSibling().(*ast.Text)
coalesce := prevSibIsText
r.processString(
w,
v.Text(source),
coalesce)
if v.SoftLineBreak() {
r.doubleSpace(w)
}
}
return ast.WalkContinue, nil
case *ast.Link:
r.processLink(w, v.Destination)
return ast.WalkSkipChildren, nil
case *ast.AutoLink:
r.processLink(w, v.URL(source))
return ast.WalkSkipChildren, nil
}
return ast.WalkContinue, nil
})
}
func (r *stripRenderer) doubleSpace(w io.Writer) {
if !r.empty {
_, _ = w.Write([]byte{'\n'})
}
}
func (r *stripRenderer) processString(w io.Writer, text []byte, coalesce bool) {
// Always break-up words
if !coalesce {
r.doubleSpace(w)
}
_, _ = w.Write(text)
r.empty = false
}
func (r *stripRenderer) processLink(w io.Writer, link []byte) {
// Links are processed out of band
r.links = append(r.links, string(link))
}
// GetLinks returns the list of link data collected while parsing
func (r *stripRenderer) GetLinks() []string {
return r.links
}
// AddOptions adds given option to this renderer.
func (r *stripRenderer) AddOptions(...renderer.Option) {
// no-op
}
// StripMarkdown parses markdown content by removing all markup and code blocks
// in order to extract links and other references
@@ -41,78 +94,40 @@ func StripMarkdown(rawBytes []byte) (string, []string) {
return string(buf), links
}
var stripParser parser.Parser
var once = sync.Once{}
// StripMarkdownBytes parses markdown content by removing all markup and code blocks
// in order to extract links and other references
func StripMarkdownBytes(rawBytes []byte) ([]byte, []string) {
stripper := &MarkdownStripper{
once.Do(func() {
gdMarkdown := goldmark.New(
goldmark.WithExtensions(extension.Table,
extension.Strikethrough,
extension.TaskList,
extension.DefinitionList,
common.FootnoteExtension,
common.Linkify,
),
goldmark.WithParserOptions(
parser.WithAttribute(),
parser.WithAutoHeadingID(),
),
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
)
stripParser = gdMarkdown.Parser()
})
stripper := &stripRenderer{
links: make([]string, 0, 10),
empty: true,
}
parser := blackfriday.New(blackfriday.WithRenderer(stripper), blackfriday.WithExtensions(blackfridayExtensions))
ast := parser.Parse(rawBytes)
reader := text.NewReader(rawBytes)
doc := stripParser.Parse(reader)
var buf bytes.Buffer
stripper.RenderHeader(&buf, ast)
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
return stripper.RenderNode(&buf, node, entering)
})
stripper.RenderFooter(&buf, ast)
if err := stripper.Render(&buf, rawBytes, doc); err != nil {
log.Error("Unable to strip: %v", err)
}
return buf.Bytes(), stripper.GetLinks()
}
// RenderNode is the main rendering method. It will be called once for
// every leaf node and twice for every non-leaf node (first with
// entering=true, then with entering=false). The method should write its
// rendition of the node to the supplied writer w.
func (r *MarkdownStripper) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
if !entering {
return blackfriday.GoToNext
}
switch node.Type {
case blackfriday.Text:
r.processString(w, node.Literal, node.Parent == nil)
return blackfriday.GoToNext
case blackfriday.Link:
r.processLink(w, node.LinkData.Destination)
r.coallesce = false
return blackfriday.SkipChildren
}
r.coallesce = false
return blackfriday.GoToNext
}
// RenderHeader is a method that allows the renderer to produce some
// content preceding the main body of the output document.
func (r *MarkdownStripper) RenderHeader(w io.Writer, ast *blackfriday.Node) {
}
// RenderFooter is a symmetric counterpart of RenderHeader.
func (r *MarkdownStripper) RenderFooter(w io.Writer, ast *blackfriday.Node) {
}
func (r *MarkdownStripper) doubleSpace(w io.Writer) {
if !r.empty {
_, _ = w.Write([]byte{'\n'})
}
}
func (r *MarkdownStripper) processString(w io.Writer, text []byte, coallesce bool) {
// Always break-up words
if !coallesce || !r.coallesce {
r.doubleSpace(w)
}
_, _ = w.Write(text)
r.coallesce = coallesce
r.empty = false
}
func (r *MarkdownStripper) processLink(w io.Writer, link []byte) {
// Links are processed out of band
r.links = append(r.links, string(link))
r.coallesce = false
}
// GetLinks returns the list of link data collected while parsing
func (r *MarkdownStripper) GetLinks() []string {
return r.links
}

View File

@@ -53,6 +53,20 @@ A HIDDEN ` + "`" + `GHOST` + "`" + ` IN THIS LINE.
[]string{
"link",
}},
{
"Simply closes: #29 yes",
[]string{
"Simply closes: #29 yes",
},
[]string{},
},
{
"Simply closes: !29 yes",
[]string{
"Simply closes: !29 yes",
},
[]string{},
},
}
for _, test := range list {

View File

@@ -6,6 +6,8 @@
package markup
import (
"bytes"
"io"
"regexp"
"sync"
@@ -67,6 +69,12 @@ func Sanitize(s string) string {
return sanitizer.policy.Sanitize(s)
}
// SanitizeReader sanitizes a Reader
func SanitizeReader(r io.Reader) *bytes.Buffer {
NewSanitizer()
return sanitizer.policy.SanitizeReader(r)
}
// SanitizeBytes takes a []byte slice that contains a HTML fragment or document and applies policy whitelist.
func SanitizeBytes(b []byte) []byte {
if len(b) == 0 {

View File

@@ -53,6 +53,7 @@ func (m *mailNotifier) NotifyNewIssue(issue *models.Issue) {
func (m *mailNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, actionComment *models.Comment, isClosed bool) {
var actionType models.ActionType
issue.Content = ""
if issue.IsPull {
if isClosed {
actionType = models.ActionClosePullRequest
@@ -105,7 +106,7 @@ func (m *mailNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mode
log.Error("pr.LoadIssue: %v", err)
return
}
pr.Issue.Content = ""
if err := mailer.MailParticipants(pr.Issue, doer, models.ActionMergePullRequest); err != nil {
log.Error("MailParticipants: %v", err)
}

View File

@@ -43,10 +43,6 @@ func TestFindAllIssueReferences(t *testing.T) {
{29, "", "", "29", true, XRefActionCloses, &RefSpan{Start: 15, End: 18}, &RefSpan{Start: 7, End: 13}},
},
},
{
"#123 no, this is a title.",
[]testResult{},
},
{
" #124 yes, this is a reference.",
[]testResult{

View File

@@ -60,9 +60,14 @@ var (
// Pull request settings
PullRequest struct {
WorkInProgressPrefixes []string
CloseKeywords []string
ReopenKeywords []string
WorkInProgressPrefixes []string
CloseKeywords []string
ReopenKeywords []string
DefaultMergeMessageCommitsLimit int
DefaultMergeMessageSize int
DefaultMergeMessageAllAuthors bool
DefaultMergeMessageMaxApprovers int
DefaultMergeMessageOfficialApproversOnly bool
} `ini:"repository.pull-request"`
// Issue Setting
@@ -127,15 +132,25 @@ var (
// Pull request settings
PullRequest: struct {
WorkInProgressPrefixes []string
CloseKeywords []string
ReopenKeywords []string
WorkInProgressPrefixes []string
CloseKeywords []string
ReopenKeywords []string
DefaultMergeMessageCommitsLimit int
DefaultMergeMessageSize int
DefaultMergeMessageAllAuthors bool
DefaultMergeMessageMaxApprovers int
DefaultMergeMessageOfficialApproversOnly bool
}{
WorkInProgressPrefixes: []string{"WIP:", "[WIP]"},
// Same as GitHub. See
// https://help.github.com/articles/closing-issues-via-commit-messages
CloseKeywords: strings.Split("close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved", ","),
ReopenKeywords: strings.Split("reopen,reopens,reopened", ","),
CloseKeywords: strings.Split("close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved", ","),
ReopenKeywords: strings.Split("reopen,reopens,reopened", ","),
DefaultMergeMessageCommitsLimit: 50,
DefaultMergeMessageSize: 5 * 1024,
DefaultMergeMessageAllAuthors: false,
DefaultMergeMessageMaxApprovers: 10,
DefaultMergeMessageOfficialApproversOnly: true,
},
// Issue settings

View File

@@ -34,15 +34,19 @@ type Hook struct {
// HookList represents a list of API hook.
type HookList []*Hook
// CreateHookOptionConfig has all config options in it
// required are "content_type" and "url" Required
type CreateHookOptionConfig map[string]string
// CreateHookOption options when create a hook
type CreateHookOption struct {
// required: true
// enum: gitea,gogs,slack,discord
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram
Type string `json:"type" binding:"Required"`
// required: true
Config map[string]string `json:"config" binding:"Required"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
Config CreateHookOptionConfig `json:"config" binding:"Required"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
// default: false
Active bool `json:"active"`
}

View File

@@ -13,8 +13,8 @@ type EditReactionOption struct {
Reaction string `json:"content"`
}
// ReactionResponse contain one reaction
type ReactionResponse struct {
// Reaction contain one reaction
type Reaction struct {
User *User `json:"user"`
Reaction string `json:"content"`
// swagger:strfmt date-time

View File

@@ -132,7 +132,7 @@ func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) {
}
func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter)
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true)
return &DingtalkPayload{
MsgType: "actionCard",
@@ -148,7 +148,7 @@ func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) {
}
func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) {
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter)
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true)
return &DingtalkPayload{
MsgType: "actionCard",
@@ -163,7 +163,7 @@ func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayloa
}
func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter)
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
return &DingtalkPayload{
MsgType: "actionCard",
@@ -236,7 +236,7 @@ func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, e
}
func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error) {
text, _ := getReleasePayloadInfo(p, noneLinkFormatter)
text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true)
return &DingtalkPayload{
MsgType: "actionCard",

View File

@@ -227,7 +227,7 @@ func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPaylo
}
func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPayload, error) {
text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter)
text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
return &DiscordPayload{
Username: meta.Username,
@@ -249,7 +249,7 @@ func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPa
}
func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) {
text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter)
text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false)
return &DiscordPayload{
Username: discord.Username,
@@ -271,7 +271,7 @@ func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordM
}
func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) {
text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter)
text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
return &DiscordPayload{
Username: meta.Username,
@@ -368,7 +368,7 @@ func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*
}
func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*DiscordPayload, error) {
text, color := getReleasePayloadInfo(p, noneLinkFormatter)
text, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
return &DiscordPayload{
Username: meta.Username,

View File

@@ -25,8 +25,7 @@ func htmlLinkFormatter(url string, text string) string {
return fmt.Sprintf(`<a href="%s">%s</a>`, url, html.EscapeString(text))
}
func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter) (string, string, string, int) {
senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)
titleLink := linkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index), issueTitle)
@@ -35,34 +34,36 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter) (str
switch p.Action {
case api.HookIssueOpened:
text = fmt.Sprintf("[%s] Issue opened: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue opened: %s", repoLink, titleLink)
color = orangeColor
case api.HookIssueClosed:
text = fmt.Sprintf("[%s] Issue closed: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue closed: %s", repoLink, titleLink)
color = redColor
case api.HookIssueReOpened:
text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue re-opened: %s", repoLink, titleLink)
case api.HookIssueEdited:
text = fmt.Sprintf("[%s] Issue edited: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue edited: %s", repoLink, titleLink)
case api.HookIssueAssigned:
text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", repoLink,
linkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName),
titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue assigned to %s: %s", repoLink,
linkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName), titleLink)
color = greenColor
case api.HookIssueUnassigned:
text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue unassigned: %s", repoLink, titleLink)
case api.HookIssueLabelUpdated:
text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue labels updated: %s", repoLink, titleLink)
case api.HookIssueLabelCleared:
text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue labels cleared: %s", repoLink, titleLink)
case api.HookIssueSynchronized:
text = fmt.Sprintf("[%s] Issue synchronized: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue synchronized: %s", repoLink, titleLink)
case api.HookIssueMilestoned:
mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID)
text = fmt.Sprintf("[%s] Issue milestoned to %s: %s by %s", repoLink,
linkFormatter(mileStoneLink, p.Issue.Milestone.Title), titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue milestoned to %s: %s", repoLink,
linkFormatter(mileStoneLink, p.Issue.Milestone.Title), titleLink)
case api.HookIssueDemilestoned:
text = fmt.Sprintf("[%s] Issue milestone cleared: %s by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink)
}
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
}
var attachmentText string
@@ -73,8 +74,7 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter) (str
return text, issueTitle, attachmentText, color
}
func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter) (string, string, string, int) {
senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
issueTitle := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
titleLink := linkFormatter(p.PullRequest.URL, issueTitle)
@@ -83,43 +83,45 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
switch p.Action {
case api.HookIssueOpened:
text = fmt.Sprintf("[%s] Pull request %s opened by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request opened: %s", repoLink, titleLink)
color = greenColor
case api.HookIssueClosed:
if p.PullRequest.HasMerged {
text = fmt.Sprintf("[%s] Pull request %s merged by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request merged: %s", repoLink, titleLink)
color = purpleColor
} else {
text = fmt.Sprintf("[%s] Pull request %s closed by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request closed: %s", repoLink, titleLink)
color = redColor
}
case api.HookIssueReOpened:
text = fmt.Sprintf("[%s] Pull request %s re-opened by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request re-opened: %s", repoLink, titleLink)
case api.HookIssueEdited:
text = fmt.Sprintf("[%s] Pull request %s edited by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request edited: %s", repoLink, titleLink)
case api.HookIssueAssigned:
list := make([]string, len(p.PullRequest.Assignees))
for i, user := range p.PullRequest.Assignees {
list[i] = linkFormatter(setting.AppURL+user.UserName, user.UserName)
}
text = fmt.Sprintf("[%s] Pull request %s assigned to %s by %s", repoLink,
strings.Join(list, ", "),
titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request assigned: %s to %s", repoLink,
strings.Join(list, ", "), titleLink)
color = greenColor
case api.HookIssueUnassigned:
text = fmt.Sprintf("[%s] Pull request %s unassigned by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request unassigned: %s", repoLink, titleLink)
case api.HookIssueLabelUpdated:
text = fmt.Sprintf("[%s] Pull request %s labels updated by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request labels updated: %s", repoLink, titleLink)
case api.HookIssueLabelCleared:
text = fmt.Sprintf("[%s] Pull request %s labels cleared by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request labels cleared: %s", repoLink, titleLink)
case api.HookIssueSynchronized:
text = fmt.Sprintf("[%s] Pull request %s synchronized by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request synchronized: %s", repoLink, titleLink)
case api.HookIssueMilestoned:
mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID)
text = fmt.Sprintf("[%s] Pull request %s milestoned to %s by %s", repoLink,
linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request milestoned: %s to %s", repoLink,
linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink)
case api.HookIssueDemilestoned:
text = fmt.Sprintf("[%s] Pull request %s milestone cleared by %s", repoLink, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink)
}
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
}
var attachmentText string
@@ -130,28 +132,29 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
return text, issueTitle, attachmentText, color
}
func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter) (text string, color int) {
senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
refLink := linkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
switch p.Action {
case api.HookReleasePublished:
text = fmt.Sprintf("[%s] Release %s created by %s", repoLink, refLink, senderLink)
text = fmt.Sprintf("[%s] Release created: %s", repoLink, refLink)
color = greenColor
case api.HookReleaseUpdated:
text = fmt.Sprintf("[%s] Release %s updated by %s", repoLink, refLink, senderLink)
text = fmt.Sprintf("[%s] Release updated: %s", repoLink, refLink)
color = yellowColor
case api.HookReleaseDeleted:
text = fmt.Sprintf("[%s] Release %s deleted by %s", repoLink, refLink, senderLink)
text = fmt.Sprintf("[%s] Release deleted: %s", repoLink, refLink)
color = redColor
}
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
}
return text, color
}
func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFormatter) (string, string, int) {
senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFormatter, withSender bool) (string, string, int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
issueTitle := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)
@@ -168,18 +171,21 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo
switch p.Action {
case api.HookIssueCommentCreated:
text = fmt.Sprintf("[%s] New comment on %s %s by %s", repoLink, typ, titleLink, senderLink)
text = fmt.Sprintf("[%s] New comment on %s %s", repoLink, typ, titleLink)
if p.IsPull {
color = greenColorLight
} else {
color = orangeColorLight
}
case api.HookIssueCommentEdited:
text = fmt.Sprintf("[%s] Comment on %s %s edited by %s", repoLink, typ, titleLink, senderLink)
text = fmt.Sprintf("[%s] Comment edited on %s %s", repoLink, typ, titleLink)
case api.HookIssueCommentDeleted:
text = fmt.Sprintf("[%s] Comment on %s %s deleted by %s", repoLink, typ, titleLink, senderLink)
text = fmt.Sprintf("[%s] Comment deleted on %s %s", repoLink, typ, titleLink)
color = redColor
}
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
}
return text, issueTitle, color
}

View File

@@ -266,7 +266,7 @@ func getMSTeamsPushPayload(p *api.PushPayload) (*MSTeamsPayload, error) {
}
func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) {
text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter)
text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
return &MSTeamsPayload{
Type: "MessageCard",
@@ -308,7 +308,7 @@ func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) {
}
func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, error) {
text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter)
text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false)
return &MSTeamsPayload{
Type: "MessageCard",
@@ -350,7 +350,7 @@ func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload,
}
func getMSTeamsPullRequestPayload(p *api.PullRequestPayload) (*MSTeamsPayload, error) {
text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter)
text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
return &MSTeamsPayload{
Type: "MessageCard",
@@ -503,7 +503,7 @@ func getMSTeamsRepositoryPayload(p *api.RepositoryPayload) (*MSTeamsPayload, err
}
func getMSTeamsReleasePayload(p *api.ReleasePayload) (*MSTeamsPayload, error) {
text, color := getReleasePayloadInfo(p, noneLinkFormatter)
text, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
return &MSTeamsPayload{
Type: "MessageCard",

View File

@@ -144,7 +144,7 @@ func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, e
}
func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload, error) {
text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter)
text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true)
pl := &SlackPayload{
Channel: slack.Channel,
@@ -167,7 +167,7 @@ func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload
}
func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) {
text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter)
text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter, true)
return &SlackPayload{
Channel: slack.Channel,
@@ -184,7 +184,7 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (
}
func getSlackReleasePayload(p *api.ReleasePayload, slack *SlackMeta) (*SlackPayload, error) {
text, _ := getReleasePayloadInfo(p, SlackLinkFormatter)
text, _ := getReleasePayloadInfo(p, SlackLinkFormatter, true)
return &SlackPayload{
Channel: slack.Channel,
@@ -239,7 +239,7 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
}
func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) {
text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter)
text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true)
pl := &SlackPayload{
Channel: slack.Channel,

View File

@@ -70,7 +70,7 @@ func TestSlackReleasePayload(t *testing.T) {
require.Nil(t, err)
require.NotNil(t, pl)
assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Release <http://localhost:3000/test/repo/src/v1.0|v1.0> created by <https://try.gitea.io/user1|user1>", pl.Text)
assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Release created: <http://localhost:3000/test/repo/src/v1.0|v1.0> by <https://try.gitea.io/user1|user1>", pl.Text)
}
func TestSlackPullRequestPayload(t *testing.T) {
@@ -84,5 +84,5 @@ func TestSlackPullRequestPayload(t *testing.T) {
require.Nil(t, err)
require.NotNil(t, pl)
assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Pull request <http://localhost:3000/test/repo/pulls/12|#2 Fix bug> opened by <https://try.gitea.io/user1|user1>", pl.Text)
assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Pull request opened: <http://localhost:3000/test/repo/pulls/12|#2 Fix bug> by <https://try.gitea.io/user1|user1>", pl.Text)
}

View File

@@ -125,7 +125,7 @@ func getTelegramPushPayload(p *api.PushPayload) (*TelegramPayload, error) {
}
func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) {
text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter)
text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true)
return &TelegramPayload{
Message: text + "\n\n" + attachmentText,
@@ -133,7 +133,7 @@ func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) {
}
func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayload, error) {
text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter)
text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true)
return &TelegramPayload{
Message: text + "\n" + p.Comment.Body,
@@ -141,7 +141,7 @@ func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayloa
}
func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload, error) {
text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter)
text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true)
return &TelegramPayload{
Message: text + "\n" + attachmentText,
@@ -166,7 +166,7 @@ func getTelegramRepositoryPayload(p *api.RepositoryPayload) (*TelegramPayload, e
}
func getTelegramReleasePayload(p *api.ReleasePayload) (*TelegramPayload, error) {
text, _ := getReleasePayloadInfo(p, htmlLinkFormatter)
text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true)
return &TelegramPayload{
Message: text + "\n",