diff --git a/Makefile b/Makefile index f273cac3a84ff..d97360c9f49ec 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ COMMA := , XGO_VERSION := go-1.22.x -AIR_PACKAGE ?= github.com/cosmtrek/air@v1 +AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.0 @@ -36,6 +36,7 @@ XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1 +GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3 DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -213,6 +214,7 @@ help: @echo " - lint-go lint go files" @echo " - lint-go-fix lint go files and fix issues" @echo " - lint-go-vet lint go files with vet" + @echo " - lint-go-gopls lint go files with gopls" @echo " - lint-js lint js files" @echo " - lint-js-fix lint js files and fix issues" @echo " - lint-css lint css files" @@ -366,7 +368,7 @@ lint-frontend: lint-js lint-css lint-frontend-fix: lint-js-fix lint-css-fix .PHONY: lint-backend -lint-backend: lint-go lint-go-vet lint-editorconfig +lint-backend: lint-go lint-go-vet lint-go-gopls lint-editorconfig .PHONY: lint-backend-fix lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig @@ -424,6 +426,11 @@ lint-go-vet: @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet @$(GO) vet -vettool=gitea-vet ./... +.PHONY: lint-go-gopls +lint-go-gopls: + @echo "Running gopls check..." + @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA) + .PHONY: lint-editorconfig lint-editorconfig: @$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES) @@ -864,6 +871,7 @@ deps-tools: $(GO) install $(GO_LICENSES_PACKAGE) $(GO) install $(GOVULNCHECK_PACKAGE) $(GO) install $(ACTIONLINT_PACKAGE) + $(GO) install $(GOPLS_PACKAGE) node_modules: package-lock.json npm install --no-save diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md index efeaf38bb2bb2..a08098a93152b 100644 --- a/docs/content/contributing/guidelines-frontend.en-us.md +++ b/docs/content/contributing/guidelines-frontend.en-us.md @@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h 9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided. 10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event. 11. Custom event names are recommended to use `ce-` prefix. -12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-word-break`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). +12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-ellipsis`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). 13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided. ### Accessibility / ARIA diff --git a/docs/content/contributing/guidelines-frontend.zh-cn.md b/docs/content/contributing/guidelines-frontend.zh-cn.md index 394097b259fda..198e1227e539e 100644 --- a/docs/content/contributing/guidelines-frontend.zh-cn.md +++ b/docs/content/contributing/guidelines-frontend.zh-cn.md @@ -47,7 +47,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。 9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。 10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。 11. 推荐使用自定义事件名称前缀`ce-`。 -12. 建议使用 Tailwind CSS,它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-word-break`),Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。 +12. 建议使用 Tailwind CSS,它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-ellipsis`),Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。 13. 尽量避免内联脚本和样式,建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免,请解释无法避免的原因。 ### 可访问性 / ARIA diff --git a/go.mod b/go.mod index 87f2b00e6ae4a..6f739ed6e9830 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/ProtonMail/go-crypto v1.0.0 github.com/PuerkitoBio/goquery v1.9.1 - github.com/alecthomas/chroma/v2 v2.13.0 + github.com/alecthomas/chroma/v2 v2.14.0 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blevesearch/bleve/v2 v2.3.10 github.com/buildkite/terminal-to-html/v3 v3.11.0 diff --git a/go.sum b/go.sum index 84f7121908b35..543bd70866681 100644 --- a/go.sum +++ b/go.sum @@ -82,11 +82,11 @@ github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06 github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I= github.com/RoaringBitmap/roaring v1.9.0 h1:lwKhr90/j0jVXJyh5X+vQN1VVn77rQFfYnh6RDRGCcE= github.com/RoaringBitmap/roaring v1.9.0/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= -github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= diff --git a/modules/markup/html.go b/modules/markup/html.go index 0af74d2680621..8dbc9582990b6 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -372,7 +372,42 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output return nil } -func visitNode(ctx *RenderContext, procs []processor, node *html.Node) { +func handleNodeImg(ctx *RenderContext, img *html.Node) { + for i, attr := range img.Attr { + if attr.Key != "src" { + continue + } + + if attr.Val != "" && !IsFullURLString(attr.Val) && !strings.HasPrefix(attr.Val, "/") { + attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val) + + // By default, the "" tag should also be clickable, + // because frontend use `` to paste the re-scaled image into the markdown, + // so it must match the default markdown image behavior. + hasParentAnchor := false + for p := img.Parent; p != nil; p = p.Parent { + if hasParentAnchor = p.Type == html.ElementNode && p.Data == "a"; hasParentAnchor { + break + } + } + if !hasParentAnchor { + imgA := &html.Node{Type: html.ElementNode, Data: "a", Attr: []html.Attribute{ + {Key: "href", Val: attr.Val}, + {Key: "target", Val: "_blank"}, + }} + parent := img.Parent + imgNext := img.NextSibling + parent.RemoveChild(img) + parent.InsertBefore(imgA, imgNext) + imgA.AppendChild(img) + } + } + attr.Val = camoHandleLink(attr.Val) + img.Attr[i] = attr + } +} + +func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Node { // Add user-content- to IDs and "#" links if they don't already have them for idx, attr := range node.Attr { val := strings.TrimPrefix(attr.Val, "#") @@ -397,21 +432,14 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) { textNode(ctx, procs, node) case html.ElementNode: if node.Data == "img" { - for i, attr := range node.Attr { - if attr.Key != "src" { - continue - } - if len(attr.Val) > 0 && !IsFullURLString(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { - attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val) - } - attr.Val = camoHandleLink(attr.Val) - node.Attr[i] = attr - } + next := node.NextSibling + handleNodeImg(ctx, node) + return next } else if node.Data == "a" { // Restrict text in links to emojis procs = emojiProcessors } else if node.Data == "code" || node.Data == "pre" { - return + return node.NextSibling } else if node.Data == "i" { for _, attr := range node.Attr { if attr.Key != "class" { @@ -434,11 +462,11 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) { } } } - for n := node.FirstChild; n != nil; n = n.NextSibling { - visitNode(ctx, procs, n) + for n := node.FirstChild; n != nil; { + n = visitNode(ctx, procs, n) } } - // ignore everything else + return node.NextSibling } // textNode runs the passed node through various processors, in order to handle @@ -851,7 +879,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered? // The "mode" approach should be refactored to some other more clear&reliable way. - crossLinkOnly := (ctx.Metas["mode"] == "document" && !ctx.IsWiki) + crossLinkOnly := ctx.Metas["mode"] == "document" && !ctx.IsWiki var ( found bool diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 3ff0597851bba..9aa9c22d701f4 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -18,8 +18,7 @@ import ( const ( TestAppURL = "http://localhost:3000/" - TestOrgRepo = "gogits/gogs" - TestRepoURL = TestAppURL + TestOrgRepo + "/" + TestRepoURL = TestAppURL + "test-owner/test-repo/" ) // externalIssueLink an HTML link to an alphanumeric-style issue @@ -64,8 +63,8 @@ var regexpMetas = map[string]string{ // these values should match the TestOrgRepo const above var localMetas = map[string]string{ - "user": "gogits", - "repo": "gogs", + "user": "test-owner", + "repo": "test-repo", } func TestRender_IssueIndexPattern(t *testing.T) { @@ -362,12 +361,12 @@ func TestRender_FullIssueURLs(t *testing.T) { `Look here person/repo#4`) test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", `person/repo#4 (comment)`) - test("http://localhost:3000/gogits/gogs/issues/4", - `#4`) - test("http://localhost:3000/gogits/gogs/issues/4 test", - `#4 test`) - test("http://localhost:3000/gogits/gogs/issues/4?a=1&b=2#comment-123 test", - `#4 (comment) test`) + test("http://localhost:3000/test-owner/test-repo/issues/4", + `#4`) + test("http://localhost:3000/test-owner/test-repo/issues/4 test", + `#4 test`) + test("http://localhost:3000/test-owner/test-repo/issues/4?a=1&b=2#comment-123 test", + `#4 (comment) test`) test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24", "http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24") test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/files", diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index e2d08692e4c3e..df3c2609ef4a3 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -120,8 +120,8 @@ func TestRender_CrossReferences(t *testing.T) { } test( - "gogits/gogs#12345", - `

