Browse Source

Merge branch 'main' into feature/remove-signin-tabs

pull/30362/head
Joshua Nazareth 3 weeks ago
parent
commit
5471834c2b
No account linked to committer's email address
91 changed files with 4771 additions and 8796 deletions
  1. 2
    2
      .eslintrc.yaml
  2. 1
    0
      .gitattributes
  3. 2
    0
      .golangci.yml
  4. 1
    1
      Makefile
  5. 3
    12
      build/code-batch-process.go
  6. 5
    9
      cmd/admin_user_change_password.go
  7. 22
    16
      cmd/admin_user_create.go
  8. 2
    3
      docs/content/administration/command-line.en-us.md
  9. 5
    0
      models/auth/oauth2.go
  10. 2
    2
      models/db/engine.go
  11. 18
    14
      models/git/commit_status_summary.go
  12. 2
    0
      models/migrations/migrations.go
  13. 16
    0
      models/migrations/v1_23/v296.go
  14. 32
    106
      modules/cache/cache.go
  15. 1
    1
      modules/cache/cache_redis.go
  16. 2
    38
      modules/cache/cache_test.go
  17. 1
    1
      modules/cache/cache_twoqueue.go
  18. 120
    0
      modules/cache/string_cache.go
  19. 4
    11
      modules/git/last_commit_cache.go
  20. 1
    1
      modules/optional/serialization.go
  21. 7
    0
      modules/session/store.go
  22. 21
    17
      modules/templates/util_misc.go
  23. 4
    3
      modules/templates/util_render.go
  24. 31
    9
      modules/templates/util_render_test.go
  25. 27
    5
      modules/web/middleware/cookie.go
  26. 27
    0
      options/license/BSD-2-clause-first-lines
  27. 13
    0
      options/license/Sun-PPP-2000
  28. 7
    0
      options/license/pkgconf
  29. 231
    246
      package-lock.json
  30. 12
    12
      package.json
  31. 3
    3
      poetry.lock
  32. 2
    4
      routers/api/v1/misc/nodeinfo.go
  33. 4
    4
      routers/web/org/projects.go
  34. 0
    2
      routers/web/repo/issue.go
  35. 2
    7
      routers/web/repo/projects.go
  36. 1
    1
      services/actions/auth_test.go
  37. 2
    1
      services/auth/source/oauth2/store.go
  38. 3
    5
      services/context/api.go
  39. 1
    1
      services/context/captcha.go
  40. 4
    4
      services/context/context.go
  41. 10
    2
      services/mirror/mirror_pull.go
  42. 5
    6
      services/repository/branch.go
  43. 1
    1
      services/repository/commitstatus/commitstatus.go
  44. 12
    20
      services/repository/contributors_graph.go
  45. 9
    11
      services/repository/contributors_graph_test.go
  46. 1
    1
      templates/admin/notice.tmpl
  47. 1
    1
      templates/admin/repo/unadopted.tmpl
  48. 1
    9
      templates/base/modal_actions_confirm.tmpl
  49. 9
    22
      templates/devtest/fomantic-modal.tmpl
  50. 3
    39
      templates/devtest/gitea-ui.tmpl
  51. 27
    0
      templates/devtest/label.tmpl
  52. 1
    1
      templates/org/team/sidebar.tmpl
  53. 1
    1
      templates/repo/code/recently_pushed_new_branches.tmpl
  54. 2
    2
      templates/repo/diff/comments.tmpl
  55. 8
    16
      templates/repo/diff/compare.tmpl
  56. 2
    2
      templates/repo/issue/view_content.tmpl
  57. 4
    6
      templates/repo/issue/view_content/add_reaction.tmpl
  58. 7
    7
      templates/repo/issue/view_content/comments.tmpl
  59. 4
    4
      templates/repo/issue/view_content/conversation.tmpl
  60. 4
    4
      templates/repo/issue/view_content/reactions.tmpl
  61. 1
    1
      templates/repo/issue/view_content/sidebar.tmpl
  62. 15
    11
      templates/repo/pulse.tmpl
  63. 1
    1
      templates/repo/settings/lfs.tmpl
  64. 1
    1
      templates/repo/settings/lfs_pointers.tmpl
  65. 1
    1
      templates/repo/settings/options.tmpl
  66. 1
    1
      templates/repo/settings/webhook/history.tmpl
  67. 5
    7
      templates/user/dashboard/dashboard.tmpl
  68. 1
    1
      templates/user/dashboard/repolist.tmpl
  69. 1
    1
      templates/user/settings/applications.tmpl
  70. 2
    0
      updates.config.js
  71. 6
    197
      web_src/css/base.css
  72. 0
    1
      web_src/css/dashboard.css
  73. 3
    0
      web_src/css/helpers.css
  74. 2
    0
      web_src/css/index.css
  75. 212
    557
      web_src/css/modules/button.css
  76. 1
    1
      web_src/css/modules/comment.css
  77. 5
    1
      web_src/css/modules/divider.css
  78. 9
    1
      web_src/css/modules/flexcontainer.css
  79. 0
    1
      web_src/css/modules/header.css
  80. 1
    1
      web_src/css/modules/label.css
  81. 802
    0
      web_src/css/modules/menu.css
  82. 4
    0
      web_src/css/modules/modal.css
  83. 24
    133
      web_src/css/repo.css
  84. 70
    0
      web_src/css/repo/reactions.css
  85. 2815
    7143
      web_src/fomantic/build/semantic.css
  86. 0
    2
      web_src/fomantic/semantic.json
  87. 1
    1
      web_src/js/components/PullRequestMergeForm.vue
  88. 27
    29
      web_src/js/features/comp/ReactionSelector.js
  89. 0
    1
      web_src/js/features/repo-diff.js
  90. 3
    5
      web_src/js/features/repo-issue.js
  91. 1
    1
      web_src/js/features/repo-legacy.js

+ 2
- 2
.eslintrc.yaml View File

@@ -318,7 +318,7 @@ rules:
jquery/no-serialize: [2]
jquery/no-show: [2]
jquery/no-size: [2]
jquery/no-sizzle: [0]
jquery/no-sizzle: [2]
jquery/no-slide: [0]
jquery/no-submit: [0]
jquery/no-text: [0]
@@ -470,7 +470,7 @@ rules:
no-jquery/no-selector-prop: [2]
no-jquery/no-serialize: [2]
no-jquery/no-size: [2]
no-jquery/no-sizzle: [0]
no-jquery/no-sizzle: [2]
no-jquery/no-slide: [2]
no-jquery/no-sub: [2]
no-jquery/no-support: [2]

+ 1
- 0
.gitattributes View File

