forked from mirror/gitea
Compare commits
14 Commits
83850cc479
...
43aa914b17
Author | SHA1 | Date | |
---|---|---|---|
silverwind | 43aa914b17 | ||
silverwind | ffeaf2d0bd | ||
silverwind | 21fe512aac | ||
Lunny Xiao | cb78648ad9 | ||
Yarden Shoham | f9b4efd42c | ||
6543 | c6e5ec51bd | ||
Yarden Shoham | 3cd64949ae | ||
wxiaoguang | a889381664 | ||
wxiaoguang | 66902d89e5 | ||
6543 | 1262ff6734 | ||
wxiaoguang | e0ea3811c4 | ||
Yarden Shoham | 6ead30dbc4 | ||
Yarden Shoham | 043f55fabf | ||
silverwind | 68169133a3 |
|
@ -283,7 +283,7 @@ rules:
|
|||
i/unambiguous: [0]
|
||||
init-declarations: [0]
|
||||
jquery/no-ajax-events: [2]
|
||||
jquery/no-ajax: [0]
|
||||
jquery/no-ajax: [2]
|
||||
jquery/no-animate: [2]
|
||||
jquery/no-attr: [0]
|
||||
jquery/no-bind: [2]
|
||||
|
@ -315,7 +315,7 @@ rules:
|
|||
jquery/no-parent: [0]
|
||||
jquery/no-parents: [0]
|
||||
jquery/no-parse-html: [2]
|
||||
jquery/no-prop: [0]
|
||||
jquery/no-prop: [2]
|
||||
jquery/no-proxy: [2]
|
||||
jquery/no-ready: [2]
|
||||
jquery/no-serialize: [2]
|
||||
|
@ -396,11 +396,11 @@ rules:
|
|||
no-irregular-whitespace: [2]
|
||||
no-iterator: [2]
|
||||
no-jquery/no-ajax-events: [2]
|
||||
no-jquery/no-ajax: [0]
|
||||
no-jquery/no-ajax: [2]
|
||||
no-jquery/no-and-self: [2]
|
||||
no-jquery/no-animate-toggle: [2]
|
||||
no-jquery/no-animate: [2]
|
||||
no-jquery/no-append-html: [0]
|
||||
no-jquery/no-append-html: [2]
|
||||
no-jquery/no-attr: [0]
|
||||
no-jquery/no-bind: [2]
|
||||
no-jquery/no-box-model: [2]
|
||||
|
@ -466,7 +466,7 @@ rules:
|
|||
no-jquery/no-parse-html: [2]
|
||||
no-jquery/no-parse-json: [2]
|
||||
no-jquery/no-parse-xml: [2]
|
||||
no-jquery/no-prop: [0]
|
||||
no-jquery/no-prop: [2]
|
||||
no-jquery/no-proxy: [2]
|
||||
no-jquery/no-ready-shorthand: [2]
|
||||
no-jquery/no-ready: [2]
|
||||
|
@ -487,7 +487,7 @@ rules:
|
|||
no-jquery/no-visibility: [2]
|
||||
no-jquery/no-when: [2]
|
||||
no-jquery/no-wrap: [2]
|
||||
no-jquery/variable-pattern: [0]
|
||||
no-jquery/variable-pattern: [2]
|
||||
no-label-var: [2]
|
||||
no-labels: [0] # handled by no-restricted-syntax
|
||||
no-lone-blocks: [2]
|
||||
|
|
|
@ -142,7 +142,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
|
|||
return err
|
||||
}
|
||||
if size, err = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64); err != nil {
|
||||
return fmt.Errorf("Misformatted git cat-file output: %w", err)
|
||||
return fmt.Errorf("misformatted git cat-file output: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,26 +233,26 @@ func (b *Indexer) Delete(_ context.Context, repoID int64) error {
|
|||
|
||||
// Search searches for files in the specified repo.
|
||||
// Returns the matching file-paths
|
||||
func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
|
||||
func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
|
||||
var (
|
||||
indexerQuery query.Query
|
||||
keywordQuery query.Query
|
||||
)
|
||||
|
||||
if isFuzzy {
|
||||
phraseQuery := bleve.NewMatchPhraseQuery(keyword)
|
||||
if opts.IsKeywordFuzzy {
|
||||
phraseQuery := bleve.NewMatchPhraseQuery(opts.Keyword)
|
||||
phraseQuery.FieldVal = "Content"
|
||||
phraseQuery.Analyzer = repoIndexerAnalyzer
|
||||
keywordQuery = phraseQuery
|
||||
} else {
|
||||
prefixQuery := bleve.NewPrefixQuery(keyword)
|
||||
prefixQuery := bleve.NewPrefixQuery(opts.Keyword)
|
||||
prefixQuery.FieldVal = "Content"
|
||||
keywordQuery = prefixQuery
|
||||
}
|
||||
|
||||
if len(repoIDs) > 0 {
|
||||
repoQueries := make([]query.Query, 0, len(repoIDs))
|
||||
for _, repoID := range repoIDs {
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
repoQueries := make([]query.Query, 0, len(opts.RepoIDs))
|
||||
for _, repoID := range opts.RepoIDs {
|
||||
repoQueries = append(repoQueries, inner_bleve.NumericEqualityQuery(repoID, "RepoID"))
|
||||
}
|
||||
|
||||
|
@ -266,8 +266,8 @@ func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword
|
|||
|
||||
// Save for reuse without language filter
|
||||
facetQuery := indexerQuery
|
||||
if len(language) > 0 {
|
||||
languageQuery := bleve.NewMatchQuery(language)
|
||||
if len(opts.Language) > 0 {
|
||||
languageQuery := bleve.NewMatchQuery(opts.Language)
|
||||
languageQuery.FieldVal = "Language"
|
||||
languageQuery.Analyzer = analyzer_keyword.Name
|
||||
|
||||
|
@ -277,12 +277,12 @@ func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword
|
|||
)
|
||||
}
|
||||
|
||||
from := (page - 1) * pageSize
|
||||
from, pageSize := opts.GetSkipTake()
|
||||
searchRequest := bleve.NewSearchRequestOptions(indexerQuery, pageSize, from, false)
|
||||
searchRequest.Fields = []string{"Content", "RepoID", "Language", "CommitID", "UpdatedAt"}
|
||||
searchRequest.IncludeLocations = true
|
||||
|
||||
if len(language) == 0 {
|
||||
if len(opts.Language) == 0 {
|
||||
searchRequest.AddFacet("languages", bleve.NewFacetRequest("Language", 10))
|
||||
}
|
||||
|
||||
|
@ -326,7 +326,7 @@ func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword
|
|||
}
|
||||
|
||||
searchResultLanguages := make([]*internal.SearchResultLanguages, 0, 10)
|
||||
if len(language) > 0 {
|
||||
if len(opts.Language) > 0 {
|
||||
// Use separate query to go get all language counts
|
||||
facetRequest := bleve.NewSearchRequestOptions(facetQuery, 1, 0, false)
|
||||
facetRequest.Fields = []string{"Content", "RepoID", "Language", "CommitID", "UpdatedAt"}
|
||||
|
|
|
@ -281,18 +281,18 @@ func extractAggs(searchResult *elastic.SearchResult) []*internal.SearchResultLan
|
|||
}
|
||||
|
||||
// Search searches for codes and language stats by given conditions.
|
||||
func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
|
||||
func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
|
||||
searchType := esMultiMatchTypePhrasePrefix
|
||||
if isFuzzy {
|
||||
if opts.IsKeywordFuzzy {
|
||||
searchType = esMultiMatchTypeBestFields
|
||||
}
|
||||
|
||||
kwQuery := elastic.NewMultiMatchQuery(keyword, "content").Type(searchType)
|
||||
kwQuery := elastic.NewMultiMatchQuery(opts.Keyword, "content").Type(searchType)
|
||||
query := elastic.NewBoolQuery()
|
||||
query = query.Must(kwQuery)
|
||||
if len(repoIDs) > 0 {
|
||||
repoStrs := make([]any, 0, len(repoIDs))
|
||||
for _, repoID := range repoIDs {
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
repoStrs := make([]any, 0, len(opts.RepoIDs))
|
||||
for _, repoID := range opts.RepoIDs {
|
||||
repoStrs = append(repoStrs, repoID)
|
||||
}
|
||||
repoQuery := elastic.NewTermsQuery("repo_id", repoStrs...)
|
||||
|
@ -300,16 +300,12 @@ func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword
|
|||
}
|
||||
|
||||
var (
|
||||
start int
|
||||
kw = "<em>" + keyword + "</em>"
|
||||
aggregation = elastic.NewTermsAggregation().Field("language").Size(10).OrderByCountDesc()
|
||||
start, pageSize = opts.GetSkipTake()
|
||||
kw = "<em>" + opts.Keyword + "</em>"
|
||||
aggregation = elastic.NewTermsAggregation().Field("language").Size(10).OrderByCountDesc()
|
||||
)
|
||||
|
||||
if page > 0 {
|
||||
start = (page - 1) * pageSize
|
||||
}
|
||||
|
||||
if len(language) == 0 {
|
||||
if len(opts.Language) == 0 {
|
||||
searchResult, err := b.inner.Client.Search().
|
||||
Index(b.inner.VersionedIndexName()).
|
||||
Aggregation("language", aggregation).
|
||||
|
@ -330,7 +326,7 @@ func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword
|
|||
return convertResult(searchResult, kw, pageSize)
|
||||
}
|
||||
|
||||
langQuery := elastic.NewMatchQuery("language", language)
|
||||
langQuery := elastic.NewMatchQuery("language", opts.Language)
|
||||
countResult, err := b.inner.Client.Search().
|
||||
Index(b.inner.VersionedIndexName()).
|
||||
Aggregation("language", aggregation).
|
||||
|
|
|
@ -32,7 +32,7 @@ func getRepoChanges(ctx context.Context, repo *repo_model.Repository, revision s
|
|||
|
||||
needGenesis := len(status.CommitSha) == 0
|
||||
if !needGenesis {
|
||||
hasAncestorCmd := git.NewCommand(ctx, "merge-base").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision)
|
||||
hasAncestorCmd := git.NewCommand(ctx, "merge-base").AddDynamicArguments(status.CommitSha, revision)
|
||||
stdout, _, _ := hasAncestorCmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
needGenesis = len(stdout) == 0
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/indexer/code/bleve"
|
||||
|
@ -70,7 +71,15 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
|||
|
||||
for _, kw := range keywords {
|
||||
t.Run(kw.Keyword, func(t *testing.T) {
|
||||
total, res, langs, err := indexer.Search(context.TODO(), kw.RepoIDs, "", kw.Keyword, 1, 10, true)
|
||||
total, res, langs, err := indexer.Search(context.TODO(), &internal.SearchOptions{
|
||||
RepoIDs: kw.RepoIDs,
|
||||
Keyword: kw.Keyword,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
},
|
||||
IsKeywordFuzzy: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, kw.IDs, int(total))
|
||||
assert.Len(t, langs, kw.Langs)
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/indexer/internal"
|
||||
)
|
||||
|
@ -16,7 +17,17 @@ type Indexer interface {
|
|||
internal.Indexer
|
||||
Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *RepoChanges) error
|
||||
Delete(ctx context.Context, repoID int64) error
|
||||
Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*SearchResult, []*SearchResultLanguages, error)
|
||||
Search(ctx context.Context, opts *SearchOptions) (int64, []*SearchResult, []*SearchResultLanguages, error)
|
||||
}
|
||||
|
||||
type SearchOptions struct {
|
||||
RepoIDs []int64
|
||||
Keyword string
|
||||
Language string
|
||||
|
||||
IsKeywordFuzzy bool
|
||||
|
||||
db.Paginator
|
||||
}
|
||||
|
||||
// NewDummyIndexer returns a dummy indexer
|
||||
|
@ -38,6 +49,6 @@ func (d *dummyIndexer) Delete(ctx context.Context, repoID int64) error {
|
|||
return fmt.Errorf("indexer is not ready")
|
||||
}
|
||||
|
||||
func (d *dummyIndexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*SearchResult, []*SearchResultLanguages, error) {
|
||||
func (d *dummyIndexer) Search(ctx context.Context, opts *SearchOptions) (int64, []*SearchResult, []*SearchResultLanguages, error) {
|
||||
return 0, nil, nil, fmt.Errorf("indexer is not ready")
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ type ResultLine struct {
|
|||
|
||||
type SearchResultLanguages = internal.SearchResultLanguages
|
||||
|
||||
type SearchOptions = internal.SearchOptions
|
||||
|
||||
func indices(content string, selectionStartIndex, selectionEndIndex int) (int, int) {
|
||||
startIndex := selectionStartIndex
|
||||
numLinesBefore := 0
|
||||
|
@ -125,12 +127,12 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
|
|||
|
||||
// PerformSearch perform a search on a repository
|
||||
// if isFuzzy is true set the Damerau-Levenshtein distance from 0 to 2
|
||||
func PerformSearch(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int, []*Result, []*internal.SearchResultLanguages, error) {
|
||||
if len(keyword) == 0 {
|
||||
func PerformSearch(ctx context.Context, opts *SearchOptions) (int, []*Result, []*SearchResultLanguages, error) {
|
||||
if opts == nil || len(opts.Keyword) == 0 {
|
||||
return 0, nil, nil, nil
|
||||
}
|
||||
|
||||
total, results, resultLanguages, err := (*globalIndexer.Load()).Search(ctx, repoIDs, language, keyword, page, pageSize, isFuzzy)
|
||||
total, results, resultLanguages, err := (*globalIndexer.Load()).Search(ctx, opts)
|
||||
if err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package meilisearch
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -217,7 +218,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
|
||||
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
|
||||
|
||||
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(options.Keyword, &meilisearch.SearchRequest{
|
||||
keyword := options.Keyword
|
||||
if !options.IsFuzzyKeyword {
|
||||
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
|
||||
// https://www.meilisearch.com/docs/reference/api/search#phrase-search
|
||||
keyword = doubleQuoteKeyword(keyword)
|
||||
}
|
||||
|
||||
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(keyword, &meilisearch.SearchRequest{
|
||||
Filter: query.Statement(),
|
||||
Limit: int64(limit),
|
||||
Offset: int64(skip),
|
||||
|
@ -228,7 +236,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
return nil, err
|
||||
}
|
||||
|
||||
hits, err := nonFuzzyWorkaround(searchRes, options.Keyword, options.IsFuzzyKeyword)
|
||||
hits, err := convertHits(searchRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -247,11 +255,20 @@ func parseSortBy(sortBy internal.SortBy) string {
|
|||
return field + ":asc"
|
||||
}
|
||||
|
||||
// nonFuzzyWorkaround is needed as meilisearch does not have an exact search
|
||||
// and you can only change "typo tolerance" per index. So we have to post-filter the results
|
||||
// https://www.meilisearch.com/docs/learn/configuration/typo_tolerance#configuring-typo-tolerance
|
||||
// TODO: remove once https://github.com/orgs/meilisearch/discussions/377 is addressed
|
||||
func nonFuzzyWorkaround(searchRes *meilisearch.SearchResponse, keyword string, isFuzzy bool) ([]internal.Match, error) {
|
||||
func doubleQuoteKeyword(k string) string {
|
||||
kp := strings.Split(k, " ")
|
||||
parts := 0
|
||||
for i := range kp {
|
||||
part := strings.Trim(kp[i], "\"")
|
||||
if part != "" {
|
||||
kp[parts] = fmt.Sprintf(`"%s"`, part)
|
||||
parts++
|
||||
}
|
||||
}
|
||||
return strings.Join(kp[:parts], " ")
|
||||
}
|
||||
|
||||
func convertHits(searchRes *meilisearch.SearchResponse) ([]internal.Match, error) {
|
||||
hits := make([]internal.Match, 0, len(searchRes.Hits))
|
||||
for _, hit := range searchRes.Hits {
|
||||
hit, ok := hit.(map[string]any)
|
||||
|
@ -259,61 +276,11 @@ func nonFuzzyWorkaround(searchRes *meilisearch.SearchResponse, keyword string, i
|
|||
return nil, ErrMalformedResponse
|
||||
}
|
||||
|
||||
if !isFuzzy {
|
||||
keyword = strings.ToLower(keyword)
|
||||
|
||||
// declare a anon func to check if the title, content or at least one comment contains the keyword
|
||||
found, err := func() (bool, error) {
|
||||
// check if title match first
|
||||
title, ok := hit["title"].(string)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
} else if strings.Contains(strings.ToLower(title), keyword) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// check if content has a match
|
||||
content, ok := hit["content"].(string)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
} else if strings.Contains(strings.ToLower(content), keyword) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// now check for each comment if one has a match
|
||||
// so we first try to cast and skip if there are no comments
|
||||
comments, ok := hit["comments"].([]any)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
} else if len(comments) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// now we iterate over all and report as soon as we detect one match
|
||||
for i := range comments {
|
||||
comment, ok := comments[i].(string)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
}
|
||||
if strings.Contains(strings.ToLower(comment), keyword) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// we got no match
|
||||
return false, nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
issueID, ok := hit["id"].(float64)
|
||||
if !ok {
|
||||
return nil, ErrMalformedResponse
|
||||
}
|
||||
|
||||
hits = append(hits, internal.Match{
|
||||
ID: int64(issueID),
|
||||
})
|
||||
|
|
|
@ -53,11 +53,10 @@ func TestMeilisearchIndexer(t *testing.T) {
|
|||
tests.TestIndexer(t, indexer)
|
||||
}
|
||||
|
||||
func TestNonFuzzyWorkaround(t *testing.T) {
|
||||
// get unexpected return
|
||||
_, err := nonFuzzyWorkaround(&meilisearch.SearchResponse{
|
||||
func TestConvertHits(t *testing.T) {
|
||||
_, err := convertHits(&meilisearch.SearchResponse{
|
||||
Hits: []any{"aa", "bb", "cc", "dd"},
|
||||
}, "bowling", false)
|
||||
})
|
||||
assert.ErrorIs(t, err, ErrMalformedResponse)
|
||||
|
||||
validResponse := &meilisearch.SearchResponse{
|
||||
|
@ -82,14 +81,15 @@ func TestNonFuzzyWorkaround(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
// nonFuzzy
|
||||
hits, err := nonFuzzyWorkaround(validResponse, "bowling", false)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}}, hits)
|
||||
|
||||
// fuzzy
|
||||
hits, err = nonFuzzyWorkaround(validResponse, "bowling", true)
|
||||
hits, err := convertHits(validResponse)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}, {ID: 33}}, hits)
|
||||
}
|
||||
|
||||
func TestDoubleQuoteKeyword(t *testing.T) {
|
||||
assert.EqualValues(t, "", doubleQuoteKeyword(""))
|
||||
assert.EqualValues(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c"))
|
||||
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
|
||||
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
|
||||
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`))
|
||||
}
|
||||
|
|
|
@ -175,13 +175,6 @@ func NewColorPreview(color []byte) *ColorPreview {
|
|||
}
|
||||
}
|
||||
|
||||
// IsColorPreview returns true if the given node implements the ColorPreview interface,
|
||||
// otherwise false.
|
||||
func IsColorPreview(node ast.Node) bool {
|
||||
_, ok := node.(*ColorPreview)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Attention is an inline for an attention
|
||||
type Attention struct {
|
||||
ast.BaseInline
|
||||
|
|
|
@ -216,7 +216,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||
attentionType := strings.ToLower(strings.TrimPrefix(string(secondTextNode.Segment.Value(reader.Source())), "!"))
|
||||
|
||||
// color the blockquote
|
||||
v.SetAttributeString("class", []byte("gt-py-3 attention attention-"+attentionType))
|
||||
v.SetAttributeString("class", []byte("attention-header attention-"+attentionType))
|
||||
|
||||
// create an emphasis to make it bold
|
||||
attentionParagraph := ast.NewParagraph()
|
||||
|
@ -364,27 +364,21 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod
|
|||
// renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg
|
||||
func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if entering {
|
||||
_, _ = w.WriteString(`<span class="gt-mr-2 gt-vm attention-`)
|
||||
n := node.(*Attention)
|
||||
_, _ = w.WriteString(strings.ToLower(n.AttentionType))
|
||||
_, _ = w.WriteString(`">`)
|
||||
|
||||
var octiconType string
|
||||
var octiconName string
|
||||
switch n.AttentionType {
|
||||
case "note":
|
||||
octiconType = "info"
|
||||
case "tip":
|
||||
octiconType = "light-bulb"
|
||||
octiconName = "light-bulb"
|
||||
case "important":
|
||||
octiconType = "report"
|
||||
octiconName = "report"
|
||||
case "warning":
|
||||
octiconType = "alert"
|
||||
octiconName = "alert"
|
||||
case "caution":
|
||||
octiconType = "stop"
|
||||
octiconName = "stop"
|
||||
default: // including "note"
|
||||
octiconName = "info"
|
||||
}
|
||||
_, _ = w.WriteString(string(svg.RenderHTML("octicon-" + octiconType)))
|
||||
} else {
|
||||
_, _ = w.WriteString("</span>\n")
|
||||
_, _ = w.WriteString(string(svg.RenderHTML("octicon-"+octiconName, 16, "attention-icon attention-"+n.AttentionType)))
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
|
|
@ -64,10 +64,9 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span")
|
||||
|
||||
// For attention
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-py-3 attention attention-\w+$`)).OnElements("blockquote")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-header attention-\w+$`)).OnElements("blockquote")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-\w+$`)).OnElements("strong")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-mr-2 gt-vm attention-\w+$`)).OnElements("span", "strong")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^svg octicon-(\w|-)+$`)).OnElements("svg")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-icon attention-\w+ svg octicon-[\w-]+$`)).OnElements("svg")
|
||||
policy.AllowAttrs("viewBox", "width", "height", "aria-hidden").OnElements("svg")
|
||||
policy.AllowAttrs("fill-rule", "d").OnElements("path")
|
||||
|
||||
|
@ -105,18 +104,12 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
// Allow icons
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i")
|
||||
|
||||
// Allow unlabelled labels
|
||||
policy.AllowNoAttrs().OnElements("label")
|
||||
|
||||
// Allow classes for emojis
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`emoji`)).OnElements("img")
|
||||
|
||||
// Allow icons, emojis, chroma syntax and keyword markup on span
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span")
|
||||
|
||||
// Allow 'style' attribute on text elements.
|
||||
policy.AllowAttrs("style").OnElements("span", "p")
|
||||
|
||||
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
||||
policy.AllowStyles("color", "background-color").OnElements("span", "p")
|
||||
|
||||
|
@ -144,7 +137,7 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
|
||||
generalSafeElements := []string{
|
||||
"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "i", "strong", "em", "a", "pre", "code", "img", "tt",
|
||||
"div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote",
|
||||
"div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote", "label",
|
||||
"dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", "s", "strike", "summary",
|
||||
"details", "caption", "figure", "figcaption",
|
||||
"abbr", "bdo", "cite", "dfn", "mark", "small", "span", "time", "video", "wbr",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -84,7 +85,7 @@ func UnadoptedRepos(ctx *context.Context) {
|
|||
if !doSearch {
|
||||
pager := context.NewPagination(0, opts.PageSize, opts.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "search", "search")
|
||||
pager.AddParamString("search", fmt.Sprint(doSearch))
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.HTML(http.StatusOK, tplUnadoptedRepos)
|
||||
return
|
||||
|
@ -98,7 +99,7 @@ func UnadoptedRepos(ctx *context.Context) {
|
|||
ctx.Data["Dirs"] = repoNames
|
||||
pager := context.NewPagination(count, opts.PageSize, opts.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "search", "search")
|
||||
pager.AddParamString("search", fmt.Sprint(doSearch))
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.HTML(http.StatusOK, tplUnadoptedRepos)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package explore
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
|
@ -76,7 +77,16 @@ func Code(ctx *context.Context) {
|
|||
)
|
||||
|
||||
if (len(repoIDs) > 0) || isAdmin {
|
||||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy)
|
||||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{
|
||||
RepoIDs: repoIDs,
|
||||
Keyword: keyword,
|
||||
IsKeywordFuzzy: isFuzzy,
|
||||
Language: language,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.RepoSearchPagingNum,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if code_indexer.IsAvailable(ctx) {
|
||||
ctx.ServerError("SearchResults", err)
|
||||
|
@ -127,7 +137,7 @@ func Code(ctx *context.Context) {
|
|||
|
||||
pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "l", "Language")
|
||||
pager.AddParamString("l", language)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplExploreCode)
|
||||
|
|
|
@ -169,8 +169,8 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
|||
|
||||
pager := context.NewPagination(int(count), opts.PageSize, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "topic", "TopicOnly")
|
||||
pager.AddParam(ctx, "language", "Language")
|
||||
pager.AddParamString("topic", fmt.Sprint(topicOnly))
|
||||
pager.AddParamString("language", language)
|
||||
pager.AddParamString(relevantReposOnlyParam, fmt.Sprint(opts.OnlyShowRelevant))
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ func Home(ctx *context.Context) {
|
|||
|
||||
pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "language", "Language")
|
||||
pager.AddParamString("language", language)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
||||
|
|
|
@ -120,7 +120,7 @@ func Projects(ctx *context.Context) {
|
|||
}
|
||||
|
||||
pager := context.NewPagination(int(total), setting.UI.IssuePagingNum, page, numPages)
|
||||
pager.AddParam(ctx, "state", "State")
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
|
||||
|
|
|
@ -163,8 +163,8 @@ func Graph(ctx *context.Context) {
|
|||
ctx.Data["CommitCount"] = commitsCount
|
||||
|
||||
paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
|
||||
paginator.AddParam(ctx, "mode", "Mode")
|
||||
paginator.AddParam(ctx, "hide-pr-refs", "HidePRRefs")
|
||||
paginator.AddParamString("mode", mode)
|
||||
paginator.AddParamString("hide-pr-refs", fmt.Sprint(hidePRRefs))
|
||||
for _, branch := range branches {
|
||||
paginator.AddParamString("branch", branch)
|
||||
}
|
||||
|
|
|
@ -472,16 +472,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
}
|
||||
ctx.Data["ShowArchivedLabels"] = archived
|
||||
|
||||
pager.AddParam(ctx, "q", "Keyword")
|
||||
pager.AddParam(ctx, "type", "ViewType")
|
||||
pager.AddParam(ctx, "sort", "SortType")
|
||||
pager.AddParam(ctx, "state", "State")
|
||||
pager.AddParam(ctx, "labels", "SelectLabels")
|
||||
pager.AddParam(ctx, "milestone", "MilestoneID")
|
||||
pager.AddParam(ctx, "project", "ProjectID")
|
||||
pager.AddParam(ctx, "assignee", "AssigneeID")
|
||||
pager.AddParam(ctx, "poster", "PosterID")
|
||||
pager.AddParam(ctx, "archived", "ShowArchivedLabels")
|
||||
pager.AddParamString("q", keyword)
|
||||
pager.AddParamString("type", viewType)
|
||||
pager.AddParamString("sort", sortType)
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
pager.AddParamString("labels", fmt.Sprint(selectLabels))
|
||||
pager.AddParamString("milestone", fmt.Sprint(milestoneID))
|
||||
pager.AddParamString("project", fmt.Sprint(projectID))
|
||||
pager.AddParamString("assignee", fmt.Sprint(assigneeID))
|
||||
pager.AddParamString("poster", fmt.Sprint(posterID))
|
||||
pager.AddParamString("archived", fmt.Sprint(archived))
|
||||
|
||||
ctx.Data["Page"] = pager
|
||||
}
|
||||
|
|
|
@ -106,8 +106,8 @@ func Milestones(ctx *context.Context) {
|
|||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
|
||||
pager := context.NewPagination(int(total), setting.UI.IssuePagingNum, page, 5)
|
||||
pager.AddParam(ctx, "state", "State")
|
||||
pager.AddParam(ctx, "q", "Keyword")
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
pager.AddParamString("q", keyword)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplMilestone)
|
||||
|
|
|
@ -70,8 +70,8 @@ func Packages(ctx *context.Context) {
|
|||
ctx.Data["RepositoryAccessMap"] = map[int64]bool{ctx.Repo.Repository.ID: true} // There is only the current repository
|
||||
|
||||
pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5)
|
||||
pager.AddParam(ctx, "q", "Query")
|
||||
pager.AddParam(ctx, "type", "PackageType")
|
||||
pager.AddParamString("q", query)
|
||||
pager.AddParamString("type", packageType)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplPackagesList)
|
||||
|
|
|
@ -118,7 +118,7 @@ func Projects(ctx *context.Context) {
|
|||
}
|
||||
|
||||
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, numPages)
|
||||
pager.AddParam(ctx, "state", "State")
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
|
||||
|
|
|
@ -6,6 +6,7 @@ package repo
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -41,8 +42,16 @@ func Search(ctx *context.Context) {
|
|||
page = 1
|
||||
}
|
||||
|
||||
total, searchResults, searchResultLanguages, err := code_indexer.PerformSearch(ctx, []int64{ctx.Repo.Repository.ID},
|
||||
language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy)
|
||||
total, searchResults, searchResultLanguages, err := code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{
|
||||
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||
Keyword: keyword,
|
||||
IsKeywordFuzzy: isFuzzy,
|
||||
Language: language,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.RepoSearchPagingNum,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if code_indexer.IsAvailable(ctx) {
|
||||
ctx.ServerError("SearchResults", err)
|
||||
|
@ -59,7 +68,7 @@ func Search(ctx *context.Context) {
|
|||
|
||||
pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "l", "Language")
|
||||
pager.AddParamString("l", language)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSearch)
|
||||
|
|
|
@ -6,6 +6,7 @@ package user
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
|
@ -74,7 +75,16 @@ func CodeSearch(ctx *context.Context) {
|
|||
)
|
||||
|
||||
if len(repoIDs) > 0 {
|
||||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy)
|
||||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{
|
||||
RepoIDs: repoIDs,
|
||||
Keyword: keyword,
|
||||
IsKeywordFuzzy: isFuzzy,
|
||||
Language: language,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.RepoSearchPagingNum,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if code_indexer.IsAvailable(ctx) {
|
||||
ctx.ServerError("SearchResults", err)
|
||||
|
@ -112,7 +122,7 @@ func CodeSearch(ctx *context.Context) {
|
|||
|
||||
pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "l", "Language")
|
||||
pager.AddParamString("l", language)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplUserCode)
|
||||
|
|
|
@ -133,7 +133,7 @@ func Dashboard(ctx *context.Context) {
|
|||
ctx.Data["Feeds"] = feeds
|
||||
|
||||
pager := context.NewPagination(int(count), setting.UI.FeedPagingNum, page, 5)
|
||||
pager.AddParam(ctx, "date", "Date")
|
||||
pager.AddParamString("date", date)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplDashboard)
|
||||
|
@ -329,10 +329,10 @@ func Milestones(ctx *context.Context) {
|
|||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
|
||||
pager := context.NewPagination(pagerCount, setting.UI.IssuePagingNum, page, 5)
|
||||
pager.AddParam(ctx, "q", "Keyword")
|
||||
pager.AddParam(ctx, "repos", "RepoIDs")
|
||||
pager.AddParam(ctx, "sort", "SortType")
|
||||
pager.AddParam(ctx, "state", "State")
|
||||
pager.AddParamString("q", keyword)
|
||||
pager.AddParamString("repos", reposQuery)
|
||||
pager.AddParamString("sort", sortType)
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplMilestones)
|
||||
|
@ -632,13 +632,11 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
|||
}
|
||||
|
||||
pager := context.NewPagination(shownIssues, setting.UI.IssuePagingNum, page, 5)
|
||||
pager.AddParam(ctx, "q", "Keyword")
|
||||
pager.AddParam(ctx, "type", "ViewType")
|
||||
pager.AddParam(ctx, "sort", "SortType")
|
||||
pager.AddParam(ctx, "state", "State")
|
||||
pager.AddParam(ctx, "labels", "SelectLabels")
|
||||
pager.AddParam(ctx, "milestone", "MilestoneID")
|
||||
pager.AddParam(ctx, "assignee", "AssigneeID")
|
||||
pager.AddParamString("q", keyword)
|
||||
pager.AddParamString("type", viewType)
|
||||
pager.AddParamString("sort", sortType)
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
pager.AddParamString("labels", selectedLabels)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssues)
|
||||
|
|
|
@ -344,8 +344,8 @@ func NotificationSubscriptions(ctx *context.Context) {
|
|||
ctx.Redirect(fmt.Sprintf("/notifications/subscriptions?page=%d", pager.Paginater.Current()))
|
||||
return
|
||||
}
|
||||
pager.AddParam(ctx, "sort", "SortType")
|
||||
pager.AddParam(ctx, "state", "State")
|
||||
pager.AddParamString("sort", sortType)
|
||||
pager.AddParamString("state", state)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplNotificationSubscriptions)
|
||||
|
|
|
@ -125,8 +125,8 @@ func ListPackages(ctx *context.Context) {
|
|||
}
|
||||
|
||||
pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5)
|
||||
pager.AddParam(ctx, "q", "Query")
|
||||
pager.AddParam(ctx, "type", "PackageType")
|
||||
pager.AddParamString("q", query)
|
||||
pager.AddParamString("type", packageType)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplPackagesList)
|
||||
|
|
|
@ -324,12 +324,14 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
|
|||
|
||||
pager := context.NewPagination(total, pagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParam(ctx, "tab", "TabName")
|
||||
pager.AddParamString("tab", tab)
|
||||
if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" {
|
||||
pager.AddParam(ctx, "language", "Language")
|
||||
pager.AddParamString("language", language)
|
||||
}
|
||||
if tab == "activity" {
|
||||
pager.AddParam(ctx, "date", "Date")
|
||||
if ctx.Data["Date"] != nil {
|
||||
pager.AddParamString("date", fmt.Sprint(ctx.Data["Date"]))
|
||||
}
|
||||
}
|
||||
ctx.Data["Page"] = pager
|
||||
}
|
||||
|
|
|
@ -26,17 +26,6 @@ func NewPagination(total, pagingNum, current, numPages int) *Pagination {
|
|||
return p
|
||||
}
|
||||
|
||||
// AddParam adds a value from context identified by ctxKey as link param under a given paramKey
|
||||
func (p *Pagination) AddParam(ctx *Context, paramKey, ctxKey string) {
|
||||
_, exists := ctx.Data[ctxKey]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
paramData := fmt.Sprintf("%v", ctx.Data[ctxKey]) // cast any to string
|
||||
urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(paramKey), url.QueryEscape(paramData))
|
||||
p.urlParams = append(p.urlParams, urlParam)
|
||||
}
|
||||
|
||||
// AddParamString adds a string parameter directly
|
||||
func (p *Pagination) AddParamString(key, value string) {
|
||||
urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(key), url.QueryEscape(value))
|
||||
|
@ -50,8 +39,14 @@ func (p *Pagination) GetParams() template.URL {
|
|||
|
||||
// SetDefaultParams sets common pagination params that are often used
|
||||
func (p *Pagination) SetDefaultParams(ctx *Context) {
|
||||
p.AddParam(ctx, "sort", "SortType")
|
||||
p.AddParam(ctx, "q", "Keyword")
|
||||
if v, ok := ctx.Data["SortType"].(string); ok {
|
||||
p.AddParamString("sort", v)
|
||||
}
|
||||
if v, ok := ctx.Data["Keyword"].(string); ok {
|
||||
p.AddParamString("q", v)
|
||||
}
|
||||
if v, ok := ctx.Data["IsFuzzy"].(bool); ok {
|
||||
p.AddParamString("fuzzy", fmt.Sprint(v))
|
||||
}
|
||||
// do not add any more uncommon params here!
|
||||
p.AddParam(ctx, "fuzzy", "IsFuzzy")
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ func TestPullView_CodeOwner(t *testing.T) {
|
|||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
forkedRepo, err := repo_service.ForkRepository(db.DefaultContext, user2, user5, repo_service.ForkRepoOptions{
|
||||
BaseRepo: repo,
|
||||
Name: "test_codeowner_fork",
|
||||
Name: "test_codeowner",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -137,9 +137,9 @@ func TestPullView_CodeOwner(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
session := loginUser(t, "user5")
|
||||
testPullCreate(t, session, "user5", "test_codeowner_fork", false, forkedRepo.DefaultBranch, "codeowner-basebranch-forked", "Test Pull Request2")
|
||||
testPullCreate(t, session, "user5", "test_codeowner", true, forkedRepo.DefaultBranch, "codeowner-basebranch-forked", "Test Pull Request2")
|
||||
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch-forked"})
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: forkedRepo.ID, HeadBranch: "codeowner-basebranch-forked"})
|
||||
unittest.AssertExistsIf(t, false, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -149,24 +149,32 @@ a {
|
|||
text-decoration-skip-ink: all;
|
||||
}
|
||||
|
||||
/* muted link = only colored when hovered */
|
||||
/* silenced link = never colored */
|
||||
/* a = always colored, underlined on hover */
|
||||
/* a.muted = colored on hover, underlined on hover */
|
||||
/* a.suppressed = never colored, underlined on hover */
|
||||
/* a.silenced = never colored, never underlined */
|
||||
|
||||
a.muted,
|
||||
a.suppressed,
|
||||
a.silenced,
|
||||
.muted-links a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a.suppressed:hover,
|
||||
a.muted:hover,
|
||||
a.muted:hover [class*="color-text"],
|
||||
.muted-links a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
a.silenced:hover {
|
||||
a.silenced:hover,
|
||||
a.suppressed:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a.silenced:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
@ -1279,42 +1287,47 @@ input:-webkit-autofill:active,
|
|||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.attention {
|
||||
.attention-header {
|
||||
padding: 0.5em 0.75em !important;
|
||||
color: var(--color-text) !important;
|
||||
}
|
||||
|
||||
.attention-icon {
|
||||
margin: 2px 6px 0 0;
|
||||
}
|
||||
|
||||
blockquote.attention-note {
|
||||
border-left-color: var(--color-blue-dark-1);
|
||||
}
|
||||
strong.attention-note, span.attention-note {
|
||||
strong.attention-note, svg.attention-note {
|
||||
color: var(--color-blue-dark-1);
|
||||
}
|
||||
|
||||
blockquote.attention-tip {
|
||||
border-left-color: var(--color-success-text);
|
||||
}
|
||||
strong.attention-tip, span.attention-tip {
|
||||
strong.attention-tip, svg.attention-tip {
|
||||
color: var(--color-success-text);
|
||||
}
|
||||
|
||||
blockquote.attention-important {
|
||||
border-left-color: var(--color-violet-dark-1);
|
||||
}
|
||||
strong.attention-important, span.attention-important {
|
||||
strong.attention-important, svg.attention-important {
|
||||
color: var(--color-violet-dark-1);
|
||||
}
|
||||
|
||||
blockquote.attention-warning {
|
||||
border-left-color: var(--color-warning-text);
|
||||
}
|
||||
strong.attention-warning, span.attention-warning {
|
||||
strong.attention-warning, svg.attention-warning {
|
||||
color: var(--color-warning-text);
|
||||
}
|
||||
|
||||
blockquote.attention-caution {
|
||||
border-left-color: var(--color-red-dark-1);
|
||||
}
|
||||
strong.attention-caution, span.attention-caution {
|
||||
strong.attention-caution, svg.attention-caution {
|
||||
color: var(--color-red-dark-1);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,33 +30,33 @@
|
|||
--color-primary-alpha-90: #4183c4e1;
|
||||
--color-primary-hover: var(--color-primary-dark-1);
|
||||
--color-primary-active: var(--color-primary-dark-2);
|
||||
--color-secondary: #dedede;
|
||||
--color-secondary-dark-1: #cecece;
|
||||
--color-secondary-dark-2: #bfbfbf;
|
||||
--color-secondary-dark-3: #a0a0a0;
|
||||
--color-secondary-dark-4: #909090;
|
||||
--color-secondary-dark-5: #818181;
|
||||
--color-secondary-dark-6: #717171;
|
||||
--color-secondary-dark-7: #626262;
|
||||
--color-secondary-dark-8: #525252;
|
||||
--color-secondary-dark-9: #434343;
|
||||
--color-secondary-dark-10: #333333;
|
||||
--color-secondary-dark-11: #242424;
|
||||
--color-secondary-dark-12: #141414;
|
||||
--color-secondary-dark-13: #040404;
|
||||
--color-secondary-light-1: #e5e5e5;
|
||||
--color-secondary-light-2: #ebebeb;
|
||||
--color-secondary-light-3: #f2f2f2;
|
||||
--color-secondary-light-4: #f8f8f8;
|
||||
--color-secondary-alpha-10: #dedede19;
|
||||
--color-secondary-alpha-20: #dedede33;
|
||||
--color-secondary-alpha-30: #dedede4b;
|
||||
--color-secondary-alpha-40: #dedede66;
|
||||
--color-secondary-alpha-50: #dedede80;
|
||||
--color-secondary-alpha-60: #dedede99;
|
||||
--color-secondary-alpha-70: #dededeb3;
|
||||
--color-secondary-alpha-80: #dededecc;
|
||||
--color-secondary-alpha-90: #dededee1;
|
||||
--color-secondary: #d0d7de;
|
||||
--color-secondary-dark-1: #c7ced5;
|
||||
--color-secondary-dark-2: #b9c0c7;
|
||||
--color-secondary-dark-3: #99a0a7;
|
||||
--color-secondary-dark-4: #899097;
|
||||
--color-secondary-dark-5: #7a8188;
|
||||
--color-secondary-dark-6: #6a7178;
|
||||
--color-secondary-dark-7: #5b6269;
|
||||
--color-secondary-dark-8: #4b5259;
|
||||
--color-secondary-dark-9: #3c434a;
|
||||
--color-secondary-dark-10: #2c333a;
|
||||
--color-secondary-dark-11: #1d242b;
|
||||
--color-secondary-dark-12: #0d141b;
|
||||
--color-secondary-dark-13: #00040b;
|
||||
--color-secondary-light-1: #dee5ec;
|
||||
--color-secondary-light-2: #e4ebf2;
|
||||
--color-secondary-light-3: #ebf2f9;
|
||||
--color-secondary-light-4: #f1f8ff;
|
||||
--color-secondary-alpha-10: #d0d7de19;
|
||||
--color-secondary-alpha-20: #d0d7de33;
|
||||
--color-secondary-alpha-30: #d0d7de4b;
|
||||
--color-secondary-alpha-40: #d0d7de66;
|
||||
--color-secondary-alpha-50: #d0d7de80;
|
||||
--color-secondary-alpha-60: #d0d7de99;
|
||||
--color-secondary-alpha-70: #d0d7deb3;
|
||||
--color-secondary-alpha-80: #d0d7decc;
|
||||
--color-secondary-alpha-90: #d0d7dee1;
|
||||
--color-secondary-button: var(--color-secondary-dark-4);
|
||||
--color-secondary-hover: var(--color-secondary-dark-5);
|
||||
--color-secondary-active: var(--color-secondary-dark-6);
|
||||
|
@ -81,7 +81,7 @@
|
|||
--color-purple: #a333c8;
|
||||
--color-pink: #e03997;
|
||||
--color-brown: #a5673f;
|
||||
--color-black: #1b1c1d;
|
||||
--color-black: #191c1d;
|
||||
/* light variants - produced via Sass scale-color(color, $lightness: +25%) */
|
||||
--color-red-light: #e45e5e;
|
||||
--color-orange-light: #f59555;
|
||||
|
@ -107,7 +107,7 @@
|
|||
--color-purple-dark-1: #932eb4;
|
||||
--color-pink-dark-1: #db228a;
|
||||
--color-brown-dark-1: #955d39;
|
||||
--color-black-dark-1: #18191a;
|
||||
--color-black-dark-1: #16191c;
|
||||
/* dark 2 variants - produced via Sass scale-color(color, $lightness: -20%) */
|
||||
--color-red-dark-2: #b11e1e;
|
||||
--color-orange-dark-2: #cc580c;
|
||||
|
@ -120,7 +120,7 @@
|
|||
--color-purple-dark-2: #8229a0;
|
||||
--color-pink-dark-2: #c21e7b;
|
||||
--color-brown-dark-2: #845232;
|
||||
--color-black-dark-2: #161617;
|
||||
--color-black-dark-2: #131619;
|
||||
/* ansi colors used for actions console and console files */
|
||||
--color-ansi-black: #1f2326;
|
||||
--color-ansi-red: #cc4848;
|
||||
|
@ -139,8 +139,8 @@
|
|||
--color-ansi-bright-cyan: #00b6ad;
|
||||
--color-ansi-bright-white: var(--color-console-fg);
|
||||
/* other colors */
|
||||
--color-grey: #707070;
|
||||
--color-grey-light: #838383;
|
||||
--color-grey: #697077;
|
||||
--color-grey-light: #7c838a;
|
||||
--color-gold: #a1882b;
|
||||
--color-white: #ffffff;
|
||||
--color-diff-removed-word-bg: #fdb8c0;
|
||||
|
@ -151,7 +151,7 @@
|
|||
--color-diff-removed-row-border: #f1c0c0;
|
||||
--color-diff-moved-row-border: #d0e27f;
|
||||
--color-diff-added-row-border: #e6ffed;
|
||||
--color-diff-inactive: #f2f2f2;
|
||||
--color-diff-inactive: #f0f2f4;
|
||||
--color-error-border: #e0b4b4;
|
||||
--color-error-bg: #fff6f6;
|
||||
--color-error-bg-active: #fbb;
|
||||
|
@ -181,56 +181,56 @@
|
|||
--color-git: #f05133;
|
||||
/* target-based colors */
|
||||
--color-body: #ffffff;
|
||||
--color-box-header: #f7f7f7;
|
||||
--color-box-header: #f1f3f5;
|
||||
--color-box-body: #ffffff;
|
||||
--color-box-body-highlight: #fafafa;
|
||||
--color-text-dark: #080808;
|
||||
--color-text: #212121;
|
||||
--color-text-light: #555555;
|
||||
--color-text-light-1: #6a6a6a;
|
||||
--color-text-light-2: #808080;
|
||||
--color-text-light-3: #a0a0a0;
|
||||
--color-box-body-highlight: #f4faff;
|
||||
--color-text-dark: #03080d;
|
||||
--color-text: #1c2126;
|
||||
--color-text-light: #3c434a;
|
||||
--color-text-light-1: #4b5259;
|
||||
--color-text-light-2: #6a7178;
|
||||
--color-text-light-3: #899097;
|
||||
--color-footer: var(--color-nav-bg);
|
||||
--color-timeline: #ececec;
|
||||
--color-timeline: #d0d7de;
|
||||
--color-input-text: var(--color-text-dark);
|
||||
--color-input-background: #fafafa;
|
||||
--color-input-toggle-background: #dedede;
|
||||
--color-input-background: #f8f9fb;
|
||||
--color-input-toggle-background: #d0d7de;
|
||||
--color-input-border: var(--color-secondary);
|
||||
--color-input-border-hover: var(--color-secondary-dark-1);
|
||||
--color-header-wrapper: transparent;
|
||||
--color-light: #00000006;
|
||||
--color-header-wrapper: #fafbfc;
|
||||
--color-light: #00001706;
|
||||
--color-light-mimic-enabled: rgba(0, 0, 0, calc(6 / 255 * 222 / 255 / var(--opacity-disabled)));
|
||||
--color-light-border: #0000001d;
|
||||
--color-hover: #00000014;
|
||||
--color-active: #0000001b;
|
||||
--color-menu: #fafafa;
|
||||
--color-card: #fafafa;
|
||||
--color-markup-table-row: #00000008;
|
||||
--color-markup-code-block: #00000010;
|
||||
--color-button: #fafafa;
|
||||
--color-code-bg: #ffffff;
|
||||
--color-code-sidebar-bg: #f5f5f5;
|
||||
--color-shadow: #00000026;
|
||||
--color-secondary-bg: #f4f4f4;
|
||||
--color-light-border: #0000171d;
|
||||
--color-hover: #00001708;
|
||||
--color-active: #00001714;
|
||||
--color-menu: #f8f9fb;
|
||||
--color-card: #f8f9fb;
|
||||
--color-markup-table-row: #00001708;
|
||||
--color-markup-code-block: #00001710;
|
||||
--color-button: #f8f9fb;
|
||||
--color-code-bg: #fafdff;
|
||||
--color-code-sidebar-bg: #f2f5f8;
|
||||
--color-shadow: #00001726;
|
||||
--color-secondary-bg: #f2f5f8;
|
||||
--color-expand-button: #d8efff;
|
||||
--color-placeholder-text: var(--color-text-light-3);
|
||||
--color-editor-line-highlight: var(--color-primary-light-6);
|
||||
--color-project-board-bg: var(--color-secondary-light-4);
|
||||
--color-project-board-dark-label: #111111;
|
||||
--color-project-board-light-label: #eeeeee;
|
||||
--color-project-board-dark-label: #0e1114;
|
||||
--color-project-board-light-label: #eaeef2;
|
||||
--color-caret: var(--color-text-dark);
|
||||
--color-reaction-bg: #0000000a;
|
||||
--color-reaction-bg: #0000170a;
|
||||
--color-reaction-hover-bg: var(--color-primary-light-5);
|
||||
--color-reaction-active-bg: var(--color-primary-light-6);
|
||||
--color-tooltip-text: #ffffff;
|
||||
--color-tooltip-bg: #000000f0;
|
||||
--color-nav-bg: #ffffff;
|
||||
--color-tooltip-text: #fbfdff;
|
||||
--color-tooltip-bg: #000017f0;
|
||||
--color-nav-bg: #f8f9fb;
|
||||
--color-nav-hover-bg: var(--color-secondary-light-1);
|
||||
--color-nav-text: var(--color-text);
|
||||
--color-label-text: var(--color-text);
|
||||
--color-label-bg: #9d9d9d4b;
|
||||
--color-label-hover-bg: #9d9d9da0;
|
||||
--color-label-active-bg: #9d9d9dff;
|
||||
--color-label-bg: #949da64b;
|
||||
--color-label-hover-bg: #949da6a0;
|
||||
--color-label-active-bg: #949da6ff;
|
||||
--color-accent: var(--color-primary-light-1);
|
||||
--color-small-accent: var(--color-primary-light-6);
|
||||
--color-active-line: #fffbdd;
|
||||
|
|
|
@ -457,7 +457,7 @@ export function initRepositoryActionView() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-step-container" ref="steps">
|
||||
<div class="job-step-container" ref="steps" v-if="currentJob.steps.length">
|
||||
<div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i">
|
||||
<div class="job-step-summary" @click.stop="toggleStepLogs(i)" :class="currentJobStepsStates[i].expanded ? 'selected' : ''">
|
||||
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
|
||||
|
@ -686,11 +686,15 @@ export function initRepositoryActionView() {
|
|||
background-color: var(--color-console-bg);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
border-radius: var(--border-radius);
|
||||
height: 60px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.job-info-header:has(+ .job-step-container) {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
.job-info-header .job-info-header-title {
|
||||
color: var(--color-console-fg);
|
||||
font-size: 16px;
|
||||
|
|
|
@ -123,7 +123,7 @@ const sfc = {
|
|||
return -1;
|
||||
},
|
||||
scrollToActive() {
|
||||
let el = this.$refs[`listItem${this.active}`];
|
||||
let el = this.$refs[`listItem${this.active}`]; // eslint-disable-line no-jquery/variable-pattern
|
||||
if (!el || !el.length) return;
|
||||
if (Array.isArray(el)) {
|
||||
el = el[0];
|
||||
|
|
|
@ -49,7 +49,7 @@ export function initAdminCommon() {
|
|||
}
|
||||
|
||||
function onUsePagedSearchChange() {
|
||||
if ($('#use_paged_search').prop('checked')) {
|
||||
if (document.getElementById('use_paged_search').checked) {
|
||||
showElem('.search-page-size');
|
||||
$('.search-page-size').find('input').attr('required', 'required');
|
||||
} else {
|
||||
|
|
|
@ -231,8 +231,8 @@ export function initDropzone(el) {
|
|||
init() {
|
||||
this.on('success', (file, data) => {
|
||||
file.uuid = data.uuid;
|
||||
const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
$dropzone.find('.files').append(input);
|
||||
const $input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
$dropzone.find('.files').append($input);
|
||||
// Create a "Copy Link" element, to conveniently copy the image
|
||||
// or file link as Markdown to the clipboard
|
||||
const copyLinkElement = document.createElement('div');
|
||||
|
@ -305,15 +305,15 @@ export function initGlobalLinkActions() {
|
|||
filter += `#${$this.attr('data-modal-id')}`;
|
||||
}
|
||||
|
||||
const dialog = $(`.delete.modal${filter}`);
|
||||
dialog.find('.name').text($this.data('name'));
|
||||
const $dialog = $(`.delete.modal${filter}`);
|
||||
$dialog.find('.name').text($this.data('name'));
|
||||
for (const [key, value] of Object.entries(dataArray)) {
|
||||
if (key && key.startsWith('data')) {
|
||||
dialog.find(`.${key}`).text(value);
|
||||
$dialog.find(`.${key}`).text(value);
|
||||
}
|
||||
}
|
||||
|
||||
dialog.modal({
|
||||
$dialog.modal({
|
||||
closable: false,
|
||||
onApprove: async () => {
|
||||
if ($this.data('type') === 'form') {
|
||||
|
@ -380,8 +380,8 @@ function initGlobalShowModal() {
|
|||
$attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
|
||||
}
|
||||
}
|
||||
const colorPickers = $modal.find('.color-picker');
|
||||
if (colorPickers.length > 0) {
|
||||
const $colorPickers = $modal.find('.color-picker');
|
||||
if ($colorPickers.length > 0) {
|
||||
initCompColorPicker(); // FIXME: this might cause duplicate init
|
||||
}
|
||||
$modal.modal('setting', {
|
||||
|
|
|
@ -6,23 +6,23 @@ function isExclusiveScopeName(name) {
|
|||
}
|
||||
|
||||
function updateExclusiveLabelEdit(form) {
|
||||
const nameInput = $(`${form} .label-name-input`);
|
||||
const exclusiveField = $(`${form} .label-exclusive-input-field`);
|
||||
const exclusiveCheckbox = $(`${form} .label-exclusive-input`);
|
||||
const exclusiveWarning = $(`${form} .label-exclusive-warning`);
|
||||
const $nameInput = $(`${form} .label-name-input`);
|
||||
const $exclusiveField = $(`${form} .label-exclusive-input-field`);
|
||||
const $exclusiveCheckbox = $(`${form} .label-exclusive-input`);
|
||||
const $exclusiveWarning = $(`${form} .label-exclusive-warning`);
|
||||
|
||||
if (isExclusiveScopeName(nameInput.val())) {
|
||||
exclusiveField.removeClass('muted');
|
||||
exclusiveField.removeAttr('aria-disabled');
|
||||
if (exclusiveCheckbox.prop('checked') && exclusiveCheckbox.data('exclusive-warn')) {
|
||||
exclusiveWarning.removeClass('gt-hidden');
|
||||
if (isExclusiveScopeName($nameInput.val())) {
|
||||
$exclusiveField.removeClass('muted');
|
||||
$exclusiveField.removeAttr('aria-disabled');
|
||||
if ($exclusiveCheckbox[0].checked && $exclusiveCheckbox.data('exclusive-warn')) {
|
||||
$exclusiveWarning.removeClass('gt-hidden');
|
||||
} else {
|
||||
exclusiveWarning.addClass('gt-hidden');
|
||||
$exclusiveWarning.addClass('gt-hidden');
|
||||
}
|
||||
} else {
|
||||
exclusiveField.addClass('muted');
|
||||
exclusiveField.attr('aria-disabled', 'true');
|
||||
exclusiveWarning.addClass('gt-hidden');
|
||||
$exclusiveField.addClass('muted');
|
||||
$exclusiveField.attr('aria-disabled', 'true');
|
||||
$exclusiveWarning.addClass('gt-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,18 +46,18 @@ export function initCompLabelEdit(selector) {
|
|||
$('.edit-label .color-picker').minicolors('value', $(this).data('color'));
|
||||
$('#label-modal-id').val($(this).data('id'));
|
||||
|
||||
const nameInput = $('.edit-label .label-name-input');
|
||||
nameInput.val($(this).data('title'));
|
||||
const $nameInput = $('.edit-label .label-name-input');
|
||||
$nameInput.val($(this).data('title'));
|
||||
|
||||
const isArchivedCheckbox = $('.edit-label .label-is-archived-input');
|
||||
isArchivedCheckbox.prop('checked', this.hasAttribute('data-is-archived'));
|
||||
const $isArchivedCheckbox = $('.edit-label .label-is-archived-input');
|
||||
$isArchivedCheckbox[0].checked = this.hasAttribute('data-is-archived');
|
||||
|
||||
const exclusiveCheckbox = $('.edit-label .label-exclusive-input');
|
||||
exclusiveCheckbox.prop('checked', this.hasAttribute('data-exclusive'));
|
||||
const $exclusiveCheckbox = $('.edit-label .label-exclusive-input');
|
||||
$exclusiveCheckbox[0].checked = this.hasAttribute('data-exclusive');
|
||||
// Warn when label was previously not exclusive and used in issues
|
||||
exclusiveCheckbox.data('exclusive-warn',
|
||||
$exclusiveCheckbox.data('exclusive-warn',
|
||||
$(this).data('num-issues') > 0 &&
|
||||
(!this.hasAttribute('data-exclusive') || !isExclusiveScopeName(nameInput.val())));
|
||||
(!this.hasAttribute('data-exclusive') || !isExclusiveScopeName($nameInput.val())));
|
||||
updateExclusiveLabelEdit('.edit-label');
|
||||
|
||||
$('.edit-label .label-desc-input').val($(this).data('description'));
|
||||
|
|
|
@ -17,21 +17,21 @@ export function initCompReactionSelector($parent) {
|
|||
|
||||
const data = await res.json();
|
||||
if (data && (data.html || data.empty)) {
|
||||
const content = $(this).closest('.content');
|
||||
let react = content.find('.segment.reactions');
|
||||
if ((!data.empty || data.html === '') && react.length > 0) {
|
||||
react.remove();
|
||||
const $content = $(this).closest('.content');
|
||||
let $react = $content.find('.segment.reactions');
|
||||
if ((!data.empty || data.html === '') && $react.length > 0) {
|
||||
$react.remove();
|
||||
}
|
||||
if (!data.empty) {
|
||||
const attachments = content.find('.segment.bottom:first');
|
||||
react = $(data.html);
|
||||
if (attachments.length > 0) {
|
||||
react.insertBefore(attachments);
|
||||
const $attachments = $content.find('.segment.bottom:first');
|
||||
$react = $(data.html);
|
||||
if ($attachments.length > 0) {
|
||||
$react.insertBefore($attachments);
|
||||
} else {
|
||||
react.appendTo(content);
|
||||
$react.appendTo($content);
|
||||
}
|
||||
react.find('.dropdown').dropdown();
|
||||
initCompReactionSelector(react);
|
||||
$react.find('.dropdown').dropdown();
|
||||
initCompReactionSelector($react);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import {GET} from '../modules/fetch.js';
|
||||
|
||||
const {appSubUrl, csrfToken, notificationSettings, assetVersionEncoded} = window.config;
|
||||
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
|
||||
let notificationSequenceNumber = 0;
|
||||
|
||||
export function initNotificationsTable() {
|
||||
|
@ -27,25 +28,6 @@ export function initNotificationsTable() {
|
|||
e.target.closest('.notifications-item').setAttribute('data-remove', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
$('#notification_table .button').on('click', function () {
|
||||
(async () => {
|
||||
const data = await updateNotification(
|
||||
$(this).data('url'),
|
||||
$(this).data('status'),
|
||||
$(this).data('page'),
|
||||
$(this).data('q'),
|
||||
$(this).data('notification-id'),
|
||||
);
|
||||
|
||||
if ($(data).data('sequence-number') === notificationSequenceNumber) {
|
||||
$('#notification_div').replaceWith(data);
|
||||
initNotificationsTable();
|
||||
}
|
||||
await updateNotificationCount();
|
||||
})();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async function receiveUpdateCount(event) {
|
||||
|
@ -63,9 +45,9 @@ async function receiveUpdateCount(event) {
|
|||
}
|
||||
|
||||
export function initNotificationCount() {
|
||||
const notificationCount = $('.notification_count');
|
||||
const $notificationCount = $('.notification_count');
|
||||
|
||||
if (!notificationCount.length) {
|
||||
if (!$notificationCount.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -73,7 +55,7 @@ export function initNotificationCount() {
|
|||
const startPeriodicPoller = (timeout, lastCount) => {
|
||||
if (timeout <= 0 || !Number.isFinite(timeout)) return;
|
||||
usingPeriodicPoller = true;
|
||||
lastCount = lastCount ?? notificationCount.text();
|
||||
lastCount = lastCount ?? $notificationCount.text();
|
||||
setTimeout(async () => {
|
||||
await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount);
|
||||
}, timeout);
|
||||
|
@ -161,60 +143,52 @@ async function updateNotificationCountWithCallback(callback, timeout, lastCount)
|
|||
}
|
||||
|
||||
async function updateNotificationTable() {
|
||||
const notificationDiv = $('#notification_div');
|
||||
if (notificationDiv.length > 0) {
|
||||
const data = await $.ajax({
|
||||
type: 'GET',
|
||||
url: `${appSubUrl}/notifications${window.location.search}`,
|
||||
data: {
|
||||
'div-only': true,
|
||||
'sequence-number': ++notificationSequenceNumber,
|
||||
const notificationDiv = document.getElementById('notification_div');
|
||||
if (notificationDiv) {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set('div-only', true);
|
||||
params.set('sequence-number', ++notificationSequenceNumber);
|
||||
const url = `${appSubUrl}/notifications?${params.toString()}`;
|
||||
const response = await GET(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch notification table');
|
||||
}
|
||||
});
|
||||
if ($(data).data('sequence-number') === notificationSequenceNumber) {
|
||||
notificationDiv.replaceWith(data);
|
||||
initNotificationsTable();
|
||||
|
||||
const data = await response.text();
|
||||
if ($(data).data('sequence-number') === notificationSequenceNumber) {
|
||||
notificationDiv.outerHTML = data;
|
||||
initNotificationsTable();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateNotificationCount() {
|
||||
const data = await $.ajax({
|
||||
type: 'GET',
|
||||
url: `${appSubUrl}/notifications/new`,
|
||||
headers: {
|
||||
'X-Csrf-Token': csrfToken,
|
||||
},
|
||||
});
|
||||
try {
|
||||
const response = await GET(`${appSubUrl}/notifications/new`);
|
||||
|
||||
const notificationCount = $('.notification_count');
|
||||
if (data.new === 0) {
|
||||
notificationCount.addClass('gt-hidden');
|
||||
} else {
|
||||
notificationCount.removeClass('gt-hidden');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch notification count');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const $notificationCount = $('.notification_count');
|
||||
if (data.new === 0) {
|
||||
$notificationCount.addClass('gt-hidden');
|
||||
} else {
|
||||
$notificationCount.removeClass('gt-hidden');
|
||||
}
|
||||
|
||||
$notificationCount.text(`${data.new}`);
|
||||
|
||||
return `${data.new}`;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return '0';
|
||||
}
|
||||
|
||||
notificationCount.text(`${data.new}`);
|
||||
|
||||
return `${data.new}`;
|
||||
}
|
||||
|
||||
async function updateNotification(url, status, page, q, notificationID) {
|
||||
if (status !== 'pinned') {
|
||||
$(`#notification_${notificationID}`).remove();
|
||||
}
|
||||
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url,
|
||||
data: {
|
||||
_csrf: csrfToken,
|
||||
notification_id: notificationID,
|
||||
status,
|
||||
page,
|
||||
q,
|
||||
noredirect: true,
|
||||
'sequence-number': ++notificationSequenceNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -97,10 +97,10 @@ function initRepoDiffConversationForm() {
|
|||
const data = await response.text();
|
||||
|
||||
if ($(this).closest('.conversation-holder').length) {
|
||||
const conversation = $(data);
|
||||
$(this).closest('.conversation-holder').replaceWith(conversation);
|
||||
conversation.find('.dropdown').dropdown();
|
||||
initCompReactionSelector(conversation);
|
||||
const $conversation = $(data);
|
||||
$(this).closest('.conversation-holder').replaceWith($conversation);
|
||||
$conversation.find('.dropdown').dropdown();
|
||||
initCompReactionSelector($conversation);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -209,8 +209,8 @@ function initRepoDiffShowMore() {
|
|||
|
||||
export function initRepoDiffView() {
|
||||
initRepoDiffConversationForm();
|
||||
const diffFileList = $('#diff-file-list');
|
||||
if (diffFileList.length === 0) return;
|
||||
const $diffFileList = $('#diff-file-list');
|
||||
if ($diffFileList.length === 0) return;
|
||||
initDiffFileTree();
|
||||
initDiffCommitSelect();
|
||||
initRepoDiffShowMore();
|
||||
|
|
|
@ -15,9 +15,9 @@ function initEditPreviewTab($form) {
|
|||
const $this = $(this);
|
||||
let context = `${$this.data('context')}/`;
|
||||
const mode = $this.data('markup-mode') || 'comment';
|
||||
const treePathEl = $form.find('input#tree_path');
|
||||
if (treePathEl.length > 0) {
|
||||
context += treePathEl.val();
|
||||
const $treePathEl = $form.find('input#tree_path');
|
||||
if ($treePathEl.length > 0) {
|
||||
context += $treePathEl.val();
|
||||
}
|
||||
context = context.substring(0, context.lastIndexOf('/'));
|
||||
|
||||
|
@ -25,7 +25,7 @@ function initEditPreviewTab($form) {
|
|||
formData.append('mode', mode);
|
||||
formData.append('context', context);
|
||||
formData.append('text', $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val());
|
||||
formData.append('file_path', treePathEl.val());
|
||||
formData.append('file_path', $treePathEl.val());
|
||||
try {
|
||||
const response = await POST($this.data('url'), {data: formData});
|
||||
const data = await response.text();
|
||||
|
@ -67,10 +67,10 @@ export function initRepoEditor() {
|
|||
$('.js-quick-pull-choice-option').on('change', function () {
|
||||
if ($(this).val() === 'commit-to-new-branch') {
|
||||
showElem($('.quick-pull-branch-name'));
|
||||
$('.quick-pull-branch-name input').prop('required', true);
|
||||
document.querySelector('.quick-pull-branch-name input').required = true;
|
||||
} else {
|
||||
hideElem($('.quick-pull-branch-name'));
|
||||
$('.quick-pull-branch-name input').prop('required', false);
|
||||
document.querySelector('.quick-pull-branch-name input').required = false;
|
||||
}
|
||||
$('#commit-button').text($(this).attr('button_text'));
|
||||
});
|
||||
|
@ -78,11 +78,11 @@ export function initRepoEditor() {
|
|||
const joinTreePath = ($fileNameEl) => {
|
||||
const parts = [];
|
||||
$('.breadcrumb span.section').each(function () {
|
||||
const element = $(this);
|
||||
if (element.find('a').length) {
|
||||
parts.push(element.find('a').text());
|
||||
const $element = $(this);
|
||||
if ($element.find('a').length) {
|
||||
parts.push($element.find('a').text());
|
||||
} else {
|
||||
parts.push(element.text());
|
||||
parts.push($element.text());
|
||||
}
|
||||
});
|
||||
if ($fileNameEl.val()) parts.push($fileNameEl.val());
|
||||
|
@ -135,13 +135,13 @@ export function initRepoEditor() {
|
|||
|
||||
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
||||
// to enable or disable the commit button
|
||||
const $commitButton = $('#commit-button');
|
||||
const commitButton = document.getElementById('commit-button');
|
||||
const $editForm = $('.ui.edit.form');
|
||||
const dirtyFileClass = 'dirty-file';
|
||||
|
||||
// Disabling the button at the start
|
||||
if ($('input[name="page_has_posted"]').val() !== 'true') {
|
||||
$commitButton.prop('disabled', true);
|
||||
commitButton.disabled = true;
|
||||
}
|
||||
|
||||
// Registering a custom listener for the file path and the file content
|
||||
|
@ -151,7 +151,7 @@ export function initRepoEditor() {
|
|||
fieldSelector: ':input:not(.commit-form-wrapper :input)',
|
||||
change() {
|
||||
const dirty = $(this).hasClass(dirtyFileClass);
|
||||
$commitButton.prop('disabled', !dirty);
|
||||
commitButton.disabled = !dirty;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -163,7 +163,7 @@ export function initRepoEditor() {
|
|||
editor.setValue(value);
|
||||
}
|
||||
|
||||
$commitButton.on('click', (event) => {
|
||||
commitButton?.addEventListener('click', (e) => {
|
||||
// A modal which asks if an empty file should be committed
|
||||
if ($editArea.val().length === 0) {
|
||||
$('#edit-empty-content-modal').modal({
|
||||
|
@ -171,7 +171,7 @@ export function initRepoEditor() {
|
|||
$('.edit.form').trigger('submit');
|
||||
},
|
||||
}).modal('show');
|
||||
event.preventDefault();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@ -181,6 +181,6 @@ export function renderPreviewPanelContent($panelPreviewer, data) {
|
|||
$panelPreviewer.html(data);
|
||||
initMarkupContent();
|
||||
|
||||
const refIssues = $panelPreviewer.find('p .ref-issue');
|
||||
attachRefIssueContextPopup(refIssues);
|
||||
const $refIssues = $panelPreviewer.find('p .ref-issue');
|
||||
attachRefIssueContextPopup($refIssues);
|
||||
}
|
||||
|
|
|
@ -63,10 +63,10 @@ export function initRepoGraphGit() {
|
|||
(async () => {
|
||||
const response = await GET(String(ajaxUrl));
|
||||
const html = await response.text();
|
||||
const div = $(html);
|
||||
$('#pagination').html(div.find('#pagination').html());
|
||||
$('#rel-container').html(div.find('#rel-container').html());
|
||||
$('#rev-container').html(div.find('#rev-container').html());
|
||||
const $div = $(html);
|
||||
$('#pagination').html($div.find('#pagination').html());
|
||||
$('#rel-container').html($div.find('#rel-container').html());
|
||||
$('#rev-container').html($div.find('#rev-container').html());
|
||||
$('#loading-indicator').addClass('gt-hidden');
|
||||
$('#rel-container').removeClass('gt-hidden');
|
||||
$('#rev-container').removeClass('gt-hidden');
|
||||
|
|
|
@ -6,55 +6,55 @@ import {POST} from '../modules/fetch.js';
|
|||
const {appSubUrl} = window.config;
|
||||
|
||||
export function initRepoTopicBar() {
|
||||
const mgrBtn = $('#manage_topic');
|
||||
if (!mgrBtn.length) return;
|
||||
const editDiv = $('#topic_edit');
|
||||
const viewDiv = $('#repo-topics');
|
||||
const saveBtn = $('#save_topic');
|
||||
const topicDropdown = $('#topic_edit .dropdown');
|
||||
const topicForm = editDiv; // the old logic, editDiv is topicForm
|
||||
const topicDropdownSearch = topicDropdown.find('input.search');
|
||||
const $mgrBtn = $('#manage_topic');
|
||||
if (!$mgrBtn.length) return;
|
||||
const $editDiv = $('#topic_edit');
|
||||
const $viewDiv = $('#repo-topics');
|
||||
const $saveBtn = $('#save_topic');
|
||||
const $topicDropdown = $('#topic_edit .dropdown');
|
||||
const $topicForm = $editDiv; // the old logic, $editDiv is topicForm
|
||||
const $topicDropdownSearch = $topicDropdown.find('input.search');
|
||||
const topicPrompts = {
|
||||
countPrompt: topicDropdown.attr('data-text-count-prompt'),
|
||||
formatPrompt: topicDropdown.attr('data-text-format-prompt'),
|
||||
countPrompt: $topicDropdown.attr('data-text-count-prompt'),
|
||||
formatPrompt: $topicDropdown.attr('data-text-format-prompt'),
|
||||
};
|
||||
|
||||
mgrBtn.on('click', () => {
|
||||
hideElem(viewDiv);
|
||||
showElem(editDiv);
|
||||
topicDropdownSearch.focus();
|
||||
$mgrBtn.on('click', () => {
|
||||
hideElem($viewDiv);
|
||||
showElem($editDiv);
|
||||
$topicDropdownSearch.trigger('focus');
|
||||
});
|
||||
|
||||
$('#cancel_topic_edit').on('click', () => {
|
||||
hideElem(editDiv);
|
||||
showElem(viewDiv);
|
||||
mgrBtn.focus();
|
||||
hideElem($editDiv);
|
||||
showElem($viewDiv);
|
||||
$mgrBtn.trigger('focus');
|
||||
});
|
||||
|
||||
saveBtn.on('click', async () => {
|
||||
$saveBtn.on('click', async () => {
|
||||
const topics = $('input[name=topics]').val();
|
||||
|
||||
const data = new FormData();
|
||||
data.append('topics', topics);
|
||||
|
||||
const response = await POST(saveBtn.attr('data-link'), {data});
|
||||
const response = await POST($saveBtn.attr('data-link'), {data});
|
||||
|
||||
if (response.ok) {
|
||||
const responseData = await response.json();
|
||||
if (responseData.status === 'ok') {
|
||||
viewDiv.children('.topic').remove();
|
||||
$viewDiv.children('.topic').remove();
|
||||
if (topics.length) {
|
||||
const topicArray = topics.split(',');
|
||||
topicArray.sort();
|
||||
for (const topic of topicArray) {
|
||||
const link = $('<a class="ui repo-topic large label topic gt-m-0"></a>');
|
||||
link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`);
|
||||
link.text(topic);
|
||||
link.insertBefore(mgrBtn); // insert all new topics before manage button
|
||||
const $link = $('<a class="ui repo-topic large label topic gt-m-0"></a>');
|
||||
$link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`);
|
||||
$link.text(topic);
|
||||
$link.insertBefore($mgrBtn); // insert all new topics before manage button
|
||||
}
|
||||
}
|
||||
hideElem(editDiv);
|
||||
showElem(viewDiv);
|
||||
hideElem($editDiv);
|
||||
showElem($viewDiv);
|
||||
}
|
||||
} else if (response.status === 422) {
|
||||
const responseData = await response.json();
|
||||
|
@ -62,10 +62,10 @@ export function initRepoTopicBar() {
|
|||
topicPrompts.formatPrompt = responseData.message;
|
||||
|
||||
const {invalidTopics} = responseData;
|
||||
const topicLabels = topicDropdown.children('a.ui.label');
|
||||
const $topicLabels = $topicDropdown.children('a.ui.label');
|
||||
for (const [index, value] of topics.split(',').entries()) {
|
||||
if (invalidTopics.includes(value)) {
|
||||
topicLabels.eq(index).removeClass('green').addClass('red');
|
||||
$topicLabels.eq(index).removeClass('green').addClass('red');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -74,10 +74,10 @@ export function initRepoTopicBar() {
|
|||
}
|
||||
|
||||
// Always validate the form
|
||||
topicForm.form('validate form');
|
||||
$topicForm.form('validate form');
|
||||
});
|
||||
|
||||
topicDropdown.dropdown({
|
||||
$topicDropdown.dropdown({
|
||||
allowAdditions: true,
|
||||
forceSelection: false,
|
||||
fullTextSearch: 'exact',
|
||||
|
@ -100,7 +100,7 @@ export function initRepoTopicBar() {
|
|||
const query = stripTags(this.urlData.query.trim());
|
||||
let found_query = false;
|
||||
const current_topics = [];
|
||||
topicDropdown.find('a.label.visible').each((_, el) => {
|
||||
$topicDropdown.find('a.label.visible').each((_, el) => {
|
||||
current_topics.push(el.getAttribute('data-value'));
|
||||
});
|
||||
|
||||
|
@ -150,15 +150,15 @@ export function initRepoTopicBar() {
|
|||
});
|
||||
|
||||
$.fn.form.settings.rules.validateTopic = function (_values, regExp) {
|
||||
const topics = topicDropdown.children('a.ui.label');
|
||||
const status = topics.length === 0 || topics.last().attr('data-value').match(regExp);
|
||||
const $topics = $topicDropdown.children('a.ui.label');
|
||||
const status = $topics.length === 0 || $topics.last().attr('data-value').match(regExp);
|
||||
if (!status) {
|
||||
topics.last().removeClass('green').addClass('red');
|
||||
$topics.last().removeClass('green').addClass('red');
|
||||
}
|
||||
return status && topicDropdown.children('a.ui.label.red').length === 0;
|
||||
return status && $topicDropdown.children('a.ui.label.red').length === 0;
|
||||
};
|
||||
|
||||
topicForm.form({
|
||||
$topicForm.form({
|
||||
on: 'change',
|
||||
inline: true,
|
||||
fields: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import {updateIssuesMeta} from './repo-issue.js';
|
||||
import {toggleElem, hideElem} from '../utils/dom.js';
|
||||
import {toggleElem, hideElem, isElemHidden} from '../utils/dom.js';
|
||||
import {htmlEscape} from 'escape-goat';
|
||||
import {confirmModal} from './comp/ConfirmModal.js';
|
||||
import {showErrorToast} from '../modules/toast.js';
|
||||
|
@ -8,32 +8,42 @@ import {createSortable} from '../modules/sortable.js';
|
|||
import {DELETE, POST} from '../modules/fetch.js';
|
||||
|
||||
function initRepoIssueListCheckboxes() {
|
||||
const $issueSelectAll = $('.issue-checkbox-all');
|
||||
const $issueCheckboxes = $('.issue-checkbox');
|
||||
const issueSelectAll = document.querySelector('.issue-checkbox-all');
|
||||
const issueCheckboxes = document.querySelectorAll('.issue-checkbox');
|
||||
|
||||
const syncIssueSelectionState = () => {
|
||||
const $checked = $issueCheckboxes.filter(':checked');
|
||||
const anyChecked = $checked.length !== 0;
|
||||
const allChecked = anyChecked && $checked.length === $issueCheckboxes.length;
|
||||
const checkedCheckboxes = Array.from(issueCheckboxes).filter((el) => el.checked);
|
||||
const anyChecked = Boolean(checkedCheckboxes.length);
|
||||
const allChecked = anyChecked && checkedCheckboxes.length === issueCheckboxes.length;
|
||||
|
||||
if (allChecked) {
|
||||
$issueSelectAll.prop({'checked': true, 'indeterminate': false});
|
||||
issueSelectAll.checked = true;
|
||||
issueSelectAll.indeterminate = false;
|
||||
} else if (anyChecked) {
|
||||
$issueSelectAll.prop({'checked': false, 'indeterminate': true});
|
||||
issueSelectAll.checked = false;
|
||||
issueSelectAll.indeterminate = true;
|
||||
} else {
|
||||
$issueSelectAll.prop({'checked': false, 'indeterminate': false});
|
||||
issueSelectAll.checked = false;
|
||||
issueSelectAll.indeterminate = false;
|
||||
}
|
||||
// if any issue is selected, show the action panel, otherwise show the filter panel
|
||||
toggleElem($('#issue-filters'), !anyChecked);
|
||||
toggleElem($('#issue-actions'), anyChecked);
|
||||
// there are two panels but only one select-all checkbox, so move the checkbox to the visible panel
|
||||
$('#issue-filters, #issue-actions').filter(':visible').find('.issue-list-toolbar-left').prepend($issueSelectAll);
|
||||
const panels = document.querySelectorAll('#issue-filters, #issue-actions');
|
||||
const visiblePanel = Array.from(panels).find((el) => !isElemHidden(el));
|
||||
const toolbarLeft = visiblePanel.querySelector('.issue-list-toolbar-left');
|
||||
toolbarLeft.prepend(issueSelectAll);
|
||||
};
|
||||
|
||||
$issueCheckboxes.on('change', syncIssueSelectionState);
|
||||
for (const el of issueCheckboxes) {
|
||||
el.addEventListener('change', syncIssueSelectionState);
|
||||
}
|
||||
|
||||
$issueSelectAll.on('change', () => {
|
||||
$issueCheckboxes.prop('checked', $issueSelectAll.is(':checked'));
|
||||
issueSelectAll.addEventListener('change', () => {
|
||||
for (const el of issueCheckboxes) {
|
||||
el.checked = issueSelectAll.checked;
|
||||
}
|
||||
syncIssueSelectionState();
|
||||
});
|
||||
|
||||
|
@ -125,7 +135,9 @@ function initRepoIssueListAuthorDropdown() {
|
|||
if (newMenuHtml) {
|
||||
const $newMenuItems = $(newMenuHtml);
|
||||
$newMenuItems.addClass('dynamic-item');
|
||||
$menu.append('<div class="divider dynamic-item"></div>', ...$newMenuItems);
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('divider', 'dynamic-item');
|
||||
$menu[0].append(div, ...$newMenuItems);
|
||||
}
|
||||
$searchDropdown.dropdown('refresh');
|
||||
// defer our selection to the next tick, because dropdown will set the selection item after this `menu` function
|
||||
|
|
|
@ -144,9 +144,9 @@ export function initRepoIssueSidebarList() {
|
|||
|
||||
$('.menu .ui.dropdown.label-filter').on('keydown', (e) => {
|
||||
if (e.altKey && e.keyCode === 13) {
|
||||
const selectedItems = $('.menu .ui.dropdown.label-filter .menu .item.selected');
|
||||
if (selectedItems.length > 0) {
|
||||
excludeLabel($(selectedItems[0]));
|
||||
const $selectedItems = $('.menu .ui.dropdown.label-filter .menu .item.selected');
|
||||
if ($selectedItems.length > 0) {
|
||||
excludeLabel($($selectedItems[0]));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -214,12 +214,12 @@ export function initRepoIssueDependencyDelete() {
|
|||
export function initRepoIssueCodeCommentCancel() {
|
||||
// Cancel inline code comment
|
||||
$(document).on('click', '.cancel-code-comment', (e) => {
|
||||
const form = $(e.currentTarget).closest('form');
|
||||
if (form.length > 0 && form.hasClass('comment-form')) {
|
||||
form.addClass('gt-hidden');
|
||||
showElem(form.closest('.comment-code-cloud').find('button.comment-form-reply'));
|
||||
const $form = $(e.currentTarget).closest('form');
|
||||
if ($form.length > 0 && $form.hasClass('comment-form')) {
|
||||
$form.addClass('gt-hidden');
|
||||
showElem($form.closest('.comment-code-cloud').find('button.comment-form-reply'));
|
||||
} else {
|
||||
form.closest('.comment-code-cloud').remove();
|
||||
$form.closest('.comment-code-cloud').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -370,10 +370,10 @@ export function initRepoIssueComments() {
|
|||
});
|
||||
|
||||
$(document).on('click', (event) => {
|
||||
const urlTarget = $(':target');
|
||||
if (urlTarget.length === 0) return;
|
||||
const $urlTarget = $(':target');
|
||||
if ($urlTarget.length === 0) return;
|
||||
|
||||
const urlTargetId = urlTarget.attr('id');
|
||||
const urlTargetId = $urlTarget.attr('id');
|
||||
if (!urlTargetId) return;
|
||||
if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
|
||||
|
||||
|
@ -390,18 +390,18 @@ export function initRepoIssueComments() {
|
|||
|
||||
export async function handleReply($el) {
|
||||
hideElem($el);
|
||||
const form = $el.closest('.comment-code-cloud').find('.comment-form');
|
||||
form.removeClass('gt-hidden');
|
||||
const $form = $el.closest('.comment-code-cloud').find('.comment-form');
|
||||
$form.removeClass('gt-hidden');
|
||||
|
||||
const $textarea = form.find('textarea');
|
||||
const $textarea = $form.find('textarea');
|
||||
let editor = getComboMarkdownEditor($textarea);
|
||||
if (!editor) {
|
||||
// FIXME: the initialization of the dropzone is not consistent.
|
||||
// When the page is loaded, the dropzone is initialized by initGlobalDropzone, but the editor is not initialized.
|
||||
// When the form is submitted and partially reload, none of them is initialized.
|
||||
const dropzone = form.find('.dropzone')[0];
|
||||
const dropzone = $form.find('.dropzone')[0];
|
||||
if (!dropzone.dropzone) initDropzone(dropzone);
|
||||
editor = await initComboMarkdownEditor(form.find('.combo-markdown-editor'));
|
||||
editor = await initComboMarkdownEditor($form.find('.combo-markdown-editor'));
|
||||
}
|
||||
editor.focus();
|
||||
return editor;
|
||||
|
@ -413,30 +413,30 @@ export function initRepoPullRequestReview() {
|
|||
if (window.history.scrollRestoration !== 'manual') {
|
||||
window.history.scrollRestoration = 'manual';
|
||||
}
|
||||
const commentDiv = $(window.location.hash);
|
||||
if (commentDiv) {
|
||||
const $commentDiv = $(window.location.hash);
|
||||
if ($commentDiv) {
|
||||
// get the name of the parent id
|
||||
const groupID = commentDiv.closest('div[id^="code-comments-"]').attr('id');
|
||||
const groupID = $commentDiv.closest('div[id^="code-comments-"]').attr('id');
|
||||
if (groupID && groupID.startsWith('code-comments-')) {
|
||||
const id = groupID.slice(14);
|
||||
const ancestorDiffBox = commentDiv.closest('.diff-file-box');
|
||||
const $ancestorDiffBox = $commentDiv.closest('.diff-file-box');
|
||||
// on pages like conversation, there is no diff header
|
||||
const diffHeader = ancestorDiffBox.find('.diff-file-header');
|
||||
const $diffHeader = $ancestorDiffBox.find('.diff-file-header');
|
||||
// offset is for scrolling
|
||||
let offset = 30;
|
||||
if (diffHeader[0]) {
|
||||
offset += $('.diff-detail-box').outerHeight() + diffHeader.outerHeight();
|
||||
if ($diffHeader[0]) {
|
||||
offset += $('.diff-detail-box').outerHeight() + $diffHeader.outerHeight();
|
||||
}
|
||||
$(`#show-outdated-${id}`).addClass('gt-hidden');
|
||||
$(`#code-comments-${id}`).removeClass('gt-hidden');
|
||||
$(`#code-preview-${id}`).removeClass('gt-hidden');
|
||||
$(`#hide-outdated-${id}`).removeClass('gt-hidden');
|
||||
// if the comment box is folded, expand it
|
||||
if (ancestorDiffBox.attr('data-folded') && ancestorDiffBox.attr('data-folded') === 'true') {
|
||||
setFileFolding(ancestorDiffBox[0], ancestorDiffBox.find('.fold-file')[0], false);
|
||||
if ($ancestorDiffBox.attr('data-folded') && $ancestorDiffBox.attr('data-folded') === 'true') {
|
||||
setFileFolding($ancestorDiffBox[0], $ancestorDiffBox.find('.fold-file')[0], false);
|
||||
}
|
||||
window.scrollTo({
|
||||
top: commentDiv.offset().top - offset,
|
||||
top: $commentDiv.offset().top - offset,
|
||||
behavior: 'instant'
|
||||
});
|
||||
}
|
||||
|
@ -504,12 +504,12 @@ export function initRepoPullRequestReview() {
|
|||
const side = $(this).data('side');
|
||||
const idx = $(this).data('idx');
|
||||
const path = $(this).closest('[data-path]').data('path');
|
||||
const tr = $(this).closest('tr');
|
||||
const lineType = tr.data('line-type');
|
||||
const $tr = $(this).closest('tr');
|
||||
const lineType = $tr.data('line-type');
|
||||
|
||||
let ntr = tr.next();
|
||||
if (!ntr.hasClass('add-comment')) {
|
||||
ntr = $(`
|
||||
let $ntr = $tr.next();
|
||||
if (!$ntr.hasClass('add-comment')) {
|
||||
$ntr = $(`
|
||||
<tr class="add-comment" data-line-type="${lineType}">
|
||||
${isSplit ? `
|
||||
<td class="add-comment-left" colspan="4"></td>
|
||||
|
@ -518,22 +518,22 @@ export function initRepoPullRequestReview() {
|
|||
<td class="add-comment-left add-comment-right" colspan="5"></td>
|
||||
`}
|
||||
</tr>`);
|
||||
tr.after(ntr);
|
||||
$tr.after($ntr);
|
||||
}
|
||||
|
||||
const td = ntr.find(`.add-comment-${side}`);
|
||||
const commentCloud = td.find('.comment-code-cloud');
|
||||
if (commentCloud.length === 0 && !ntr.find('button[name="pending_review"]').length) {
|
||||
const $td = $ntr.find(`.add-comment-${side}`);
|
||||
const $commentCloud = $td.find('.comment-code-cloud');
|
||||
if ($commentCloud.length === 0 && !$ntr.find('button[name="pending_review"]').length) {
|
||||
try {
|
||||
const response = await GET($(this).closest('[data-new-comment-url]').attr('data-new-comment-url'));
|
||||
const html = await response.text();
|
||||
td.html(html);
|
||||
td.find("input[name='line']").val(idx);
|
||||
td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
|
||||
td.find("input[name='path']").val(path);
|
||||
$td.html(html);
|
||||
$td.find("input[name='line']").val(idx);
|
||||
$td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
|
||||
$td.find("input[name='path']").val(path);
|
||||
|
||||
initDropzone(td.find('.dropzone')[0]);
|
||||
const editor = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
|
||||
initDropzone($td.find('.dropzone')[0]);
|
||||
const editor = await initComboMarkdownEditor($td.find('.combo-markdown-editor'));
|
||||
editor.focus();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -646,18 +646,18 @@ export function initRepoIssueTitleEdit() {
|
|||
|
||||
export function initRepoIssueBranchSelect() {
|
||||
const changeBranchSelect = function () {
|
||||
const selectionTextField = $('#pull-target-branch');
|
||||
const $selectionTextField = $('#pull-target-branch');
|
||||
|
||||
const baseName = selectionTextField.data('basename');
|
||||
const baseName = $selectionTextField.data('basename');
|
||||
const branchNameNew = $(this).data('branch');
|
||||
const branchNameOld = selectionTextField.data('branch');
|
||||
const branchNameOld = $selectionTextField.data('branch');
|
||||
|
||||
// Replace branch name to keep translation from HTML template
|
||||
selectionTextField.html(selectionTextField.html().replace(
|
||||
$selectionTextField.html($selectionTextField.html().replace(
|
||||
`${baseName}:${branchNameOld}`,
|
||||
`${baseName}:${branchNameNew}`
|
||||
));
|
||||
selectionTextField.data('branch', branchNameNew); // update branch name in setting
|
||||
$selectionTextField.data('branch', branchNameNew); // update branch name in setting
|
||||
};
|
||||
$('#branch-select > .item').on('click', changeBranchSelect);
|
||||
}
|
||||
|
|
|
@ -76,11 +76,11 @@ export function initRepoCommentForm() {
|
|||
}
|
||||
|
||||
if (editMode === 'true') {
|
||||
const form = $('#update_issueref_form');
|
||||
const $form = $('#update_issueref_form');
|
||||
const params = new URLSearchParams();
|
||||
params.append('ref', selectedValue);
|
||||
try {
|
||||
await POST(form.attr('action'), {data: params});
|
||||
await POST($form.attr('action'), {data: params});
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -139,7 +139,7 @@ export function initRepoCommentForm() {
|
|||
|
||||
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
|
||||
|
||||
const clickedItem = $(this);
|
||||
const $clickedItem = $(this);
|
||||
const scope = $(this).attr('data-scope');
|
||||
|
||||
$(this).parent().find('.item').each(function () {
|
||||
|
@ -148,10 +148,10 @@ export function initRepoCommentForm() {
|
|||
if ($(this).attr('data-scope') !== scope) {
|
||||
return true;
|
||||
}
|
||||
if (!$(this).is(clickedItem) && !$(this).hasClass('checked')) {
|
||||
if (!$(this).is($clickedItem) && !$(this).hasClass('checked')) {
|
||||
return true;
|
||||
}
|
||||
} else if (!$(this).is(clickedItem)) {
|
||||
} else if (!$(this).is($clickedItem)) {
|
||||
// Toggle for other labels
|
||||
return true;
|
||||
}
|
||||
|
@ -352,8 +352,8 @@ async function onEditContent(event) {
|
|||
this.on('success', (file, data) => {
|
||||
file.uuid = data.uuid;
|
||||
fileUuidDict[file.uuid] = {submitted: false};
|
||||
const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
$dropzone.find('.files').append(input);
|
||||
const $input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
$dropzone.find('.files').append($input);
|
||||
});
|
||||
this.on('removedfile', async (file) => {
|
||||
if (disableRemovedfileEvent) return;
|
||||
|
@ -390,8 +390,8 @@ async function onEditContent(event) {
|
|||
dz.files.push(attachment);
|
||||
fileUuidDict[attachment.uuid] = {submitted: true};
|
||||
$dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%');
|
||||
const input = $(`<input id="${attachment.uuid}" name="files" type="hidden">`).val(attachment.uuid);
|
||||
$dropzone.find('.files').append(input);
|
||||
const $input = $(`<input id="${attachment.uuid}" name="files" type="hidden">`).val(attachment.uuid);
|
||||
$dropzone.find('.files').append($input);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -430,19 +430,18 @@ async function onEditContent(event) {
|
|||
} else {
|
||||
$renderContent.html(data.content);
|
||||
$rawContent.text(comboMarkdownEditor.value());
|
||||
const refIssues = $renderContent.find('p .ref-issue');
|
||||
attachRefIssueContextPopup(refIssues);
|
||||
const $refIssues = $renderContent.find('p .ref-issue');
|
||||
attachRefIssueContextPopup($refIssues);
|
||||
}
|
||||
const $content = $segment;
|
||||
if (!$content.find('.dropzone-attachments').length) {
|
||||
if (data.attachments !== '') {
|
||||
$content.append(`<div class="dropzone-attachments"></div>`);
|
||||
$content.find('.dropzone-attachments').replaceWith(data.attachments);
|
||||
$content[0].append(data.attachments);
|
||||
}
|
||||
} else if (data.attachments === '') {
|
||||
$content.find('.dropzone-attachments').remove();
|
||||
} else {
|
||||
$content.find('.dropzone-attachments').replaceWith(data.attachments);
|
||||
$content.find('.dropzone-attachments')[0].outerHTML = data.attachments;
|
||||
}
|
||||
if (dz) {
|
||||
dz.emit('submit');
|
||||
|
@ -534,7 +533,7 @@ export function initRepository() {
|
|||
const gitignores = $('input[name="gitignores"]').val();
|
||||
const license = $('input[name="license"]').val();
|
||||
if (gitignores || license) {
|
||||
$('input[name="auto_init"]').prop('checked', true);
|
||||
document.querySelector('input[name="auto_init"]').checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -105,14 +105,14 @@ export function initRepoProject() {
|
|||
const _promise = initRepoProjectSortable();
|
||||
|
||||
$('.edit-project-column-modal').each(function () {
|
||||
const projectHeader = $(this).closest('.project-column-header');
|
||||
const projectTitleLabel = projectHeader.find('.project-column-title');
|
||||
const projectTitleInput = $(this).find('.project-column-title-input');
|
||||
const projectColorInput = $(this).find('#new_project_column_color');
|
||||
const boardColumn = $(this).closest('.project-column');
|
||||
const $projectHeader = $(this).closest('.project-column-header');
|
||||
const $projectTitleLabel = $projectHeader.find('.project-column-title');
|
||||
const $projectTitleInput = $(this).find('.project-column-title-input');
|
||||
const $projectColorInput = $(this).find('#new_project_column_color');
|
||||
const $boardColumn = $(this).closest('.project-column');
|
||||
|
||||
if (boardColumn.css('backgroundColor')) {
|
||||
setLabelColor(projectHeader, rgbToHex(boardColumn.css('backgroundColor')));
|
||||
if ($boardColumn.css('backgroundColor')) {
|
||||
setLabelColor($projectHeader, rgbToHex($boardColumn.css('backgroundColor')));
|
||||
}
|
||||
|
||||
$(this).find('.edit-project-column-button').on('click', async function (e) {
|
||||
|
@ -121,34 +121,34 @@ export function initRepoProject() {
|
|||
try {
|
||||
await PUT($(this).data('url'), {
|
||||
data: {
|
||||
title: projectTitleInput.val(),
|
||||
color: projectColorInput.val(),
|
||||
title: $projectTitleInput.val(),
|
||||
color: $projectColorInput.val(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
projectTitleLabel.text(projectTitleInput.val());
|
||||
projectTitleInput.closest('form').removeClass('dirty');
|
||||
if (projectColorInput.val()) {
|
||||
setLabelColor(projectHeader, projectColorInput.val());
|
||||
$projectTitleLabel.text($projectTitleInput.val());
|
||||
$projectTitleInput.closest('form').removeClass('dirty');
|
||||
if ($projectColorInput.val()) {
|
||||
setLabelColor($projectHeader, $projectColorInput.val());
|
||||
}
|
||||
boardColumn.attr('style', `background: ${projectColorInput.val()}!important`);
|
||||
$boardColumn.attr('style', `background: ${$projectColorInput.val()}!important`);
|
||||
$('.ui.modal').modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.default-project-column-modal').each(function () {
|
||||
const boardColumn = $(this).closest('.project-column');
|
||||
const showButton = $(boardColumn).find('.default-project-column-show');
|
||||
const commitButton = $(this).find('.actions > .ok.button');
|
||||
const $boardColumn = $(this).closest('.project-column');
|
||||
const $showButton = $($boardColumn).find('.default-project-column-show');
|
||||
const $commitButton = $(this).find('.actions > .ok.button');
|
||||
|
||||
$(commitButton).on('click', async (e) => {
|
||||
$($commitButton).on('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await POST($(showButton).data('url'));
|
||||
await POST($($showButton).data('url'));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
|
@ -158,11 +158,11 @@ export function initRepoProject() {
|
|||
});
|
||||
|
||||
$('.show-delete-project-column-modal').each(function () {
|
||||
const deleteColumnModal = $(`${$(this).attr('data-modal')}`);
|
||||
const deleteColumnButton = deleteColumnModal.find('.actions > .ok.button');
|
||||
const $deleteColumnModal = $(`${$(this).attr('data-modal')}`);
|
||||
const $deleteColumnButton = $deleteColumnModal.find('.actions > .ok.button');
|
||||
const deleteUrl = $(this).attr('data-url');
|
||||
|
||||
deleteColumnButton.on('click', async (e) => {
|
||||
$deleteColumnButton.on('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
|
@ -177,13 +177,13 @@ export function initRepoProject() {
|
|||
|
||||
$('#new_project_column_submit').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
const columnTitle = $('#new_project_column');
|
||||
const projectColorInput = $('#new_project_column_color_picker');
|
||||
if (!columnTitle.val()) {
|
||||
const $columnTitle = $('#new_project_column');
|
||||
const $projectColorInput = $('#new_project_column_color_picker');
|
||||
if (!$columnTitle.val()) {
|
||||
return;
|
||||
}
|
||||
const url = e.target.getAttribute('data-url');
|
||||
createNewColumn(url, columnTitle, projectColorInput);
|
||||
createNewColumn(url, $columnTitle, $projectColorInput);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
window.$ = window.jQuery = $;
|
||||
window.$ = window.jQuery = $; // eslint-disable-line no-jquery/variable-pattern
|
||||
|
|
|
@ -72,7 +72,9 @@ function delegateOne($dropdown) {
|
|||
dropdownTemplates.menu = function(response, fields, preserveHTML, className) {
|
||||
// when the dropdown menu items are loaded from AJAX requests, the items are created dynamically
|
||||
const menuItems = dropdownTemplatesMenuOld(response, fields, preserveHTML, className);
|
||||
const $wrapper = $('<div>').append(menuItems);
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = menuItems;
|
||||
const $wrapper = $(div);
|
||||
const $items = $wrapper.find('> .item');
|
||||
$items.each((_, item) => updateMenuItem($dropdown[0], item));
|
||||
$dropdown[0][ariaPatchKey].deferredRefreshAriaActiveItem();
|
||||
|
|
Loading…
Reference in New Issue