gogits/gogs#12345

`) + "test-owner/test-repo#12345", + `

test-owner/test-repo#12345

`) test( "go-gitea/gitea#12345", `

go-gitea/gitea#12345

`) @@ -530,43 +530,31 @@ func TestRender_ShortLinks(t *testing.T) { } func TestRender_RelativeImages(t *testing.T) { - setting.AppURL = markup.TestAppURL - - test := func(input, expected, expectedWiki string) { + render := func(input string, isWiki bool, links markup.Links) string { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - Links: markup.Links{ - Base: markup.TestRepoURL, - BranchPath: "master", - }, - Metas: localMetas, - }, input) - assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) - buffer, err = markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - Links: markup.Links{ - Base: markup.TestRepoURL, - }, + Ctx: git.DefaultContext, + Links: links, Metas: localMetas, - IsWiki: true, + IsWiki: isWiki, }, input) assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer))) + return strings.TrimSpace(string(buffer)) } - rawwiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw") - mediatree := util.URLJoin(markup.TestRepoURL, "media", "master") + out := render(``, false, markup.Links{Base: "/test-owner/test-repo"}) + assert.Equal(t, ``, out) - test( - ``, - ``, - ``) + out = render(``, true, markup.Links{Base: "/test-owner/test-repo"}) + assert.Equal(t, ``, out) - test( - ``, - ``, - ``) + out = render(``, false, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) + assert.Equal(t, ``, out) + + out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) + assert.Equal(t, ``, out) + + out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) + assert.Equal(t, ``, out) } func Test_ParseClusterFuzz(t *testing.T) { @@ -719,5 +707,6 @@ func TestIssue18471(t *testing.T) { func TestIsFullURL(t *testing.T) { assert.True(t, markup.IsFullURLString("https://example.com")) assert.True(t, markup.IsFullURLString("mailto:test@example.com")) + assert.True(t, markup.IsFullURLString("data:image/11111")) assert.False(t, markup.IsFullURLString("/foo:bar")) } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index b4a7efa8dd876..8c41ec12e3fb1 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -1019,4 +1019,10 @@ func TestAttention(t *testing.T) { test(`> [!important]`, renderAttention("important", "octicon-report")+"\n") test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n") test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n") + + // escaped by mdformat + test(`> \[!NOTE\]`, renderAttention("note", "octicon-info")+"\n") + + // legacy GitHub style + test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n") } diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go index 7f714d7239292..37f6caf11ce2c 100644 --- a/modules/markup/markdown/math/block_parser.go +++ b/modules/markup/markdown/math/block_parser.go @@ -31,10 +31,16 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex return nil, parser.NoChildren } - dollars := false + var dollars bool if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' { dollars = true - } else if line[pos] != '\\' || line[pos+1] != '[' { + } else if line[pos] == '\\' && line[pos+1] == '[' { + if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) { + // do not process escaped attention block: "> \[!NOTE\]" + return nil, parser.NoChildren + } + dollars = false + } else { return nil, parser.NoChildren } diff --git a/modules/markup/markdown/transform_blockquote.go b/modules/markup/markdown/transform_blockquote.go index 933f0e5c59384..d2dc025052d00 100644 --- a/modules/markup/markdown/transform_blockquote.go +++ b/modules/markup/markdown/transform_blockquote.go @@ -15,7 +15,7 @@ import ( "golang.org/x/text/language" ) -// renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg +// renderAttention renders a quote marked with i.e. "> **Note**" or "> [!Warning]" with a corresponding svg func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if entering { n := node.(*Attention) @@ -37,38 +37,93 @@ func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast return ast.WalkContinue, nil } -func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Reader) (ast.WalkStatus, error) { - // We only want attention blockquotes when the AST looks like: - // > Text("[") Text("!TYPE") Text("]") +func (g *ASTTransformer) extractBlockquoteAttentionEmphasis(firstParagraph ast.Node, reader text.Reader) (string, []ast.Node) { + if firstParagraph.ChildCount() < 1 { + return "", nil + } + node1, ok := firstParagraph.FirstChild().(*ast.Emphasis) + if !ok { + return "", nil + } + val1 := string(node1.Text(reader.Source())) + attentionType := strings.ToLower(val1) + if g.attentionTypes.Contains(attentionType) { + return attentionType, []ast.Node{node1} + } + return "", nil +} - // grab these nodes and make sure we adhere to the attention blockquote structure - firstParagraph := v.FirstChild() - g.applyElementDir(firstParagraph) +func (g *ASTTransformer) extractBlockquoteAttention2(firstParagraph ast.Node, reader text.Reader) (string, []ast.Node) { + if firstParagraph.ChildCount() < 2 { + return "", nil + } + node1, ok := firstParagraph.FirstChild().(*ast.Text) + if !ok { + return "", nil + } + node2, ok := node1.NextSibling().(*ast.Text) + if !ok { + return "", nil + } + val1 := string(node1.Segment.Value(reader.Source())) + val2 := string(node2.Segment.Value(reader.Source())) + if strings.HasPrefix(val1, `\[!`) && val2 == `\]` { + attentionType := strings.ToLower(val1[3:]) + if g.attentionTypes.Contains(attentionType) { + return attentionType, []ast.Node{node1, node2} + } + } + return "", nil +} + +func (g *ASTTransformer) extractBlockquoteAttention3(firstParagraph ast.Node, reader text.Reader) (string, []ast.Node) { if firstParagraph.ChildCount() < 3 { - return ast.WalkContinue, nil + return "", nil } node1, ok := firstParagraph.FirstChild().(*ast.Text) if !ok { - return ast.WalkContinue, nil + return "", nil } node2, ok := node1.NextSibling().(*ast.Text) if !ok { - return ast.WalkContinue, nil + return "", nil } node3, ok := node2.NextSibling().(*ast.Text) if !ok { - return ast.WalkContinue, nil + return "", nil } val1 := string(node1.Segment.Value(reader.Source())) val2 := string(node2.Segment.Value(reader.Source())) val3 := string(node3.Segment.Value(reader.Source())) if val1 != "[" || val3 != "]" || !strings.HasPrefix(val2, "!") { - return ast.WalkContinue, nil + return "", nil } - // grab attention type from markdown source attentionType := strings.ToLower(val2[1:]) - if !g.attentionTypes.Contains(attentionType) { + if g.attentionTypes.Contains(attentionType) { + return attentionType, []ast.Node{node1, node2, node3} + } + return "", nil +} + +func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Reader) (ast.WalkStatus, error) { + // We only want attention blockquotes when the AST looks like: + // > Text("[") Text("!TYPE") Text("]") + // > Text("\[!TYPE") TEXT("\]") + // > Text("**TYPE**") + + // grab these nodes and make sure we adhere to the attention blockquote structure + firstParagraph := v.FirstChild() + g.applyElementDir(firstParagraph) + + attentionType, processedNodes := g.extractBlockquoteAttentionEmphasis(firstParagraph, reader) + if attentionType == "" { + attentionType, processedNodes = g.extractBlockquoteAttention2(firstParagraph, reader) + } + if attentionType == "" { + attentionType, processedNodes = g.extractBlockquoteAttention3(firstParagraph, reader) + } + if attentionType == "" { return ast.WalkContinue, nil } @@ -88,9 +143,9 @@ func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Read attentionParagraph.AppendChild(attentionParagraph, NewAttention(attentionType)) attentionParagraph.AppendChild(attentionParagraph, emphasis) firstParagraph.Parent().InsertBefore(firstParagraph.Parent(), firstParagraph, attentionParagraph) - firstParagraph.RemoveChild(firstParagraph, node1) - firstParagraph.RemoveChild(firstParagraph, node2) - firstParagraph.RemoveChild(firstParagraph, node3) + for _, processed := range processedNodes { + firstParagraph.RemoveChild(firstParagraph, processed) + } if firstParagraph.ChildCount() == 0 { firstParagraph.Parent().RemoveChild(firstParagraph.Parent(), firstParagraph) } diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 44dedf638bc60..66e8cf611d468 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -74,7 +74,7 @@ type RenderContext struct { Type string IsWiki bool Links Links - Metas map[string]string + Metas map[string]string // user, repo, mode(comment/document) DefaultLink string GitRepo *git.Repository Repo gitrepo.Repository diff --git a/package-lock.json b/package-lock.json index e20aafe671c91..52e5727575214 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "@citation-js/plugin-csl": "0.7.11", "@citation-js/plugin-software-formats": "0.6.1", "@github/markdown-toolbar-element": "2.2.3", - "@github/relative-time-element": "4.4.0", + "@github/relative-time-element": "4.4.1", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.9.0", @@ -1028,9 +1028,9 @@ "integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==" }, "node_modules/@github/relative-time-element": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.0.tgz", - "integrity": "sha512-CrI6oAecoahG7PF5dsgjdvlF5kCtusVMjg810EULD81TvnDsP+k/FRi/ClFubWLgBo4EGpr2EfvmumtqQFo7ow==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.1.tgz", + "integrity": "sha512-E2vRcIgDj8AHv/iHpQMLJ/RqKOJ704OXkKw6+Zdhk3X+kVQhOf3Wj8KVz4DfCQ1eOJR8XxY6XVv73yd+pjMfXA==" }, "node_modules/@github/text-expander-element": { "version": "2.6.1", diff --git a/package.json b/package.json index d7588e093f9a1..5add488bb68a6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@citation-js/plugin-csl": "0.7.11", "@citation-js/plugin-software-formats": "0.6.1", "@github/markdown-toolbar-element": "2.2.3", - "@github/relative-time-element": "4.4.0", + "@github/relative-time-element": "4.4.1", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.9.0", diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 26b0ae226e45b..3633d0d00704c 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -96,20 +96,34 @@ func FeedCapabilityResource(ctx *context.Context) { xmlResponse(ctx, http.StatusOK, Metadata) } -var searchTermExtract = regexp.MustCompile(`'([^']+)'`) +var ( + searchTermExtract = regexp.MustCompile(`'([^']+)'`) + searchTermExact = regexp.MustCompile(`\s+eq\s+'`) +) -func getSearchTerm(ctx *context.Context) string { +func getSearchTerm(ctx *context.Context) packages_model.SearchValue { searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'") - if searchTerm == "" { - // $filter contains a query like: - // (((Id ne null) and substringof('microsoft',tolower(Id))) - // We don't support these queries, just extract the search term. - match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter")) - if len(match) == 2 { - searchTerm = strings.TrimSpace(match[1]) + if searchTerm != "" { + return packages_model.SearchValue{ + Value: searchTerm, + ExactMatch: false, + } + } + + // $filter contains a query like: + // (((Id ne null) and substringof('microsoft',tolower(Id))) + // https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5 + // We don't support these queries, just extract the search term. + filter := ctx.FormTrim("$filter") + match := searchTermExtract.FindStringSubmatch(filter) + if len(match) == 2 { + return packages_model.SearchValue{ + Value: strings.TrimSpace(match[1]), + ExactMatch: searchTermExact.MatchString(filter), } } - return searchTerm + + return packages_model.SearchValue{} } // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs @@ -118,11 +132,9 @@ func SearchServiceV2(ctx *context.Context) { paginator := db.NewAbsoluteListOptions(skip, take) pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Type: packages_model.TypeNuGet, - Name: packages_model.SearchValue{ - Value: getSearchTerm(ctx), - }, + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeNuGet, + Name: getSearchTerm(ctx), IsInternal: optional.Some(false), Paginator: paginator, }) @@ -169,10 +181,8 @@ func SearchServiceV2(ctx *context.Context) { // http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351 func SearchServiceV2Count(ctx *context.Context) { count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Name: packages_model.SearchValue{ - Value: getSearchTerm(ctx), - }, + OwnerID: ctx.Package.Owner.ID, + Name: getSearchTerm(ctx), IsInternal: optional.Some(false), }) if err != nil { diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 5bb3056161290..21bdc68e73208 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -176,7 +176,7 @@ func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptio // migrateRepository will download information and then upload it to Uploader, this is a simple // process for small repository. For a big repository, save all the data to disk // before upload is better -func migrateRepository(ctx context.Context, doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { +func migrateRepository(_ context.Context, doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { if messenger == nil { messenger = base.NilMessenger } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 3d0bce18d01d3..9e0ff7ae1405a 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -285,7 +285,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName } // changeRepositoryName changes all corresponding setting from old repository name to new one. -func changeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newRepoName string) (err error) { +func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newRepoName string) (err error) { oldRepoName := repo.Name newRepoName = strings.ToLower(newRepoName) if err = repo_model.IsUsableRepoName(newRepoName); err != nil { @@ -347,7 +347,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo // local copy's origin accordingly. repoWorkingPool.CheckIn(fmt.Sprint(repo.ID)) - if err := changeRepositoryName(ctx, doer, repo, newRepoName); err != nil { + if err := changeRepositoryName(ctx, repo, newRepoName); err != nil { repoWorkingPool.CheckOut(fmt.Sprint(repo.ID)) return err } diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 4b27d87a4552f..69031e42ebe76 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -47,13 +47,13 @@ {{.ID}} - {{.Owner.Name}} + {{.Owner.Name}} {{if .Owner.Visibility.IsPrivate}} {{svg "octicon-lock"}} {{end}} - {{.Name}} + {{.Name}} {{if .IsArchived}} {{ctx.Locale.Tr "repo.desc.archived"}} {{end}} diff --git a/templates/package/content/container.tmpl b/templates/package/content/container.tmpl index fe393f4388968..138fedecb3fc3 100644 --- a/templates/package/content/container.tmpl +++ b/templates/package/content/container.tmpl @@ -54,7 +54,7 @@ {{end}} {{if .PackageDescriptor.Metadata.ImageLayers}}

{{ctx.Locale.Tr "packages.container.layers"}}

-
+
{{range .PackageDescriptor.Metadata.ImageLayers}} @@ -80,7 +80,7 @@ {{range $key, $value := .PackageDescriptor.Metadata.Labels}} - + {{end}} diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index 9424baf4939ef..4b8773477b4af 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -59,7 +59,7 @@ {{ctx.Locale.Tr "packages.settings.delete"}}
-
+
{{ctx.Locale.Tr "packages.settings.delete.notice" .PackageDescriptor.Package.Name .PackageDescriptor.Version.Version}}
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 45c8461218877..584462d2a24dd 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -66,7 +66,7 @@
{{range .Columns}} -
+
{{.NumIssues ctx}} @@ -152,7 +152,7 @@
{{range (index $.IssuesMap .ID)}} -
+
{{template "repo/issue/card" (dict "Issue" . "Page" $)}}
{{end}} diff --git a/templates/repo/code/recently_pushed_new_branches.tmpl b/templates/repo/code/recently_pushed_new_branches.tmpl index 7f613fcba7eae..025cc1a403d8e 100644 --- a/templates/repo/code/recently_pushed_new_branches.tmpl +++ b/templates/repo/code/recently_pushed_new_branches.tmpl @@ -1,6 +1,6 @@ {{range .RecentlyPushedNewBranches}} -
-
+
+
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}} {{$branchLink := HTMLFormat `%s` .BranchLink .BranchDisplayName}} {{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $branchLink $timeSince}} diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index ef76f3ed5d46b..ff82f2ca80360 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -5,7 +5,7 @@ {{template "base/alert" .}} {{template "repo/code/recently_pushed_new_branches" .}} {{if and (not .HideRepoInfo) (not .IsBlame)}} -
+
{{- $description := .Repository.DescriptionHTML ctx -}} {{if $description}}{{$description | RenderCodeBlock}}{{end}} {{if .Repository.Website}}{{.Repository.Website}}{{end}} diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 30edf825f1224..01b610b39db0b 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -7,7 +7,7 @@ {{if .PinnedIssues}}
{{range .PinnedIssues}} -
+
{{template "repo/issue/card" (dict "Issue" . "Page" $ "isPinnedIssueCard" true)}}
{{end}} diff --git a/templates/repo/issue/view_content/conversation.tmpl b/templates/repo/issue/view_content/conversation.tmpl index 43ec9d75c4176..ccea9b690d7e8 100644 --- a/templates/repo/issue/view_content/conversation.tmpl +++ b/templates/repo/issue/view_content/conversation.tmpl @@ -8,7 +8,7 @@
- {{$comment.TreePath}} + {{$comment.TreePath}} {{if $invalid}} {{ctx.Locale.Tr "repo.issues.review.outdated"}} diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 34548672b5dff..e5bf23faacf12 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -17,7 +17,7 @@
-