@@ -1,5 +1,6 @@
* text=auto eol=lf
*.tmpl linguist-language=Handlebars
*.pb.go linguist-generated
/assets/*.json linguist-generated
/public/assets/img/svg/*.svg linguist-generated
/templates/swagger/v1_json.tmpl linguist-generated

+ 2
- 0
.golangci.yml View File

@@ -86,6 +86,8 @@ linters-settings:
desc: do not use the internal package, use AddXxx function instead
- pkg: gopkg.in/ini.v1
desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system

issues:
max-issues-per-linter: 0

+ 1
- 1
Makefile View File

@@ -295,7 +295,7 @@ clean:

.PHONY: fmt
fmt:
GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
@# whitespace before it

+ 3
- 12
build/code-batch-process.go View File

@@ -69,6 +69,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error)
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))

co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`\.pb\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
@@ -203,17 +204,6 @@ Example:
`, "file-batch-exec")
}

func getGoVersion() string {
goModFile, err := os.ReadFile("go.mod")
if err != nil {
log.Fatalf(`Faild to read "go.mod": %v`, err)
os.Exit(1)
}
goModVersionRegex := regexp.MustCompile(`go \d+\.\d+`)
goModVersionLine := goModVersionRegex.Find(goModFile)
return string(goModVersionLine[3:])
}

func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
fileFilter := mainOptions["file-filter"]
if fileFilter == "" {
@@ -278,7 +268,8 @@ func main() {
log.Print("the -d option is not supported by gitea-fmt")
}
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...)))
cmdErrors = append(cmdErrors, passThroughCmd("gofmt", append([]string{"-w", "-r", "interface{} -> any"}, substArgs...)))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra"}, substArgs...)))
default:
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
}

+ 5
- 9
cmd/admin_user_change_password.go View File

@@ -36,6 +36,7 @@ var microcmdUserChangePassword = &cli.Command{
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password",
Value: true,
},
},
}
@@ -57,23 +58,18 @@ func runChangePassword(c *cli.Context) error {
return err
}

var mustChangePassword optional.Option[bool]
if c.IsSet("must-change-password") {
mustChangePassword = optional.Some(c.Bool("must-change-password"))
}

opts := &user_service.UpdateAuthOptions{
Password: optional.Some(c.String("password")),
MustChangePassword: mustChangePassword,
MustChangePassword: optional.Some(c.Bool("must-change-password")),
}
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
switch {
case errors.Is(err, password.ErrMinLength):
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
return fmt.Errorf("password is not long enough, needs to be at least %d characters", setting.MinPasswordLength)
case errors.Is(err, password.ErrComplexity):
return errors.New("Password does not meet complexity requirements")
return errors.New("password does not meet complexity requirements")
case errors.Is(err, password.ErrIsPwned):
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
return errors.New("the password is in a list of stolen passwords previously exposed in public data breaches, please try again with a different password, to see more details: https://haveibeenpwned.com/Passwords")
default:
return err
}

+ 22
- 16
cmd/admin_user_create.go View File

@@ -8,6 +8,7 @@ import (
"fmt"

auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional"
@@ -46,8 +47,9 @@ var microcmdUserCreate = &cli.Command{
Usage: "Generate a random password for the user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
Name: "must-change-password",
Usage: "Set to false to prevent forcing the user to change their password after initial login",
DisableDefaultText: true,
},
&cli.IntFlag{
Name: "random-password-length",
@@ -71,10 +73,10 @@ func runCreateUser(c *cli.Context) error {
}

if c.IsSet("name") && c.IsSet("username") {
return errors.New("Cannot set both --name and --username flags")
return errors.New("cannot set both --name and --username flags")
}
if !c.IsSet("name") && !c.IsSet("username") {
return errors.New("One of --name or --username flags must be set")
return errors.New("one of --name or --username flags must be set")
}

if c.IsSet("password") && c.IsSet("random-password") {
@@ -110,17 +112,21 @@ func runCreateUser(c *cli.Context) error {
return errors.New("must set either password or random-password flag")
}

// always default to true
changePassword := true

// If this is the first user being created.
// Take it as the admin and don't force a password update.
if n := user_model.CountUsers(ctx, nil); n == 0 {
changePassword = false
}

isAdmin := c.Bool("admin")
mustChangePassword := true // always default to true
if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password")
// if the flag is set, use the value provided by the user
mustChangePassword = c.Bool("must-change-password")
} else {
// check whether there are users in the database
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
if err != nil {
return fmt.Errorf("IsTableNotEmpty: %w", err)
}
if !hasUserRecord && isAdmin {
// if this is the first admin being created, don't force to change password (keep the old behavior)
mustChangePassword = false
}
}

restricted := optional.None[bool]()
@@ -136,8 +142,8 @@ func runCreateUser(c *cli.Context) error {
Name: username,
Email: c.String("email"),
Passwd: password,
IsAdmin: c.Bool("admin"),
MustChangePassword: changePassword,
IsAdmin: isAdmin,
MustChangePassword: mustChangePassword,
Visibility: visibility,
}


+ 2
- 3
docs/content/administration/command-line.en-us.md View File

@@ -83,8 +83,7 @@ Admin operations:
- `--email value`: Email. Required.
- `--admin`: If provided, this makes the user an admin. Optional.
- `--access-token`: If provided, an access token will be created for the user. Optional. (default: false).
- `--must-change-password`: If provided, the created user will be required to choose a newer password after the
initial login. Optional. (default: true).
- `--must-change-password`: The created user will be required to set a new password after the initial login, default: true. It could be disabled by `--must-change-password=false`.
- `--random-password`: If provided, a randomly generated password will be used as the password of the created
user. The value of `--password` will be discarded. Optional.
- `--random-password-length`: If provided, it will be used to configure the length of the randomly generated
@@ -95,7 +94,7 @@ Admin operations:
- Options:
- `--username value`, `-u value`: Username. Required.
- `--password value`, `-p value`: New password. Required.
- `--must-change-password`: If provided, the user is required to choose a new password after the login. Optional.
- `--must-change-password`: The user is required to set a new password after the login, default: true. It could be disabled by `--must-change-password=false`.
- Examples:
- `gitea admin user change-password --username myname --password asecurepassword`
- `must-change-password`:

+ 5
- 0
models/auth/oauth2.go View File

@@ -137,6 +137,11 @@ func (app *OAuth2Application) TableName() string {

// ContainsRedirectURI checks if redirectURI is allowed for app
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
// OAuth2 requires the redirect URI to be an exact match, no dynamic parts are allowed.
// https://stackoverflow.com/questions/55524480/should-dynamic-query-parameters-be-present-in-the-redirection-uri-for-an-oauth2
// https://www.rfc-editor.org/rfc/rfc6819#section-5.2.3.3
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1
contains := func(s string) bool {
s = strings.TrimSuffix(strings.ToLower(s), "/")
for _, u := range app.RedirectURIs {

+ 2
- 2
models/db/engine.go View File

@@ -284,8 +284,8 @@ func MaxBatchInsertSize(bean any) int {
}

// IsTableNotEmpty returns true if table has at least one record
func IsTableNotEmpty(tableName string) (bool, error) {
return x.Table(tableName).Exist()
func IsTableNotEmpty(beanOrTableName any) (bool, error) {
return x.Table(beanOrTableName).Exist()
}

// DeleteAllRecords will delete all the records of this table

+ 18
- 14
models/git/commit_status_summary.go View File

@@ -15,10 +15,11 @@ import (

// CommitStatusSummary holds the latest commit Status of a single Commit
type CommitStatusSummary struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
TargetURL string `xorm:"TEXT"`
}

func init() {
@@ -44,9 +45,10 @@ func GetLatestCommitStatusForRepoAndSHAs(ctx context.Context, repoSHAs []RepoSHA
commitStatuses := make([]*CommitStatus, 0, len(repoSHAs))
for _, summary := range summaries {
commitStatuses = append(commitStatuses, &CommitStatus{
RepoID: summary.RepoID,
SHA: summary.SHA,
State: summary.State,
RepoID: summary.RepoID,
SHA: summary.SHA,
State: summary.State,
TargetURL: summary.TargetURL,
})
}
return commitStatuses, nil
@@ -61,22 +63,24 @@ func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) er
// mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database,
// so we need to use insert in on duplicate
if setting.Database.Type.IsMySQL() {
_, err := db.GetEngine(ctx).Exec("INSERT INTO commit_status_summary (repo_id,sha,state) VALUES (?,?,?) ON DUPLICATE KEY UPDATE state=?",
repoID, sha, state.State, state.State)
_, err := db.GetEngine(ctx).Exec("INSERT INTO commit_status_summary (repo_id,sha,state,target_url) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE state=?",
repoID, sha, state.State, state.TargetURL, state.State)
return err
}

if cnt, err := db.GetEngine(ctx).Where("repo_id=? AND sha=?", repoID, sha).
Cols("state").
Cols("state, target_url").
Update(&CommitStatusSummary{
State: state.State,
State: state.State,
TargetURL: state.TargetURL,
}); err != nil {
return err
} else if cnt == 0 {
_, err = db.GetEngine(ctx).Insert(&CommitStatusSummary{
RepoID: repoID,
SHA: sha,
State: state.State,
RepoID: repoID,
SHA: sha,
State: state.State,
TargetURL: state.TargetURL,
})
return err
}

+ 2
- 0
models/migrations/migrations.go View File

@@ -580,6 +580,8 @@ var migrations = []Migration{
NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
// v295 -> v296
NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
// v296 -> v297
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
}

// GetCurrentDBVersion returns the current db version

+ 16
- 0
models/migrations/v1_23/v296.go View File

@@ -0,0 +1,16 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_23 //nolint

import "xorm.io/xorm"

func AddCommitStatusSummary2(x *xorm.Engine) error {
type CommitStatusSummary struct {
ID int64 `xorm:"pk autoincr"`
TargetURL string `xorm:"TEXT"`
}
// there is no migrations because if there is no data on this table, it will fall back to get data
// from commit status
return x.Sync(new(CommitStatusSummary))
}

+ 32
- 106
modules/cache/cache.go View File

@@ -4,149 +4,75 @@
package cache

import (
"fmt"
"strconv"
"time"

"code.gitea.io/gitea/modules/setting"

mc "gitea.com/go-chi/cache"

_ "gitea.com/go-chi/cache/memcache" // memcache plugin for cache
)

var conn mc.Cache

func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
return mc.NewCacher(mc.Options{
Adapter: cacheConfig.Adapter,
AdapterConfig: cacheConfig.Conn,
Interval: cacheConfig.Interval,
})
}
var defaultCache StringCache

// Init start cache service
func Init() error {
var err error

if conn == nil {
if conn, err = newCache(setting.CacheService.Cache); err != nil {
if defaultCache == nil {
c, err := NewStringCache(setting.CacheService.Cache)
if err != nil {
return err
}
if err = conn.Ping(); err != nil {
for i := 0; i < 10; i++ {
if err = c.Ping(); err == nil {
break
}
time.Sleep(time.Second)
}
if err != nil {
return err
}
defaultCache = c
}

return err
return nil
}

// GetCache returns the currently configured cache
func GetCache() mc.Cache {
return conn
func GetCache() StringCache {
return defaultCache
}

// GetString returns the key value from cache with callback when no key exists in cache
func GetString(key string, getFunc func() (string, error)) (string, error) {
if conn == nil || setting.CacheService.TTL == 0 {
if defaultCache == nil || setting.CacheService.TTL == 0 {
return getFunc()
}

cached := conn.Get(key)

if cached == nil {
cached, exist := defaultCache.Get(key)
if !exist {
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}

if value, ok := cached.(string); ok {
return value, nil
}

if stringer, ok := cached.(fmt.Stringer); ok {
return stringer.String(), nil
}

return fmt.Sprintf("%s", cached), nil
}

// GetInt returns key value from cache with callback when no key exists in cache
func GetInt(key string, getFunc func() (int, error)) (int, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}

cached := conn.Get(key)

if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}

return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}

switch v := cached.(type) {
case int:
return v, nil
case string:
value, err := strconv.Atoi(v)
if err != nil {
return 0, err
}
return value, nil
default:
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
return value, defaultCache.Put(key, value, setting.CacheService.TTLSeconds())
}
return cached, nil
}

// GetInt64 returns key value from cache with callback when no key exists in cache
func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}

cached := conn.Get(key)

if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}

return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
s, err := GetString(key, func() (string, error) {
v, err := getFunc()
return strconv.FormatInt(v, 10), err
})
if err != nil {
return 0, err
}

switch v := conn.Get(key).(type) {
case int64:
return v, nil
case string:
value, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, err
}
return value, nil
default:
value, err := getFunc()
if err != nil {
return value, err
}

return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
if s == "" {
return 0, nil
}
return strconv.ParseInt(s, 10, 64)
}

// Remove key from cache
func Remove(key string) {
if conn == nil {
if defaultCache == nil {
return
}
_ = conn.Delete(key)
_ = defaultCache.Delete(key)
}

+ 1
- 1
modules/cache/cache_redis.go View File

@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/nosql"

"gitea.com/go-chi/cache"
"gitea.com/go-chi/cache" //nolint:depguard
"github.com/redis/go-redis/v9"
)


+ 2
- 38
modules/cache/cache_test.go View File

@@ -14,7 +14,7 @@ import (
)

func createTestCache() {
conn, _ = newCache(setting.Cache{
defaultCache, _ = NewStringCache(setting.Cache{
Adapter: "memory",
TTL: time.Minute,
})
@@ -25,7 +25,7 @@ func TestNewContext(t *testing.T) {
assert.NoError(t, Init())

setting.CacheService.Cache = setting.Cache{Adapter: "redis", Conn: "some random string"}
con, err := newCache(setting.Cache{
con, err := NewStringCache(setting.Cache{
Adapter: "rand",
Conn: "false conf",
Interval: 100,
@@ -76,42 +76,6 @@ func TestGetString(t *testing.T) {
Remove("key")
}

func TestGetInt(t *testing.T) {
createTestCache()

data, err := GetInt("key", func() (int, error) {
return 0, fmt.Errorf("some error")
})
assert.Error(t, err)
assert.Equal(t, 0, data)

data, err = GetInt("key", func() (int, error) {
return 0, nil
})
assert.NoError(t, err)
assert.Equal(t, 0, data)

data, err = GetInt("key", func() (int, error) {
return 100, nil
})
assert.NoError(t, err)
assert.Equal(t, 0, data)
Remove("key")

data, err = GetInt("key", func() (int, error) {
return 100, nil
})
assert.NoError(t, err)
assert.Equal(t, 100, data)

data, err = GetInt("key", func() (int, error) {
return 0, fmt.Errorf("some error")
})
assert.NoError(t, err)
assert.Equal(t, 100, data)
Remove("key")
}

func TestGetInt64(t *testing.T) {
createTestCache()


+ 1
- 1
modules/cache/cache_twoqueue.go View File

@@ -10,7 +10,7 @@ import (

"code.gitea.io/gitea/modules/json"

mc "gitea.com/go-chi/cache"
mc "gitea.com/go-chi/cache" //nolint:depguard
lru "github.com/hashicorp/golang-lru/v2"
)


+ 120
- 0
modules/cache/string_cache.go View File

@@ -0,0 +1,120 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cache

import (
"errors"
"strings"

"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

chi_cache "gitea.com/go-chi/cache" //nolint:depguard
)

type GetJSONError struct {
err error
cachedError string // Golang error can't be stored in cache, only the string message could be stored
}

func (e *GetJSONError) ToError() error {
if e.err != nil {
return e.err
}
return errors.New("cached error: " + e.cachedError)
}

type StringCache interface {
Ping() error

Get(key string) (string, bool)
Put(key, value string, ttl int64) error
Delete(key string) error
IsExist(key string) bool

PutJSON(key string, v any, ttl int64) error
GetJSON(key string, ptr any) (exist bool, err *GetJSONError)

ChiCache() chi_cache.Cache
}

type stringCache struct {
chiCache chi_cache.Cache
}

func NewStringCache(cacheConfig setting.Cache) (StringCache, error) {
adapter := util.IfZero(cacheConfig.Adapter, "memory")
interval := util.IfZero(cacheConfig.Interval, 60)
cc, err := chi_cache.NewCacher(chi_cache.Options{
Adapter: adapter,
AdapterConfig: cacheConfig.Conn,
Interval: interval,
})
if err != nil {
return nil, err
}
return &stringCache{chiCache: cc}, nil
}

func (sc *stringCache) Ping() error {
return sc.chiCache.Ping()
}

func (sc *stringCache) Get(key string) (string, bool) {
v := sc.chiCache.Get(key)
if v == nil {
return "", false
}
s, ok := v.(string)
return s, ok
}

func (sc *stringCache) Put(key, value string, ttl int64) error {
return sc.chiCache.Put(key, value, ttl)
}

func (sc *stringCache) Delete(key string) error {
return sc.chiCache.Delete(key)
}

func (sc *stringCache) IsExist(key string) bool {
return sc.chiCache.IsExist(key)
}

const cachedErrorPrefix = "<CACHED-ERROR>:"

func (sc *stringCache) PutJSON(key string, v any, ttl int64) error {
var s string
switch v := v.(type) {
case error:
s = cachedErrorPrefix + v.Error()
default:
b, err := json.Marshal(v)
if err != nil {
return err
}
s = util.UnsafeBytesToString(b)
}
return sc.chiCache.Put(key, s, ttl)
}

func (sc *stringCache) GetJSON(key string, ptr any) (exist bool, getErr *GetJSONError) {
s, ok := sc.Get(key)
if !ok || s == "" {
return false, nil
}
s, isCachedError := strings.CutPrefix(s, cachedErrorPrefix)
if isCachedError {
return true, &GetJSONError{cachedError: s}
}
if err := json.Unmarshal(util.UnsafeStringToBytes(s), ptr); err != nil {
return false, &GetJSONError{err: err}
}
return true, nil
}

func (sc *stringCache) ChiCache() chi_cache.Cache {
return sc.chiCache
}

+ 4
- 11
modules/git/last_commit_cache.go View File

@@ -7,18 +7,11 @@ import (
"crypto/sha256"
"fmt"

"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)

// Cache represents a caching interface
type Cache interface {
// Put puts value into cache with key and expire time.
Put(key string, val any, timeout int64) error
// Get gets cached value by given key.
Get(key string) any
}

func getCacheKey(repoPath, commitID, entryPath string) string {
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
return fmt.Sprintf("last_commit:%x", hashBytes)
@@ -30,11 +23,11 @@ type LastCommitCache struct {
ttl func() int64
repo *Repository
commitCache map[string]*Commit
cache Cache
cache cache.StringCache
}

// NewLastCommitCache creates a new last commit cache for repo
func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache Cache) *LastCommitCache {
func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache cache.StringCache) *LastCommitCache {
if cache == nil {
return nil
}
@@ -65,7 +58,7 @@ func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) {
return nil, nil
}

commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)).(string)
commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath))
if !ok || commitID == "" {
return nil, nil
}

+ 1
- 1
modules/optional/serialization.go View File

@@ -35,7 +35,7 @@ func (o *Option[T]) UnmarshalYAML(value *yaml.Node) error {
return nil
}

func (o Option[T]) MarshalYAML() (interface{}, error) {
func (o Option[T]) MarshalYAML() (any, error) {
if !o.Has() {
return nil, nil
}

+ 7
- 0
modules/session/store.go View File

@@ -6,6 +6,9 @@ package session
import (
"net/http"

"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"

"gitea.com/go-chi/session"
)

@@ -18,6 +21,10 @@ type Store interface {

// RegenerateSession regenerates the underlying session and returns the new store
func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, error) {
// Ensure that a cookie with a trailing slash does not take precedence over
// the cookie written by the middleware.
middleware.DeleteLegacySiteCookie(resp, setting.SessionConfig.CookieName)

s, err := session.RegenerateSession(resp, req)
return s, err
}

+ 21
- 17
modules/templates/util_misc.go View File

@@ -142,35 +142,39 @@ type remoteAddress struct {
Password string
}

func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress {
a := remoteAddress{}

remoteURL := m.OriginalURL
if ignoreOriginalURL || remoteURL == "" {
var err error
remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
if err != nil {
log.Error("GetRemoteURL %v", err)
return a
}
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
ret := remoteAddress{}
remoteURL, err := git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
if err != nil {
log.Error("GetRemoteURL %v", err)
return ret
}

u, err := giturl.Parse(remoteURL)
if err != nil {
log.Error("giturl.Parse %v", err)
return a
return ret
}

if u.Scheme != "ssh" && u.Scheme != "file" {
if u.User != nil {
a.Username = u.User.Username()
a.Password, _ = u.User.Password()
ret.Username = u.User.Username()
ret.Password, _ = u.User.Password()
}
u.User = nil
}
a.Address = u.String()

return a
// The URL stored in the git repo could contain authentication,
// erase it, or it will be shown in the UI.
u.User = nil
ret.Address = u.String()
// Why not use m.OriginalURL to set ret.Address?
// It should be OK to use it, since m.OriginalURL should be the same as the authentication-erased URL from the Git repository.
// However, the old code has already stored authentication in m.OriginalURL when updating mirror settings.
// That means we need to use "giturl.Parse" for m.OriginalURL again to ensure authentication is erased.
// Instead of doing this, why not directly use the authentication-erased URL from the Git repository?
// It should be the same as long as there are no bugs.

return ret
}

func FilenameIsImage(filename string) bool {

+ 4
- 3
modules/templates/util_render.go View File

@@ -216,15 +216,16 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
return output
}

func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string) template.HTML {
func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
isPullRequest := issue != nil && issue.IsPull
baseLink := fmt.Sprintf("%s/%s", repoLink, util.Iif(isPullRequest, "pulls", "issues"))
htmlCode := `<span class="labels-list">`
for _, label := range labels {
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
if label == nil {
continue
}
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ",
repoLink, label.ID, RenderLabel(ctx, locale, label))
htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, RenderLabel(ctx, locale, label))
}
htmlCode += "</span>"
return template.HTML(htmlCode)

+ 31
- 9
modules/templates/util_render_test.go View File

@@ -7,17 +7,21 @@ import (
"context"
"html/template"
"os"
"strings"
"testing"

"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/translation"

"github.com/stretchr/testify/assert"
)

const testInput = ` space @mention-user
func testInput() string {
s := ` space @mention-user<SPACE><SPACE>
/just/a/path.bin
https://example.com/file.bin
[local link](file.bin)
@@ -36,8 +40,10 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com
@mention-user test
#123
space
space<SPACE><SPACE>
`
return strings.ReplaceAll(s, "<SPACE>", " ")
}

var testMetas = map[string]string{
"user": "user13",
@@ -121,23 +127,23 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space`

assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput(), testMetas))
}

func TestRenderCommitMessage(t *testing.T) {
expected := `space <a href="/mention-user" class="mention">@mention-user</a> `

assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas))
assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput(), testMetas))
}

func TestRenderCommitMessageLinkSubject(t *testing.T) {
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" class="mention">@mention-user</a>`

assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas))
assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput(), "https://example.com/link", testMetas))
}

func TestRenderIssueTitle(t *testing.T) {
expected := ` space @mention-user
expected := ` space @mention-user<SPACE><SPACE>
/just/a/path.bin
https://example.com/file.bin
[local link](file.bin)
@@ -156,9 +162,10 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com
@mention-user test
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space
space<SPACE><SPACE>
`
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput(), testMetas))
}

func TestRenderMarkdownToHtml(t *testing.T) {
@@ -183,5 +190,20 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
#123
space</p>
`
assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput))
assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput()))
}

func TestRenderLabels(t *testing.T) {
ctx := context.Background()
locale := &translation.MockLocale{}

label := &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
issue := &issues.Issue{}
expected := `/owner/repo/issues?labels=123`
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)

label = &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
issue = &issues.Issue{IsPull: true}
expected = `/owner/repo/pulls?labels=123`
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
}

+ 27
- 5
modules/web/middleware/cookie.go View File

@@ -45,10 +45,32 @@ func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
SameSite: setting.SessionConfig.SameSite,
}
resp.Header().Add("Set-Cookie", cookie.String())
if maxAge < 0 {
// There was a bug in "setting.SessionConfig.CookiePath" code, the old default value of it was empty "".
// So we have to delete the cookie on path="" again, because some old code leaves cookies on path="".
cookie.Path = strings.TrimSuffix(setting.SessionConfig.CookiePath, "/")
resp.Header().Add("Set-Cookie", cookie.String())
// Previous versions would use a cookie path with a trailing /.
// These are more specific than cookies without a trailing /, so
// we need to delete these if they exist.
DeleteLegacySiteCookie(resp, name)
}

// DeleteLegacySiteCookie deletes the cookie with the given name at the cookie
// path with a trailing /, which would unintentionally override the cookie.
func DeleteLegacySiteCookie(resp http.ResponseWriter, name string) {
if setting.SessionConfig.CookiePath == "" || strings.HasSuffix(setting.SessionConfig.CookiePath, "/") {
// If the cookie path ends with /, no legacy cookies will take
// precedence, so do nothing. The exception is that cookies with no
// path could override other cookies, but it's complicated and we don't
// currently handle that.
return
}

cookie := &http.Cookie{
Name: name,
Value: "",
MaxAge: -1,
Path: setting.SessionConfig.CookiePath + "/",
Domain: setting.SessionConfig.Domain,
Secure: setting.SessionConfig.Secure,
HttpOnly: true,
SameSite: setting.SessionConfig.SameSite,
}
resp.Header().Add("Set-Cookie", cookie.String())
}

+ 27
- 0
options/license/BSD-2-clause-first-lines View File

@@ -0,0 +1,27 @@
Copyright (C) 2006,2007,2009 NTT (Nippon Telegraph and Telephone
Corporation). All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer as the first lines of this file unmodified.

2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.

THIS SOFTWARE IS PROVIDED BY NTT "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL NTT BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.

+ 13
- 0
options/license/Sun-PPP-2000 View File

@@ -0,0 +1,13 @@
Copyright (c) 2000 by Sun Microsystems, Inc.
All rights reserved.

Permission to use, copy, modify, and distribute this software and its
documentation is hereby granted, provided that the above copyright
notice appears in all copies.

SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF
THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES

+ 7
- 0
options/license/pkgconf View File

@@ -0,0 +1,7 @@
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

This software is provided 'as is' and without any warranty, express or
implied. In no event shall the authors be liable for any damages arising
from the use of this software.

+ 231
- 246
package-lock.json View File

@@ -21,7 +21,7 @@
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.0.1",
"clippie": "4.0.7",
"css-loader": "7.0.0",
"css-loader": "7.1.1",
"dayjs": "1.11.10",
"dropzone": "6.0.0-beta.2",
"easymde": "2.18.0",
@@ -44,9 +44,9 @@
"postcss-nesting": "12.1.1",
"pretty-ms": "9.0.0",
"sortablejs": "1.15.2",
"swagger-ui-dist": "5.13.0",
"swagger-ui-dist": "5.15.1",
"tailwindcss": "3.4.3",
"temporal-polyfill": "0.2.3",
"temporal-polyfill": "0.2.4",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tippy.js": "6.3.7",
@@ -56,7 +56,7 @@
"vanilla-colorful": "0.7.2",
"vue": "3.4.21",
"vue-bar-graph": "2.0.0",
"vue-chartjs": "5.3.0",
"vue-chartjs": "5.3.1",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.91.0",
@@ -64,8 +64,8 @@
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
"@playwright/test": "1.42.1",
"@eslint-community/eslint-plugin-eslint-comments": "4.3.0",
"@playwright/test": "1.43.1",
"@stoplight/spectral-cli": "6.11.1",
"@stylistic/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.1",
@@ -77,15 +77,15 @@
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.4.0",
"eslint-plugin-regexp": "2.5.0",
"eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-unicorn": "52.0.0",
"eslint-plugin-vitest": "0.4.1",
"eslint-plugin-vitest-globals": "1.5.0",
"eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue": "9.24.1",
"eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4",
"happy-dom": "14.5.0",
"eslint-plugin-wc": "2.1.0",
"happy-dom": "14.7.1",
"markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0",
"stylelint": "16.3.1",
@@ -93,9 +93,9 @@
"stylelint-declaration-strict-value": "1.10.4",
"stylelint-value-no-unknown-custom-properties": "6.0.1",
"svgo": "3.2.0",
"updates": "16.0.0",
"updates": "16.0.1",
"vite-string-plugin": "1.1.5",
"vitest": "1.4.0"
"vitest": "1.5.0"
},
"engines": {
"node": ">= 18.0.0"
@@ -865,9 +865,9 @@
}
},
"node_modules/@eslint-community/eslint-plugin-eslint-comments": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.1.0.tgz",
"integrity": "sha512-B2mwipifrBS5E00vN8vME68laPMZ0h3sNGOEDj5g9iUN9k5EU99Omq0Nc325eKNoFFDnDtiHp3DqIjO+1bstag==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.3.0.tgz",
"integrity": "sha512-6e93KtgsndNkvwCCa07LOQJSwzzLLxwrFll3+huyFoiiQXWG0KBcmo0Q1bVgYQQDLfWOOZl2VPBsXqZL6vHIBQ==",
"dev": true,
"dependencies": {
"escape-string-regexp": "^4.0.0",
@@ -877,7 +877,7 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -1344,12 +1344,12 @@
}
},
"node_modules/@playwright/test": {
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz",
"integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==",
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
"integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==",
"dev": true,
"dependencies": {
"playwright": "1.42.1"
"playwright": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@@ -1420,9 +1420,9 @@
"dev": true
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz",
"integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.2.tgz",
"integrity": "sha512-ahxSgCkAEk+P/AVO0vYr7DxOD3CwAQrT0Go9BJyGQ9Ef0QxVOfjDZMiF4Y2s3mLyPrjonchIMH/tbWHucJMykQ==",
"cpu": [
"arm"
],
@@ -1433,9 +1433,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz",
"integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.2.tgz",
"integrity": "sha512-lAarIdxZWbFSHFSDao9+I/F5jDaKyCqAPMq5HqnfpBw8dKDiCaaqM0lq5h1pQTLeIqueeay4PieGR5jGZMWprw==",
"cpu": [
"arm64"
],
@@ -1446,9 +1446,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz",
"integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.2.tgz",
"integrity": "sha512-SWsr8zEUk82KSqquIMgZEg2GE5mCSfr9sE/thDROkX6pb3QQWPp8Vw8zOq2GyxZ2t0XoSIUlvHDkrf5Gmf7x3Q==",
"cpu": [
"arm64"
],
@@ -1459,9 +1459,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz",
"integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.2.tgz",
"integrity": "sha512-o/HAIrQq0jIxJAhgtIvV5FWviYK4WB0WwV91SLUnsliw1lSAoLsmgEEgRWzDguAFeUEUUoIWXiJrPqU7vGiVkA==",
"cpu": [
"x64"
],
@@ -1472,9 +1472,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz",
"integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.2.tgz",
"integrity": "sha512-nwlJ65UY9eGq91cBi6VyDfArUJSKOYt5dJQBq8xyLhvS23qO+4Nr/RreibFHjP6t+5ap2ohZrUJcHv5zk5ju/g==",
"cpu": [
"arm"
],
@@ -1485,9 +1485,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz",
"integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.2.tgz",
"integrity": "sha512-Pg5TxxO2IVlMj79+c/9G0LREC9SY3HM+pfAwX7zj5/cAuwrbfj2Wv9JbMHIdPCfQpYsI4g9mE+2Bw/3aeSs2rQ==",
"cpu": [
"arm64"
],
@@ -1498,9 +1498,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz",
"integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.2.tgz",
"integrity": "sha512-cAOTjGNm84gc6tS02D1EXtG7tDRsVSDTBVXOLbj31DkwfZwgTPYZ6aafSU7rD/4R2a34JOwlF9fQayuTSkoclA==",
"cpu": [
"arm64"
],
@@ -1511,11 +1511,11 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz",
"integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.2.tgz",
"integrity": "sha512-4RyT6v1kXb7C0fn6zV33rvaX05P0zHoNzaXI/5oFHklfKm602j+N4mn2YvoezQViRLPnxP8M1NaY4s/5kXO5cw==",
"cpu": [
"ppc64le"
"ppc64"
],
"dev": true,
"optional": true,
@@ -1524,9 +1524,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz",
"integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.2.tgz",
"integrity": "sha512-KNUH6jC/vRGAKSorySTyc/yRYlCwN/5pnMjXylfBniwtJx5O7X17KG/0efj8XM3TZU7raYRXJFFReOzNmL1n1w==",
"cpu": [
"riscv64"
],
@@ -1537,9 +1537,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz",
"integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.2.tgz",
"integrity": "sha512-xPV4y73IBEXToNPa3h5lbgXOi/v0NcvKxU0xejiFw6DtIYQqOTMhZ2DN18/HrrP0PmiL3rGtRG9gz1QE8vFKXQ==",
"cpu": [
"s390x"
],
@@ -1550,9 +1550,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz",
"integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.2.tgz",
"integrity": "sha512-QBhtr07iFGmF9egrPOWyO5wciwgtzKkYPNLVCFZTmr4TWmY0oY2Dm/bmhHjKRwZoGiaKdNcKhFtUMBKvlchH+Q==",
"cpu": [
"x64"
],
@@ -1563,9 +1563,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz",
"integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.2.tgz",
"integrity": "sha512-8zfsQRQGH23O6qazZSFY5jP5gt4cFvRuKTpuBsC1ZnSWxV8ZKQpPqOZIUtdfMOugCcBvFGRa1pDC/tkf19EgBw==",
"cpu": [
"x64"
],
@@ -1576,9 +1576,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz",
"integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.2.tgz",
"integrity": "sha512-H4s8UjgkPnlChl6JF5empNvFHp77Jx+Wfy2EtmYPe9G22XV+PMuCinZVHurNe8ggtwoaohxARJZbaH/3xjB/FA==",
"cpu": [
"arm64"
],
@@ -1589,9 +1589,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz",
"integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.2.tgz",
"integrity": "sha512-djqpAjm/i8erWYF0K6UY4kRO3X5+T4TypIqw60Q8MTqSBaQNpNXDhxdjpZ3ikgb+wn99svA7jxcXpiyg9MUsdw==",
"cpu": [
"ia32"
],
@@ -1602,9 +1602,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz",
"integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.2.tgz",
"integrity": "sha512-teAqzLT0yTYZa8ZP7zhFKEx4cotS8Tkk5XiqNMJhD4CpaWB1BHARE4Qy+RzwnXvSAYv+Q3jAqCVBS+PS+Yee8Q==",
"cpu": [
"x64"
],
@@ -2217,9 +2217,9 @@
}
},
"node_modules/@types/eslint": {
"version": "8.56.7",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz",
"integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==",
"version": "8.56.9",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz",
"integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==",
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -2269,9 +2269,9 @@
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
},
"node_modules/@types/node": {
"version": "20.12.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz",
"integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==",
"version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dependencies": {
"undici-types": "~5.26.4"
}
@@ -2314,22 +2314,22 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz",
"integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz",
"integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "7.5.0",
"@typescript-eslint/type-utils": "7.5.0",
"@typescript-eslint/utils": "7.5.0",
"@typescript-eslint/visitor-keys": "7.5.0",
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.6.0",
"@typescript-eslint/type-utils": "7.6.0",
"@typescript-eslint/utils": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2349,15 +2349,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz",
"integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz",
"integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.5.0",
"@typescript-eslint/types": "7.5.0",
"@typescript-eslint/typescript-estree": "7.5.0",
"@typescript-eslint/visitor-keys": "7.5.0",
"@typescript-eslint/scope-manager": "7.6.0",
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/typescript-estree": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2377,13 +2377,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz",
"integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz",
"integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.5.0",
"@typescript-eslint/visitor-keys": "7.5.0"
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2394,15 +2394,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz",
"integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz",
"integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "7.5.0",
"@typescript-eslint/utils": "7.5.0",
"@typescript-eslint/typescript-estree": "7.6.0",
"@typescript-eslint/utils": "7.6.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2421,9 +2421,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz",
"integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
"integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
"dev": true,
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2434,19 +2434,19 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz",
"integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz",
"integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.5.0",
"@typescript-eslint/visitor-keys": "7.5.0",
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2461,34 +2461,19 @@
}
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz",
"integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz",
"integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "7.5.0",
"@typescript-eslint/types": "7.5.0",
"@typescript-eslint/typescript-estree": "7.5.0",
"semver": "^7.5.4"
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.6.0",
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/typescript-estree": "7.6.0",
"semver": "^7.6.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2502,13 +2487,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz",
"integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz",
"integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.5.0",
"eslint-visitor-keys": "^3.4.1"
"@typescript-eslint/types": "7.6.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2538,13 +2523,13 @@
}
},
"node_modules/@vitest/expect": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz",
"integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz",
"integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==",
"dev": true,
"dependencies": {
"@vitest/spy": "1.4.0",
"@vitest/utils": "1.4.0",
"@vitest/spy": "1.5.0",
"@vitest/utils": "1.5.0",
"chai": "^4.3.10"
},
"funding": {
@@ -2552,12 +2537,12 @@
}
},
"node_modules/@vitest/runner": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz",
"integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz",
"integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==",
"dev": true,
"dependencies": {
"@vitest/utils": "1.4.0",
"@vitest/utils": "1.5.0",
"p-limit": "^5.0.0",
"pathe": "^1.1.1"
},
@@ -2593,9 +2578,9 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz",
"integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz",
"integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==",
"dev": true,
"dependencies": {
"magic-string": "^0.30.5",
@@ -2619,9 +2604,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz",
"integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz",
"integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==",
"dev": true,
"dependencies": {
"tinyspy": "^2.2.0"
@@ -2631,9 +2616,9 @@
}
},
"node_modules/@vitest/utils": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz",
"integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz",
"integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==",
"dev": true,
"dependencies": {
"diff-sequences": "^29.6.3",
@@ -3566,9 +3551,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001605",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz",
"integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==",
"version": "1.0.30001609",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001609.tgz",
"integrity": "sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==",
"funding": [
{
"type": "opencollective",
@@ -3960,9 +3945,9 @@
}
},
"node_modules/css-loader": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.0.0.tgz",
"integrity": "sha512-WrO4FVoamxt5zY9CauZjoJgXRi/LZKIk+Ta7YvpSGr5r/eMYPNp5/T9ODlMe4/1rF5DYlycG1avhV4g3A/tiAw==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.1.tgz",
"integrity": "sha512-OxIR5P2mjO1PSXk44bWuQ8XtMK4dpEqpIyERCx3ewOo3I8EmbcxMPUc5ScLtQfgXtOojoMv57So4V/C02HQLsw==",
"dependencies": {
"icss-utils": "^5.1.0",
"postcss": "^8.4.33",
@@ -4812,9 +4797,9 @@
}
},
"node_modules/dompurify": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.11.tgz",
"integrity": "sha512-Fan4uMuyB26gFV3ovPoEoQbxRRPfTu3CvImyZnhGq5fsIEO+gEFLp45ISFt+kQBWsK5ulDdT0oV28jS1UrwQLg=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.0.tgz",
"integrity": "sha512-yoU4rhgPKCo+p5UrWWWNKiIq+ToGqmVVhk0PmMYBK4kRsR3/qhemNFL8f6CFmBd4gMwm3F4T7HBoydP5uY07fA=="
},
"node_modules/domutils": {
"version": "3.1.0",
@@ -4857,9 +4842,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.727",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.727.tgz",
"integrity": "sha512-brpv4KTeC4g0Fx2FeIKytLd4UGn1zBQq5Lauy7zEWT9oqkaj5mgsxblEZIAOf1HHLlXxzr6adGViiBy5Z39/CA=="
"version": "1.4.736",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz",
"integrity": "sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q=="
},
"node_modules/elkjs": {
"version": "0.9.2",
@@ -4911,9 +4896,9 @@
}
},
"node_modules/envinfo": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz",
"integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==",
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.12.0.tgz",
"integrity": "sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==",
"bin": {
"envinfo": "dist/cli.js"
},
@@ -5689,9 +5674,9 @@
}
},
"node_modules/eslint-plugin-regexp": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.4.0.tgz",
"integrity": "sha512-OL2S6VPjQhs9s/NclQ0qattVq1J0GU8ox70/HIVy5Dxw+qbbdd7KQkyucsez2clEQjvdtDe12DTnPphFFUyXFg==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.5.0.tgz",
"integrity": "sha512-I7vKcP0o75WS5SHiVNXN+Eshq49sbrweMQIuqSL3AId9AwDe9Dhbfug65vw64LxmOd4v+yf5l5Xt41y9puiq0g==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
@@ -5785,9 +5770,9 @@
"dev": true
},
"node_modules/eslint-plugin-vue": {
"version": "9.24.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz",
"integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==",
"version": "9.24.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.1.tgz",
"integrity": "sha512-wk3SuwmS1pZdcuJlokGYEi/buDOwD6KltvhIZyOnpJ/378dcQ4zchu9PAMbbLAaydCz1iYc5AozszcOOgZIIOg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
@@ -5803,7 +5788,7 @@
"node": "^14.17.0 || >=16.0.0"
},
"peerDependencies": {
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-vue-scoped-css": {
@@ -5833,9 +5818,9 @@
}
},
"node_modules/eslint-plugin-wc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.0.4.tgz",
"integrity": "sha512-ORu7MBv0hXIvq894EJad70m+AvHGbmrDdKT6lcgtCVVhEbuIAyxg0ilfqqqHOmsh8PfcUBeEae3y7CElKvm1KQ==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.1.0.tgz",
"integrity": "sha512-s/BGOtmpgQ2yifR6EC1OM9t0DwYLgg4ZAL07Kw4eXvBb5TYaPafI+65tswvnZvhH8FqcjERLbBZPPvYsvinkfg==",
"dev": true,
"dependencies": {
"is-valid-element-name": "^1.0.0",
@@ -6594,9 +6579,9 @@
}
},
"node_modules/happy-dom": {
"version": "14.5.0",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.5.0.tgz",
"integrity": "sha512-KvOtCq7eamc7cjihM0F1wj6FptuXzooc3Typa7Vgu6ns2uKGXC4BIFlK80SdH2w8zcW0gtxpBVI/sUqbYtljDA==",
"version": "14.7.1",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.7.1.tgz",
"integrity": "sha512-v60Q0evZ4clvMcrAh5/F8EdxDdfHdFrtffz/CNe10jKD+nFweZVxM91tW+UyY2L4AtpgIaXdZ7TQmiO1pfcwbg==",
"dev": true,
"dependencies": {
"entities": "^4.5.0",
@@ -9381,12 +9366,12 @@
"dev": true
},
"node_modules/playwright": {
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
"integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==",
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz",
"integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==",
"dev": true,
"dependencies": {
"playwright-core": "1.42.1"
"playwright-core": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@@ -9399,9 +9384,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
"integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz",
"integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -11241,9 +11226,9 @@
}
},
"node_modules/swagger-ui-dist": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.13.0.tgz",
"integrity": "sha512-uaWhh6j18IIs5tOX0arvIBnVINAzpTXaQXkr7qAk8zoupegJVg0UU/5+S/FgsgVCnzVsJ9d7QLjIxkswEeTg0Q=="
"version": "5.15.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.15.1.tgz",
"integrity": "sha512-Et/WY0NFdKj8sUBOyEx5P3VybsvGl7bo/y9JvgQ22TkH1a/KscQ0ZiQST2YeJ3cwCrIjYTbHbt165fkku0y1Ig=="
},
"node_modules/sync-fetch": {
"version": "0.4.5",
@@ -11379,17 +11364,17 @@
}
},
"node_modules/temporal-polyfill": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.2.3.tgz",
"integrity": "sha512-7ZJRc7wq/1XjrOQYkkNpgo2qfE9XLrUU8D/DS+LAC/T0bYqZ46rW6dow0sOTXTPZS4bwer8bD/0OyuKQBfA3yw==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.2.4.tgz",
"integrity": "sha512-WA5p0CjQTkMjF9m8sP4wSYgpqI8m2d4q7wPUyaJOWhy4bI9mReLb2yGvTV4qf/DPMTe6H6M/Dig5KmTMB7ev6Q==",
"dependencies": {
"temporal-spec": "^0.2.0"
"temporal-spec": "^0.2.4"
}
},
"node_modules/temporal-spec": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.2.0.tgz",
"integrity": "sha512-r1AT0XdEp8TMQ13FLvOt8mOtAxDQsRt2QU5rSWCA7YfshddU651Y1NHVrceLANvixKdf9fYO8B/S9fXHodB7HQ=="
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.2.4.tgz",
"integrity": "sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ=="
},
"node_modules/terser": {
"version": "5.30.3",
@@ -11749,9 +11734,9 @@
}
},
"node_modules/typescript": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz",
"integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==",
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"devOptional": true,
"peer": true,
"bin": {
@@ -11855,9 +11840,9 @@
}
},
"node_modules/updates": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/updates/-/updates-16.0.0.tgz",
"integrity": "sha512-Ra3QUu/rfbSCsG83zNNvoRQt0FVT3qULBSALYTlwTDX6oyz7R5GQAYwqJoIG/RDjYAXpwr3usrInoyHHTP6cag==",
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/updates/-/updates-16.0.1.tgz",
"integrity": "sha512-If3NQKzGcA3aVgz2VyOXqQ+4uqYjPUPqh2PeZPtD+OKT4CTmxRYqoyFO+T3nwfccy4SiWy5AabWrBXXhVQ89Aw==",
"dev": true,
"bin": {
"updates": "dist/updates.js"
@@ -12003,9 +11988,9 @@
}
},
"node_modules/vite-node": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz",
"integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz",
"integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==",
"dev": true,
"dependencies": {
"cac": "^6.7.14",
@@ -12051,9 +12036,9 @@
}
},
"node_modules/vite/node_modules/rollup": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz",
"integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==",
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.2.tgz",
"integrity": "sha512-WkeoTWvuBoFjFAhsEOHKRoZ3r9GfTyhh7Vff1zwebEFLEFjT1lG3784xEgKiTa7E+e70vsC81roVL2MP4tgEEQ==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -12066,35 +12051,35 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.14.0",
"@rollup/rollup-android-arm64": "4.14.0",
"@rollup/rollup-darwin-arm64": "4.14.0",
"@rollup/rollup-darwin-x64": "4.14.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.14.0",
"@rollup/rollup-linux-arm64-gnu": "4.14.0",
"@rollup/rollup-linux-arm64-musl": "4.14.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.0",
"@rollup/rollup-linux-riscv64-gnu": "4.14.0",
"@rollup/rollup-linux-s390x-gnu": "4.14.0",
"@rollup/rollup-linux-x64-gnu": "4.14.0",
"@rollup/rollup-linux-x64-musl": "4.14.0",
"@rollup/rollup-win32-arm64-msvc": "4.14.0",
"@rollup/rollup-win32-ia32-msvc": "4.14.0",
"@rollup/rollup-win32-x64-msvc": "4.14.0",
"@rollup/rollup-android-arm-eabi": "4.14.2",
"@rollup/rollup-android-arm64": "4.14.2",
"@rollup/rollup-darwin-arm64": "4.14.2",
"@rollup/rollup-darwin-x64": "4.14.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.14.2",
"@rollup/rollup-linux-arm64-gnu": "4.14.2",
"@rollup/rollup-linux-arm64-musl": "4.14.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.2",
"@rollup/rollup-linux-riscv64-gnu": "4.14.2",
"@rollup/rollup-linux-s390x-gnu": "4.14.2",
"@rollup/rollup-linux-x64-gnu": "4.14.2",
"@rollup/rollup-linux-x64-musl": "4.14.2",
"@rollup/rollup-win32-arm64-msvc": "4.14.2",
"@rollup/rollup-win32-ia32-msvc": "4.14.2",
"@rollup/rollup-win32-x64-msvc": "4.14.2",
"fsevents": "~2.3.2"
}
},
"node_modules/vitest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz",
"integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz",
"integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==",
"dev": true,
"dependencies": {
"@vitest/expect": "1.4.0",
"@vitest/runner": "1.4.0",
"@vitest/snapshot": "1.4.0",
"@vitest/spy": "1.4.0",
"@vitest/utils": "1.4.0",
"@vitest/expect": "1.5.0",
"@vitest/runner": "1.5.0",
"@vitest/snapshot": "1.5.0",
"@vitest/spy": "1.5.0",
"@vitest/utils": "1.5.0",
"acorn-walk": "^8.3.2",
"chai": "^4.3.10",
"debug": "^4.3.4",
@@ -12106,9 +12091,9 @@
"std-env": "^3.5.0",
"strip-literal": "^2.0.0",
"tinybench": "^2.5.1",
"tinypool": "^0.8.2",
"tinypool": "^0.8.3",
"vite": "^5.0.0",
"vite-node": "1.4.0",
"vite-node": "1.5.0",
"why-is-node-running": "^2.2.2"
},
"bin": {
@@ -12123,8 +12108,8 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0",
"@vitest/browser": "1.4.0",
"@vitest/ui": "1.4.0",
"@vitest/browser": "1.5.0",
"@vitest/ui": "1.5.0",
"happy-dom": "*",
"jsdom": "*"
},
@@ -12191,9 +12176,9 @@
}
},
"node_modules/vue-chartjs": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.0.tgz",
"integrity": "sha512-8XqX0JU8vFZ+WA2/knz4z3ThClduni2Nm0BMe2u0mXgTfd9pXrmJ07QBI+WAij5P/aPmPMX54HCE1seWL37ZdQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz",
"integrity": "sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==",
"peerDependencies": {
"chart.js": "^4.1.1",
"vue": "^3.0.0-0 || ^2.7.0"

+ 12
- 12
package.json View File

@@ -20,7 +20,7 @@
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.0.1",
"clippie": "4.0.7",
"css-loader": "7.0.0",
"css-loader": "7.1.1",
"dayjs": "1.11.10",
"dropzone": "6.0.0-beta.2",
"easymde": "2.18.0",
@@ -43,9 +43,9 @@
"postcss-nesting": "12.1.1",
"pretty-ms": "9.0.0",
"sortablejs": "1.15.2",
"swagger-ui-dist": "5.13.0",
"swagger-ui-dist": "5.15.1",
"tailwindcss": "3.4.3",
"temporal-polyfill": "0.2.3",
"temporal-polyfill": "0.2.4",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tippy.js": "6.3.7",
@@ -55,7 +55,7 @@
"vanilla-colorful": "0.7.2",
"vue": "3.4.21",
"vue-bar-graph": "2.0.0",
"vue-chartjs": "5.3.0",
"vue-chartjs": "5.3.1",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.91.0",
@@ -63,8 +63,8 @@
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
"@playwright/test": "1.42.1",
"@eslint-community/eslint-plugin-eslint-comments": "4.3.0",
"@playwright/test": "1.43.1",
"@stoplight/spectral-cli": "6.11.1",
"@stylistic/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.1",
@@ -76,15 +76,15 @@
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.4.0",
"eslint-plugin-regexp": "2.5.0",
"eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-unicorn": "52.0.0",
"eslint-plugin-vitest": "0.4.1",
"eslint-plugin-vitest-globals": "1.5.0",
"eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue": "9.24.1",
"eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4",
"happy-dom": "14.5.0",
"eslint-plugin-wc": "2.1.0",
"happy-dom": "14.7.1",
"markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0",
"stylelint": "16.3.1",
@@ -92,9 +92,9 @@
"stylelint-declaration-strict-value": "1.10.4",
"stylelint-value-no-unknown-custom-properties": "6.0.1",
"svgo": "3.2.0",
"updates": "16.0.0",
"updates": "16.0.1",
"vite-string-plugin": "1.1.5",
"vitest": "1.4.0"
"vitest": "1.5.0"
},
"browserslist": [
"defaults"

+ 3
- 3
poetry.lock View File

@@ -113,13 +113,13 @@ six = ">=1.13.0"

[[package]]
name = "json5"
version = "0.9.24"
version = "0.9.25"
description = "A Python implementation of the JSON5 data format."
optional = false
python-versions = ">=3.8"
files = [
{file = "json5-0.9.24-py3-none-any.whl", hash = "sha256:4ca101fd5c7cb47960c055ef8f4d0e31e15a7c6c48c3b6f1473fc83b6c462a13"},
{file = "json5-0.9.24.tar.gz", hash = "sha256:0c638399421da959a20952782800e5c1a78c14e08e1dc9738fa10d8ec14d58c8"},
{file = "json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f"},
{file = "json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"},
]

[[package]]

+ 2
- 4
routers/api/v1/misc/nodeinfo.go View File

@@ -29,9 +29,7 @@ func NodeInfo(ctx *context.APIContext) {

nodeInfoUsage := structs.NodeInfoUsage{}
if setting.Federation.ShareUserStatistics {
var cached bool
nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)

cached, _ := ctx.Cache.GetJSON(cacheKeyNodeInfoUsage, &nodeInfoUsage)
if !cached {
usersTotal := int(user_model.CountUsers(ctx, nil))
now := time.Now()
@@ -53,7 +51,7 @@ func NodeInfo(ctx *context.APIContext) {
LocalComments: int(allComments),
}

if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
if err := ctx.Cache.PutJSON(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
ctx.InternalServerError(err)
return
}

+ 4
- 4
routers/web/org/projects.go View File

@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"

@@ -195,14 +194,15 @@ func NewProjectPost(ctx *context.Context) {

// ChangeProjectStatus updates the status of a project between "open" and "close"
func ChangeProjectStatus(ctx *context.Context) {
toClose := false
var toClose bool
switch ctx.Params(":action") {
case "open":
toClose = false
case "close":
toClose = true
default:
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects")
ctx.JSONRedirect(ctx.ContextUser.HomeLink() + "/-/projects")
return
}
id := ctx.ParamsInt64(":id")

@@ -210,7 +210,7 @@ func ChangeProjectStatus(ctx *context.Context) {
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return
}
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action")))
ctx.JSONRedirect(fmt.Sprintf("%s/-/projects/%d", ctx.ContextUser.HomeLink(), id))
}

// DeleteProject delete a project

+ 0
- 2
routers/web/repo/issue.go View File

@@ -3318,7 +3318,6 @@ func ChangeIssueReaction(ctx *context.Context) {
}

html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ctxData": ctx.Data,
"ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index),
"Reactions": issue.Reactions.GroupByType(),
})
@@ -3425,7 +3424,6 @@ func ChangeCommentReaction(ctx *context.Context) {
}

html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ctxData": ctx.Data,
"ActionURL": fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID),
"Reactions": comment.Reactions.GroupByType(),
})

+ 2
- 7
routers/web/repo/projects.go View File

@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"

"code.gitea.io/gitea/models/db"
@@ -181,14 +180,10 @@ func ChangeProjectStatus(ctx *context.Context) {
id := ctx.ParamsInt64(":id")

if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", err)
} else {
ctx.ServerError("ChangeProjectStatusByIDAndRepoID", err)
}
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return
}
ctx.JSONRedirect(ctx.Repo.RepoLink + "/projects?state=" + url.QueryEscape(ctx.Params(":action")))
ctx.JSONRedirect(fmt.Sprintf("%s/projects/%d", ctx.Repo.RepoLink, id))
}

// DeleteProject delete a project

+ 1
- 1
services/actions/auth_test.go View File

@@ -20,7 +20,7 @@ func TestCreateAuthorizationToken(t *testing.T) {
assert.Nil(t, err)
assert.NotEqual(t, "", token)
claims := jwt.MapClaims{}
_, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
_, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
return setting.GetGeneralTokenSigningSecret(), nil
})
assert.Nil(t, err)

+ 2
- 1
services/auth/source/oauth2/store.go View File

@@ -9,6 +9,7 @@ import (
"net/http"

"code.gitea.io/gitea/modules/log"
session_module "code.gitea.io/gitea/modules/session"

chiSession "gitea.com/go-chi/session"
"github.com/gorilla/sessions"
@@ -65,7 +66,7 @@ func (st *SessionsStore) Save(r *http.Request, w http.ResponseWriter, session *s
chiStore := chiSession.GetSession(r)

if session.IsNew {
_, _ = chiSession.RegenerateSession(w, r)
_, _ = session_module.RegenerateSession(w, r)
session.IsNew = false
}


+ 3
- 5
services/context/api.go View File

@@ -13,7 +13,7 @@ import (

"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
mc "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
@@ -21,15 +21,13 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
web_types "code.gitea.io/gitea/modules/web/types"

"gitea.com/go-chi/cache"
)

// APIContext is a specific context for API service
type APIContext struct {
*Base

Cache cache.Cache
Cache cache.StringCache

Doer *user_model.User // current signed-in user
IsSigned bool
@@ -217,7 +215,7 @@ func APIContexter() func(http.Handler) http.Handler {
base, baseCleanUp := NewBaseContext(w, req)
ctx := &APIContext{
Base: base,
Cache: mc.GetCache(),
Cache: cache.GetCache(),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &APIOrganization{},
}

+ 1
- 1
services/context/captcha.go View File

@@ -30,7 +30,7 @@ func GetImageCaptcha() *captcha.Captcha {
cpt = captcha.NewCaptcha(captcha.Options{
SubURL: setting.AppSubURL,
})
cpt.Store = cache.GetCache()
cpt.Store = cache.GetCache().ChiCache()
})
return cpt
}

+ 4
- 4
services/context/context.go View File

@@ -17,7 +17,7 @@ import (

"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
mc "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/setting"
@@ -27,7 +27,6 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types"

"gitea.com/go-chi/cache"
"gitea.com/go-chi/session"
)

@@ -46,7 +45,7 @@ type Context struct {
Render Render
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`

Cache cache.Cache
Cache cache.StringCache
Csrf CSRFProtector
Flash *middleware.Flash
Session session.Store
@@ -102,6 +101,7 @@ func NewTemplateContextForWeb(ctx *Context) TemplateContext {
tmplCtx := NewTemplateContext(ctx)
tmplCtx["Locale"] = ctx.Base.Locale
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
tmplCtx["RootData"] = ctx.Data
return tmplCtx
}

@@ -111,7 +111,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
Render: render,
Session: session,

Cache: mc.GetCache(),
Cache: cache.GetCache(),
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &Organization{},

+ 10
- 2
services/mirror/mirror_pull.go View File

@@ -13,6 +13,7 @@ import (
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
@@ -30,10 +31,15 @@ const gitShortEmptySha = "0000000"

// UpdateAddress writes new address to Git repository and database
func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error {
u, err := giturl.Parse(addr)
if err != nil {
return fmt.Errorf("invalid addr: %v", err)
}

remoteName := m.GetRemoteName()
repoPath := m.GetRepository(ctx).RepoPath()
// Remove old remote
_, _, err := git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath})
_, _, err = git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
return err
}
@@ -70,7 +76,9 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
}
}

m.Repo.OriginalURL = addr
// erase authentication before storing in database
u.User = nil
m.Repo.OriginalURL = u.String()
return repo_model.UpdateRepositoryCols(ctx, m.Repo, "original_url")
}


+ 5
- 6
services/repository/branch.go View File

@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -119,17 +120,15 @@ func getDivergenceCacheKey(repoID int64, branchName string) string {

// getDivergenceFromCache gets the divergence from cache
func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
data := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
res := git.DivergeObject{
Ahead: -1,
Behind: -1,
}
s, ok := data.([]byte)
if !ok || len(s) == 0 {
if !ok || data == "" {
return &res, false
}

if err := json.Unmarshal(s, &res); err != nil {
if err := json.Unmarshal(util.UnsafeStringToBytes(data), &res); err != nil {
log.Error("json.UnMarshal failed: %v", err)
return &res, false
}
@@ -141,7 +140,7 @@ func putDivergenceFromCache(repoID int64, branchName string, divergence *git.Div
if err != nil {
return err
}
return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), bs, 30*24*60*60)
return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), util.UnsafeBytesToString(bs), 30*24*60*60)
}

func DelDivergenceFromCache(repoID int64, branchName string) error {

+ 1
- 1
services/repository/commitstatus/commitstatus.go View File

@@ -34,7 +34,7 @@ type commitStatusCacheValue struct {

func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheValue {
c := cache.GetCache()
statusStr, ok := c.Get(getCacheKey(repoID, branchName)).(string)
statusStr, ok := c.Get(getCacheKey(repoID, branchName))
if ok && statusStr != "" {
var cv commitStatusCacheValue
err := json.Unmarshal([]byte(statusStr), &cv)

+ 12
- 20
services/repository/contributors_graph.go View File

@@ -17,13 +17,12 @@ import (
"code.gitea.io/gitea/models/avatars"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"

"gitea.com/go-chi/cache"
)

const (
@@ -79,13 +78,13 @@ func findLastSundayBeforeDate(dateStr string) (string, error) {
}

// GetContributorStats returns contributors stats for git commits for given revision or default branch
func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) {
func GetContributorStats(ctx context.Context, cache cache.StringCache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) {
// as GetContributorStats is resource intensive we cache the result
cacheKey := fmt.Sprintf(contributorStatsCacheKey, repo.FullName(), revision)
if !cache.IsExist(cacheKey) {
genReady := make(chan struct{})

// dont start multible async generations
// dont start multiple async generations
_, run := generateLock.Load(cacheKey)
if run {
return nil, ErrAwaitGeneration
@@ -104,15 +103,11 @@ func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_mode
}
}
// TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout)

switch v := cache.Get(cacheKey).(type) {
case error:
return nil, v
case map[string]*ContributorData:
return v, nil
default:
return nil, fmt.Errorf("unexpected type in cache detected")
var res map[string]*ContributorData
if _, cacheErr := cache.GetJSON(cacheKey, &res); cacheErr != nil {
return nil, fmt.Errorf("cached error: %w", cacheErr.ToError())
}
return res, nil
}

// getExtendedCommitStats return the list of *ExtendedCommitStats for the given revision
@@ -205,13 +200,12 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
return extendedCommitStats, nil
}

func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey string, repo *repo_model.Repository, revision string) {
func generateContributorStats(genDone chan struct{}, cache cache.StringCache, cacheKey string, repo *repo_model.Repository, revision string) {
ctx := graceful.GetManager().HammerContext()

gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
if err != nil {
err := fmt.Errorf("OpenRepository: %w", err)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, fmt.Errorf("OpenRepository: %w", err), contributorStatsCacheTimeout)
return
}
defer closer.Close()
@@ -221,13 +215,11 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey
}
extendedCommitStats, err := getExtendedCommitStats(gitRepo, revision)
if err != nil {
err := fmt.Errorf("ExtendedCommitStats: %w", err)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, fmt.Errorf("ExtendedCommitStats: %w", err), contributorStatsCacheTimeout)
return
}
if len(extendedCommitStats) == 0 {
err := fmt.Errorf("no commit stats returned for revision '%s'", revision)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, fmt.Errorf("no commit stats returned for revision '%s'", revision), contributorStatsCacheTimeout)
return
}

@@ -309,7 +301,7 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey
total.TotalCommits++
}

_ = cache.Put(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
generateLock.Delete(cacheKey)
if genDone != nil {
genDone <- struct{}{}

+ 9
- 11
services/repository/contributors_graph_test.go View File

@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/setting"

"gitea.com/go-chi/cache"
"github.com/stretchr/testify/assert"
)

@@ -20,20 +20,18 @@ func TestRepository_ContributorsGraph(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
mockCache, err := cache.NewCacher(cache.Options{
Adapter: "memory",
Interval: 24 * 60,
})
mockCache, err := cache.NewStringCache(setting.Cache{})
assert.NoError(t, err)

generateContributorStats(nil, mockCache, "key", repo, "404ref")
err, isErr := mockCache.Get("key").(error)
assert.True(t, isErr)
assert.ErrorAs(t, err, &git.ErrNotExist{})
var data map[string]*ContributorData
_, getErr := mockCache.GetJSON("key", &data)
assert.NotNil(t, getErr)
assert.ErrorContains(t, getErr.ToError(), "object does not exist")

generateContributorStats(nil, mockCache, "key2", repo, "master")
data, isData := mockCache.Get("key2").(map[string]*ContributorData)
assert.True(t, isData)
exist, _ := mockCache.GetJSON("key2", &data)
assert.True(t, exist)
var keys []string
for k := range data {
keys = append(keys, k)

+ 1
- 1
templates/admin/notice.tmpl View File

@@ -49,7 +49,7 @@
</div>
</div>
</div>
<button class="ui small teal button" id="delete-selection" data-link="{{.Link}}/delete" data-redirect="?page={{.Page.Paginater.Current}}">
<button class="ui small button" id="delete-selection" data-link="{{.Link}}/delete" data-redirect="?page={{.Page.Paginater.Current}}">
<span class="text">{{ctx.Locale.Tr "admin.notices.delete_selected"}}</span>
</button>
</th>

+ 1
- 1
templates/admin/repo/unadopted.tmpl View File

@@ -54,7 +54,7 @@
<input type="hidden" name="action" value="delete">
<input type="hidden" name="q" value="{{$.Keyword}}">
<input type="hidden" name="page" value="{{$.CurrentPage}}">
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "yellow")}}
{{template "base/modal_actions_confirm"}}
</form>
</div>
</div>

+ 1
- 9
templates/base/modal_actions_confirm.tmpl View File

@@ -1,7 +1,6 @@
{{/*
Two buttons (negative, positive):
* ModalButtonTypes: "yes" (default) or "confirm"
* ModalButtonColors: "primary" (default) / "blue" / "yellow"
* ModalButtonCancelText
* ModalButtonOkText

@@ -22,14 +21,7 @@ The ".ok.button" and ".cancel.button" selectors are also used by Fomantic Modal
{{end}}
{{if .ModalButtonCancelText}}{{$textNegitive = .ModalButtonCancelText}}{{end}}
{{if .ModalButtonOkText}}{{$textPositive = .ModalButtonOkText}}{{end}}

{{$stylePositive := "primary"}}
{{if eq .ModalButtonColors "blue"}}
{{$stylePositive = "blue"}}
{{else if eq .ModalButtonColors "yellow"}}
{{$stylePositive = "yellow"}}
{{end}}
<button class="ui cancel button">{{svg "octicon-x"}} {{$textNegitive}}</button>
<button class="ui {{$stylePositive}} ok button">{{svg "octicon-check"}} {{$textPositive}}</button>
<button class="ui primary ok button">{{svg "octicon-check"}} {{$textPositive}}</button>
{{end}}
</div>

+ 9
- 22
templates/devtest/fomantic-modal.tmpl View File

@@ -1,6 +1,15 @@
{{template "base/head" .}}
<div class="page-content devtest ui container">
{{template "base/alert" .}}
<div class="modal-buttons flex-text-block tw-flex-wrap"></div>
<script type="module">
for (const el of $('.ui.modal')) {
const $btn = $('<button class="ui button">').text(`${el.id}`).on('click', () => {
$(el).modal({onApprove() {alert('confirmed')}}).modal('show');
});
$('.modal-buttons').append($btn);
}
</script>

<div id="test-modal-form-1" class="ui mini modal">
<div class="header">Form dialog (layout 1)</div>
@@ -54,33 +63,11 @@
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</div>

<div class="ui g-modal-confirm modal" id="test-modal-blue">
<div class="header">Blue dialog</div>
<div class="content">hello, this is the modal dialog content</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "blue")}}
</div>

<div class="ui g-modal-confirm modal" id="test-modal-yellow">
<div class="header">yellow dialog</div>
<div class="content">hello, this is the modal dialog content</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "yellow")}}
</div>

<div class="ui g-modal-confirm modal" id="test-modal-danger">
{{svg "octicon-x" 16 "inside close"}}
<div class="header">dangerous action dialog</div>
<div class="content">hello, this is the modal dialog content, this is a dangerous operation</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" "I know and must do this is dangerous operation")}}
</div>

<div class="modal-buttons flex-text-block tw-flex-wrap"></div>
<script type="module">
for (const el of $('.ui.modal')) {
const $btn = $('<button>').text(`${el.id}`).on('click', () => {
$(el).modal({onApprove() {alert('confirmed')}}).modal('show');
});
$('.modal-buttons').append($btn);
}
</script>
</div>
{{template "base/footer" .}}

+ 3
- 39
templates/devtest/gitea-ui.tmpl View File

@@ -29,41 +29,13 @@
<button class="ui basic button">Basic Unclassed</button>
<button class="ui primary button">Primary</button>
<button class="ui basic primary button">Basic Primary</button>
<button class="ui negative button">Negative</button>
<button class="ui basic negative button">Basic Negative</button>
<button class="ui positive button">Positive</button>
<button class="ui basic positive button">Basic Positive</button>
</li>
<li class="sample-group">
<h2>Recommended colors:</h2>
<button class="ui red button">Red</button>
<button class="ui basic red button">Basic Red</button>
<button class="ui primary button">Green</button>
<button class="ui basic primary button">Basic Green</button>
<button class="ui blue button">Blue</button>
<button class="ui basic blue button">Basic Blue</button>
<button class="ui orange button">Orange</button>
<button class="ui basic orange button">Basic Orange</button>
<button class="ui yellow button">Yellow</button>
<button class="ui basic yellow button">Basic Yellow</button>
</li>
<li class="sample-group">
<h2>Supported but not recommended:</h2>
<p>Do not use if there is no strong requirement. Do not use grey/black buttons, they don't work well with dark theme.</p>
<button class="ui secondary button">Secondary</button>
<button class="ui basic secondary button">Basic Secondary</button>
<button class="ui olive button">Olive</button>
<button class="ui basic olive button">Basic Olive</button>
<button class="ui teal button">Teal</button>
<button class="ui basic teal button">Basic Teal</button>
<button class="ui violet button">Violet</button>
<button class="ui basic violet button">Basic Violet</button>
<button class="ui purple button">Purple</button>
<button class="ui basic purple button">Basic Purple</button>
<button class="ui pink button">Pink</button>
<button class="ui basic pink button">Basic Pink</button>
<button class="ui brown button">Brown</button>
<button class="ui basic brown button">Basic Brown</button>
<button class="ui green button">Green</button>
<button class="ui basic green button">Basic Green</button>
</li>
<li class="sample-group">
<h2>Inline / Plain:</h2>
@@ -198,7 +170,7 @@
<button class="ui basic button">labeled button</button>
<a class="ui basic label">123</a>
</div>
<button class="ui yellow button">{{svg "octicon-x" 16}} button with very very very very very very very very long text</button>
<button class="ui button">{{svg "octicon-x" 16}} button with very very very very very very very very long text</button>
</div>

<h2>Input with SVG</h2>
@@ -271,10 +243,6 @@
<span class="text">button dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui dropdown large button">
<span class="text">large dropdown</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>

<div>
@@ -290,10 +258,6 @@
<span class="text">button compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="ui dropdown large compact button">
<span class="text">large compact</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
</div>

<div>

+ 27
- 0
templates/devtest/label.tmpl View File

@@ -0,0 +1,27 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
<div class="page-content devtest ui container">
<div>
<h1>Label</h1>
<div class="flex-text-block tw-my-2">
<span class="ui label">simple label</span>
<span class="ui red label">red label</span>
<span class="ui green label">green label</span>
</div>
<div class="flex-text-block tw-my-2">
<span class="ui basic label">basic label</span>
<span class="ui basic red label">basic red label</span>
<span class="ui basic green label">basic green label</span>
</div>
<div class="flex-text-block tw-my-2">
<span class="ui label">long content must be in a non-flex "gt-ellipsis" element, otherwise it won't get ellipsis. very looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong label</span>
</div>
<div class="flex-text-block tw-my-2">
<span class="ui label"><span class="gt-ellipsis">very looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong label</span></span>
</div>
<div class="tw-my-2">
<span class="ui label tw-max-w-full"><span class="gt-ellipsis">very looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong label</span></span>
</div>
</div>
</div>
{{template "base/footer" .}}

+ 1
- 1
templates/org/team/sidebar.tmpl View File

@@ -79,7 +79,7 @@
</div>
{{if .IsOrganizationOwner}}
<div class="ui bottom attached segment">
<a class="ui teal small button" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/edit">{{svg "octicon-gear"}} {{ctx.Locale.Tr "org.teams.settings"}}</a>
<a class="ui small button" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/edit">{{svg "octicon-gear"}} {{ctx.Locale.Tr "org.teams.settings"}}</a>
</div>
{{end}}
</div>

+ 1
- 1
templates/repo/code/recently_pushed_new_branches.tmpl View File

@@ -5,7 +5,7 @@
{{$branchLink := HTMLFormat `<a href="%s/src/branch/%s">%s</a>` $.RepoLink (PathEscapeSegments .Name) .Name}}
{{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $branchLink $timeSince}}
</div>
<a role="button" class="ui compact positive button tw-m-0" href="{{$.Repository.ComposeBranchCompareURL $.Repository.BaseRepo .Name}}">
<a role="button" class="ui compact green button tw-m-0" href="{{$.Repository.ComposeBranchCompareURL $.Repository.BaseRepo .Name}}">
{{ctx.Locale.Tr "repo.pulls.compare_changes"}}
</a>
</div>

+ 2
- 2
templates/repo/diff/comments.tmpl View File

@@ -48,7 +48,7 @@
</div>
{{end}}
{{end}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $.root "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}}
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}}
{{template "repo/issue/view_content/context_menu" dict "ctxData" $.root "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
</div>
</div>
@@ -68,7 +68,7 @@
</div>
{{$reactions := .Reactions.GroupByType}}
{{if $reactions}}
{{template "repo/issue/view_content/reactions" dict "ctxData" $.root "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID) "Reactions" $reactions}}
{{template "repo/issue/view_content/reactions" dict "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID) "Reactions" $reactions}}
{{end}}
</div>
</div>

+ 8
- 16
templates/repo/diff/compare.tmpl View File

@@ -184,23 +184,15 @@
{{end}}
{{else if and .PageIsComparePull (gt .CommitCount 0)}}
{{if .HasPullRequest}}
<div class="ui segment grid title">
<div class="twelve wide column issue-title">
{{ctx.Locale.Tr "repo.pulls.has_pull_request" (print $.RepoLink "/pulls/" .PullRequest.Issue.Index) $.RepoRelPath .PullRequest.Index}}
<h1>
<span id="issue-title">{{RenderIssueTitle $.Context .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx)}}</span>
<span class="index">#{{.PullRequest.Issue.Index}}</span>
</h1>
</div>
<div class="four wide column middle aligned text right">
{{- if .PullRequest.HasMerged -}}
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui button purple show-form">{{svg "octicon-git-merge" 16}} {{ctx.Locale.Tr "repo.pulls.view"}}</a>
{{else if .Issue.IsClosed}}
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui button red show-form">{{svg "octicon-issue-closed" 16}} {{ctx.Locale.Tr "repo.pulls.view"}}</a>
{{else}}
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui button primary show-form">{{svg "octicon-git-pull-request" 16}} {{ctx.Locale.Tr "repo.pulls.view"}}</a>
{{end}}
<div class="ui segment flex-text-block tw-gap-4">
{{template "shared/issueicon" .}}
<div class="issue-title tw-break-anywhere">
{{RenderIssueTitle $.Context .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}}
<span class="index">#{{.PullRequest.Issue.Index}}</span>
</div>
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
{{ctx.Locale.Tr "repo.pulls.view"}}
</a>
</div>
{{else}}
{{if and $.IsSigned (not .Repository.IsArchived)}}

+ 2
- 2
templates/repo/issue/view_content.tmpl View File

@@ -46,7 +46,7 @@
<div class="comment-header-right actions tw-flex tw-items-center">
{{template "repo/issue/view_content/show_role" dict "ShowRole" .Issue.ShowRole "IgnorePoster" true}}
{{if not $.Repository.IsArchived}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $ "ActionURL" (printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index)}}
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index)}}
{{end}}
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" .Issue "delete" false "issue" true "diff" false "IsCommentPoster" $.IsIssuePoster}}
</div>
@@ -67,7 +67,7 @@
</div>
{{$reactions := .Issue.Reactions.GroupByType}}
{{if $reactions}}
{{template "repo/issue/view_content/reactions" dict "ctxData" $ "ActionURL" (printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) "Reactions" $reactions}}
{{template "repo/issue/view_content/reactions" dict "ActionURL" (printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) "Reactions" $reactions}}
{{end}}
</div>
</div>

+ 4
- 6
templates/repo/issue/view_content/add_reaction.tmpl View File

@@ -1,11 +1,9 @@
{{if .ctxData.IsSigned}}
{{if ctx.RootData.IsSigned}}
<div class="item action ui dropdown jump pointing top right select-reaction" data-action-url="{{.ActionURL}}">
<a class="add-reaction muted">
{{svg "octicon-smiley"}}
</a>
<div class="menu reactions-menu">
<a class="muted">{{svg "octicon-smiley"}}</a>
<div class="menu">
{{range $value := AllowedReactions}}
<a class="item reaction" data-tooltip-content="{{$value}}" aria-label="{{$value}}" data-reaction-content="{{$value}}">{{ReactionToEmoji $value}}</a>
<a class="item emoji comment-reaction-button" data-tooltip-content="{{$value}}" aria-label="{{$value}}" data-reaction-content="{{$value}}">{{ReactionToEmoji $value}}</a>
{{end}}
</div>
</div>

+ 7
- 7
templates/repo/issue/view_content/comments.tmpl View File

@@ -53,7 +53,7 @@
<div class="comment-header-right actions tw-flex tw-items-center">
{{template "repo/issue/view_content/show_role" dict "ShowRole" .ShowRole}}
{{if not $.Repository.IsArchived}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{end}}
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" true "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
</div>
@@ -74,7 +74,7 @@
</div>
{{$reactions := .Reactions.GroupByType}}
{{if $reactions}}
{{template "repo/issue/view_content/reactions" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{template "repo/issue/view_content/reactions" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}}
</div>
</div>
@@ -173,11 +173,11 @@
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}}
{{if and .AddedLabels (not .RemovedLabels)}}
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink) $createdStr}}
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels ctx ctx.Locale .AddedLabels $.RepoLink .Issue) $createdStr}}
{{else if and (not .AddedLabels) .RemovedLabels}}
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink) $createdStr}}
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels ctx ctx.Locale .RemovedLabels $.RepoLink .Issue) $createdStr}}
{{else}}
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink) (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink) $createdStr}}
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels ctx ctx.Locale .AddedLabels $.RepoLink .Issue) (RenderLabels ctx ctx.Locale .RemovedLabels $.RepoLink .Issue) $createdStr}}
{{end}}
</span>
</div>
@@ -427,7 +427,7 @@
<div class="comment-header-right actions tw-flex tw-items-center">
{{template "repo/issue/view_content/show_role" dict "ShowRole" .ShowRole}}
{{if not $.Repository.IsArchived}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" false "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
{{end}}
</div>
@@ -448,7 +448,7 @@
</div>
{{$reactions := .Reactions.GroupByType}}
{{if $reactions}}
{{template "repo/issue/view_content/reactions" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{template "repo/issue/view_content/reactions" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}}
</div>
</div>

+ 4
- 4
templates/repo/issue/view_content/conversation.tmpl View File

@@ -55,8 +55,8 @@
<div class="ui comments tw-mb-0">
{{range .comments}}
{{$createdSubStr:= TimeSinceUnix .CreatedUnix ctx.Locale}}
<div class="comment code-comment tw-pb-4" id="{{.HashTag}}">
<div class="content">
<div class="comment code-comment" id="{{.HashTag}}">
<div class="content comment-container">
<div class="header comment-header">
<div class="comment-header-left tw-flex tw-items-center">
{{if not .OriginalAuthor}}
@@ -82,7 +82,7 @@
<div class="comment-header-right actions tw-flex tw-items-center">
{{template "repo/issue/view_content/show_role" dict "ShowRole" .ShowRole}}
{{if not $.Repository.IsArchived}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" true "issue" true "diff" true "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
{{end}}
</div>
@@ -103,7 +103,7 @@
</div>
{{$reactions := .Reactions.GroupByType}}
{{if $reactions}}
{{template "repo/issue/view_content/reactions" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{template "repo/issue/view_content/reactions" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}}
</div>
</div>

+ 4
- 4
templates/repo/issue/view_content/reactions.tmpl View File

@@ -1,7 +1,7 @@
<div class="ui attached segment reactions" data-action-url="{{$.ActionURL}}">
<div class="bottom-reactions" data-action-url="{{$.ActionURL}}">
{{range $key, $value := .Reactions}}
{{$hasReacted := $value.HasUser $.ctxData.SignedUserID}}
<a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not $.ctxData.IsSigned}} disabled{{end}} comment-reaction-button"
{{$hasReacted := $value.HasUser ctx.RootData.SignedUserID}}
<a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not ctx.RootData.IsSigned}} disabled{{end}} comment-reaction-button"
data-tooltip-content
title="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
aria-label="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
@@ -12,6 +12,6 @@
</a>
{{end}}
{{if AllowedReactions}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $.ctxData "ActionURL" .ActionURL}}
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" .ActionURL}}
{{end}}
</div>

+ 1
- 1
templates/repo/issue/view_content/sidebar.tmpl View File

@@ -572,7 +572,7 @@
</form>
{{end}}

<button class="tw-mt-1 fluid ui show-modal button {{if .Issue.IsLocked}} negative {{end}}" data-modal="#lock">
<button class="tw-mt-1 fluid ui show-modal button{{if .Issue.IsLocked}} red{{end}}" data-modal="#lock">
{{if .Issue.IsLocked}}
{{svg "octicon-key"}}
{{ctx.Locale.Tr "repo.issues.unlock"}}

+ 15
- 11
templates/repo/pulse.tmpl View File

@@ -25,12 +25,14 @@
<div class="column">
{{if gt .Activity.ActivePRCount 0}}
<div class="stats-table">
<a href="#merged-pull-requests" class="table-cell tiny background purple" style="width: {{.Activity.MergedPRPerc}}{{if ne .Activity.MergedPRPerc 0}}%{{end}}"></a>
<a href="#proposed-pull-requests" class="table-cell tiny background green"></a>
{{if gt .Activity.MergedPRPerc 0}}
<a href="#merged-pull-requests" class="table-cell tiny tw-bg-purple" style="width: {{.Activity.MergedPRPerc}}%"></a>
{{end}}
<a href="#proposed-pull-requests" class="table-cell tiny tw-bg-green"></a>
</div>
{{else}}
<div class="stats-table">
<a class="table-cell tiny background light grey"></a>
<a class="table-cell tiny tw-bg-grey"></a>
</div>
{{end}}
{{ctx.Locale.TrN .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n" .Activity.ActivePRCount}}
@@ -40,8 +42,10 @@
<div class="column">
{{if gt .Activity.ActiveIssueCount 0}}
<div class="stats-table">
<a href="#closed-issues" class="table-cell tiny background red" style="width: {{.Activity.ClosedIssuePerc}}{{if ne .Activity.ClosedIssuePerc 0}}%{{end}}"></a>
<a href="#new-issues" class="table-cell tiny background green"></a>
{{if gt .Activity.ClosedIssuePerc 0}}
<a href="#closed-issues" class="table-cell tiny tw-bg-red" style="width: {{.Activity.ClosedIssuePerc}}%"></a>
{{end}}
<a href="#new-issues" class="table-cell tiny tw-bg-green"></a>
</div>
{{else}}
<div class="stats-table">
@@ -108,7 +112,7 @@
{{end}}

{{if gt .Activity.PublishedReleaseCount 0}}
<h4 class="divider divider-text tw-normal-case" id="published-releases">
<h4 class="divider divider-text" id="published-releases">
{{svg "octicon-tag" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.releases_published_by"
(ctx.Locale.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount)
@@ -130,7 +134,7 @@
{{end}}

{{if gt .Activity.MergedPRCount 0}}
<h4 class="divider divider-text tw-normal-case" id="merged-pull-requests">
<h4 class="divider divider-text" id="merged-pull-requests">
{{svg "octicon-git-pull-request" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.prs_merged_by"
(ctx.Locale.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount)
@@ -149,7 +153,7 @@
{{end}}

{{if gt .Activity.OpenedPRCount 0}}
<h4 class="divider divider-text tw-normal-case" id="proposed-pull-requests">
<h4 class="divider divider-text" id="proposed-pull-requests">
{{svg "octicon-git-branch" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.prs_opened_by"
(ctx.Locale.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount)
@@ -168,7 +172,7 @@
{{end}}

{{if gt .Activity.ClosedIssueCount 0}}
<h4 class="divider divider-text tw-normal-case" id="closed-issues">
<h4 class="divider divider-text" id="closed-issues">
{{svg "octicon-issue-closed" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.issues_closed_from"
(ctx.Locale.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount)
@@ -187,7 +191,7 @@
{{end}}

{{if gt .Activity.OpenedIssueCount 0}}
<h4 class="divider divider-text tw-normal-case" id="new-issues">
<h4 class="divider divider-text" id="new-issues">
{{svg "octicon-issue-opened" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.issues_created_by"
(ctx.Locale.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount)
@@ -206,7 +210,7 @@
{{end}}

{{if gt .Activity.UnresolvedIssueCount 0}}
<h4 class="divider divider-text tw-normal-case" id="unresolved-conversations" data-tooltip-content="{{ctx.Locale.Tr "repo.activity.unresolved_conv_desc"}}">
<h4 class="divider divider-text" id="unresolved-conversations" data-tooltip-content="{{ctx.Locale.Tr "repo.activity.unresolved_conv_desc"}}">
{{svg "octicon-comment-discussion" 16 "tw-mr-2"}}
{{ctx.Locale.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}}
</h4>

+ 1
- 1
templates/repo/settings/lfs.tmpl View File

@@ -44,7 +44,7 @@
</p>
<form class="ui form" action="{{$.Link}}/delete/{{.Oid}}" method="post">
{{$.CsrfTokenHtml}}
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "yellow")}}
{{template "base/modal_actions_confirm"}}
</form>
</div>
</div>

+ 1
- 1
templates/repo/settings/lfs_pointers.tmpl View File

@@ -37,7 +37,7 @@
</a>
</td>
<td>
<a {{if and .Exists .InRepo}}href="{{$.LFSFilesLink}}/show/{{.Oid}}" rel="nofollow" target="_blank"{{end}} title="{{.Oid}}" class="ui brown button tw-font-mono">
<a {{if and .Exists .InRepo}}href="{{$.LFSFilesLink}}/show/{{.Oid}}" rel="nofollow" target="_blank"{{end}} title="{{.Oid}}" class="ui button tw-font-mono">
{{ShortSha .Oid}}
</a>
</td>

+ 1
- 1
templates/repo/settings/options.tmpl View File

@@ -156,7 +156,7 @@
<label for="interval">{{ctx.Locale.Tr "repo.mirror_interval" .MinimumMirrorInterval}}</label>
<input id="interval" name="interval" value="{{.PullMirror.Interval}}">
</div>
{{$address := MirrorRemoteAddress $.Context .Repository .PullMirror.GetRemoteName false}}
{{$address := MirrorRemoteAddress $.Context .Repository .PullMirror.GetRemoteName}}
<div class="field {{if .Err_MirrorAddress}}error{{end}}">
<label for="mirror_address">{{ctx.Locale.Tr "repo.mirror_address"}}</label>
<input id="mirror_address" name="mirror_address" value="{{$address.Address}}" required>

+ 1
- 1
templates/repo/settings/webhook/history.tmpl View File

@@ -6,7 +6,7 @@
<div class="ui right">
<!-- the button is wrapped with a span because the tooltip doesn't show on hover if we put data-tooltip-content directly on the button -->
<span data-tooltip-content="{{if or $isNew .Webhook.IsActive}}{{ctx.Locale.Tr "repo.settings.webhook.test_delivery_desc"}}{{else}}{{ctx.Locale.Tr "repo.settings.webhook.test_delivery_desc_disabled"}}{{end}}">
<button class="ui teal tiny button{{if not (or $isNew .Webhook.IsActive)}} disabled{{end}}" id="test-delivery" data-link="{{.Link}}/test" data-redirect="{{.Link}}">
<button class="ui tiny button{{if not (or $isNew .Webhook.IsActive)}} disabled{{end}}" id="test-delivery" data-link="{{.Link}}/test" data-redirect="{{.Link}}">
<span class="text">{{ctx.Locale.Tr "repo.settings.webhook.test_delivery"}}</span>
</button>
</span>

+ 5
- 7
templates/user/dashboard/dashboard.tmpl View File

@@ -1,15 +1,13 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content dashboard feeds">
{{template "user/dashboard/navbar" .}}
<div class="ui container">
<div class="ui container flex-container">
{{template "base/alert" .}}
<div class="ui mobile reversed stackable grid">
<div class="ui container ten wide column">
{{template "user/heatmap" .}}
{{template "user/dashboard/feeds" .}}
</div>
{{template "user/dashboard/repolist" .}}
<div class="flex-container-main">
{{template "user/heatmap" .}}
{{template "user/dashboard/feeds" .}}
</div>
{{template "user/dashboard/repolist" .}}
</div>
</div>
{{template "base/footer" .}}

+ 1
- 1
templates/user/dashboard/repolist.tmpl View File

@@ -56,4 +56,4 @@ data.organizationId = {{.ContextUser.ID}};
window.config.pageData.dashboardRepoList = data;
</script>

<div id="dashboard-repo-list" class="six wide column"></div>
<div id="dashboard-repo-list" class="flex-container-sidebar"></div>

+ 1
- 1
templates/user/settings/applications.tmpl View File

@@ -109,7 +109,7 @@
<div class="content">
<p>{{ctx.Locale.Tr "settings.access_token_deletion_desc"}}</p>
</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "yellow")}}
{{template "base/modal_actions_confirm"}}
</div>

{{template "user/settings/layout_footer" .}}

+ 2
- 0
updates.config.js View File

@@ -1,6 +1,8 @@
export default {
exclude: [
'@mcaptcha/vanilla-glue', // breaking changes in rc versions need to be handled
'eslint', // need to migrate to eslint flat config first
'eslint-plugin-array-func', // need to migrate to eslint flat config first
'eslint-plugin-vitest', // need to migrate to eslint flat config first
],
};

+ 6
- 197
web_src/css/base.css View File

@@ -319,27 +319,6 @@ a.label,
background-color: var(--color-label-bg);
}

.ui.menu {
display: flex;
}

.ui.menu,
.ui.vertical.menu {
background: var(--color-menu);
border-color: var(--color-secondary);
box-shadow: none;
}

.ui.menu .item {
color: var(--color-text);
user-select: auto;
line-height: var(--line-height-default); /* fomantic uses "1" which causes overflow problems because "1" doesn't consider the descent part */
}

