Skip to content

Commit 9f44fa0

Browse files
committed
fix
1 parent 23687a0 commit 9f44fa0

19 files changed

+332
-116
lines changed

models/unittest/testdb.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ func CreateTestEngine(opts FixturesOptions) error {
206206
x, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate")
207207
if err != nil {
208208
if strings.Contains(err.Error(), "unknown driver") {
209-
return fmt.Errorf(`sqlite3 requires: import _ "github.com/mattn/go-sqlite3" or -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
209+
return fmt.Errorf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
210210
}
211211
return err
212212
}

modules/markup/html_commit.go

+19
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"code.gitea.io/gitea/modules/base"
11+
"code.gitea.io/gitea/modules/references"
1112
"code.gitea.io/gitea/modules/util"
1213

1314
"golang.org/x/net/html"
@@ -194,3 +195,21 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
194195
node = node.NextSibling.NextSibling
195196
}
196197
}
198+
199+
func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
200+
next := node.NextSibling
201+
202+
for node != nil && node != next {
203+
found, ref := references.FindRenderizableCommitCrossReference(node.Data)
204+
if !found {
205+
return
206+
}
207+
208+
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
209+
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp)
210+
link := createLink(ctx, linkHref, reftext, "commit")
211+
212+
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
213+
node = node.NextSibling.NextSibling
214+
}
215+
}

modules/markup/html_issue.go

+51-39
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
package markup
55

66
import (
7+
"strconv"
78
"strings"
89

9-
"code.gitea.io/gitea/modules/base"
1010
"code.gitea.io/gitea/modules/httplib"
1111
"code.gitea.io/gitea/modules/log"
1212
"code.gitea.io/gitea/modules/references"
@@ -16,8 +16,16 @@ import (
1616
"code.gitea.io/gitea/modules/util"
1717

1818
"golang.org/x/net/html"
19+
"golang.org/x/net/html/atom"
1920
)
2021

22+
type RenderIssueIconTitleOptions struct {
23+
OwnerName string
24+
RepoName string
25+
LinkHref string
26+
IssueIndex int64
27+
}
28+
2129
func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
2230
if ctx.RenderOptions.Metas == nil {
2331
return
@@ -66,6 +74,27 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
6674
}
6775
}
6876

77+
func createIssueLinkContentWithSummary(ctx *RenderContext, linkHref string, ref *references.RenderizableReference) *html.Node {
78+
if DefaultRenderHelperFuncs.RenderRepoIssueIconTitle == nil {
79+
return nil
80+
}
81+
issueIndex, _ := strconv.ParseInt(ref.Issue, 10, 64)
82+
h, err := DefaultRenderHelperFuncs.RenderRepoIssueIconTitle(ctx, RenderIssueIconTitleOptions{
83+
OwnerName: ref.Owner,
84+
RepoName: ref.Name,
85+
LinkHref: linkHref,
86+
IssueIndex: issueIndex,
87+
})
88+
if err != nil {
89+
log.Error("RenderRepoIssueIconTitle failed: %v", err)
90+
return nil
91+
}
92+
if h == "" {
93+
return nil
94+
}
95+
return &html.Node{Type: html.RawNode, Data: string(ctx.RenderInternal.ProtectSafeAttrs(h))}
96+
}
97+
6998
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
7099
if ctx.RenderOptions.Metas == nil {
71100
return
@@ -76,50 +105,46 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
76105
// old logic: crossLinkOnly := ctx.RenderOptions.Metas["mode"] == "document" && !ctx.IsWiki
77106
crossLinkOnly := ctx.RenderOptions.Metas["markupAllowShortIssuePattern"] != "true"
78107

79-
var (
80-
found bool
81-
ref *references.RenderizableReference
82-
)
108+
var ref *references.RenderizableReference
83109

84110
next := node.NextSibling
85-
86111
for node != nil && node != next {
87112
_, hasExtTrackFormat := ctx.RenderOptions.Metas["format"]
88113

89114
// Repos with external issue trackers might still need to reference local PRs
90115
// We need to concern with the first one that shows up in the text, whichever it is
91116
isNumericStyle := ctx.RenderOptions.Metas["style"] == "" || ctx.RenderOptions.Metas["style"] == IssueNameStyleNumeric
92-
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
117+
refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
93118

94119
switch ctx.RenderOptions.Metas["style"] {
95120
case "", IssueNameStyleNumeric:
96-
found, ref = foundNumeric, refNumeric
121+
ref = refNumeric
97122
case IssueNameStyleAlphanumeric:
98-
found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
123+
ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
99124
case IssueNameStyleRegexp:
100125
pattern, err := regexplru.GetCompiled(ctx.RenderOptions.Metas["regexp"])
101126
if err != nil {
102127
return
103128
}
104-
found, ref = references.FindRenderizableReferenceRegexp(node.Data, pattern)
129+
ref = references.FindRenderizableReferenceRegexp(node.Data, pattern)
105130
}
106131

107132
// Repos with external issue trackers might still need to reference local PRs
108133
// We need to concern with the first one that shows up in the text, whichever it is
109134
if hasExtTrackFormat && !isNumericStyle && refNumeric != nil {
110135
// If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that
111136
// Allow a free-pass when non-numeric pattern wasn't found.
112-
if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) {
113-
found = foundNumeric
137+
if ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start {
114138
ref = refNumeric
115139
}
116140
}
117-
if !found {
141+
142+
if ref == nil {
118143
return
119144
}
120145

121146
var link *html.Node
122-
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
147+
refText := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
123148
if hasExtTrackFormat && !ref.IsPull {
124149
ctx.RenderOptions.Metas["index"] = ref.Issue
125150

@@ -129,18 +154,23 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
129154
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
130155
}
131156

132-
link = createLink(ctx, res, reftext, "ref-issue ref-external-issue")
157+
link = createLink(ctx, res, refText, "ref-issue ref-external-issue")
133158
} else {
134159
// Path determines the type of link that will be rendered. It's unknown at this point whether
135160
// the linked item is actually a PR or an issue. Luckily it's of no real consequence because
136161
// Gitea will redirect on click as appropriate.
162+
issueOwner := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["user"], ref.Owner)
163+
issueRepo := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["repo"], ref.Name)
137164
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
138-
if ref.Owner == "" {
139-
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), LinkTypeApp)
140-
link = createLink(ctx, linkHref, reftext, "ref-issue")
141-
} else {
142-
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, issuePath, ref.Issue), LinkTypeApp)
143-
link = createLink(ctx, linkHref, reftext, "ref-issue")
165+
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(issueOwner, issueRepo, issuePath, ref.Issue), LinkTypeApp)
166+
167+
// at the moment, only render the issue index in a full line (or simple line) as icon+title
168+
// otherwise it would be too noisy for "take #1 as an example" in a sentence
169+
if node.Parent.DataAtom == atom.Li && ref.RefLocation.Start < 20 && ref.RefLocation.End == len(node.Data) {
170+
link = createIssueLinkContentWithSummary(ctx, linkHref, ref)
171+
}
172+
if link == nil {
173+
link = createLink(ctx, linkHref, refText, "ref-issue")
144174
}
145175
}
146176

@@ -168,21 +198,3 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
168198
node = node.NextSibling.NextSibling.NextSibling.NextSibling
169199
}
170200
}
171-
172-
func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
173-
next := node.NextSibling
174-
175-
for node != nil && node != next {
176-
found, ref := references.FindRenderizableCommitCrossReference(node.Data)
177-
if !found {
178-
return
179-
}
180-
181-
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
182-
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp)
183-
link := createLink(ctx, linkHref, reftext, "commit")
184-
185-
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
186-
node = node.NextSibling.NextSibling
187-
}
188-
}

modules/markup/html_issue_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package markup_test
5+
6+
import (
7+
"context"
8+
"html/template"
9+
"strings"
10+
"testing"
11+
12+
"code.gitea.io/gitea/modules/htmlutil"
13+
"code.gitea.io/gitea/modules/markup"
14+
"code.gitea.io/gitea/modules/markup/markdown"
15+
testModule "code.gitea.io/gitea/modules/test"
16+
17+
"github.com/stretchr/testify/assert"
18+
"github.com/stretchr/testify/require"
19+
)
20+
21+
func TestRender_IssueList(t *testing.T) {
22+
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
23+
markup.Init(&markup.RenderHelperFuncs{
24+
RenderRepoIssueIconTitle: func(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (template.HTML, error) {
25+
return htmlutil.HTMLFormat("<div>issue #%d</div>", opts.IssueIndex), nil
26+
},
27+
})
28+
29+
test := func(input, expected string) {
30+
rctx := markup.NewTestRenderContext(markup.TestAppURL, map[string]string{
31+
"user": "test-user", "repo": "test-repo",
32+
"markupAllowShortIssuePattern": "true",
33+
})
34+
out, err := markdown.RenderString(rctx, input)
35+
require.NoError(t, err)
36+
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out)))
37+
}
38+
39+
t.Run("NormalIssueRef", func(t *testing.T) {
40+
test(
41+
"#12345",
42+
`<p><a href="http://localhost:3000/test-user/test-repo/issues/12345" class="ref-issue" rel="nofollow">#12345</a></p>`,
43+
)
44+
})
45+
46+
t.Run("ListIssueRef", func(t *testing.T) {
47+
test(
48+
"* #12345",
49+
`<ul>
50+
<li><div>issue #12345</div></li>
51+
</ul>`,
52+
)
53+
})
54+
55+
t.Run("ListIssueRefNormal", func(t *testing.T) {
56+
test(
57+
"* foo #12345 bar",
58+
`<ul>
59+
<li>foo <a href="http://localhost:3000/test-user/test-repo/issues/12345" class="ref-issue" rel="nofollow">#12345</a> bar</li>
60+
</ul>`,
61+
)
62+
})
63+
64+
t.Run("ListTodoIssueRef", func(t *testing.T) {
65+
test(
66+
"* [ ] #12345",
67+
`<ul>
68+
<li class="task-list-item"><input type="checkbox" disabled="" data-source-position="2"/><div>issue #12345</div></li>
69+
</ul>`,
70+
)
71+
})
72+
}

