Sync with Forgejo

This commit is contained in:
Otto Richter 2025-03-06 23:26:35 +01:00
commit cf868695f5
50 changed files with 762 additions and 254 deletions

View file

@ -164,7 +164,7 @@ jobs:
- name: build container & release - name: build container & release
if: ${{ secrets.TOKEN != '' }} if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.1 uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4
with: with:
forgejo: "${{ env.GITHUB_SERVER_URL }}" forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
@ -183,7 +183,7 @@ jobs:
- name: build rootless container - name: build rootless container
if: ${{ secrets.TOKEN != '' }} if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.1 uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4
with: with:
forgejo: "${{ env.GITHUB_SERVER_URL }}" forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"

View file

@ -42,7 +42,7 @@ jobs:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://data.forgejo.org/actions/checkout@v4
- name: copy & sign - name: copy & sign
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.1 uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.4
with: with:
from-forgejo: ${{ vars.FORGEJO }} from-forgejo: ${{ vars.FORGEJO }}
to-forgejo: ${{ vars.FORGEJO }} to-forgejo: ${{ vars.FORGEJO }}

View file

@ -28,7 +28,7 @@ jobs:
runs-on: docker runs-on: docker
container: container:
image: data.forgejo.org/renovate/renovate:39.171.2 image: data.forgejo.org/renovate/renovate:39.178.1
steps: steps:
- name: Load renovate repo cache - name: Load renovate repo cache

View file

@ -1,6 +1,6 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.21 AS build-env FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}

View file

@ -1,6 +1,6 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.21 AS build-env FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}
@ -50,6 +50,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM data.forgejo.org/oci/alpine:3.21 FROM data.forgejo.org/oci/alpine:3.21
ARG RELEASE_VERSION
LABEL maintainer="contact@forgejo.org" \ LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \ org.opencontainers.image.authors="Forgejo" \
org.opencontainers.image.url="https://forgejo.org" \ org.opencontainers.image.url="https://forgejo.org" \

View file

@ -48,8 +48,8 @@ GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasour
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.30.0 # renovate: datasource=go DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.30.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.0 # renovate: datasource=go GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@39.171.2 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate RENOVATE_NPM_PACKAGE ?= renovate@39.178.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...

10
go.mod
View file