.ui.menu .item > .svg {
margin-right: 0.35em;
}

.ui.menu .dropdown.item:hover,
.ui.menu a.item:hover,
.ui.menu details.item summary:hover {
@@ -347,42 +326,6 @@ a.label,
background: var(--color-hover);
}

.ui.menu .active.item,
.ui.menu .active.item:hover,
.ui.vertical.menu .active.item,
.ui.vertical.menu .active.item:hover {
color: var(--color-text);
background: var(--color-active);
}

.ui.menu a.item:active {
color: var(--color-text);
background: none;
}

.ui.ui.menu .item.disabled {
color: var(--color-text-light-3);
}

.ui.menu .item::before, .ui.vertical.menu .item::before {
background: var(--color-secondary);
}

/* sub menu of vertical menu */
.ui.vertical.menu .item .menu .item {
color: var(--color-text-light-2);
text-indent: 16px;
}

.ui.vertical.menu .item .menu .item:hover,
.ui.vertical.menu .item .menu a.item:hover {
color: var(--color-text-light-1);
}

.ui.vertical.menu .item .menu .active.item {
color: var(--color-text);
}

/* slightly more contrast for filters on issue list */
.ui.ui.menu .dropdown.item.disabled {
color: var(--color-text-light-2);
@@ -441,11 +384,6 @@ a.label,
background: var(--color-hover);
}

