mirror of https://github.com/go-gitea/gitea
Add sub issue list support (#32940)
Just like GitHub, show issue icon/title when the issue number is in a listpull/32956/head^2
parent
02c64e48b7
commit
781c6df40f
@ -0,0 +1,72 @@ |
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package markup_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"html/template" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/htmlutil" |
||||||
|
"code.gitea.io/gitea/modules/markup" |
||||||
|
"code.gitea.io/gitea/modules/markup/markdown" |
||||||
|
testModule "code.gitea.io/gitea/modules/test" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
func TestRender_IssueList(t *testing.T) { |
||||||
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() |
||||||
|
markup.Init(&markup.RenderHelperFuncs{ |
||||||
|
RenderRepoIssueIconTitle: func(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (template.HTML, error) { |
||||||
|
return htmlutil.HTMLFormat("<div>issue #%d</div>", opts.IssueIndex), nil |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
test := func(input, expected string) { |
||||||
|
rctx := markup.NewTestRenderContext(markup.TestAppURL, map[string]string{ |
||||||
|
"user": "test-user", "repo": "test-repo", |
||||||
|
"markupAllowShortIssuePattern": "true", |
||||||
|
}) |
||||||
|
out, err := markdown.RenderString(rctx, input) |
||||||
|
require.NoError(t, err) |
||||||
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) |
||||||
|
} |
||||||
|
|
||||||
|
t.Run("NormalIssueRef", func(t *testing.T) { |
||||||
|
test( |
||||||
|
"#12345", |
||||||
|
`<p><a href="http://localhost:3000/test-user/test-repo/issues/12345" class="ref-issue" rel="nofollow">#12345</a></p>`, |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("ListIssueRef", func(t *testing.T) { |
||||||
|
test( |
||||||
|
"* #12345", |
||||||
|
`<ul> |
||||||
|
<li><div>issue #12345</div></li> |
||||||
|
</ul>`, |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("ListIssueRefNormal", func(t *testing.T) { |
||||||
|
test( |
||||||
|
"* foo #12345 bar", |
||||||
|
`<ul> |
||||||
|
<li>foo <a href="http://localhost:3000/test-user/test-repo/issues/12345" class="ref-issue" rel="nofollow">#12345</a> bar</li> |
||||||
|
</ul>`, |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("ListTodoIssueRef", func(t *testing.T) { |
||||||
|
test( |
||||||
|
"* [ ] #12345", |
||||||
|
`<ul> |
||||||
|
<li class="task-list-item"><input type="checkbox" disabled="" data-source-position="2"/><div>issue #12345</div></li> |
||||||
|
</ul>`, |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package markup |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"html/template" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/issues" |
||||||
|
"code.gitea.io/gitea/models/perm/access" |
||||||
|
"code.gitea.io/gitea/models/repo" |
||||||
|
"code.gitea.io/gitea/modules/htmlutil" |
||||||
|
"code.gitea.io/gitea/modules/markup" |
||||||
|
"code.gitea.io/gitea/modules/util" |
||||||
|
gitea_context "code.gitea.io/gitea/services/context" |
||||||
|
) |
||||||
|
|
||||||
|
func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (_ template.HTML, err error) { |
||||||
|
webCtx, ok := ctx.Value(gitea_context.WebContextKey).(*gitea_context.Context) |
||||||
|
if !ok { |
||||||
|
return "", fmt.Errorf("context is not a web context") |
||||||
|
} |
||||||
|
|
||||||
|
textIssueIndex := fmt.Sprintf("(#%d)", opts.IssueIndex) |
||||||
|
dbRepo := webCtx.Repo.Repository |
||||||
|
if opts.OwnerName != "" { |
||||||
|
dbRepo, err = repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
textIssueIndex = fmt.Sprintf("(%s/%s#%d)", dbRepo.OwnerName, dbRepo.Name, opts.IssueIndex) |
||||||
|
} |
||||||
|
if dbRepo == nil { |
||||||
|
return "", nil |
||||||
|
} |
||||||
|
|
||||||
|
issue, err := issues.GetIssueByIndex(ctx, dbRepo.ID, opts.IssueIndex) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
if webCtx.Repo.Repository == nil || dbRepo.ID != webCtx.Repo.Repository.ID { |
||||||
|
perms, err := access.GetUserRepoPermission(ctx, dbRepo, webCtx.Doer) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
if !perms.CanReadIssuesOrPulls(issue.IsPull) { |
||||||
|
return "", util.ErrPermissionDenied |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if issue.IsPull { |
||||||
|
if err = issue.LoadPullRequest(ctx); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
htmlIcon, err := webCtx.RenderToHTML("shared/issueicon", issue) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
return htmlutil.HTMLFormat(`<a href="%s">%s %s %s</a>`, opts.LinkHref, htmlIcon, issue.Title, textIssueIndex), nil |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package markup |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/repo" |
||||||
|
"code.gitea.io/gitea/models/unittest" |
||||||
|
"code.gitea.io/gitea/modules/markup" |
||||||
|
"code.gitea.io/gitea/modules/templates" |
||||||
|
"code.gitea.io/gitea/modules/util" |
||||||
|
"code.gitea.io/gitea/services/contexttest" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
) |
||||||
|
|
||||||
|
func TestRenderHelperIssueIconTitle(t *testing.T) { |
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||||
|
|
||||||
|
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()}) |
||||||
|
ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) |
||||||
|
htm, err := renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ |
||||||
|
LinkHref: "/link", |
||||||
|
IssueIndex: 1, |
||||||
|
}) |
||||||
|
assert.NoError(t, err) |
||||||
|
assert.Equal(t, `<a href="/link"><span>octicon-issue-opened(16/text green)</span> issue1 (#1)</a>`, string(htm)) |
||||||
|
|
||||||
|
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()}) |
||||||
|
htm, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ |
||||||
|
OwnerName: "user2", |
||||||
|
RepoName: "repo1", |
||||||
|
LinkHref: "/link", |
||||||
|
IssueIndex: 1, |
||||||
|
}) |
||||||
|
assert.NoError(t, err) |
||||||
|
assert.Equal(t, `<a href="/link"><span>octicon-issue-opened(16/text green)</span> issue1 (user2/repo1#1)</a>`, string(htm)) |
||||||
|
|
||||||
|
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()}) |
||||||
|
_, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ |
||||||
|
OwnerName: "user2", |
||||||
|
RepoName: "repo2", |
||||||
|
LinkHref: "/link", |
||||||
|
IssueIndex: 2, |
||||||
|
}) |
||||||
|
assert.ErrorIs(t, err, util.ErrPermissionDenied) |
||||||
|
} |
@ -1,25 +1,25 @@ |
|||||||
{{if .IsPull}} |
{{- if .IsPull -}} |
||||||
{{if not .PullRequest}} |
{{- if not .PullRequest -}} |
||||||
No PullRequest |
No PullRequest |
||||||
{{else}} |
{{- else -}} |
||||||
{{if .IsClosed}} |
{{- if .IsClosed -}} |
||||||
{{if .PullRequest.HasMerged}} |
{{- if .PullRequest.HasMerged -}} |
||||||
{{svg "octicon-git-merge" 16 "text purple"}} |
{{- svg "octicon-git-merge" 16 "text purple" -}} |
||||||
{{else}} |
{{- else -}} |
||||||
{{svg "octicon-git-pull-request" 16 "text red"}} |
{{- svg "octicon-git-pull-request" 16 "text red" -}} |
||||||
{{end}} |
{{- end -}} |
||||||
{{else}} |
{{- else -}} |
||||||
{{if .PullRequest.IsWorkInProgress ctx}} |
{{- if .PullRequest.IsWorkInProgress ctx -}} |
||||||
{{svg "octicon-git-pull-request-draft" 16 "text grey"}} |
{{- svg "octicon-git-pull-request-draft" 16 "text grey" -}} |
||||||
{{else}} |
{{- else -}} |
||||||
{{svg "octicon-git-pull-request" 16 "text green"}} |
{{- svg "octicon-git-pull-request" 16 "text green" -}} |
||||||
{{end}} |
{{- end -}} |
||||||
{{end}} |
{{- end -}} |
||||||
{{end}} |
{{- end -}} |
||||||
{{else}} |
{{- else -}} |
||||||
{{if .IsClosed}} |
{{- if .IsClosed -}} |
||||||
{{svg "octicon-issue-closed" 16 "text red"}} |
{{- svg "octicon-issue-closed" 16 "text red" -}} |
||||||
{{else}} |
{{- else -}} |
||||||
{{svg "octicon-issue-opened" 16 "text green"}} |
{{- svg "octicon-issue-opened" 16 "text green" -}} |
||||||
{{end}} |
{{- end -}} |
||||||
{{end}} |
{{- end -}} |
||||||
|
Loading…
Reference in new issue