@ -1,8 +1,8 @@
module code.gitea.io/gitea module code.gitea.io/gitea
go 1.23 go 1.24
toolchain go1.23.5 toolchain go1.24.0
require ( require (
code.forgejo.org/f3/gof3/v3 v3.10.2 code.forgejo.org/f3/gof3/v3 v3.10.2
@ -84,7 +84,7 @@ require (
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/image-spec v1.1.0
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_golang v1.21.0
github.com/redis/go-redis/v9 v9.7.0 github.com/redis/go-redis/v9 v9.7.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
@ -101,7 +101,7 @@ require (
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go v0.119.0 gitlab.com/gitlab-org/api/client-go v0.119.0
go.uber.org/mock v0.4.0 go.uber.org/mock v0.4.0
golang.org/x/crypto v0.33.0 golang.org/x/crypto v0.35.0
golang.org/x/image v0.23.0 golang.org/x/image v0.23.0
golang.org/x/net v0.35.0 golang.org/x/net v0.35.0
golang.org/x/oauth2 v0.24.0 golang.org/x/oauth2 v0.24.0
@ -239,7 +239,7 @@ require (
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/rhysd/actionlint v1.6.27 // indirect github.com/rhysd/actionlint v1.6.27 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect

12
go.sum
View file

@ -1331,15 +1331,15 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
@ -1507,8 +1507,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=

View file

@ -0,0 +1 @@
[] # empty

View file

@ -156,25 +156,32 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
var queries []query.Query var queries []query.Query
if options.Keyword != "" { if options.Keyword != "" {
if options.IsFuzzyKeyword { tokens, err := options.Tokens()
fuzziness := 1 if err != nil {
if kl := len(options.Keyword); kl > 3 { return nil, err
fuzziness = 2
} else if kl < 2 {
fuzziness = 0
} }
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{ q := bleve.NewBooleanQuery()
inner_bleve.MatchQuery(options.Keyword, "title", issueIndexerAnalyzer, fuzziness), for _, token := range tokens {
inner_bleve.MatchQuery(options.Keyword, "content", issueIndexerAnalyzer, fuzziness), fuzziness := 0
inner_bleve.MatchQuery(options.Keyword, "comments", issueIndexerAnalyzer, fuzziness), if token.Fuzzy {
}...)) // TODO: replace with "auto" after bleve update
} else { fuzziness = min(len(token.Term)/4, 2)
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer, 0),
inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer, 0),
inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer, 0),
}...))
} }
innerQ := bleve.NewDisjunctionQuery(
inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, fuzziness),
inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, fuzziness),
inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, fuzziness))
switch token.Kind {
case internal.BoolOptMust:
q.AddMust(innerQ)
case internal.BoolOptShould:
q.AddShould(innerQ)
case internal.BoolOptNot:
q.AddMustNot(innerQ)
}
}
queries = append(queries, q)
} }
if len(options.RepoIDs) > 0 || options.AllPublic { if len(options.RepoIDs) > 0 || options.AllPublic {

View file

@ -23,6 +23,10 @@ const (
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
esMultiMatchTypeBestFields = "best_fields" esMultiMatchTypeBestFields = "best_fields"
esMultiMatchTypePhrasePrefix = "phrase_prefix" esMultiMatchTypePhrasePrefix = "phrase_prefix"
// fuzziness options
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/common-options.html#fuzziness
esFuzzyAuto = "AUTO"
) )
var _ internal.Indexer = &Indexer{} var _ internal.Indexer = &Indexer{}
@ -145,12 +149,30 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query := elastic.NewBoolQuery() query := elastic.NewBoolQuery()
if options.Keyword != "" { if options.Keyword != "" {
searchType := esMultiMatchTypePhrasePrefix q := elastic.NewBoolQuery()
if options.IsFuzzyKeyword { tokens, err := options.Tokens()
searchType = esMultiMatchTypeBestFields if err != nil {
return nil, err
}
for _, token := range tokens {
innerQ := elastic.NewMultiMatchQuery(token.Term, "title", "content", "comments")
if token.Fuzzy {
// If the term is not a phrase use fuzziness set to AUTO
innerQ = innerQ.Type(esMultiMatchTypeBestFields).Fuzziness(esFuzzyAuto)
} else {
innerQ = innerQ.Type(esMultiMatchTypePhrasePrefix)
} }
query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments").Type(searchType)) switch token.Kind {
case internal.BoolOptMust:
q.Must(innerQ)
case internal.BoolOptShould:
q.Should(innerQ)
case internal.BoolOptNot:
q.MustNot(innerQ)
}
}
query.Must(q)
} }
if len(options.RepoIDs) > 0 { if len(options.RepoIDs) > 0 {

View file

@ -74,8 +74,6 @@ type SearchResult struct {
type SearchOptions struct { type SearchOptions struct {
Keyword string // keyword to search Keyword string // keyword to search
IsFuzzyKeyword bool // if false the levenshtein distance is 0
RepoIDs []int64 // repository IDs which the issues belong to RepoIDs []int64 // repository IDs which the issues belong to
AllPublic bool // if include all public repositories AllPublic bool // if include all public repositories

View file

@ -0,0 +1,112 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package internal
import (
"io"
"strings"
)
type BoolOpt int
const (
BoolOptMust BoolOpt = iota
BoolOptShould
BoolOptNot
)
type Token struct {
Term string
Kind BoolOpt
Fuzzy bool
}
type Tokenizer struct {
in *strings.Reader
}
func (t *Tokenizer) next() (tk Token, err error) {
var (
sb strings.Builder
r rune
)
tk.Kind = BoolOptShould
tk.Fuzzy = true
// skip all leading white space
for {
if r, _, err = t.in.ReadRune(); err == nil && r == ' ' {
//nolint:staticcheck,wastedassign // SA4006 the variable is used after the loop
r, _, err = t.in.ReadRune()
continue
}
break
}
if err != nil {
return tk, err
}
// check for +/- op, increment to the next rune in both cases
switch r {
case '+':
tk.Kind = BoolOptMust
r, _, err = t.in.ReadRune()
case '-':
tk.Kind = BoolOptNot
r, _, err = t.in.ReadRune()
}
if err != nil {
return tk, err
}
// parse the string, escaping special characters
for esc := false; err == nil; r, _, err = t.in.ReadRune() {
if esc {
if !strings.ContainsRune("+-\\\"", r) {
sb.WriteRune('\\')
}
sb.WriteRune(r)
esc = false
continue
}
switch r {
case '\\':
esc = true
case '"':
if !tk.Fuzzy {
goto nextEnd
}
tk.Fuzzy = false
case ' ', '\t':
if tk.Fuzzy {
goto nextEnd
}
sb.WriteRune(r)
default:
sb.WriteRune(r)
}
}
nextEnd:
tk.Term = sb.String()
if err == io.EOF {
err = nil
} // do not consider EOF as an error at the end
return tk, err
}
// Tokenize the keyword
func (o *SearchOptions) Tokens() (tokens []Token, err error) {
in := strings.NewReader(o.Keyword)
it := Tokenizer{in: in}
for token, err := it.next(); err == nil; token, err = it.next() {
tokens = append(tokens, token)
}
if err != nil && err != io.EOF {
return nil, err
}
return tokens, nil
}

View file

@ -0,0 +1,171 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testIssueQueryStringOpt struct {
Keyword string
Results []Token
}
var testOpts = []testIssueQueryStringOpt{
{
Keyword: "Hello",
Results: []Token{
{
Term: "Hello",
Fuzzy: true,
Kind: BoolOptShould,
},
},
},
{
Keyword: "Hello World",
Results: []Token{
{
Term: "Hello",
Fuzzy: true,
Kind: BoolOptShould,
},
{
Term: "World",
Fuzzy: true,
Kind: BoolOptShould,
},
},
},
{
Keyword: "+Hello +World",
Results: []Token{
{
Term: "Hello",
Fuzzy: true,
Kind: BoolOptMust,
},
{
Term: "World",
Fuzzy: true,
Kind: BoolOptMust,
},
},
},
{
Keyword: "+Hello World",
Results: []Token{
{
Term: "Hello",
Fuzzy: true,
Kind: BoolOptMust,
},
{
Term: "World",
Fuzzy: true,
Kind: BoolOptShould,
},
},
},
{
Keyword: "+Hello -World",
Results: []Token{
{
Term: "Hello",
Fuzzy: true,
Kind: BoolOptMust,
},
{
Term: "World",
Fuzzy: true,
Kind: BoolOptNot,
},
},
},
{
Keyword: "\"Hello World\"",
Results: []Token{
{
Term: "Hello World",
Fuzzy: false,
Kind: BoolOptShould,
},
},
},
{
Keyword: "+\"Hello World\"",
Results: []Token{
{
Term: "Hello World",
Fuzzy: false,
Kind: BoolOptMust,
},
},
},
{
Keyword: "-\"Hello World\"",
Results: []Token{
{
Term: "Hello World",
Fuzzy: false,
Kind: BoolOptNot,
},
},
},
{
Keyword: "\"+Hello -World\"",
Results: []Token{
{
Term: "+Hello -World",
Fuzzy: false,
Kind: BoolOptShould,
},
},
},
{
Keyword: "\\+Hello", // \+Hello => +Hello
Results: []Token{
{
Term: "+Hello",
Fuzzy: true,
Kind: BoolOptShould,
},
},
},
{
Keyword: "\\\\Hello", // \\Hello => \Hello
Results: []Token{
{
Term: "\\Hello",
Fuzzy: true,
Kind: BoolOptShould,
},
},
},
{
Keyword: "\\\"Hello", // \"Hello => "Hello
Results: []Token{
{
Term: "\"Hello",
Fuzzy: true,
Kind: BoolOptShould,
},
},
},
}
func TestIssueQueryString(t *testing.T) {
var opt SearchOptions
for _, res := range testOpts {
t.Run(opt.Keyword, func(t *testing.T) {
opt.Keyword = res.Keyword
tokens, err := opt.Tokens()
require.NoError(t, err)
assert.Equal(t, res.Results, tokens)
})
}
}

View file

@ -131,6 +131,20 @@ var cases = []*testIndexerCase{
ExpectedIDs: []int64{1002, 1001, 1000}, ExpectedIDs: []int64{1002, 1001, 1000},
ExpectedTotal: 3, ExpectedTotal: 3,
}, },
{
Name: "Keyword Exclude",
ExtraData: []*internal.IndexerData{
{ID: 1000, Title: "hi hello world"},
{ID: 1001, Content: "hi hello world"},
{ID: 1002, Comments: []string{"hello", "hello world"}},
},
SearchOptions: &internal.SearchOptions{
Keyword: "hello world -hi",
SortBy: internal.SortByCreatedDesc,
},
ExpectedIDs: []int64{1002},
ExpectedTotal: 1,
},
{ {
Name: "Keyword Fuzzy", Name: "Keyword Fuzzy",
ExtraData: []*internal.IndexerData{ ExtraData: []*internal.IndexerData{
@ -141,7 +155,6 @@ var cases = []*testIndexerCase{
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Keyword: "hello world", Keyword: "hello world",
SortBy: internal.SortByCreatedDesc, SortBy: internal.SortByCreatedDesc,
IsFuzzyKeyword: true,
}, },
ExpectedIDs: []int64{1002, 1001, 1000}, ExpectedIDs: []int64{1002, 1001, 1000},
ExpectedTotal: 3, ExpectedTotal: 3,

View file

@ -232,14 +232,30 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
limit = 1 limit = 1
} }
keyword := options.Keyword var keywords []string
if !options.IsFuzzyKeyword { if options.Keyword != "" {
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s) tokens, err := options.Tokens()
if err != nil {
return nil, err
}
for _, token := range tokens {
if !token.Fuzzy {
// to make it a phrase search, we have to quote the keyword(s)
// https://www.meilisearch.com/docs/reference/api/search#phrase-search // https://www.meilisearch.com/docs/reference/api/search#phrase-search
keyword = doubleQuoteKeyword(keyword) token.Term = doubleQuoteKeyword(token.Term)
} }
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(keyword, &meilisearch.SearchRequest{ // internal.BoolOptShould (Default, requires no modifications)
// internal.BoolOptMust (Not supported by meilisearch)
if token.Kind == internal.BoolOptNot {
token.Term = "-" + token.Term
}
keywords = append(keywords, token.Term)
}
}
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).
Search(strings.Join(keywords, " "), &meilisearch.SearchRequest{
Filter: query.Statement(), Filter: query.Statement(),
Limit: int64(limit), Limit: int64(limit),
Offset: int64(skip), Offset: int64(skip),

View file

@ -1,9 +1,11 @@
// Copyright 2019 The Gitea Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved.
// Copyright 2025 The Forgejo Authors. All rights reserved
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package setting package setting
import ( import (
"net/url"
"regexp" "regexp"
"slices" "slices"
"strings" "strings"
@ -145,6 +147,20 @@ func LoadServiceSetting() {
loadServiceFrom(CfgProvider) loadServiceFrom(CfgProvider)
} }
func appURLAsGlob(fqdn string) (glob.Glob, error) {
localFqdn, err := url.ParseRequestURI(fqdn)
if err != nil {
log.Error("Error in EmailDomainAllowList: %v", err)
return nil, err
}
appFqdn, err := glob.Compile(localFqdn.Hostname(), ',')
if err != nil {
log.Error("Error in EmailDomainAllowList: %v", err)
return nil, err
}
return appFqdn, nil
}
func loadServiceFrom(rootCfg ConfigProvider) { func loadServiceFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("service") sec := rootCfg.Section("service")
Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
@ -164,7 +180,15 @@ func loadServiceFrom(rootCfg ConfigProvider) {
if sec.HasKey("EMAIL_DOMAIN_WHITELIST") { if sec.HasKey("EMAIL_DOMAIN_WHITELIST") {
deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21") deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21")
} }
Service.EmailDomainAllowList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST") emailDomainAllowList := CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST")
if len(emailDomainAllowList) > 0 && Federation.Enabled {
appURL, err := appURLAsGlob(AppURL)
if err == nil {
emailDomainAllowList = append(emailDomainAllowList, appURL)
}
}
Service.EmailDomainAllowList = emailDomainAllowList
Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST") Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
Service.EmailDomainBlockDisposable = sec.Key("EMAIL_DOMAIN_BLOCK_DISPOSABLE").MustBool(false) Service.EmailDomainBlockDisposable = sec.Key("EMAIL_DOMAIN_BLOCK_DISPOSABLE").MustBool(false)
if Service.EmailDomainBlockDisposable { if Service.EmailDomainBlockDisposable {

View file

@ -1,5 +1,6 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2017 The Gitea Authors. All rights reserved. // Copyright 2017 The Gitea Authors. All rights reserved.
// Copyright 2025 The Forgejo Authors. All rights reserved
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package setting package setting
@ -210,6 +211,7 @@ func LoadSettings() {
initAllLoggers() initAllLoggers()
loadDBSetting(CfgProvider) loadDBSetting(CfgProvider)
loadFederationFrom(CfgProvider)
loadServiceFrom(CfgProvider) loadServiceFrom(CfgProvider)
loadOAuth2ClientFrom(CfgProvider) loadOAuth2ClientFrom(CfgProvider)
loadCacheFrom(CfgProvider) loadCacheFrom(CfgProvider)
@ -224,7 +226,6 @@ func LoadSettings() {
LoadQueueSettings() LoadQueueSettings()
loadProjectFrom(CfgProvider) loadProjectFrom(CfgProvider)
loadMimeTypeMapFrom(CfgProvider) loadMimeTypeMapFrom(CfgProvider)
loadFederationFrom(CfgProvider)
loadF3From(CfgProvider) loadF3From(CfgProvider)
} }

View file

@ -1,4 +1,5 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2025 The Forgejo Authors. All rights reserved
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package setting package setting
@ -9,6 +10,7 @@ import (
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestMakeAbsoluteAssetURL(t *testing.T) { func TestMakeAbsoluteAssetURL(t *testing.T) {
@ -30,3 +32,84 @@ func TestMakeManifestData(t *testing.T) {
jsonBytes := MakeManifestData(`Example App '\"`, "https://example.com", "https://example.com/foo/bar") jsonBytes := MakeManifestData(`Example App '\"`, "https://example.com", "https://example.com/foo/bar")
assert.True(t, json.Valid(jsonBytes)) assert.True(t, json.Valid(jsonBytes))
} }
func TestLoadServiceDomainListsForFederation(t *testing.T) {
oldAppURL := AppURL
oldFederation := Federation
oldService := Service
defer func() {
AppURL = oldAppURL
Federation = oldFederation
Service = oldService
}()
cfg, err := NewConfigProviderFromData(`
[federation]
ENABLED = true
[service]
EMAIL_DOMAIN_ALLOWLIST = *.allow.random
EMAIL_DOMAIN_BLOCKLIST = *.block.random
`)
require.NoError(t, err)
loadServerFrom(cfg)
loadFederationFrom(cfg)
loadServiceFrom(cfg)
assert.True(t, match(Service.EmailDomainAllowList, "d1.allow.random"))
assert.True(t, match(Service.EmailDomainAllowList, "localhost"))
}
func TestLoadServiceDomainListsNoFederation(t *testing.T) {
oldAppURL := AppURL
oldFederation := Federation
oldService := Service
defer func() {
AppURL = oldAppURL
Federation = oldFederation
Service = oldService
}()
cfg, err := NewConfigProviderFromData(`
[federation]
ENABLED = false
[service]
EMAIL_DOMAIN_ALLOWLIST = *.allow.random
EMAIL_DOMAIN_BLOCKLIST = *.block.random
`)
require.NoError(t, err)
loadServerFrom(cfg)
loadFederationFrom(cfg)
loadServiceFrom(cfg)
assert.True(t, match(Service.EmailDomainAllowList, "d1.allow.random"))
}
func TestLoadServiceDomainListsFederationEmptyAllowList(t *testing.T) {
oldAppURL := AppURL
oldFederation := Federation
oldService := Service
defer func() {
AppURL = oldAppURL
Federation = oldFederation
Service = oldService
}()
cfg, err := NewConfigProviderFromData(`
[federation]
ENABLED = true
[service]
EMAIL_DOMAIN_BLOCKLIST = *.block.random
`)
require.NoError(t, err)
loadServerFrom(cfg)
loadFederationFrom(cfg)
loadServiceFrom(cfg)
assert.Empty(t, Service.EmailDomainAllowList)
}

View file

@ -1,5 +1,6 @@
// Copyright 2016 The Gogs Authors. All rights reserved. // Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package validation package validation
@ -100,11 +101,25 @@ func validateEmailDomain(email string) error {
} }
func IsEmailDomainAllowed(email string) bool { func IsEmailDomainAllowed(email string) bool {
if len(setting.Service.EmailDomainAllowList) == 0 { return isEmailDomainAllowedInternal(
return !isEmailDomainListed(setting.Service.EmailDomainBlockList, email) email,
setting.Service.EmailDomainAllowList,
setting.Service.EmailDomainBlockList)
} }
return isEmailDomainListed(setting.Service.EmailDomainAllowList, email) func isEmailDomainAllowedInternal(
email string,
emailDomainAllowList []glob.Glob,
emailDomainBlockList []glob.Glob,
) bool {
var result bool
if len(emailDomainAllowList) == 0 {
result = !isEmailDomainListed(emailDomainBlockList, email)
} else {
result = isEmailDomainListed(emailDomainAllowList, email)
}
return result
} }
// isEmailDomainListed checks whether the domain of an email address // isEmailDomainListed checks whether the domain of an email address

View file

@ -1,4 +1,5 @@
// Copyright 2017 The Gitea Authors. All rights reserved. // Copyright 2017 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package validation package validation
@ -65,3 +66,8 @@ func TestEmailAddressValidate(t *testing.T) {
}) })
} }
} }
func TestEmailDomainAllowList(t *testing.T) {
res := IsEmailDomainAllowed("someuser@localhost.localdomain")
assert.True(t, res)
}

View file

@ -119,7 +119,7 @@ preview = Preview
loading = Loading… loading = Loading…
error = Error error = Error
error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it. error404 = The page you are trying to reach either <strong>does not exist</strong>, <strong>has been removed</strong> or <strong>you are not authorized</strong> to view it.
error413 = You have exhausted your quota. error413 = You have exhausted your quota.
go_back = Go Back go_back = Go Back
invalid_data = Invalid data: %v invalid_data = Invalid data: %v
@ -1190,8 +1190,8 @@ template.issue_labels = Issue labels
template.one_item = Must select at least one template item template.one_item = Must select at least one template item
template.invalid = Must select a template repository template.invalid = Must select a template repository
archive.title = This repository is archived. You can view files and clone it, but you cannot make any changes to the state of this repository, such as pushing and creating new issues, pull requests or comments. archive.title = This repository is archived. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
archive.title_date = This repository has been archived on %s. You can view files and clone it, but you cannot make any changes to the state of this repository, such as pushing and creating new issues, pull requests or comments. archive.title_date = This repository has been archived on %s. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
archive.issue.nocomment = This repository is archived. You cannot comment on issues. archive.issue.nocomment = This repository is archived. You cannot comment on issues.
archive.pull.nocomment = This repository is archived. You cannot comment on pull requests. archive.pull.nocomment = This repository is archived. You cannot comment on pull requests.
archive.pull.noreview = This repository is archived. You cannot review pull requests. archive.pull.noreview = This repository is archived. You cannot review pull requests.

View file

@ -1,4 +1,9 @@
{ {
"home.welcome.no_activity": "No activity",
"home.welcome.activity_hint": "There is nothing in your feed yet. Your actions and activity from repositories that you watch will show up here.",
"home.explore_repos": "Explore repositories",
"home.explore_users": "Explore users",
"home.explore_orgs": "Explore organizations",
"repo.pulls.merged_title_desc": { "repo.pulls.merged_title_desc": {
"one": "merged %[1]d commit from <code>%[2]s</code> into <code>%[3]s</code> %[4]s", "one": "merged %[1]d commit from <code>%[2]s</code> into <code>%[3]s</code> %[4]s",
"other": "merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s" "other": "merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s"

View file

@ -34,13 +34,6 @@ func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token}) ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
} }
// RunJobList is a list of action run jobs
// swagger:response RunJobList
type RunJobList struct {
// in:body
Body []*structs.ActionRunJob `json:"body"`
}
func GetActionRunJobs(ctx *context.APIContext, ownerID, repoID int64) { func GetActionRunJobs(ctx *context.APIContext, ownerID, repoID int64) {
labels := strings.Split(ctx.FormTrim("labels"), ",") labels := strings.Split(ctx.FormTrim("labels"), ",")
@ -54,8 +47,7 @@ func GetActionRunJobs(ctx *context.APIContext, ownerID, repoID int64) {
return return
} }
res := new(RunJobList) res := fromRunJobModelToResponse(total, labels)
res.Body = fromRunJobModelToResponse(total, labels)
ctx.JSON(http.StatusOK, res) ctx.JSON(http.StatusOK, res)
} }

View file

@ -32,3 +32,10 @@ type swaggerResponseVariableList struct {
// in:body // in:body
Body []api.ActionVariable `json:"body"` Body []api.ActionVariable `json:"body"`
} }
// RunJobList is a list of action run jobs
// swagger:response RunJobList
type swaggerRunJobList struct {
// in:body
Body []*api.ActionRunJob `json:"body"`
}

View file

@ -204,8 +204,6 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
keyword = "" keyword = ""
} }
isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true)
var mileIDs []int64 var mileIDs []int64
if milestoneID > 0 || milestoneID == db.NoConditionID { // -1 to get those issues which have no any milestone assigned if milestoneID > 0 || milestoneID == db.NoConditionID { // -1 to get those issues which have no any milestone assigned
mileIDs = []int64{milestoneID} mileIDs = []int64{milestoneID}
@ -226,7 +224,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
IssueIDs: nil, IssueIDs: nil,
} }
if keyword != "" { if keyword != "" {
allIssueIDs, err := issueIDsFromSearch(ctx, keyword, isFuzzy, statsOpts) allIssueIDs, err := issueIDsFromSearch(ctx, keyword, statsOpts)
if err != nil { if err != nil {
if issue_indexer.IsAvailable(ctx) { if issue_indexer.IsAvailable(ctx) {
ctx.ServerError("issueIDsFromSearch", err) ctx.ServerError("issueIDsFromSearch", err)
@ -294,7 +292,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
var issues issues_model.IssueList var issues issues_model.IssueList
{ {
ids, err := issueIDsFromSearch(ctx, keyword, isFuzzy, &issues_model.IssuesOptions{ ids, err := issueIDsFromSearch(ctx, keyword, &issues_model.IssuesOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptions{
Page: pager.Paginater.Current(), Page: pager.Paginater.Current(),
PageSize: setting.UI.IssuePagingNum, PageSize: setting.UI.IssuePagingNum,
@ -458,16 +456,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
ctx.Data["OpenCount"] = issueStats.OpenCount ctx.Data["OpenCount"] = issueStats.OpenCount
ctx.Data["ClosedCount"] = issueStats.ClosedCount ctx.Data["ClosedCount"] = issueStats.ClosedCount
ctx.Data["AllCount"] = issueStats.AllCount ctx.Data["AllCount"] = issueStats.AllCount
linkStr := "?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&fuzzy=%t&archived=%t" linkStr := "?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t"
ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels), url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels),
milestoneID, projectID, assigneeID, posterID, isFuzzy, archived) milestoneID, projectID, assigneeID, posterID, archived)
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Data["OpenLink"] = fmt.Sprintf(linkStr,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels), url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels),
milestoneID, projectID, assigneeID, posterID, isFuzzy, archived) milestoneID, projectID, assigneeID, posterID, archived)
ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr, ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "closed", url.QueryEscape(selectLabels), url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "closed", url.QueryEscape(selectLabels),
milestoneID, projectID, assigneeID, posterID, isFuzzy, archived) milestoneID, projectID, assigneeID, posterID, archived)
ctx.Data["SelLabelIDs"] = labelIDs ctx.Data["SelLabelIDs"] = labelIDs
ctx.Data["SelectLabels"] = selectLabels ctx.Data["SelectLabels"] = selectLabels
ctx.Data["ViewType"] = viewType ctx.Data["ViewType"] = viewType
@ -476,7 +474,6 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
ctx.Data["ProjectID"] = projectID ctx.Data["ProjectID"] = projectID
ctx.Data["AssigneeID"] = assigneeID ctx.Data["AssigneeID"] = assigneeID
ctx.Data["PosterID"] = posterID ctx.Data["PosterID"] = posterID
ctx.Data["IsFuzzy"] = isFuzzy
ctx.Data["Keyword"] = keyword ctx.Data["Keyword"] = keyword
ctx.Data["IsShowClosed"] = isShowClosed ctx.Data["IsShowClosed"] = isShowClosed
switch { switch {
@ -499,17 +496,12 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
pager.AddParam(ctx, "assignee", "AssigneeID") pager.AddParam(ctx, "assignee", "AssigneeID")
pager.AddParam(ctx, "poster", "PosterID") pager.AddParam(ctx, "poster", "PosterID")
pager.AddParam(ctx, "archived", "ShowArchivedLabels") pager.AddParam(ctx, "archived", "ShowArchivedLabels")
pager.AddParam(ctx, "fuzzy", "IsFuzzy")
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
} }
func issueIDsFromSearch(ctx *context.Context, keyword string, fuzzy bool, opts *issues_model.IssuesOptions) ([]int64, error) { func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) {
ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts).Copy( ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts))
func(o *issue_indexer.SearchOptions) {
o.IsFuzzyKeyword = fuzzy
},
))
if err != nil { if err != nil {
return nil, fmt.Errorf("SearchIssues: %w", err) return nil, fmt.Errorf("SearchIssues: %w", err)
} }