.ui.menu .ui.dropdown .menu > .selected.item {
color: var(--color-text) !important;
background: var(--color-hover) !important;
}

.ui.dropdown .menu > .message:not(.ui) {
color: var(--color-text-light-2);
}
@@ -462,58 +400,6 @@ a.label,
color: var(--color-text-light-2);
}

/* replace item margin on secondary menu items with gap and remove both the
negative margins on the menu as well as margin on the items */
.ui.secondary.menu {
margin-left: 0;
margin-right: 0;
gap: .35714286em;
}
.ui.secondary.menu .item {
margin-left: 0;
margin-right: 0;
}

.ui.secondary.menu .dropdown.item:hover,
.ui.secondary.menu a.item:hover {
color: var(--color-text);
background: var(--color-hover);
}

.ui.secondary.menu .active.item,
.ui.secondary.menu .active.item:hover {
color: var(--color-text);
background: var(--color-active);
}

.ui.secondary.menu.tight .item {
padding-left: 0.85714286em;
padding-right: 0.85714286em;
}

/* remove the menu clearfix so that it won't add undesired gaps when using "gap" */
.ui.menu::after {
content: normal;
}

.ui.menu .dropdown.item .menu {
background: var(--color-body);
}

.ui.menu .ui.dropdown .menu > .item {
color: var(--color-text) !important;
}