modules/markup/render_helper.go

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type RenderHelper interface {
3838
type RenderHelperFuncs struct {
3939
IsUsernameMentionable func(ctx context.Context, username string) bool
4040
RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
41+
RenderRepoIssueIconTitle func(ctx context.Context, options RenderIssueIconTitleOptions) (template.HTML, error)
4142
}
4243

4344
var DefaultRenderHelperFuncs *RenderHelperFuncs

modules/references/references.go

+10-12
Original file line numberDiff line numberDiff line change
@@ -330,22 +330,22 @@ func FindAllIssueReferences(content string) []IssueReference {
330330
}
331331

332332
// FindRenderizableReferenceNumeric returns the first unvalidated reference found in a string.
333-
func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool) (bool, *RenderizableReference) {
333+
func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool) *RenderizableReference {
334334
var match []int
335335
if !crossLinkOnly {
336336
match = issueNumericPattern.FindStringSubmatchIndex(content)
337337
}
338338
if match == nil {
339339
if match = crossReferenceIssueNumericPattern.FindStringSubmatchIndex(content); match == nil {
340-
return false, nil
340+
return nil
341341
}
342342
}
343343
r := getCrossReference(util.UnsafeStringToBytes(content), match[2], match[3], false, prOnly)
344344
if r == nil {
345-
return false, nil
345+
return nil
346346
}
347347

348-
return true, &RenderizableReference{
348+
return &RenderizableReference{
349349
Issue: r.issue,
350350
Owner: r.owner,
351351
Name: r.name,
@@ -372,15 +372,14 @@ func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableRe
372372
}
373373

374374
// FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string.
375-
func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) {
375+
func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) *RenderizableReference {
376376
match := pattern.FindStringSubmatchIndex(content)
377377
if len(match) < 4 {
378-
return false, nil
378+
return nil
379379
}
380380

381381
action, location := findActionKeywords([]byte(content), match[2])
382-
383-
return true, &RenderizableReference{
382+
return &RenderizableReference{
384383
Issue: content[match[2]:match[3]],
385384
RefLocation: &RefSpan{Start: match[0], End: match[1]},
386385
Action: action,
@@ -390,15 +389,14 @@ func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bo
390389
}
391390

392391
// FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string.
393-
func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableReference) {
392+
func FindRenderizableReferenceAlphanumeric(content string) *RenderizableReference {
394393
match := issueAlphanumericPattern.FindStringSubmatchIndex(content)
395394
if match == nil {
396-
return false, nil
395+
return nil
397396
}
398397

399398
action, location := findActionKeywords([]byte(content), match[2])
400-
401-
return true, &RenderizableReference{
399+
return &RenderizableReference{
402400
Issue: content[match[2]:match[3]],
403401
RefLocation: &RefSpan{Start: match[2], End: match[3]},
404402
Action: action,

modules/references/references_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,10 @@ func TestFindAllIssueReferences(t *testing.T) {
249249
}
250250

251251
for _, fixture := range alnumFixtures {
252-
found, ref := FindRenderizableReferenceAlphanumeric(fixture.input)
252+
ref := FindRenderizableReferenceAlphanumeric(fixture.input)
253253
if fixture.issue == "" {
254-
assert.False(t, found, "Failed to parse: {%s}", fixture.input)
254+
assert.Nil(t, ref, "Failed to parse: {%s}", fixture.input)
255255
} else {
256-
assert.True(t, found, "Failed to parse: {%s}", fixture.input)
257256
assert.Equal(t, fixture.issue, ref.Issue, "Failed to parse: {%s}", fixture.input)
258257
assert.Equal(t, fixture.refLocation, ref.RefLocation, "Failed to parse: {%s}", fixture.input)
259258
assert.Equal(t, fixture.action, ref.Action, "Failed to parse: {%s}", fixture.input)

0 commit comments

Comments
 (0)