View file

@ -90,6 +90,8 @@ func Dashboard(ctx *context.Context) {
cnt, _ := organization.GetOrganizationCount(ctx, ctxUser) cnt, _ := organization.GetOrganizationCount(ctx, ctxUser)
ctx.Data["UserOrgsCount"] = cnt ctx.Data["UserOrgsCount"] = cnt
ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["Date"] = date ctx.Data["Date"] = date
var uid int64 var uid int64
@ -463,8 +465,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
User: ctx.Doer, User: ctx.Doer,
} }
isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true)
// Search all repositories which // Search all repositories which
// //
// As user: // As user:
@ -594,9 +594,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// USING FINAL STATE OF opts FOR A QUERY. // USING FINAL STATE OF opts FOR A QUERY.
var issues issues_model.IssueList var issues issues_model.IssueList
{ {
issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts).Copy( issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts))
func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy },
))
if err != nil { if err != nil {
ctx.ServerError("issueIDsFromSearch", err) ctx.ServerError("issueIDsFromSearch", err)
return return
@ -622,9 +620,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// ------------------------------- // -------------------------------
// Fill stats to post to ctx.Data. // Fill stats to post to ctx.Data.
// ------------------------------- // -------------------------------
issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy( issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts))
func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy },
))
if err != nil { if err != nil {
ctx.ServerError("getUserIssueStats", err) ctx.ServerError("getUserIssueStats", err)
return return
@ -679,7 +675,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
ctx.Data["IsShowClosed"] = isShowClosed ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["SelectLabels"] = selectedLabels ctx.Data["SelectLabels"] = selectedLabels
ctx.Data["PageIsOrgIssues"] = org != nil ctx.Data["PageIsOrgIssues"] = org != nil
ctx.Data["IsFuzzy"] = isFuzzy
if isShowClosed { if isShowClosed {
ctx.Data["State"] = "closed" ctx.Data["State"] = "closed"
@ -695,7 +690,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
pager.AddParam(ctx, "labels", "SelectLabels") pager.AddParam(ctx, "labels", "SelectLabels")
pager.AddParam(ctx, "milestone", "MilestoneID") pager.AddParam(ctx, "milestone", "MilestoneID")
pager.AddParam(ctx, "assignee", "AssigneeID") pager.AddParam(ctx, "assignee", "AssigneeID")
pager.AddParam(ctx, "fuzzy", "IsFuzzy")
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.HTML(http.StatusOK, tplIssues) ctx.HTML(http.StatusOK, tplIssues)

View file

@ -141,9 +141,20 @@
{{template "repo/cite/cite_modal" .}} {{template "repo/cite/cite_modal" .}}
{{end}} {{end}}
{{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}} {{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
<div class="button-row folder-actions">
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}"> <a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}} {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
</a> </a>
{{if not $.DisableDownloadSourceArchives}}
<button class="ui jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments (printf "%s:%s" $.RefName .TreePath)}}.zip" rel="nofollow" type="application/zip">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments (printf "%s:%s" $.RefName .TreePath)}}.tar.gz" rel="nofollow" type="application/gzip">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
</div>
</button>
{{end}}
</div>
{{end}} {{end}}
</div> </div>
</div> </div>

View file

@ -14,13 +14,13 @@
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_milestone"}}"> <input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_milestone"}}">
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<a rel="nofollow" class="{{if not $.MilestoneID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=0&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_all"}}</a> <a rel="nofollow" class="{{if not $.MilestoneID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=0&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_all"}}</a>
<a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID -1}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=-1&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_none"}}</a> <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID -1}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=-1&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_none"}}</a>
{{if .OpenMilestones}} {{if .OpenMilestones}}
<div class="divider"></div> <div class="divider"></div>
<div class="header">{{ctx.Locale.Tr "repo.issues.filter_milestone_open"}}</div> <div class="header">{{ctx.Locale.Tr "repo.issues.filter_milestone_open"}}</div>
{{range .OpenMilestones}} {{range .OpenMilestones}}
<a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
{{svg "octicon-milestone" 16 "mr-2"}} {{svg "octicon-milestone" 16 "mr-2"}}
{{.Name}} {{.Name}}
</a> </a>
@ -30,7 +30,7 @@
<div class="divider"></div> <div class="divider"></div>
<div class="header">{{ctx.Locale.Tr "repo.issues.filter_milestone_closed"}}</div> <div class="header">{{ctx.Locale.Tr "repo.issues.filter_milestone_closed"}}</div>
{{range .ClosedMilestones}} {{range .ClosedMilestones}}
<a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
{{svg "octicon-milestone" 16 "mr-2"}} {{svg "octicon-milestone" 16 "mr-2"}}
{{.Name}} {{.Name}}
</a> </a>
@ -51,15 +51,15 @@
<i class="icon">{{svg "octicon-search" 16}}</i> <i class="icon">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_project"}}"> <input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_project"}}">
</div> </div>
<a rel="nofollow" class="{{if not .ProjectID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_all"}}</a> <a rel="nofollow" class="{{if not .ProjectID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_all"}}</a>
<a rel="nofollow" class="{{if eq .ProjectID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_none"}}</a> <a rel="nofollow" class="{{if eq .ProjectID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_none"}}</a>
{{if .OpenProjects}} {{if .OpenProjects}}
<div class="divider"></div> <div class="divider"></div>
<div class="header"> <div class="header">
{{ctx.Locale.Tr "repo.issues.new.open_projects"}} {{ctx.Locale.Tr "repo.issues.new.open_projects"}}
</div> </div>
{{range .OpenProjects}} {{range .OpenProjects}}
<a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> <a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
{{svg .IconName 18 "tw-mr-2 tw-shrink-0"}}<span class="gt-ellipsis">{{.Title}}</span> {{svg .IconName 18 "tw-mr-2 tw-shrink-0"}}<span class="gt-ellipsis">{{.Title}}</span>
</a> </a>
{{end}} {{end}}
@ -70,7 +70,7 @@
{{ctx.Locale.Tr "repo.issues.new.closed_projects"}} {{ctx.Locale.Tr "repo.issues.new.closed_projects"}}
</div> </div>
{{range .ClosedProjects}} {{range .ClosedProjects}}
<a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> <a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
{{svg .IconName 18 "tw-mr-2"}}{{.Title}} {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</a> </a>
{{end}} {{end}}
@ -82,7 +82,7 @@
<div class="list-header-author ui dropdown jump item user-remote-search" data-tooltip-content="{{ctx.Locale.Tr "repo.author_search_tooltip"}}" <div class="list-header-author ui dropdown jump item user-remote-search" data-tooltip-content="{{ctx.Locale.Tr "repo.author_search_tooltip"}}"
data-search-url="{{if .Milestone}}{{$.RepoLink}}/issues/posters{{else}}{{$.Link}}/posters{{end}}" data-search-url="{{if .Milestone}}{{$.RepoLink}}/issues/posters{{else}}{{$.Link}}/posters{{end}}"
data-selected-user-id="{{$.PosterID}}" data-selected-user-id="{{$.PosterID}}"
data-action-jump-url="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&fuzzy={{$.IsFuzzy}}&poster={user_id}{{if $.ShowArchivedLabels}}&archived=true{{end}}" data-action-jump-url="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={user_id}{{if $.ShowArchivedLabels}}&archived=true{{end}}"
> >
<span class="text"> <span class="text">
{{ctx.Locale.Tr "repo.issues.filter_poster"}} {{ctx.Locale.Tr "repo.issues.filter_poster"}}
@ -108,11 +108,11 @@
<i class="icon">{{svg "octicon-search" 16}}</i> <i class="icon">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignee"}}"> <input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignee"}}">
</div> </div>
<a rel="nofollow" class="{{if not .AssigneeID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_select"}}</a> <a rel="nofollow" class="{{if not .AssigneeID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_select"}}</a>
<a rel="nofollow" class="{{if eq .AssigneeID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=-1&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee"}}</a> <a rel="nofollow" class="{{if eq .AssigneeID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=-1&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee"}}</a>
<div class="divider"></div> <div class="divider"></div>
{{range .Assignees}} {{range .Assignees}}
<a rel="nofollow" class="{{if eq $.AssigneeID .ID}}active selected{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> <a rel="nofollow" class="{{if eq $.AssigneeID .ID}}active selected{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
{{ctx.AvatarUtils.Avatar . 20}}{{template "repo/search_name" .}} {{ctx.AvatarUtils.Avatar . 20}}{{template "repo/search_name" .}}
</a> </a>
{{end}} {{end}}
@ -127,14 +127,14 @@
</span> </span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu"> <div class="menu">
<a rel="nofollow" class="{{if eq .ViewType "all"}}active {{end}}item" href="?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.all_issues"}}</a> <a rel="nofollow" class="{{if eq .ViewType "all"}}active {{end}}item" href="?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.all_issues"}}</a>
<a rel="nofollow" class="{{if eq .ViewType "assigned"}}active {{end}}item" href="?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a> <a rel="nofollow" class="{{if eq .ViewType "assigned"}}active {{end}}item" href="?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a rel="nofollow" class="{{if eq .ViewType "created_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}}</a> <a rel="nofollow" class="{{if eq .ViewType "created_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}}</a>
{{if .PageIsPullList}} {{if .PageIsPullList}}
<a rel="nofollow" class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}}</a> <a rel="nofollow" class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}}</a>
<a rel="nofollow" class="{{if eq .ViewType "reviewed_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=reviewed_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}}</a> <a rel="nofollow" class="{{if eq .ViewType "reviewed_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=reviewed_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}}</a>
{{end}} {{end}}
<a rel="nofollow" class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}}</a> <a rel="nofollow" class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}}</a>
</div> </div>
</div> </div>
{{end}} {{end}}
@ -146,11 +146,11 @@
</span> </span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu"> <div class="menu">
<a rel="nofollow" class="{{if or (eq .SortType "relevance") (not .SortType)}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort=relevency&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_sort.relevance"}}</a> <a rel="nofollow" class="{{if or (eq .SortType "relevance") (not .SortType)}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort=relevency&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_sort.relevance"}}</a>
{{$o := .}} {{$o := .}}
{{range $opt := StringUtils.Make "latest" "oldest" "recentupdate" "leastupdate" "mostcomment" "leastcomment" "nearduedate" "farduedate"}} {{range $opt := StringUtils.Make "latest" "oldest" "recentupdate" "leastupdate" "mostcomment" "leastcomment" "nearduedate" "farduedate"}}
{{$text := ctx.Locale.Tr (printf "repo.issues.filter_sort.%s" $opt)}} {{$text := ctx.Locale.Tr (printf "repo.issues.filter_sort.%s" $opt)}}
<a rel="nofollow" class="{{if eq $o.SortType $opt}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$opt}}&state={{$.State}}&labels={{$o.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{$text}}</a> <a rel="nofollow" class="{{if eq $o.SortType $opt}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$opt}}&state={{$.State}}&labels={{$o.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{$text}}</a>
{{end}} {{end}}
</div> </div>
</div> </div>

View file

@ -10,11 +10,11 @@
<input type="hidden" name="poster" value="{{$.PosterID}}"> <input type="hidden" name="poster" value="{{$.PosterID}}">
{{end}} {{end}}
{{if .PageIsPullList}} {{if .PageIsPullList}}
{{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}}
{{else if .PageIsMilestones}} {{else if .PageIsMilestones}}
{{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.milestone_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.milestone_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}}
{{else}} {{else}}
{{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}}
{{end}} {{end}}
</div> </div>
</form> </form>

View file

@ -21,7 +21,7 @@
{{end}} {{end}}
<span class="labels-list tw-ml-1"> <span class="labels-list tw-ml-1">
{{range .Labels}} {{range .Labels}}
<a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" rel="nofollow">{{RenderLabel $.Context ctx.Locale .}}</a> <a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" rel="nofollow">{{RenderLabel $.Context ctx.Locale .}}</a>
{{end}} {{end}}
</span> </span>
</div> </div>

View file

@ -23,8 +23,8 @@
</div> </div>
<span class="info">{{ctx.Locale.Tr "repo.issues.filter_label_exclude"}}</span> <span class="info">{{ctx.Locale.Tr "repo.issues.filter_label_exclude"}}</span>
<div class="divider"></div> <div class="divider"></div>
<a rel="nofollow" class="{{if .AllLabels}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_no_select"}}</a> <a rel="nofollow" class="{{if .AllLabels}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_no_select"}}</a>
<a rel="nofollow" class="{{if .NoLabel}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a> <a rel="nofollow" class="{{if .NoLabel}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a>
{{$previousExclusiveScope := "_no_scope"}} {{$previousExclusiveScope := "_no_scope"}}
{{range .Labels}} {{range .Labels}}
{{$exclusiveScope := .ExclusiveScope}} {{$exclusiveScope := .ExclusiveScope}}
@ -32,7 +32,7 @@
<div class="divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$previousExclusiveScope = $exclusiveScope}} {{$previousExclusiveScope = $exclusiveScope}}
<a rel="nofollow" class="item label-filter-item tw-flex tw-items-center" {{if .IsArchived}}data-is-archived{{end}} href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&labels={{.QueryString}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" data-label-id="{{.ID}}"> <a rel="nofollow" class="item label-filter-item tw-flex tw-items-center" {{if .IsArchived}}data-is-archived{{end}} href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&labels={{.QueryString}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" data-label-id="{{.ID}}">
{{if .IsExcluded}} {{if .IsExcluded}}
{{svg "octicon-circle-slash"}} {{svg "octicon-circle-slash"}}
{{else if .IsSelected}} {{else if .IsSelected}}

View file

@ -3,6 +3,10 @@
{{/* Placeholder (optional) - placeholder text to be used */}} {{/* Placeholder (optional) - placeholder text to be used */}}
{{/* Tooltip (optional) - a tooltip to be displayed on button hover */}} {{/* Tooltip (optional) - a tooltip to be displayed on button hover */}}
<div class="ui small fluid action input"> <div class="ui small fluid action input">
{{template "shared/search/input" dict "Value" .Value "Disabled" .Disabled "Placeholder" .Placeholder}} {{template "shared/search/input"
dict
"Value" .Value
"Disabled" .Disabled
"Placeholder" .Placeholder}}
{{template "shared/search/button" dict "Disabled" .Disabled "Tooltip" .Tooltip}} {{template "shared/search/button" dict "Disabled" .Disabled "Tooltip" .Tooltip}}
</div> </div>

View file

@ -1,13 +0,0 @@
{{/* Value - value of the search field (for search results page) */}}
{{/* Disabled (optional) - if search field/button has to be disabled */}}
{{/* Placeholder (optional) - placeholder text to be used */}}
{{/* IsFuzzy - state of the fuzzy/union search toggle */}}
{{/* Tooltip (optional) - a tooltip to be displayed on button hover */}}
<div class="ui small fluid action input">
{{template "shared/search/input" dict "Value" .Value "Disabled" .Disabled "Placeholder" .Placeholder}}
{{template "shared/search/fuzzy"
dict
"Disabled" .Disabled
"IsFuzzy" .IsFuzzy}}
{{template "shared/search/button" dict "Disabled" .Disabled "Tooltip" .Tooltip}}
</div>

View file

@ -1,15 +0,0 @@
{{/* Disabled (optional) - if dropdown has to be disabled */}}
{{/* IsFuzzy - state of the fuzzy search toggle */}}
<div class="ui small dropdown selection {{if .Disabled}} disabled{{end}}" data-tooltip-content="{{ctx.Locale.Tr "search.type_tooltip"}}">
<input name="fuzzy" type="hidden"{{if .Disabled}} disabled{{end}} value="{{.IsFuzzy}}">{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="text">{{if .IsFuzzy}}{{/*
*/}}{{ctx.Locale.Tr "search.fuzzy"}}{{/*
*/}}{{else}}{{/*
*/}}{{ctx.Locale.Tr "search.exact"}}{{/*
*/}}{{end}}</div>
<div class="menu">
<div class="item" data-value="true" data-tooltip-content="{{ctx.Locale.Tr "search.fuzzy_tooltip"}}">{{/*
*/}}{{ctx.Locale.Tr "search.fuzzy"}}</div>
<div class="item" data-value="false" data-tooltip-content="{{ctx.Locale.Tr "search.exact_tooltip"}}">{{ctx.Locale.Tr "search.exact"}}</div>
</div>
</div>

View file

@ -5,7 +5,11 @@
<div class="flex-container-main"> <div class="flex-container-main">
{{template "base/alert" .}} {{template "base/alert" .}}
{{template "user/heatmap" .}} {{template "user/heatmap" .}}
{{if .Feeds}}
{{template "user/dashboard/feeds" .}} {{template "user/dashboard/feeds" .}}
{{else}}
{{template "user/dashboard/guide" .}}
{{end}}
</div> </div>
{{template "user/dashboard/repolist" .}} {{template "user/dashboard/repolist" .}}
</div> </div>

View file

@ -0,0 +1,16 @@
<div id="empty-feed" class="tw-text-center tw-p-8">
{{svg "octicon-inbox" 64 "tw-text-placeholder-text"}}
<h2>{{ctx.Locale.Tr "home.welcome.no_activity"}}</h2>
<p class="help">{{ctx.Locale.Tr "home.welcome.activity_hint"}}</p>
<div>
<a href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "home.explore_repos"}}</a>
{{if not .UsersPageIsDisabled}}
<span>·</span>
<a href="{{AppSubUrl}}/explore/users">{{ctx.Locale.Tr "home.explore_users"}}</a>
{{end}}
{{if not .OrganizationsPageIsDisabled}}
<span>·</span>
<a href="{{AppSubUrl}}/explore/organizations">{{ctx.Locale.Tr "home.explore_orgs"}}</a>
{{end}}
</div>
</div>

View file

@ -5,11 +5,11 @@
{{template "base/alert" .}} {{template "base/alert" .}}
<div class="list-header"> <div class="list-header">
<div class="switch list-header-toggle"> <div class="switch list-header-toggle">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&q={{$.Keyword}}">
{{svg "octicon-issue-opened" 16 "tw-mr-2"}} {{svg "octicon-issue-opened" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .IssueStats.OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}} {{ctx.Locale.PrettyNumber .IssueStats.OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}}
</a> </a>
<a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&q={{$.Keyword}}">
{{svg "octicon-issue-closed" 16 "tw-mr-2"}} {{svg "octicon-issue-closed" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .IssueStats.ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}} {{ctx.Locale.PrettyNumber .IssueStats.ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a> </a>
@ -20,9 +20,9 @@
<input type="hidden" name="sort" value="{{$.SortType}}"> <input type="hidden" name="sort" value="{{$.SortType}}">
<input type="hidden" name="state" value="{{$.State}}"> <input type="hidden" name="state" value="{{$.State}}">
{{if .PageIsPulls}} {{if .PageIsPulls}}
{{template "shared/search/combo_fuzzy" dict "Value" $.Keyword "IsFuzzy" $.IsFuzzy "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} {{template "shared/search/combo" dict "Value" $.Keyword "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}}
{{else}} {{else}}
{{template "shared/search/combo_fuzzy" dict "Value" $.Keyword "IsFuzzy" $.IsFuzzy "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} {{template "shared/search/combo" dict "Value" $.Keyword "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}}
{{end}} {{end}}
</div> </div>
</form> </form>
@ -38,29 +38,29 @@
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span> </span>
<div class="ui menu"> <div class="ui menu">
<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="?type=created_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="?type=created_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}">
<div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.CreateCount}}</div> <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.CreateCount}}</div>
{{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}} {{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}}
</a> </a>
<a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}">
<div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.YourRepositoriesCount}}</div> <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.YourRepositoriesCount}}</div>
{{ctx.Locale.Tr "home.issues.in_your_repos"}} {{ctx.Locale.Tr "home.issues.in_your_repos"}}
</a> </a>
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="?type=assigned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="?type=assigned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}">
<div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.AssignCount}}</div> <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.AssignCount}}</div>
{{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}} {{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}}
</a> </a>
{{if .PageIsPulls}} {{if .PageIsPulls}}
<a class="{{if eq .ViewType "review_requested"}}active{{end}} item" href="?type=review_requested&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="{{if eq .ViewType "review_requested"}}active{{end}} item" href="?type=review_requested&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}">
<div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.ReviewRequestedCount}}</div> <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.ReviewRequestedCount}}</div>
{{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}} {{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}}
</a> </a>
<a class="{{if eq .ViewType "reviewed_by"}}active{{end}} item" href="?type=reviewed_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="{{if eq .ViewType "reviewed_by"}}active{{end}} item" href="?type=reviewed_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}">
<div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.ReviewedCount}}</div> <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.ReviewedCount}}</div>
{{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}} {{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}}
</a> </a>
{{end}} {{end}}
<a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="?type=mentioned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> <a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="?type=mentioned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}">
<div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.MentionCount}}</div> <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.MentionCount}}</div>
{{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}} {{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}}
</a> </a>
@ -73,14 +73,13 @@
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span> </span>
<div class="menu"> <div class="menu">
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a> {{$o := .}}
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a> {{range $opt := StringUtils.Make "recentupdate" "leastupdate" "latest" "oldest" "mostcomment" "leastcomment" "nearduedate" "farduedate"}}
<a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="?type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a> {{$text := ctx.Locale.Tr (printf "repo.issues.filter_sort.%s" $opt)}}
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="?type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a> <a class="{{if or (eq $o.SortType $opt) (and (eq $opt "latest") (not $o.SortType))}}active {{end}}item" href="?type={{$.ViewType}}&sort={{$opt}}&state={{$.State}}&labels={{$o.SelectLabels}}&q={{$.Keyword}}">{{
<a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.mostcomment"}}</a> $text
<a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastcomment"}}</a> }}</a>
<a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.nearduedate"}}</a> {{end}}
<a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=farduedate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.farduedate"}}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -30,8 +30,7 @@ test('External Release Attachments', async ({page, isMobile}) => {
await validate_form({page}, 'fieldset'); await validate_form({page}, 'fieldset');
const textarea = page.locator('input[name=tag_name]'); const textarea = page.locator('input[name=tag_name]');
await textarea.pressSequentially('2.0'); await textarea.pressSequentially('2.0');
await expect(page.locator('input[name=title]')).toHaveAttribute('placeholder', '2.0'); await expect(page.locator('input[name=title]')).toHaveValue('2.0');
await page.fill('input[name=title]', '2.0');
await page.click('#add-external-link'); await page.click('#add-external-link');
await page.click('#add-external-link'); await page.click('#add-external-link');
await page.fill('input[name=attachment-new-name-2]', 'Test'); await page.fill('input[name=attachment-new-name-2]', 'Test');