.ui.menu .ui.dropdown .menu > .item:hover {
color: var(--color-text) !important;
background: var(--color-hover) !important;
}

.ui.menu .ui.dropdown .menu > .active.item {
color: var(--color-text) !important;
background: var(--color-active) !important;
}

.ui.form textarea:not([rows]) {
height: var(--min-height-textarea); /* override fomantic default 12em */
min-height: var(--min-height-textarea); /* override fomantic default 8em */
@@ -606,11 +492,6 @@ img.ui.avatar,
margin-top: calc(var(--page-spacing) - 1rem);
}

.ui.pagination.menu .active.item {
color: var(--color-text);
background: var(--color-active);
}

.ui.form .fields.error .field textarea,
.ui.form .fields.error .field select,
.ui.form .fields.error .field input:not([type]),
@@ -782,11 +663,7 @@ input:-webkit-autofill:active,
font-size: 0.75em;
}

.ui.form .ui.button {
font-weight: var(--font-weight-normal);
}

/* replace fomantic popover box shadows */
/* popover box shadows */
.ui.dropdown .menu,
.ui.upward.dropdown > .menu,
.ui.menu .dropdown.item .menu,
@@ -804,22 +681,6 @@ input:-webkit-autofill:active,
background: var(--color-overlay-backdrop);
}

/* Override semantic selector '.ui.menu:not(.vertical) .item > .button' */
/* This fixes the commit graph button on the commits page */
/* modal svg icons, copied from fomantic except width and height */
/* center text in fomantic modal dialogs */
.ui .menu:not(.vertical) .item > .button.compact {
padding: 0.58928571em 1.125em;
}

.ui .menu:not(.vertical) .item > .button.small {
font-size: 0.92857143rem;
}

.ui.menu .ui.dropdown.item .menu .item {
width: 100%;
}

.ui.dropdown .menu > .header {
font-size: 0.8em;
}
@@ -1010,24 +871,6 @@ input:-webkit-autofill:active,
border-color: var(--color-gold) !important;
}

@media (max-width: 767.98px) {
.ui.pagination.menu .item:not(.active,.navigation),
.ui.pagination.menu .item.navigation span.navigation_label {
display: none;
}
}

.ui.pagination.menu.narrow .item {
padding-left: 8px;
padding-right: 8px;
min-width: 1em;
text-align: center;
}

.ui.pagination.menu.narrow .item .icon {
margin-right: 0;
}