+

{{if $.PageIsSingleTag}}{{$release.Title}}{{else}}{{$release.Title}}{{end}} {{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "tw-flex"}} {{if $release.IsDraft}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 6c49f00094622..4f98133df3679 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -217,7 +217,7 @@

{{range .PushMirrors}} - +
{{$key}}{{$value}}{{$value}}
{{.RemoteAddress}}{{.RemoteAddress}} {{ctx.Locale.Tr "repo.settings.mirror_settings.direction.push"}} {{if .LastUpdateUnix}}{{DateTime "full" .LastUpdateUnix}}{{else}}{{ctx.Locale.Tr "never"}}{{end}} {{if .LastError}}
{{ctx.Locale.Tr "error"}}
{{end}}
diff --git a/templates/repo/wiki/revision.tmpl b/templates/repo/wiki/revision.tmpl index c5abf9f169c57..7745ee37ac87d 100644 --- a/templates/repo/wiki/revision.tmpl +++ b/templates/repo/wiki/revision.tmpl @@ -8,7 +8,7 @@
{{.revision}} {{svg "octicon-home"}} {{$title}} -
+
{{$timeSince := TimeSince .Author.When ctx.Locale}} {{ctx.Locale.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince}}
diff --git a/templates/shared/user/org_profile_avatar.tmpl b/templates/shared/user/org_profile_avatar.tmpl index d67f133abf62c..c0abcabff1b9d 100644 --- a/templates/shared/user/org_profile_avatar.tmpl +++ b/templates/shared/user/org_profile_avatar.tmpl @@ -2,7 +2,7 @@
-
+
{{ctx.AvatarUtils.Avatar . 100}} {{.DisplayName}} diff --git a/templates/shared/user/profile_big_avatar.tmpl b/templates/shared/user/profile_big_avatar.tmpl index 868f8d5a1305f..29c6eb0eb0bd8 100644 --- a/templates/shared/user/profile_big_avatar.tmpl +++ b/templates/shared/user/profile_big_avatar.tmpl @@ -11,7 +11,7 @@ {{end}}
-
+
{{if .ContextUser.FullName}}{{.ContextUser.FullName}}{{end}} {{.ContextUser.Name}} {{if .IsAdmin}} @@ -25,7 +25,7 @@ {{end}}
-
+
    {{if .UserBlocking}}
  • {{svg "octicon-circle-slash"}} {{ctx.Locale.Tr "user.block.blocked"}}
  • diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl index bf3b51ee3baa0..9790a7087a9c4 100644 --- a/templates/user/notification/notification_div.tmpl +++ b/templates/user/notification/notification_div.tmpl @@ -44,14 +44,14 @@ {{end}}
-
+
{{.Repository.FullName}} {{if .Issue}}#{{.Issue.Index}}{{end}} {{if eq .Status 3}} {{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}} {{end}}
- + {{if .Issue}} {{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}} {{else}} diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 83947ff9671ec..630b4de3f92b6 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -429,22 +429,33 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) t.Run("SearchService", func(t *testing.T) { cases := []struct { - Query string - Skip int - Take int - ExpectedTotal int64 - ExpectedResults int + Query string + Skip int + Take int + ExpectedTotal int64 + ExpectedResults int + ExpectedExactMatch bool }{ - {"", 0, 0, 1, 1}, - {"", 0, 10, 1, 1}, - {"gitea", 0, 10, 0, 0}, - {"test", 0, 10, 1, 1}, - {"test", 1, 10, 1, 0}, + {"", 0, 0, 4, 4, false}, + {"", 0, 10, 4, 4, false}, + {"gitea", 0, 10, 0, 0, false}, + {"test", 0, 10, 1, 1, false}, + {"test", 1, 10, 1, 0, false}, + {"almost.similar", 0, 0, 3, 3, true}, } - req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + fakePackages := []string{ + packageName, + "almost.similar.dependency", + "almost.similar", + "almost.similar.dependant", + } + + for _, fakePackageName := range fakePackages { + req := NewRequestWithBody(t, "PUT", url, createPackage(fakePackageName, "1.0.99")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + } t.Run("v2", func(t *testing.T) { t.Run("Search()", func(t *testing.T) { @@ -491,6 +502,63 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) } }) + t.Run("Packages()", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("substringof", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + for i, c := range cases { + req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result FeedResponse + decodeXML(t, resp, &result) + + assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) + assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) + } + }) + + t.Run("IdEq", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + for i, c := range cases { + if c.Query == "" { + // Ignore the `tolower(Id) eq ''` as it's unlikely to happen + continue + } + req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result FeedResponse + decodeXML(t, resp, &result) + + expectedCount := 0 + if c.ExpectedExactMatch { + expectedCount = 1 + } + + assert.Equal(t, int64(expectedCount), result.Count, "case %d: unexpected total hits", i) + assert.Len(t, result.Entries, expectedCount, "case %d: unexpected result count", i) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, strconv.FormatInt(int64(expectedCount), 10), resp.Body.String(), "case %d: unexpected total hits", i) + } + }) + }) + t.Run("Next", func(t *testing.T) { req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)). AddBasicAuth(user.Name) @@ -548,9 +616,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) }) }) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusNoContent) + for _, fakePackageName := range fakePackages { + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, fakePackageName, "1.0.99")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + } }) t.Run("RegistrationService", func(t *testing.T) { diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index c6eeb404c02c4..a7f021ca4fbfa 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -42,7 +42,7 @@ func TestAPIRepoTags(t *testing.T) { assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL) assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL) - newTag := createNewTagUsingAPI(t, session, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text") + newTag := createNewTagUsingAPI(t, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &tags) assert.Len(t, tags, 2) @@ -72,7 +72,7 @@ func TestAPIRepoTags(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) } -func createNewTagUsingAPI(t *testing.T, session *TestSession, token, ownerName, repoName, name, target, msg string) *api.Tag { +func createNewTagUsingAPI(t *testing.T, token, ownerName, repoName, name, target, msg string) *api.Tag { urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags", ownerName, repoName) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateTagOption{ TagName: name, diff --git a/tests/integration/dump_restore_test.go b/tests/integration/dump_restore_test.go index bed245305447d..47bb6f76e9791 100644 --- a/tests/integration/dump_restore_test.go +++ b/tests/integration/dump_restore_test.go @@ -237,7 +237,7 @@ func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t re // // Given []Something{} create afterPtr, beforePtr []*Something{} // - sliceType := reflect.SliceOf(reflect.PtrTo(t.Elem())) + sliceType := reflect.SliceOf(reflect.PointerTo(t.Elem())) beforeSlice := reflect.MakeSlice(sliceType, 0, 10) beforePtr = reflect.New(beforeSlice.Type()) beforePtr.Elem().Set(beforeSlice) diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go index 3ba4a5882cf96..047c049c7f45d 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_git_test.go @@ -35,7 +35,7 @@ func TestGPGGit(t *testing.T) { defer os.Setenv("GNUPGHOME", oldGNUPGHome) // Need to create a root key - rootKeyPair, err := importTestingKey(tmpDir, "gitea", "gitea@fake.local") + rootKeyPair, err := importTestingKey() if !assert.NoError(t, err, "importTestingKey") { return } @@ -262,7 +262,7 @@ func TestGPGGit(t *testing.T) { }) } -func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { +func crudActionCreateFile(_ *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { return doAPICreateFile(ctx, path, &api.CreateFileOptions{ FileOptions: api.FileOptions{ BranchName: from, @@ -281,7 +281,7 @@ func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.Use }, callback...) } -func importTestingKey(tmpDir, name, email string) (*openpgp.Entity, error) { +func importTestingKey() (*openpgp.Entity, error) { if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil { return nil, err } diff --git a/tools/lint-go-gopls.sh b/tools/lint-go-gopls.sh new file mode 100755 index 0000000000000..4bb69f4c16c1a --- /dev/null +++ b/tools/lint-go-gopls.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -uo pipefail + +cd "$(dirname -- "${BASH_SOURCE[0]}")" && cd .. + +IGNORE_PATTERNS=( + "is deprecated" # TODO: fix these +) + +# lint all go files with 'gopls check' and look for lines starting with the +# current absolute path, indicating a error was found. This is neccessary +# because the tool does not set non-zero exit code when errors are found. +# ref: https://github.com/golang/go/issues/67078 +ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}")); +NUM_ERRORS=$(echo -n "$ERROR_LINES" | wc -l) + +if [ "$NUM_ERRORS" -eq "0" ]; then + exit 0; +else + echo "$ERROR_LINES" + echo "Found $NUM_ERRORS 'gopls check' errors" + exit 1; +fi diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index e25182051a291..151b0a23d9dc7 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -9,6 +9,7 @@ .project-column { background-color: var(--color-project-column-bg) !important; border: 1px solid var(--color-secondary) !important; + border-radius: var(--border-radius); margin: 0 0.5rem !important; padding: 0.5rem !important; width: 320px; diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 15df9f3a4532e..42d06e2e66a7d 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -3,11 +3,6 @@ Gitea's tailwind-style CSS helper classes have `gt-` prefix. Gitea's private styles use `g-` prefix. */ -.gt-word-break { - word-wrap: break-word !important; - overflow-wrap: anywhere; -} - .gt-ellipsis { overflow: hidden !important; white-space: nowrap !important; diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js index b35502d52f9fa..3c90b546b86b9 100644 --- a/web_src/js/features/admin/common.js +++ b/web_src/js/features/admin/common.js @@ -67,39 +67,44 @@ export function initAdminCommon() { input.removeAttribute('required'); } - const provider = document.getElementById('oauth2_provider')?.value; + const provider = document.getElementById('oauth2_provider').value; switch (provider) { case 'openidConnect': - for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input')) { - input.setAttribute('required', 'required'); - } + document.querySelector('.open_id_connect_auto_discovery_url input').setAttribute('required', 'required'); showElem('.open_id_connect_auto_discovery_url'); break; - default: - if (document.getElementById(`#${provider}_customURLSettings`)?.getAttribute('data-required')) { - document.getElementById('oauth2_use_custom_url')?.setAttribute('checked', 'checked'); + default: { + const elProviderCustomUrlSettings = document.querySelector(`#${provider}_customURLSettings`); + if (!elProviderCustomUrlSettings) break; // some providers do not have custom URL settings + const couldChangeCustomURLs = elProviderCustomUrlSettings.getAttribute('data-available') === 'true'; + const mustProvideCustomURLs = elProviderCustomUrlSettings.getAttribute('data-required') === 'true'; + if (couldChangeCustomURLs) { + showElem('.oauth2_use_custom_url'); // show the checkbox } - if (document.getElementById(`#${provider}_customURLSettings`)?.getAttribute('data-available')) { - showElem('.oauth2_use_custom_url'); + if (mustProvideCustomURLs) { + document.querySelector('#oauth2_use_custom_url').checked = true; // make the checkbox checked } + break; + } } onOAuth2UseCustomURLChange(applyDefaultValues); } function onOAuth2UseCustomURLChange(applyDefaultValues) { - const provider = document.getElementById('oauth2_provider')?.value; + const provider = document.getElementById('oauth2_provider').value; hideElem('.oauth2_use_custom_url_field'); for (const input of document.querySelectorAll('.oauth2_use_custom_url_field input[required]')) { input.removeAttribute('required'); } - if (document.getElementById('oauth2_use_custom_url')?.checked) { + const elProviderCustomUrlSettings = document.querySelector(`#${provider}_customURLSettings`); + if (elProviderCustomUrlSettings && document.getElementById('oauth2_use_custom_url').checked) { for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) { if (applyDefaultValues) { document.getElementById(`oauth2_${custom}`).value = document.getElementById(`${provider}_${custom}`).value; } const customInput = document.getElementById(`${provider}_${custom}`); - if (customInput && customInput.getAttribute('data-available')) { + if (customInput && customInput.getAttribute('data-available') === 'true') { for (const input of document.querySelectorAll(`.oauth2_${custom} input`)) { input.setAttribute('required', 'required'); } diff --git a/web_src/js/features/comp/Paste.js b/web_src/js/features/comp/Paste.js index b26296d1fc967..35a7ceaef8be2 100644 --- a/web_src/js/features/comp/Paste.js +++ b/web_src/js/features/comp/Paste.js @@ -100,13 +100,17 @@ async function handleClipboardImages(editor, dropzone, images, e) { const {uuid} = await uploadFile(img, uploadUrl); const {width, dppx} = await imageInfo(img); - const url = `/attachments/${uuid}`; let text; if (width > 0 && dppx > 1) { // Scale down images from HiDPI monitors. This uses the tag because it's the only // method to change image size in Markdown that is supported by all implementations. + // Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}" + const url = `attachments/${uuid}`; text = `${htmlEscape(name)}`; } else { + // Markdown always renders the image with a relative path, so the final URL is "/sub-path/owner/repo/attachments/{uuid}" + // TODO: it should also use relative path for consistency, because absolute is ambiguous for "/sub-path/attachments" or "/attachments" + const url = `/attachments/${uuid}`; text = `![${name}](${url})`; } editor.replacePlaceholder(placeholder, text); diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 519db34934b65..95910e34bc9af 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -125,7 +125,7 @@ export function initRepoIssueSidebarList() { } filteredResponse.results.push({ name: `
#${issue.number} ${htmlEscape(issue.title)}
-
${htmlEscape(issue.repository.full_name)}
`, +
${htmlEscape(issue.repository.full_name)}
`, value: issue.id, }); });