View file

@ -11,7 +11,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/routers/api/v1/shared" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -31,9 +31,9 @@ func TestAPISearchActionJobs_GlobalRunner(t *testing.T) {
).AddTokenAuth(token) ).AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK) res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList var jobs []*api.ActionRunJob
DecodeJSON(t, res, &jobs) DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1) assert.Len(t, jobs, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID) assert.EqualValues(t, job.ID, jobs[0].ID)
} }

View file

@ -11,7 +11,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/routers/api/v1/shared" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -30,9 +30,9 @@ func TestAPISearchActionJobs_OrgRunner(t *testing.T) {
AddTokenAuth(token) AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK) res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList var jobs []*api.ActionRunJob
DecodeJSON(t, res, &jobs) DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1) assert.Len(t, jobs, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID) assert.EqualValues(t, job.ID, jobs[0].ID)
} }

View file

@ -12,7 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/routers/api/v1/shared" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -35,9 +35,9 @@ func TestAPISearchActionJobs_RepoRunner(t *testing.T) {
).AddTokenAuth(token) ).AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK) res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList var jobs []*api.ActionRunJob
DecodeJSON(t, res, &jobs) DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1) assert.Len(t, jobs, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID) assert.EqualValues(t, job.ID, jobs[0].ID)
} }