.ui.floating.dropdown .overflow.menu .scrolling.menu.items {
border-radius: 0 !important;
box-shadow: none !important;
@@ -1149,11 +992,6 @@ overflow-menu .ui.label {
margin-top: 1px;
}

.ui.menu .item > .label {
background: var(--color-label-bg);
color: var(--color-label-text);
}

.lines-blame-btn {
padding: 0 0 0 5px;
display: flex;
@@ -1366,8 +1204,7 @@ table th[data-sortt-desc] .svg {
box-shadow: 0 0 0 1px var(--color-secondary) inset;
}

.emoji,
.reaction {
.emoji {
font-size: 1.25em;
line-height: var(--line-height-default);
font-style: normal !important;
@@ -1375,8 +1212,7 @@ table th[data-sortt-desc] .svg {
vertical-align: -0.075em;
}

.emoji img,
.reaction img {
.emoji img {
border-width: 0 !important;
margin: 0 !important;
width: 1em !important;
@@ -1384,26 +1220,6 @@ table th[data-sortt-desc] .svg {
vertical-align: -0.15em;
}

.ui.tabular.menu {
border-color: var(--color-secondary);
}

.ui.tabular.menu .active.item,
.ui.tabular.menu .active.item:hover {
background: var(--color-body);
border-color: var(--color-secondary);
color: var(--color-text);
}

.ui.segment .ui.tabular.menu .active.item,
.ui.segment .ui.tabular.menu .active.item:hover {
background: var(--color-box-body);
}

.ui.secondary.pointing.menu {
border-color: var(--color-secondary);
}

.ui.tabular.menu .item,
.ui.secondary.pointing.menu .item {
padding: 11.55px 12px !important; /* match .dashboard-navbar in height */
@@ -1415,12 +1231,6 @@ table th[data-sortt-desc] .svg {
color: var(--color-text);
}

.ui.secondary.pointing.menu .active.item,
.ui.secondary.pointing.menu .active.item:hover,
.ui.secondary.pointing.menu .dropdown.item:hover {
color: var(--color-text-dark);
}

.ui.tabular.menu .active.item,
.ui.secondary.pointing.menu .active.item,
.resize-for-semibold::before {
@@ -1531,10 +1341,7 @@ table th[data-sortt-desc] .svg {
align-items: center;
gap: .25rem;
vertical-align: middle;
}

.ui.ui.button {
justify-content: center;
min-width: 0;
}

.ui.dropdown .ui.label .svg {
@@ -1551,6 +1358,7 @@ table th[data-sortt-desc] .svg {
display: flex;
align-items: center;
gap: .25rem;
min-width: 0;
}

/* to override Fomantic's default display: block for ".menu .item", and use a slightly larger gap for menu item content */
@@ -1558,4 +1366,5 @@ table th[data-sortt-desc] .svg {
display: flex !important;
align-items: center;
gap: .5rem;
min-width: 0;
}

+ 0
- 1
web_src/css/dashboard.css View File

@@ -7,7 +7,6 @@
.dashboard.feeds .context.user.menu .ui.header,
.dashboard.issues .context.user.menu .ui.header {
font-size: 1rem;
text-transform: none;
}

.dashboard.feeds .filter.menu,

+ 3
- 0
web_src/css/helpers.css View File

@@ -52,6 +52,9 @@ only use:
*/
.tw-hidden.tw-hidden { display: none !important; }

/* proposed class from https://github.com/tailwindlabs/tailwindcss/pull/12128 */
.tw-break-anywhere { overflow-wrap: anywhere !important; }

@media (max-width: 767.98px) {
/* double selector so it wins over .tw-flex (old .gt-df) etc */
.not-mobile.not-mobile {

+ 2
- 0
web_src/css/index.css View File

@@ -11,6 +11,7 @@
@import "./modules/list.css";
@import "./modules/segment.css";
@import "./modules/grid.css";
@import "./modules/menu.css";
@import "./modules/message.css";
@import "./modules/table.css";
@import "./modules/card.css";
@@ -62,6 +63,7 @@
@import "./repo/linebutton.css";
@import "./repo/wiki.css";
@import "./repo/header.css";
@import "./repo/reactions.css";

@import "./editor/fileeditor.css";
@import "./editor/combomarkdowneditor.css";

+ 212
- 557
web_src/css/modules/button.css View File

@@ -1,11 +1,33 @@
/* this contains override styles for buttons and related elements */

/* these styles changed the Fomantic UI's rules, Fomantic UI expects only "basic" buttons have borders */
.ui.button,
.ui.button:focus {
/* based on Fomantic UI checkbox module, with just the parts extracted that we use. If you find any
unused rules here after refactoring, please remove them. */

.ui.button {
cursor: pointer;
display: inline-block;
min-height: 1em;
outline: none;
vertical-align: baseline;
font-family: var(--fonts-regular);
margin: 0 0.25em 0 0;
padding: 0.78571429em 1.5em;
font-weight: var(--font-weight-normal);
text-align: center;
text-decoration: none;
line-height: 1;
border-radius: 0.28571429rem;
user-select: none;
-webkit-tap-highlight-color: transparent;
justify-content: center;
background: var(--color-button);
border: 1px solid var(--color-light-border);
color: var(--color-text);
white-space: nowrap;
}

@media (max-width: 767.98px) {
.ui.button {
white-space: normal;
}
}

.ui.button:hover {
@@ -13,10 +35,6 @@
color: var(--color-text);
}

.page-content .ui.button {
box-shadow: none !important;
}

.ui.active.button,
.ui.button:active,
.ui.active.button:active,
@@ -25,89 +43,116 @@
color: var(--color-text);
}

.delete-button,
.delete-button:hover {
color: var(--color-red);
.ui.buttons .disabled.button:not(.basic),
.ui.disabled.button,
.ui.button:disabled,
.ui.disabled.button:hover,
.ui.disabled.active.button {
cursor: default;
opacity: var(--opacity-disabled) !important;
background-image: none;
pointer-events: none !important;
}

/* btn is a plain button without any opinionated styling, it only uses flex for vertical alignment like ".ui.button" in base.css */

.btn {
background: transparent;
border-radius: var(--border-radius);
.ui.labeled.button:not(.icon) {
display: inline-flex;
flex-direction: row;
background: none;
padding: 0 !important;
border: none;
color: inherit;
}
.ui.labeled.button > .button {
margin: 0;
padding: 0;
}
.btn:hover,
.btn:active,
.btn:focus {
background: none;
border: none;
.ui.labeled.button > .label {
display: flex;
align-items: center;
margin: 0 0 0 -1px !important;
font-size: 1em;
border-color: var(--color-light-border);
}

a.btn,
a.btn:hover {
color: inherit;
.ui.button > .icon:not(.button) {
height: auto;
opacity: 0.8;
}

/* By default, Fomantic UI doesn't support "bordered" buttons group, but Gitea would like to use it.
And the default buttons always have borders now (not the same as Fomantic UI's default buttons, see above).
It needs some tricks to tweak the left/right borders with active state */

.ui.buttons .button {
border-right: none;
.ui.button:not(.icon) > .icon:not(.button):not(.dropdown),
.ui.button:not(.icon) > .icons:not(.button):not(.dropdown) {
margin: 0 0.42857143em 0 -0.21428571em;
vertical-align: baseline;
}

.ui.buttons .button:hover {
border-color: var(--color-secondary-dark-2);
.ui.button:not(.icon) > .icons:not(.button):not(.dropdown) > .icon {
vertical-align: baseline;
}

.ui.buttons .button:hover + .button {
border-left: 1px solid var(--color-secondary-dark-2);
.ui.button:not(.icon) > .right.icon:not(.button):not(.dropdown) {
margin: 0 -0.21428571em 0 0.42857143em;
}

/* TODO: these "tw-hidden" selectors are only used by "blame.tmpl" buttons: Raw/Normal View/History/Unescape, need to be refactored to a clear solution later */
.ui.buttons .button:first-child,
.ui.buttons .button.tw-hidden:first-child + .button {
border-left: 1px solid var(--color-light-border);
.ui.compact.buttons .button,
.ui.compact.button {
padding: 0.58928571em 1.125em;
}

.ui.buttons .button:last-child,
.ui.buttons .button:nth-last-child(2):has(+ .button.tw-hidden) {
border-right: 1px solid var(--color-light-border);
.ui.compact.icon.buttons .button,
.ui.compact.icon.button {
padding: 0.58928571em;
}
.ui.compact.labeled.icon.button {
padding: 0.58928571em 3.69642857em;
}
.ui.compact.labeled.icon.button > .icon {
padding: 0.58928571em 0;
}

.ui.buttons .button.active {
border-left: 1px solid var(--color-light-border);
border-right: 1px solid var(--color-light-border);
.ui.buttons .button,
.ui.button {
font-size: 1rem;
}
.ui.mini.buttons .dropdown,
.ui.mini.buttons .dropdown .menu > .item,
.ui.mini.buttons .button,
.ui.ui.ui.ui.mini.button {
font-size: 0.78571429rem;
}
.ui.tiny.buttons .dropdown,
.ui.tiny.buttons .dropdown .menu > .item,
.ui.tiny.buttons .button,
.ui.ui.ui.ui.tiny.button {
font-size: 0.85714286rem;
}
.ui.small.buttons .dropdown,
.ui.small.buttons .dropdown .menu > .item,
.ui.small.buttons .button,
.ui.ui.ui.ui.small.button {
font-size: 0.92857143rem;
}

.ui.buttons .button.active + .button {
border-left: none;
.ui.icon.buttons .button,
.ui.icon.button:not(.compact) {
padding: 0.78571429em;
}
.ui.icon.buttons .button > .icon,
.ui.icon.button > .icon {
margin: 0 !important;
vertical-align: top;
}

.ui.basic.buttons .button,
.ui.basic.button,
.ui.basic.buttons .button:hover,
.ui.basic.button:hover {
box-shadow: none;
.ui.basic.button {
border-radius: 0.28571429rem;
background: none;
}

/* apply the vertical padding of .compact to non-compact buttons when they contain a svg as they
would otherwise appear too large. Seen on "RSS Feed" button on repo releases tab. */
.ui.small.button:not(.compact):has(.svg) {
padding-top: 0.58928571em;
padding-bottom: 0.58928571em;
.ui.basic.buttons {
border: 1px solid var(--color-secondary);
border-radius: 0.28571429rem;
}
.ui.basic.buttons .button {
border-radius: 0;
border-left: 1px solid var(--color-secondary);
}

.ui.labeled.button.disabled > .button,
.ui.basic.buttons .button,
.ui.basic.button,
.ui.basic.buttons .button:focus,
.ui.basic.button:focus {
.ui.basic.button {
color: var(--color-text-light);
background: var(--color-button);
}
@@ -129,23 +174,45 @@ It needs some tricks to tweak the left/right borders with active state */
background: var(--color-active);
}

.ui.labeled.button > .label {
border-color: var(--color-light-border);
.ui.labeled.icon.button {
position: relative;
padding-left: 4.07142857em !important;
padding-right: 1.5em !important;
}

.ui.labeled.icon.buttons > .button > .icon,
.ui.labeled.icon.button > .icon {
position: absolute;
top: 0;
left: 0;
height: 100%;
line-height: 1;
border-radius: 0;
border-top-left-radius: inherit;
border-bottom-left-radius: inherit;
text-align: center;
animation: none;
padding: 0.78571429em 0;
margin: 0;
width: 2.57142857em;
background: var(--color-hover);
}

/* primary */
.ui.button.toggle.active {
background-color: var(--color-green);
color: var(--color-white);
}
.ui.button.toggle.active:hover {
background-color: var(--color-green-dark-1);
color: var(--color-white);
}

.ui.fluid.button {
width: 100%;
display: block;
}

.ui.primary.labels .label,
.ui.ui.ui.primary.label,
.ui.primary.button,
.ui.primary.buttons .button,
.ui.primary.button:focus,
.ui.primary.buttons .button:focus {
.ui.primary.buttons .button {
background: var(--color-primary);
color: var(--color-primary-contrast);
}
@@ -162,11 +229,10 @@ It needs some tricks to tweak the left/right borders with active state */
}

.ui.basic.primary.buttons .button,
.ui.basic.primary.button,
.ui.basic.primary.buttons .button:focus,
.ui.basic.primary.button:focus {
.ui.basic.primary.button {
color: var(--color-primary);
border-color: var(--color-primary);
background: none;
}

.ui.basic.primary.buttons .button:hover,
@@ -181,55 +247,8 @@ It needs some tricks to tweak the left/right borders with active state */
border-color: var(--color-primary-active);
}

/* secondary */

.ui.secondary.labels .label,
.ui.ui.ui.secondary.label,
.ui.secondary.button,
.ui.secondary.buttons .button,
.ui.secondary.button:focus,
.ui.secondary.buttons .button:focus {
background: var(--color-secondary-button);
}

.ui.secondary.button:hover,
.ui.secondary.buttons .button:hover {
background: var(--color-secondary-hover);
}

.ui.secondary.button:active,
.ui.secondary.buttons .button:active {
background: var(--color-secondary-active);
}

.ui.basic.secondary.buttons .button,
.ui.basic.secondary.button,
.ui.basic.secondary.button:focus,
.ui.basic.secondary.buttons .button:focus {
color: var(--color-secondary-button);
border-color: var(--color-secondary-button);
}

.ui.basic.secondary.buttons .button:hover,
.ui.basic.secondary.button:hover {
color: var(--color-secondary-hover);
border-color: var(--color-secondary-hover);
}

.ui.basic.secondary.buttons .button:active,
.ui.basic.secondary.button:active {
color: var(--color-secondary-active);
border-color: var(--color-secondary-active);
}

/* red */

.ui.red.labels .label,
.ui.ui.ui.red.label,
.ui.red.button,
.ui.red.buttons .button,
.ui.red.button:focus,
.ui.red.buttons .button:focus {
.ui.red.buttons .button {
background: var(--color-red);
}

@@ -244,11 +263,10 @@ It needs some tricks to tweak the left/right borders with active state */
}

.ui.basic.red.buttons .button,
.ui.basic.red.button,
.ui.basic.red.buttons .button:focus,
.ui.basic.red.button:focus {
.ui.basic.red.button {
color: var(--color-red);
border-color: var(--color-red);
background: none;
}

.ui.basic.red.buttons .button:hover,
@@ -263,137 +281,8 @@ It needs some tricks to tweak the left/right borders with active state */
border-color: var(--color-red-dark-2);
}

/* orange */

.ui.orange.labels .label,
.ui.ui.ui.orange.label,
.ui.orange.button,
.ui.orange.buttons .button,
.ui.orange.button:focus,
.ui.orange.buttons .button:focus {
background: var(--color-orange);
}

.ui.orange.button:hover,
.ui.orange.buttons .button:hover {
background: var(--color-orange-dark-1);
}

.ui.orange.button:active,
.ui.orange.buttons .button:active {
background: var(--color-orange-dark-2);
}

.ui.basic.orange.buttons .button,
.ui.basic.orange.button,
.ui.basic.orange.buttons .button:focus,
.ui.basic.orange.button:focus {
color: var(--color-orange);
border-color: var(--color-orange);
}

.ui.basic.orange.buttons .button:hover,
.ui.basic.orange.button:hover {
color: var(--color-orange-dark-1);
border-color: var(--color-orange-dark-1);
}

.ui.basic.orange.buttons .button:active,
.ui.basic.orange.button:active {
color: var(--color-orange-dark-2);
border-color: var(--color-orange-dark-2);
}

/* yellow */

.ui.yellow.labels .label,
.ui.ui.ui.yellow.label,
.ui.yellow.button,
.ui.yellow.buttons .button,
.ui.yellow.button:focus,
.ui.yellow.buttons .button:focus {
background: var(--color-yellow);
}

.ui.yellow.button:hover,
.ui.yellow.buttons .button:hover {
background: var(--color-yellow-dark-1);
}

.ui.yellow.button:active,
.ui.yellow.buttons .button:active {
background: var(--color-yellow-dark-2);
}

.ui.basic.yellow.buttons .button,
.ui.basic.yellow.button,
.ui.basic.yellow.buttons .button:focus,
.ui.basic.yellow.button:focus {
color: var(--color-yellow);
border-color: var(--color-yellow);
}

.ui.basic.yellow.buttons .button:hover,
.ui.basic.yellow.button:hover {
color: var(--color-yellow-dark-1);
border-color: var(--color-yellow-dark-1);
}

.ui.basic.yellow.buttons .button:active,
.ui.basic.yellow.button:active {
color: var(--color-yellow-dark-2);
border-color: var(--color-yellow-dark-2);
}

/* olive */

.ui.olive.labels .label,
.ui.ui.ui.olive.label,
.ui.olive.button,
.ui.olive.buttons .button,
.ui.olive.button:focus,
.ui.olive.buttons .button:focus {
background: var(--color-olive);
}

.ui.olive.button:hover,
.ui.olive.buttons .button:hover {
background: var(--color-olive-dark-1);
}

.ui.olive.button:active,
.ui.olive.buttons .button:active {
background: var(--color-olive-dark-2);
}

.ui.basic.olive.buttons .button,
.ui.basic.olive.button,
.ui.basic.olive.buttons .button:focus,
.ui.basic.olive.button:focus {
color: var(--color-olive);
border-color: var(--color-olive);
}

.ui.basic.olive.buttons .button:hover,
.ui.basic.olive.button:hover {
color: var(--color-olive-dark-1);
border-color: var(--color-olive-dark-1);
}

.ui.basic.olive.buttons .button:active,
.ui.basic.olive.button:active {
color: var(--color-olive-dark-2);
border-color: var(--color-olive-dark-2);
}

/* green */

.ui.green.labels .label,
.ui.ui.ui.green.label,
.ui.green.button,
.ui.green.buttons .button,
.ui.green.button:focus,
.ui.green.buttons .button:focus {
.ui.green.buttons .button {
background: var(--color-green);
}

@@ -408,11 +297,10 @@ It needs some tricks to tweak the left/right borders with active state */
}

.ui.basic.green.buttons .button,
.ui.basic.green.button,
.ui.basic.green.buttons .button:focus,
.ui.basic.green.button:focus {
.ui.basic.green.button {
color: var(--color-green);
border-color: var(--color-green);
background: none;
}

.ui.basic.green.buttons .button:hover,
@@ -427,326 +315,93 @@ It needs some tricks to tweak the left/right borders with active state */
border-color: var(--color-green-dark-2);
}

/* teal */

.ui.teal.labels .label,
.ui.ui.ui.teal.label,
.ui.teal.button,
.ui.teal.buttons .button,
.ui.teal.button:focus,
.ui.teal.buttons .button:focus {
background: var(--color-teal);
}

.ui.teal.button:hover,
.ui.teal.buttons .button:hover {
background: var(--color-teal-dark-1);
.ui.buttons {
display: inline-flex;
flex-direction: row;
font-size: 0;
vertical-align: baseline;
margin: 0 0.25em 0 0;
}

.ui.teal.button:active,
.ui.teal.buttons .button:active {
background: var(--color-teal-dark-2);
}

.ui.basic.teal.buttons .button,
.ui.basic.teal.button,
.ui.basic.teal.buttons .button:focus,
.ui.basic.teal.button:focus {
color: var(--color-teal);
border-color: var(--color-teal);
}

.ui.basic.teal.buttons .button:hover,
.ui.basic.teal.button:hover {
color: var(--color-teal-dark-1);
border-color: var(--color-teal-dark-1);
}

.ui.basic.teal.buttons .button:active,
.ui.basic.teal.button:active {
color: var(--color-teal-dark-2);
border-color: var(--color-teal-dark-2);
}

/* blue */

.ui.blue.labels .label,
.ui.ui.ui.blue.label,
.ui.blue.button,
.ui.blue.buttons .button,
.ui.blue.button:focus,
.ui.blue.buttons .button:focus {
background: var(--color-blue);
}

.ui.blue.button:hover,
.ui.blue.buttons .button:hover {
background: var(--color-blue-dark-1);
}

.ui.blue.button:active,
.ui.blue.buttons .button:active {
background: var(--color-blue-dark-2);
}

.ui.basic.blue.buttons .button,
.ui.basic.blue.button,
.ui.basic.blue.buttons .button:focus,
.ui.basic.blue.button:focus {
color: var(--color-blue);
border-color: var(--color-blue);
}

.ui.basic.blue.buttons .button:hover,
.ui.basic.blue.button:hover {
color: var(--color-blue-dark-1);
border-color: var(--color-blue-dark-1);
}

.ui.basic.blue.buttons .button:active,
.ui.basic.blue.button:active {
color: var(--color-blue-dark-2);
border-color: var(--color-blue-dark-2);
}

/* violet */

.ui.violet.labels .label,
.ui.ui.ui.violet.label,
.ui.violet.button,
.ui.violet.buttons .button,
.ui.violet.button:focus,
.ui.violet.buttons .button:focus {
background: var(--color-violet);
}

.ui.violet.button:hover,
.ui.violet.buttons .button:hover {
background: var(--color-violet-dark-1);
}

.ui.violet.button:active,
.ui.violet.buttons .button:active {
background: var(--color-violet-dark-2);
}

.ui.basic.violet.buttons .button,
.ui.basic.violet.button,
.ui.basic.violet.buttons .button:focus,
.ui.basic.violet.button:focus {
color: var(--color-violet);
border-color: var(--color-violet);
}

.ui.basic.violet.buttons .button:hover,
.ui.basic.violet.button:hover {
color: var(--color-violet-dark-1);
border-color: var(--color-violet-dark-1);
}

.ui.basic.violet.buttons .button:active,
.ui.basic.violet.button:active {
color: var(--color-violet-dark-2);
border-color: var(--color-violet-dark-2);
}

/* purple */

.ui.purple.labels .label,
.ui.ui.ui.purple.label,
.ui.purple.button,
.ui.purple.buttons .button,
.ui.purple.button:focus,
.ui.purple.buttons .button:focus {
background: var(--color-purple);
}

.ui.purple.button:hover,
.ui.purple.buttons .button:hover {
background: var(--color-purple-dark-1);
}

.ui.purple.button:active,
.ui.purple.buttons .button:active {
background: var(--color-purple-dark-2);
}

.ui.basic.purple.buttons .button,
.ui.basic.purple.button,
.ui.basic.purple.buttons .button:focus,
.ui.basic.purple.button:focus {
color: var(--color-purple);
border-color: var(--color-purple);
}

.ui.basic.purple.buttons .button:hover,
.ui.basic.purple.button:hover {
color: var(--color-purple-dark-1);
border-color: var(--color-purple-dark-1);
}

.ui.basic.purple.buttons .button:active,
.ui.basic.purple.button:active {
color: var(--color-purple-dark-2);
border-color: var(--color-purple-dark-2);
}

/* pink */

.ui.pink.labels .label,
.ui.ui.ui.pink.label,
.ui.pink.button,
.ui.pink.buttons .button,
.ui.pink.button:focus,
.ui.pink.buttons .button:focus {
background: var(--color-pink);
}

.ui.pink.button:hover,
.ui.pink.buttons .button:hover {
background: var(--color-pink-dark-1);
}

.ui.pink.button:active,
.ui.pink.buttons .button:active {
background: var(--color-pink-dark-2);
}

.ui.basic.pink.buttons .button,
.ui.basic.pink.button,
.ui.basic.pink.buttons .button:focus,
.ui.basic.pink.button:focus {
color: var(--color-pink);
border-color: var(--color-pink);
}

.ui.basic.pink.buttons .button:hover,
.ui.basic.pink.button:hover {
color: var(--color-pink-dark-1);
border-color: var(--color-pink-dark-1);
}

.ui.basic.pink.buttons .button:active,
.ui.basic.pink.button:active {
color: var(--color-pink-dark-2);
border-color: var(--color-pink-dark-2);
}

/* brown */

.ui.brown.labels .label,
.ui.ui.ui.brown.label,
.ui.brown.button,
.ui.brown.buttons .button,
.ui.brown.button:focus,
.ui.brown.buttons .button:focus {
background: var(--color-brown);
}

.ui.brown.button:hover,
.ui.brown.buttons .button:hover {
background: var(--color-brown-dark-1);
}

.ui.brown.button:active,
.ui.brown.buttons .button:active {
background: var(--color-brown-dark-2);
.delete-button,
.delete-button:hover {
color: var(--color-red);
}

.ui.basic.brown.buttons .button,
.ui.basic.brown.button,
.ui.basic.brown.buttons .button:focus,
.ui.basic.brown.button:focus {
color: var(--color-brown);
border-color: var(--color-brown);
}
/* btn is a plain button without any opinionated styling, it only uses flex for vertical alignment like ".ui.button" in base.css */

.ui.basic.brown.buttons .button:hover,
.ui.basic.brown.button:hover {
color: var(--color-brown-dark-1);
border-color: var(--color-brown-dark-1);
.btn {
background: transparent;
border-radius: var(--border-radius);
border: none;
color: inherit;
margin: 0;
padding: 0;
}

.ui.basic.brown.buttons .button:active,
.ui.basic.brown.button:active {
color: var(--color-brown-dark-2);
border-color: var(--color-brown-dark-2);
.btn:hover,
.btn:active {
background: none;
border: none;
}

/* negative */

.ui.negative.buttons .button,
.ui.negative.button,
.ui.negative.buttons .button:focus,
.ui.negative.button:focus {
background: var(--color-red);
a.btn,
a.btn:hover {
color: inherit;
}

.ui.negative.buttons .button:hover,
.ui.negative.button:hover {
background: var(--color-red-dark-1);
}
/* By default, Fomantic UI doesn't support "bordered" buttons group, but Gitea would like to use it.
And the default buttons always have borders now (not the same as Fomantic UI's default buttons, see above).
It needs some tricks to tweak the left/right borders with active state */

.ui.negative.buttons .button:active,
.ui.negative.button:active {
background: var(--color-red-dark-2);
.ui.buttons .button {
border-right: none;
flex: 1 0 auto;
border-radius: 0;
margin: 0;
}

.ui.basic.negative.buttons .button,
.ui.basic.negative.button,
.ui.basic.negative.buttons .button:focus,
.ui.basic.negative.button:focus {
color: var(--color-red);
border-color: var(--color-red);
.ui.buttons .button:first-child {
border-left: none;
margin-left: 0;
border-top-left-radius: 0.28571429rem;
border-bottom-left-radius: 0.28571429rem;
}

.ui.basic.negative.buttons .button:hover,
.ui.basic.negative.button:hover {
color: var(--color-red-dark-1);
border-color: var(--color-red-dark-1);
.ui.buttons .button:last-child {
border-top-right-radius: 0.28571429rem;
border-bottom-right-radius: 0.28571429rem;
}

.ui.basic.negative.buttons .button:active,
.ui.basic.negative.button:active {
color: var(--color-red-dark-2);
border-color: var(--color-red-dark-2);
.ui.buttons .button:hover {
border-color: var(--color-secondary-dark-2);
}

/* positive */

.ui.positive.buttons .button,
.ui.positive.button,
.ui.positive.buttons .button:focus,
.ui.positive.button:focus {
background: var(--color-green);
.ui.buttons .button:hover + .button {
border-left: 1px solid var(--color-secondary-dark-2);
}

.ui.positive.buttons .button:hover,
.ui.positive.button:hover {
background: var(--color-green-dark-1);
/* TODO: these "tw-hidden" selectors are only used by "blame.tmpl" buttons: Raw/Normal View/History/Unescape, need to be refactored to a clear solution later */
.ui.buttons .button:first-child,
.ui.buttons .button.tw-hidden:first-child + .button {
border-left: 1px solid var(--color-light-border);
}

.ui.positive.buttons .button:active,
.ui.positive.button:active {
background: var(--color-green-dark-2);
.ui.buttons .button:last-child,
.ui.buttons .button:nth-last-child(2):has(+ .button.tw-hidden) {
border-right: 1px solid var(--color-light-border);
}

.ui.basic.positive.buttons .button,
.ui.basic.positive.button,
.ui.basic.positive.buttons .button:focus,
.ui.basic.positive.button:focus {
color: var(--color-green);
border-color: var(--color-green);
.ui.buttons .button.active {
border-left: 1px solid var(--color-light-border);
border-right: 1px solid var(--color-light-border);
}

.ui.basic.positive.buttons .button:hover,
.ui.basic.positive.button:hover {
color: var(--color-green-dark-1);
border-color: var(--color-green-dark-1);
.ui.buttons .button.active + .button {
border-left: none;
}

.ui.basic.positive.buttons .button:active,
.ui.basic.positive.button:active {
color: var(--color-green-dark-2);
border-color: var(--color-green-dark-2);
/* apply the vertical padding of .compact to non-compact buttons when they contain a svg as they
would otherwise appear too large. Seen on "RSS Feed" button on repo releases tab. */
.ui.small.button:not(.compact):has(.svg) {
padding-top: 0.58928571em;
padding-bottom: 0.58928571em;
}

+ 1
- 1
web_src/css/modules/comment.css View File

@@ -16,7 +16,7 @@
.ui.comments .comment {
position: relative;
background: none;
margin: 0.5em 0 0;
margin: 3px 0 0;
padding: 0.5em 0 0;
border: none;
border-top: none;

+ 5
- 1
web_src/css/modules/divider.css View File

@@ -2,12 +2,16 @@
margin: 10px 0;
height: 0;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
color: var(--color-text);
font-size: 1rem;
width: 100%;
}

h4.divider {
margin-top: 1.25rem;
margin-bottom: 1.25rem;
}

.divider:not(.divider-text) {
border-top: 1px solid var(--color-secondary);
}

+ 9
- 1
web_src/css/modules/flexcontainer.css View File

@@ -6,10 +6,16 @@
margin-top: var(--page-spacing);
}

/* small options menu on the left, used in settings/admin pages */
.flex-container-nav {
width: 240px;
}

/* wide sidebar on the right, used in frontpage */
.flex-container-sidebar {
width: 35%;
}

.flex-container-main {
flex: 1;
min-width: 0; /* make the "text truncate" work, otherwise the flex axis is not limited and the text just overflows */
@@ -19,7 +25,9 @@
.flex-container {
flex-direction: column;
}
.flex-container-nav {
.flex-container-nav,
.flex-container-sidebar {
order: -1;
width: auto;
}
}

+ 0
- 1
web_src/css/modules/header.css View File

@@ -9,7 +9,6 @@
font-family: var(--fonts-regular);
font-weight: var(--font-weight-medium);
line-height: 1.28571429;
text-transform: none;
}

.ui.header:first-child {

+ 1
- 1
web_src/css/modules/label.css View File

@@ -5,12 +5,12 @@
display: inline-flex;
align-items: center;
gap: .25rem;
min-width: 0;
vertical-align: middle;
line-height: 1;
background: var(--color-label-bg);
color: var(--color-label-text);
padding: 0.3em 0.5em;
text-transform: none;
font-size: 0.85714286rem;
font-weight: var(--font-weight-medium);
border: 0 solid transparent;

+ 802
- 0
web_src/css/modules/menu.css View File

@@ -0,0 +1,802 @@
.ui.menu {
display: flex;
margin: 1rem 0;
font-family: var(--fonts-regular);
font-weight: var(--font-weight-normal);
background: var(--color-menu);
border: 1px solid var(--color-secondary);
border-radius: 0.28571429rem;
min-height: 2.85714286em;
font-size: 1rem;
}
.ui.menu:first-child {
margin-top: 0;
}
.ui.menu:last-child {
margin-bottom: 0;
}

.ui.menu .menu {
margin: 0;
}
.ui.menu:not(.vertical) > .menu {
display: flex;
}

.ui.menu:not(.vertical) .item {
display: flex;
align-items: center;
}

.ui.menu .item {
position: relative;
vertical-align: middle;
line-height: var(--line-height-default);
text-decoration: none;
flex: 0 0 auto;
background: none;
padding: 0.92857143em 1.14285714em;
color: var(--color-text);
font-weight: var(--font-weight-normal);
}
.ui.menu > .item:first-child {
border-radius: 0.28571429rem 0 0 0.28571429rem;
}

.ui.menu .item::before {
position: absolute;
content: "";
top: 0;
right: 0;
height: 100%;
width: 1px;
background: var(--color-secondary);
}

.ui.menu .item > .svg {
margin-right: 0.35em;
}

.ui.menu .item > a:not(.ui),
.ui.menu .item > p:only-child {
line-height: 1.3;
}
.ui.menu .item > p:first-child {
margin-top: 0;
}
.ui.menu .item > p:last-child {
margin-bottom: 0;
}

.ui.menu .item > i.icon {
opacity: 0.9;
float: none;
margin: 0 0.35714286em 0 0;
}

.ui.menu:not(.vertical) .item > .button {
position: relative;
top: 0;
margin: -0.5em 0;
padding: 0.58928571em 1.125em;
font-size: 1em;
}

.ui.menu > .grid,
.ui.menu > .container {
display: flex;
align-items: inherit;
flex-direction: inherit;
}

.ui.menu .item > .input {
width: 100%;
}
.ui.menu:not(.vertical) .item > .input {
position: relative;
top: 0;
margin: -0.5em 0;
}
.ui.menu .item > .input input {
font-size: 1em;
padding-top: 0.57142857em;
padding-bottom: 0.57142857em;
}

.ui.menu .header.item,
.ui.vertical.menu .header.item {
margin: 0;
font-size: 1.1em;
background: var(--color-box-header);
font-weight: var(--font-weight-medium);
}
.ui.vertical.menu .item > .header:not(.ui) {
margin: 0 0 0.5em;
font-size: 1em;
font-weight: var(--font-weight-medium);
}

.ui.menu .item > i.dropdown.icon {
padding: 0;
float: right;
margin: 0 0 0 1em;
}

.ui.menu .dropdown.item .menu {
min-width: calc(100% - 1px);
border-radius: 0 0 0.28571429rem 0.28571429rem;
background: var(--color-body);
margin: 0;
flex-direction: column !important;
}

.ui.menu .ui.dropdown .menu > .item {
margin: 0;
text-align: left;
font-size: 1em !important;
padding: 0.78571429em 1.14285714em !important;
background: transparent !important;
color: var(--color-text) !important;
font-weight: var(--font-weight-normal) !important;
}
.ui.menu .ui.dropdown .menu > .item:hover {
color: var(--color-text) !important;
background: var(--color-hover) !important;
}
.ui.menu .ui.dropdown .menu > .selected.item {
color: var(--color-text) !important;
background: var(--color-hover) !important;
}
.ui.menu .ui.dropdown .menu > .active.item {
color: var(--color-text) !important;
background: var(--color-active) !important;
font-weight: var(--font-weight-medium) !important;
}

.ui.menu .ui.dropdown.item .menu .item {
width: 100%;
}

.ui.menu .ui.dropdown.item .menu .item:not(.filtered) {
display: block;
}
.ui.menu .ui.dropdown .menu > .item > i.icon:not(.dropdown) {
display: inline-block;
font-size: 1em !important;
float: none;
margin: 0 0.75em 0 0 !important;
}

.ui.secondary.menu .dropdown.item > .menu {
border-radius: 0.28571429rem;
margin-top: 0.35714286em;
}

.ui.menu .pointing.dropdown.item .menu {
margin-top: 0.75em;
}

.ui.menu .item > .label:not(.floating) {
margin-left: 1em;
padding: 0.3em 0.78571429em;
}
.ui.vertical.menu .item > .label {
margin-top: -0.15em;
margin-bottom: -0.15em;
padding: 0.3em 0.78571429em;
float: right;
text-align: center;
}
.ui.menu .item > .floating.label {
padding: 0.3em 0.78571429em;
}
.ui.menu .item > .label {
background: var(--color-label-bg);
color: var(--color-label-text);
}
.ui.menu .item > .image.label img {
margin: -0.2833em 0.8em -0.2833em -0.8em;
height: 1.5666em;
}

.ui.menu .item > img:not(.ui) {
display: inline-block;
vertical-align: middle;
margin: -0.3em 0;
width: 2.5em;
}
.ui.vertical.menu .item > img:not(.ui):only-child {
display: block;
max-width: 100%;
width: auto;
}

.ui.menu .list .item::before {
background: none !important;
}

@media only screen and (max-width: 767.98px) {
.ui.menu > .ui.container {
width: 100% !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
}

.ui.menu .dropdown.item:hover,
.ui.menu a.item:hover {
cursor: pointer;
}

.ui.menu a.item:active {
color: var(--color-text);
background: none;
}

.ui.menu .active.item {
color: var(--color-text);
background: var(--color-active);
font-weight: var(--font-weight-normal);
}
.ui.menu .active.item > i.icon {
opacity: 1;
}

.ui.ui.menu .item.disabled {
cursor: default;
background-color: transparent;
pointer-events: none;
opacity: var(--opacity-disabled);
}

.ui.menu:not(.vertical) .left.item,
.ui.menu:not(.vertical) .left.menu {
display: flex;
margin-right: auto !important;
}

.ui.menu:not(.vertical) .right.item,
.ui.menu:not(.vertical) .right.menu {
display: flex;
margin-left: auto !important;
}
.ui.menu:not(.vertical) :not(.dropdown) > .left.menu,
.ui.menu:not(.vertical) :not(.dropdown) > .right.menu {
display: inherit;
}

.ui.menu:not(.vertical) .center.item {
display: flex;
margin-left: auto !important;
margin-right: auto !important;
}

.ui.menu .right.item::before,
.ui.menu .right.menu > .item::before {
right: auto;
left: 0;
}

.ui.menu .center.item:last-child::before {
display: none;
}

.ui.vertical.menu {
display: block;
flex-direction: column;
background: var(--color-menu);
width: 15rem;
}

.ui.vertical.menu .item {
display: block;
background: none;
border-top: none;
border-right: none;
}
.ui.vertical.menu > .item:first-child {
border-radius: 0.28571429rem 0.28571429rem 0 0;
}
.ui.vertical.menu > .item:last-child {
border-radius: 0 0 0.28571429rem 0.28571429rem;
}

.ui.vertical.menu .item > i.icon {
width: 1.18em;
float: right;
margin: 0 0 0 0.5em;
}
.ui.vertical.menu .item > .label + i.icon {
float: none;
margin: 0 0.5em 0 0;
}

.ui.vertical.menu .item::before {
position: absolute;
content: "";
top: 0;
left: 0;
width: 100%;
height: 1px;
background: var(--color-secondary);
}
.ui.vertical.menu .item:first-child::before {
display: none !important;
}

.ui.vertical.menu .item > .menu {
margin: 0.5em -1.14285714em 0;
}
.ui.vertical.menu .menu .item {
background: none;
padding: 0.5em 1.33333333em;
font-size: 0.85714286em;
color: var(--color-text-light-2);
}

.ui.vertical.menu .item .menu .item {
color: var(--color-text-light-2);
text-indent: 16px;
}

.ui.vertical.menu .item .menu .item:hover,
.ui.vertical.menu .item .menu a.item:hover {
color: var(--color-text-light-1);
}

.ui.vertical.menu .item .menu .active.item {
background-color: transparent;
font-weight: var(--font-weight-medium);
color: var(--color-text);
}

.ui.vertical.menu .item .menu a.item:hover {
color: var(--color-text);
}
.ui.vertical.menu .menu .item::before {
display: none;
}

.ui.vertical.menu .active.item {
border-radius: 0;
}
.ui.vertical.menu > .active.item:first-child {
border-radius: 0.28571429rem 0.28571429rem 0 0;
}
.ui.vertical.menu > .active.item:last-child {
border-radius: 0 0 0.28571429rem 0.28571429rem;
}
.ui.vertical.menu > .active.item:only-child {
border-radius: 0.28571429rem;
}
.ui.vertical.menu .active.item .menu .active.item {
border-left: none;
}

.ui.tabular.menu {
border-radius: 0;
border: none;
background: none transparent;
border-bottom: 1px solid var(--color-secondary);
}
.ui.tabular.fluid.menu {
width: calc(100% + 2px) !important;
}
.ui.tabular.menu .item {
background: transparent;
border-bottom: none;
border-left: 1px solid transparent;
border-right: 1px solid transparent;
border-top: 2px solid transparent;
color: var(--color-text-light-2);
}
.ui.tabular.menu .item::before {
display: none;
}

.ui.tabular.menu .item:hover {
background-color: transparent;
}

.ui.tabular.menu .active.item,
.ui.tabular.menu .active.item:hover {
background: var(--color-body);
border-top-width: 1px;
border-color: var(--color-secondary);
font-weight: var(--font-weight-medium);
margin-bottom: -1px;
border-radius: 0.28571429rem 0.28571429rem 0 0 !important;
}

.ui.tabular.menu + .attached:not(.top).segment,
.ui.tabular.menu + .attached:not(.top).segment + .attached:not(.top).segment {
border-top: none;
margin-left: 0;
margin-top: 0;
margin-right: 0;
width: 100%;
}

.ui.tabular.menu .active.dropdown.item {
margin-bottom: 0;
border-left: 1px solid transparent;
border-right: 1px solid transparent;
border-top: 2px solid transparent;
border-bottom: none;
}

.ui.pagination.menu {
margin: 0;
display: inline-flex;
vertical-align: middle;
}
.ui.pagination.menu .item:last-child {
border-radius: 0 0.28571429rem 0.28571429rem 0;
}
.ui.compact.menu .item:last-child {
border-radius: 0 0.28571429rem 0.28571429rem 0;
}
.ui.pagination.menu .item:last-child::before {
display: none;
}
.ui.pagination.menu .item {
min-width: 3em;
text-align: center;
}
.ui.pagination.menu .icon.item i.icon {
vertical-align: top;
}

.ui.pagination.menu .active.item,
.ui.pagination.menu .active.item:hover {
border-top: none;
padding-top: 0.92857143em;
color: var(--color-text);
background: var(--color-active);
}

@media (max-width: 767.98px) {
.ui.pagination.menu .item:not(.active,.navigation),
.ui.pagination.menu .item.navigation span.navigation_label {
display: none;
}
}

.ui.pagination.menu.narrow .item {
padding-left: 8px;
padding-right: 8px;
min-width: 1em;
text-align: center;
}

.ui.pagination.menu.narrow .item .icon {
margin-right: 0;
}

.ui.secondary.menu {
background: none;
margin-left: 0;
margin-right: 0;
gap: .35714286em;
border-radius: 0;
border: none;
}

.ui.secondary.menu .item {
align-self: center;
border: none;
padding: 0.78571429em 0.92857143em;
margin: 0;
background: none;
border-radius: 0.28571429rem;
}

.ui.secondary.menu .item::before {
display: none !important;
}

.ui.secondary.menu .header.item {
border-radius: 0;
border-right: none;
background: none transparent;
}

.ui.secondary.menu .item > img:not(.ui) {
margin: 0;
}

.ui.secondary.menu .dropdown.item:hover,
.ui.secondary.menu a.item:hover {
color: var(--color-text);
background: var(--color-hover);
}

.ui.secondary.menu .active.item,
.ui.secondary.menu .active.item:hover {
color: var(--color-text-dark);
background: var(--color-active);
border-radius: 0.28571429rem;
}

.ui.secondary.item.menu {
margin-left: 0;
margin-right: 0;
}
.ui.secondary.item.menu .item:last-child {
margin-right: 0;
}

.ui.vertical.secondary.menu .item:not(.dropdown) > .menu {
margin: 0 -0.92857143em;
}
.ui.vertical.secondary.menu .item:not(.dropdown) > .menu > .item {
margin: 0;
padding: 0.5em 1.33333333em;
}
.ui.secondary.vertical.menu > .item {
border: none;
margin: 0 0 0.35714286em;
border-radius: 0.28571429rem !important;
}
.ui.secondary.vertical.menu > .header.item {
border-radius: 0;
}

.ui.vertical.secondary.menu .item > .menu .item {
background-color: transparent;
}

.ui.secondary.pointing.menu {
margin-left: 0;
margin-right: 0;
border-bottom: 2px solid var(--color-secondary);
}
.ui.secondary.pointing.menu .item {
border-bottom-color: transparent;
border-bottom-style: solid;
border-radius: 0;
align-self: flex-end;
margin: 0 0 -2px;
padding: 0.85714286em 1.14285714em;
border-bottom-width: 2px;
}
.ui.secondary.pointing.menu .ui.dropdown .menu .item {
border-bottom-width: 0;
}
.ui.secondary.pointing.menu .item > .label:not(.floating) {
margin-top: -0.3em;
margin-bottom: -0.3em;
}
.ui.secondary.pointing.menu .item > .circular.label {
margin-top: -0.5em;
margin-bottom: -0.5em;
}

.ui.secondary.pointing.menu .header.item {
color: var(--color-text) !important;
}
.ui.secondary.pointing.menu .item::after {
display: none;
}

.ui.secondary.pointing.menu .dropdown.item:hover,
.ui.secondary.pointing.menu a.item:hover {
background-color: transparent;
color: var(--color-text);
}

.ui.secondary.pointing.menu .dropdown.item:active,
.ui.secondary.pointing.menu a.item:active {
background-color: transparent;
border-color: var(--color-secondary);
}

.ui.secondary.pointing.menu .active.item {
background-color: transparent;
border-color: currentcolor;
font-weight: var(--font-weight-medium);
}

.ui.secondary.pointing.menu .active.item,
.ui.secondary.pointing.menu .active.item:hover,
.ui.secondary.pointing.menu .dropdown.item:hover {
color: var(--color-text-dark);
}

.ui.secondary.pointing.menu .active.dropdown.item {
border-color: transparent;
}

@media only screen and (max-width: 767.98px) {
.ui.stackable.menu {
flex-direction: column;
}
.ui.stackable.menu .item {
width: 100% !important;
}
.ui.stackable.menu .left.menu,
.ui.stackable.menu .left.item {
margin-right: 0 !important;
}
.ui.stackable.menu .right.menu,
.ui.stackable.menu .right.item {
margin-left: 0 !important;
}
.ui.stackable.menu .center.item {
margin-left: 0 !important;
margin-right: 0 !important;
}
.ui.stackable.menu .right.menu,
.ui.stackable.menu .left.menu {
flex-direction: column;
}
}

.ui.floated.menu {
float: left;
margin: 0 0.5rem 0 0;
}
.ui.floated.menu .item:last-child::before {
display: none;
}
.ui.right.floated.menu {
float: right;
margin: 0 0 0 0.5rem;
}

.ui.borderless.menu .item::before,
.ui.borderless.menu .item .menu .item::before,
.ui.menu .borderless.item::before {
background: none !important;
}

.ui.compact.menu {
display: inline-flex;
margin: 0;
vertical-align: middle;
}
.ui.compact.vertical.menu {
display: inline-block;
width: auto !important;
}
.ui.compact.menu:not(.secondary) .item:last-child {
border-radius: 0 0.28571429rem 0.28571429rem 0;
}
.ui.compact.menu .item:last-child::before {
display: none;
}
.ui.compact.vertical.menu .item:last-child::before {
display: block;
}

.ui.menu.fluid,
.ui.vertical.menu.fluid {
width: 100% !important;
}

.ui.item.menu,
.ui.item.menu .item {
width: 100%;
padding-left: 0 !important;
padding-right: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
text-align: center;
justify-content: center;
}
.ui.attached.item.menu:not(.tabular) {
margin: 0 -1px !important;
}
.ui.item.menu .item:last-child::before {
display: none;
}
.ui.menu.two.item .item {
width: 50%;
}

.ui.pointing.menu .item::after {
visibility: hidden;
position: absolute;
content: "";
top: 100%;
left: 50%;
transform: translateX(-50%) translateY(-50%) rotate(45deg);
background: none;
margin: 0.5px 0 0;
width: 0.57142857em;
height: 0.57142857em;
border: none;
border-bottom: 1px solid var(--color-secondary);
border-right: 1px solid var(--color-secondary);
z-index: 2;
}
.ui.pointing.menu .ui.dropdown .menu .item::after {
display: none;
}

.ui.pointing.menu .active.item::after {
visibility: visible;
}
.ui.pointing.menu .active.dropdown.item::after {
visibility: hidden;
}

.ui.pointing.menu .dropdown.active.item::after,
.ui.pointing.menu .active.item .menu .active.item::after {
display: none;
}

.ui.pointing.menu .active.item::after,
.ui.pointing.menu .active.item:hover::after {
background-color: var(--color-active);
}

.ui.attached.menu {
top: 0;
bottom: 0;
border-radius: 0;
margin: 0 -1px;
width: calc(100% + 2px);
max-width: calc(100% + 2px);
}
.ui.attached + .ui.attached.menu:not(.top) {
border-top: none;
}

.ui[class*="top attached"].menu {
bottom: 0;
margin-bottom: 0;
top: 0;
margin-top: 1rem;
border-radius: 0.28571429rem 0.28571429rem 0 0;
}
.ui.menu[class*="top attached"]:first-child {
margin-top: 0;
}

.ui.top.attached.menu > .item:first-child {
border-radius: 0.28571429rem 0 0;
}

.ui.attached.menu:not(.tabular) {
border: 1px solid var(--color-secondary);
}
.ui.attached.tabular.menu {
margin-left: 0;
margin-right: 0;
width: 100%;
}

.ui.mini.menu,
.ui.mini.menu .dropdown,
.ui.mini.menu .dropdown .menu > .item {
font-size: 0.78571429rem;
}
.ui.mini.vertical.menu:not(.icon) {
width: 9rem;
}
.ui.tiny.menu,
.ui.tiny.menu .dropdown,
.ui.tiny.menu .dropdown .menu > .item {
font-size: 0.85714286rem;
}
.ui.tiny.vertical.menu:not(.icon) {
width: 11rem;
}
.ui.small.menu,
.ui.small.menu .dropdown,
.ui.small.menu .dropdown .menu > .item {
font-size: 0.92857143rem;
}
.ui.small.vertical.menu:not(.icon) {
width: 13rem;
}

.ui .menu:not(.vertical) .item > .button.small {
font-size: 0.92857143rem;
}

.ui.segment .ui.tabular.menu .active.item,
.ui.segment .ui.tabular.menu .active.item:hover {
background: var(--color-box-body);
}

+ 4
- 0
web_src/css/modules/modal.css View File

@@ -10,6 +10,10 @@
top: 1.2em;
}

.ui.modal > .close.inside {
color: inherit;
}

.ui.modal > .close.icon[height="16"] {
top: 0.7em; /* fomantic uses absolute layout, so if we have special icon size, it needs this trick to align vertically */
color: var(--color-text-dark);

+ 24
- 133
web_src/css/repo.css View File

@@ -654,15 +654,15 @@ td .commit-summary {
padding: 2px .5rem;
}

.repository.view.issue .issue-title .index {
.issue-title .index {
color: var(--color-text-light-2);
}

.repository.view.issue .issue-title .label {
.issue-title .label {
margin-right: 10px;
}

.repository.view.issue .issue-title .edit-zone {
.issue-title .edit-zone {
margin-top: 10px;
}

@@ -913,6 +913,9 @@ td .commit-summary {

.repository.view.issue .comment-list .ui.comments {
max-width: 100%;
display: flex;
flex-direction: column;
gap: 3px;
}

.repository.view.issue .comment-list .comment > .content > div:first-child {
@@ -928,6 +931,11 @@ td .commit-summary {
.repository.view.issue .comment-list .comment .comment-container {
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
background: var(--color-box-body);
}

.repository.view.issue .comment-list .conversation-holder .comment .comment-container {
border: none;
}

@media (max-width: 767.98px) {
@@ -1042,30 +1050,6 @@ td .commit-summary {
margin-left: 42px;
}

.repository.view.issue .comment-list .comment-code-cloud .segment.reactions {
margin-top: 16px !important;
margin-bottom: -8px !important;
border-top: none !important;
}

.repository.view.issue .comment-list .comment-code-cloud .segment.reactions .ui.label {
border: 1px solid;
padding: 5px 8px !important;
margin: 0 2px;
border-radius: var(--border-radius);
border-color: var(--color-secondary-dark-1) !important;
}

.repository.view.issue .comment-list .comment-code-cloud .segment.reactions .ui.label.basic.primary {
background-color: var(--color-reaction-active-bg) !important;
border-color: var(--color-primary-alpha-80) !important;
}

.repository.view.issue .comment-list .comment-code-cloud .segment.reactions .ui.label.basic.primary:hover {
background-color: var(--color-reaction-hover-bg) !important;
border-color: var(--color-primary-alpha-80) !important;
}

.repository.view.issue .comment-list .comment-code-cloud button.comment-form-reply {
margin: 0;
}
@@ -1180,14 +1164,6 @@ td .commit-summary {
font-size: 14px;
}

.repository.compare.pull .title .issue-title {
margin-bottom: 0.5rem;
}

.repository.compare.pull .title .issue-title .index {
color: var(--color-text-light-2);
}

.repository .ui.dropdown.filter > .menu {
margin-top: 1px;
}
@@ -1902,98 +1878,6 @@ td .commit-summary {
border-bottom: 1px solid var(--color-warning-border);
}

.repository .segment.reactions.dropdown .menu,
.repository .select-reaction.dropdown .menu {
right: 0 !important;
left: auto !important;
min-width: 170px;
}

.repository .segment.reactions.dropdown .menu > .header,
.repository .select-reaction.dropdown .menu > .header {
margin: 0.75rem 0 0.5rem;
}

.repository .segment.reactions.dropdown .menu > .item,
.repository .select-reaction.dropdown .menu > .item {
float: left;
margin: 4px;
font-size: 20px;
width: 34px;
height: 34px;
min-height: 0 !important;
border-radius: var(--border-radius);
display: flex !important;
align-items: center;
justify-content: center;
}

.repository .segment.reactions {
padding: 0;
display: flex;
border: none !important;
border-top: 1px solid var(--color-secondary) !important;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
border-radius: 0 0 var(--border-radius) var(--border-radius);
}

.repository .segment.reactions .ui.label {
max-height: 40px;
padding: 8px 16px !important;
display: flex !important;
align-items: center;
border: 0;
border-right: 1px solid;
border-radius: 0;
margin: 0;
font-size: 12px;
font-weight: var(--font-weight-normal);
border-color: var(--color-secondary) !important;
background: var(--color-reaction-bg);
}

.repository .segment.reactions .ui.label:first-of-type {
border-bottom-left-radius: 3px;
}

.repository .segment.reactions .ui.label.disabled {
cursor: default;
opacity: 1;
}

.repository .segment.reactions .ui.label.basic.primary {
color: var(--color-primary) !important;
background-color: var(--color-reaction-active-bg) !important;
border-color: var(--color-secondary-dark-1) !important;
}

.repository .segment.reactions .ui.label.basic:hover {
background-color: var(--color-reaction-hover-bg) !important;
}

.repository .segment.reactions .reaction-count {
margin-left: 0.5rem;
}

.repository .segment.reactions .select-reaction {
display: flex;
align-items: center;
}

.repository .segment.reactions .select-reaction a {
padding: 0 14px;
}

.repository .segment.reactions .select-reaction:not(.active) a {
display: none;
}

.repository .segment.reactions:hover .select-reaction a {
display: block;
}

.repository .ui.fluid.action.input .ui.search.action.input {
flex: auto;
}
@@ -2187,11 +2071,6 @@ td .commit-summary {
padding: 10px 0 0;
}

.ui.vertical.menu .header.item {
font-size: 1.1em;
background: var(--color-box-header);
}

.comment:target .comment-container {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important;
@@ -2304,6 +2183,8 @@ td .commit-summary {
.stats-table {
display: table;
width: 100%;
margin: 6px 0;
border-spacing: 2px;
}

.stats-table .table-cell {
@@ -2311,7 +2192,17 @@ td .commit-summary {
}

.stats-table .table-cell.tiny {
height: 0.5em;
height: 8px;
}

.stats-table .table-cell:first-child {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}

.stats-table .table-cell:last-child {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}

.labels-list {

+ 70
- 0
web_src/css/repo/reactions.css View File

@@ -0,0 +1,70 @@
.bottom-reactions {
display: flex;
gap: 6px;
margin: 0 1em 1em;
}

.timeline-item .conversation-holder .bottom-reactions {
margin: 1em 0 0 36px;
padding-bottom: 8px;
}

.bottom-reactions .ui.label {
padding: 5px 8px;
font-weight: var(--font-weight-normal);
}

.bottom-reactions .ui.label.primary {
background-color: var(--color-reaction-active-bg) !important;
}

.bottom-reactions .ui.label:hover {
background-color: var(--color-reaction-hover-bg) !important;
}

.bottom-reactions .ui.label.disabled {
cursor: default;
opacity: 1;
}

.bottom-reactions .ui.label .reaction {
font-size: 16px;
display: flex;
}

.bottom-reactions .ui.label .reaction img {
height: 16px;
aspect-ratio: 1;
}

.bottom-reactions .reaction-count {
margin-left: 4px;
}

.ui.dropdown.select-reaction .menu {
min-width: 170px; /* item-outer-width * 4 */
}

.ui.dropdown.select-reaction .menu > .item {
float: left;
margin: 4px;
font-size: 20px;
width: 34px;
height: 34px;
border-radius: var(--border-radius);
display: flex;
align-items: center;
justify-content: center;
}

.bottom-reactions .select-reaction {
padding: 0 10px;
}

.bottom-reactions .select-reaction:not(.active) {
visibility: hidden;
}

.bottom-reactions:hover .select-reaction {
visibility: visible;
}

+ 2815
- 7143
web_src/fomantic/build/semantic.css
File diff suppressed because it is too large
View File


+ 0
- 2
web_src/fomantic/semantic.json View File

@@ -22,11 +22,9 @@
"admin": false,
"components": [
"api",
"button",
"dimmer",
"dropdown",
"form",
"menu",
"modal",
"search",
"tab"

+ 1
- 1
web_src/js/components/PullRequestMergeForm.vue View File

@@ -138,7 +138,7 @@ export default {

<div v-if="!showActionForm" class="tw-flex">
<!-- the merge button -->
<div class="ui buttons merge-button" :class="[mergeForm.emptyCommit ? 'grey' : mergeForm.allOverridableChecksOk ? 'primary' : 'red']" @click="toggleActionForm(true)">
<div class="ui buttons merge-button" :class="[mergeForm.emptyCommit ? '' : mergeForm.allOverridableChecksOk ? 'primary' : 'red']" @click="toggleActionForm(true)">
<button class="ui button">
<svg-icon name="octicon-git-merge"/>
<span class="button-text">

+ 27
- 29
web_src/js/features/comp/ReactionSelector.js View File

@@ -1,38 +1,36 @@
import $ from 'jquery';
import {POST} from '../../modules/fetch.js';

export function initCompReactionSelector($parent) {
$parent.find(`.select-reaction .item.reaction, .comment-reaction-button`).on('click', async function (e) {
e.preventDefault();
export function initCompReactionSelector() {
for (const container of document.querySelectorAll('.issue-content, .diff-file-body')) {
container.addEventListener('click', async (e) => {
// there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment
const target = e.target.closest('.comment-reaction-button');
if (!target) return;
e.preventDefault();

if (this.classList.contains('disabled')) return;
if (target.classList.contains('disabled')) return;

const actionUrl = this.closest('[data-action-url]')?.getAttribute('data-action-url');
const reactionContent = this.getAttribute('data-reaction-content');
const hasReacted = this.closest('.ui.segment.reactions')?.querySelector(`a[data-reaction-content="${reactionContent}"]`)?.getAttribute('data-has-reacted') === 'true';
const actionUrl = target.closest('[data-action-url]').getAttribute('data-action-url');
const reactionContent = target.getAttribute('data-reaction-content');

const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, {
data: new URLSearchParams({content: reactionContent}),
});
const commentContainer = target.closest('.comment-container');

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();
}
if (!data.empty) {
const $attachments = $content.find('.segment.bottom:first');
$react = $(data.html);
if ($attachments.length > 0) {
$react.insertBefore($attachments);
} else {
$react.appendTo($content);
}
$react.find('.dropdown').dropdown();
initCompReactionSelector($react);
const bottomReactions = commentContainer.querySelector('.bottom-reactions'); // may not exist if there is no reaction
const bottomReactionBtn = bottomReactions?.querySelector(`a[data-reaction-content="${CSS.escape(reactionContent)}"]`);
const hasReacted = bottomReactionBtn?.getAttribute('data-has-reacted') === 'true';

const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, {
data: new URLSearchParams({content: reactionContent}),
});

const data = await res.json();
bottomReactions?.remove();
if (data.html) {
commentContainer.insertAdjacentHTML('beforeend', data.html);
const bottomReactionsDropdowns = commentContainer.querySelectorAll('.bottom-reactions .dropdown.select-reaction');
$(bottomReactionsDropdowns).dropdown(); // re-init the dropdown
}
}
});
});
}
}

+ 0
- 1
web_src/js/features/repo-diff.js View File

@@ -87,7 +87,6 @@ function initRepoDiffConversationForm() {
el.classList.add('tw-invisible');
}
$newConversationHolder.find('.dropdown').dropdown();
initCompReactionSelector($newConversationHolder);
} catch (error) {
console.error('Error:', error);
showErrorToast(i18n.network_error);

+ 3
- 5
web_src/js/features/repo-issue.js View File

@@ -449,12 +449,10 @@ export function initRepoPullRequestReview() {
offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight();
}

document.getElementById(`show-outdated-${id}`).classList.add('tw-hidden');
document.getElementById(`code-comments-${id}`).classList.remove('tw-hidden');
document.getElementById(`code-preview-${id}`).classList.remove('tw-hidden');
document.getElementById(`hide-outdated-${id}`).classList.remove('tw-hidden');
hideElem(`#show-outdated-${id}`);
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
// if the comment box is folded, expand it
if (ancestorDiffBox.getAttribute('data-folded') === 'true') {
if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
}


+ 1
- 1
web_src/js/features/repo-legacy.js View File

@@ -393,7 +393,7 @@ export function initRepository() {
initRepoIssueDependencyDelete();
initRepoIssueCodeCommentCancel();
initRepoPullRequestUpdate();
initCompReactionSelector($(document));
initCompReactionSelector();

initRepoPullRequestMergeForm();
initRepoPullRequestCommitStatus();

Loading…
Cancel
Save