View file

@ -11,7 +11,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/routers/api/v1/shared" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -30,9 +30,9 @@ func TestAPISearchActionJobs_UserRunner(t *testing.T) {
AddTokenAuth(token) AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK) res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList var jobs []*api.ActionRunJob
DecodeJSON(t, res, &jobs) DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1) assert.Len(t, jobs, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID) assert.EqualValues(t, job.ID, jobs[0].ID)
} }

View file

@ -138,16 +138,15 @@ func TestViewIssuesKeyword(t *testing.T) {
}) })
// keyword: 'firstt' // keyword: 'firstt'
// should not match when fuzzy searching is disabled // should not match when using phrase search
req = NewRequestf(t, "GET", "%s/issues?q=%st&fuzzy=false", repo.Link(), keyword) req = NewRequestf(t, "GET", "%s/issues?q=\"%st\"", repo.Link(), keyword)
resp = MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body) htmlDoc = NewHTMLParser(t, resp.Body)
issuesSelection = getIssuesSelection(t, htmlDoc) issuesSelection = getIssuesSelection(t, htmlDoc)
assert.EqualValues(t, 0, issuesSelection.Length()) assert.EqualValues(t, 0, issuesSelection.Length())
// should match as 'first' when fuzzy seaeching is enabled // should match as 'first' when using a standard query
for _, fmt := range []string{"%s/issues?q=%st&fuzzy=true", "%s/issues?q=%st"} { req = NewRequestf(t, "GET", "%s/issues?q=%st", repo.Link(), keyword)
req = NewRequestf(t, "GET", fmt, repo.Link(), keyword)
resp = MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body) htmlDoc = NewHTMLParser(t, resp.Body)
issuesSelection = getIssuesSelection(t, htmlDoc) issuesSelection = getIssuesSelection(t, htmlDoc)
@ -159,7 +158,6 @@ func TestViewIssuesKeyword(t *testing.T) {
assertMatch(t, issue, keyword) assertMatch(t, issue, keyword)
}) })
} }
}
func TestViewIssuesSearchOptions(t *testing.T) { func TestViewIssuesSearchOptions(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()

View file

@ -1,13 +1,21 @@
// Copyright 2024 The Gitea Authors. All rights reserved. // Copyright 2024 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package integration package integration
import ( import (
"archive/tar"
"compress/gzip"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"testing" "testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
@ -32,3 +40,48 @@ func TestRepoDownloadArchive(t *testing.T) {
assert.Empty(t, resp.Header().Get("Content-Encoding")) assert.Empty(t, resp.Header().Get("Content-Encoding"))
assert.Len(t, bs, 320) assert.Len(t, bs, 320)
} }
func TestRepoDownloadArchiveSubdir(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
defer test.MockVariableValue(&setting.EnableGzip, true)()
defer test.MockVariableValue(&web.GzipMinSize, 10)()
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
// Create a subdirectory
err := createOrReplaceFileInBranch(user, repo, "subdir/test.txt", "master", "Test")
require.NoError(t, err)
t.Run("Frontend", func(t *testing.T) {
resp := MakeRequest(t, NewRequestf(t, "GET", "/%s/src/branch/master/subdir", repo.FullName()), http.StatusOK)
page := NewHTMLParser(t, resp.Body)
page.AssertElement(t, fmt.Sprintf(".folder-actions a.archive-link[href='/%s/archive/master:subdir.zip'][type='application/zip']", repo.FullName()), true)
page.AssertElement(t, fmt.Sprintf(".folder-actions a.archive-link[href='/%s/archive/master:subdir.tar.gz'][type='application/gzip']", repo.FullName()), true)
})
t.Run("Backend", func(t *testing.T) {
resp := MakeRequest(t, NewRequestf(t, "GET", "/%s/archive/master:subdir.tar.gz", repo.FullName()), http.StatusOK)
uncompressedStream, err := gzip.NewReader(resp.Body)
require.NoError(t, err)
tarReader := tar.NewReader(uncompressedStream)
header, err := tarReader.Next()
require.NoError(t, err)
assert.Equal(t, tar.TypeDir, int32(header.Typeflag))
assert.Equal(t, fmt.Sprintf("%s/", repo.Name), header.Name)
header, err = tarReader.Next()
require.NoError(t, err)
assert.Equal(t, tar.TypeReg, int32(header.Typeflag))
assert.Equal(t, fmt.Sprintf("%s/test.txt", repo.Name), header.Name)
_, err = tarReader.Next()
assert.Equal(t, io.EOF, err)
})
})
}

View file

@ -1120,7 +1120,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1145,32 +1144,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
})
assert.True(t, called)
})
t.Run("Fuzzy", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1/issues?fuzzy=false")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
called := false
htmlDoc.Find("#issue-filters a[href^='?']").Each(func(_ int, s *goquery.Selection) {
called = true
href, _ := s.Attr("href")
assert.Contains(t, href, "?q=&")
assert.Contains(t, href, "&type=")
assert.Contains(t, href, "&sort=")
assert.Contains(t, href, "&state=")
assert.Contains(t, href, "&labels=")
assert.Contains(t, href, "&milestone=")
assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=false")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1195,7 +1168,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1220,7 +1192,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1245,7 +1216,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1270,7 +1240,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1295,7 +1264,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1320,7 +1288,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=1") assert.Contains(t, href, "&project=1")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1345,7 +1312,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=1") assert.Contains(t, href, "&assignee=1")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1370,7 +1336,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=1") assert.Contains(t, href, "&poster=1")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1395,7 +1360,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
}) })
assert.True(t, called) assert.True(t, called)
}) })
@ -1420,7 +1384,6 @@ func TestRepoIssueFilterLinks(t *testing.T) {
assert.Contains(t, href, "&project=") assert.Contains(t, href, "&project=")
assert.Contains(t, href, "&assignee=") assert.Contains(t, href, "&assignee=")
assert.Contains(t, href, "&poster=") assert.Contains(t, href, "&poster=")
assert.Contains(t, href, "&fuzzy=")
assert.Contains(t, href, "&archived=true") assert.Contains(t, href, "&archived=true")
}) })
assert.True(t, called) assert.True(t, called)

View file

@ -1,5 +1,5 @@
// Copyright 2024 The Forgejo Authors. All rights reserved. // Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: GPL-3.0-or-later
package integration package integration
@ -38,6 +38,25 @@ func TestUserDashboardActionLinks(t *testing.T) {
assert.EqualValues(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text())) assert.EqualValues(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text()))
} }
func TestUserDashboardFeedWelcome(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
// User2 has some activity in feed
session := loginUser(t, "user2")
page := NewHTMLParser(t, session.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK).Body)
testUserDashboardFeedType(t, page, false)
// User1 doesn't have any activity in feed
session = loginUser(t, "user1")
page = NewHTMLParser(t, session.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK).Body)
testUserDashboardFeedType(t, page, true)
}
func testUserDashboardFeedType(t *testing.T, page *HTMLDoc, isEmpty bool) {
page.AssertElement(t, "#activity-feed", !isEmpty)
page.AssertElement(t, "#empty-feed", isEmpty)
}
func TestDashboardTitleRendering(t *testing.T) { func TestDashboardTitleRendering(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})

View file

@ -79,3 +79,7 @@
.dashboard .secondary-nav .ui.dropdown { .dashboard .secondary-nav .ui.dropdown {
max-width: 100%; max-width: 100%;
} }
.dashboard .help {
color: var(--color-secondary-dark-8);
}

View file

@ -153,7 +153,9 @@
border-radius: 0 var(--border-radius) var(--border-radius) 0 !important; border-radius: 0 var(--border-radius) var(--border-radius) 0 !important;
} }
.repository .clone-panel .dropdown .menu { /* Dropdown alignment */
.repository .clone-panel .dropdown .menu,
.repository .folder-actions .dropdown .menu {
right: 0 !important; right: 0 !important;
left: auto !important; left: auto !important;
} }

View file

@ -32,6 +32,7 @@ function initTagNameEditor() {
const newTagHelperText = el.getAttribute('data-tag-helper-new'); const newTagHelperText = el.getAttribute('data-tag-helper-new');
const existingTagHelperText = el.getAttribute('data-tag-helper-existing'); const existingTagHelperText = el.getAttribute('data-tag-helper-existing');
let previousTag = '';
document.getElementById('tag-name').addEventListener('keyup', (e) => { document.getElementById('tag-name').addEventListener('keyup', (e) => {
const value = e.target.value; const value = e.target.value;
const tagHelper = document.getElementById('tag-helper'); const tagHelper = document.getElementById('tag-helper');
@ -45,7 +46,10 @@ function initTagNameEditor() {
} }
const title_input = document.getElementById('release-title'); const title_input = document.getElementById('release-title');
title_input.placeholder = value; if (!title_input.value || previousTag === title_input.value) {
title_input.value = value;
}
previousTag = value;
}); });
} }