Merge pull request '[gitea] week 14 cherry-pick' (#2872) from earl-warren/forgejo:wip-gitea-cherry-pick into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2872
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-03-30 08:01:06 +00:00
commit 91b745a5b6
324 changed files with 3486 additions and 5440 deletions

View file

@ -14,7 +14,7 @@ _test
# MS VSCode # MS VSCode
.vscode .vscode
__debug_bin __debug_bin*
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

View file

@ -167,7 +167,7 @@ rules:
"@stylistic/js/semi-spacing": [2, {before: false, after: true}] "@stylistic/js/semi-spacing": [2, {before: false, after: true}]
"@stylistic/js/semi-style": [2, last] "@stylistic/js/semi-style": [2, last]
"@stylistic/js/space-before-blocks": [2, always] "@stylistic/js/space-before-blocks": [2, always]
"@stylistic/js/space-before-function-paren": [0] "@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}]
"@stylistic/js/space-in-parens": [2, never] "@stylistic/js/space-in-parens": [2, never]
"@stylistic/js/space-infix-ops": [2] "@stylistic/js/space-infix-ops": [2]
"@stylistic/js/space-unary-ops": [2] "@stylistic/js/space-unary-ops": [2]
@ -281,7 +281,7 @@ rules:
jquery/no-ajax-events: [2] jquery/no-ajax-events: [2]
jquery/no-ajax: [2] jquery/no-ajax: [2]
jquery/no-animate: [2] jquery/no-animate: [2]
jquery/no-attr: [0] jquery/no-attr: [2]
jquery/no-bind: [2] jquery/no-bind: [2]
jquery/no-class: [0] jquery/no-class: [0]
jquery/no-clone: [2] jquery/no-clone: [2]
@ -303,7 +303,7 @@ rules:
jquery/no-in-array: [2] jquery/no-in-array: [2]
jquery/no-is-array: [2] jquery/no-is-array: [2]
jquery/no-is-function: [2] jquery/no-is-function: [2]
jquery/no-is: [0] jquery/no-is: [2]
jquery/no-load: [2] jquery/no-load: [2]
jquery/no-map: [2] jquery/no-map: [2]
jquery/no-merge: [2] jquery/no-merge: [2]
@ -397,7 +397,7 @@ rules:
no-jquery/no-animate-toggle: [2] no-jquery/no-animate-toggle: [2]
no-jquery/no-animate: [2] no-jquery/no-animate: [2]
no-jquery/no-append-html: [2] no-jquery/no-append-html: [2]
no-jquery/no-attr: [0] no-jquery/no-attr: [2]
no-jquery/no-bind: [2] no-jquery/no-bind: [2]
no-jquery/no-box-model: [2] no-jquery/no-box-model: [2]
no-jquery/no-browser: [2] no-jquery/no-browser: [2]
@ -440,7 +440,7 @@ rules:
no-jquery/no-is-numeric: [2] no-jquery/no-is-numeric: [2]
no-jquery/no-is-plain-object: [2] no-jquery/no-is-plain-object: [2]
no-jquery/no-is-window: [2] no-jquery/no-is-window: [2]
no-jquery/no-is: [0] no-jquery/no-is: [2]
no-jquery/no-jquery-constructor: [0] no-jquery/no-jquery-constructor: [0]
no-jquery/no-live: [2] no-jquery/no-live: [2]
no-jquery/no-load-shorthand: [2] no-jquery/no-load-shorthand: [2]

View file

@ -458,7 +458,8 @@ lint-actions:
$(GO) run $(ACTIONLINT_PACKAGE) $(GO) run $(ACTIONLINT_PACKAGE)
.PHONY: lint-templates .PHONY: lint-templates
lint-templates: .venv lint-templates: .venv node_modules
@node tools/lint-templates-svg.js
@poetry run djlint $(shell find templates -type f -iname '*.tmpl') @poetry run djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-yaml .PHONY: lint-yaml
@ -999,7 +1000,7 @@ generate-gitignore:
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules
npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7 npm install --no-save fabric@6.0.0-beta20 imagemin-zopfli@7
node tools/generate-images.js $(TAGS) node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage

View file

@ -441,7 +441,7 @@ INTERNAL_TOKEN =
;INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token ;INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token
;; ;;
;; How long to remember that a user is logged in before requiring relogin (in days) ;; How long to remember that a user is logged in before requiring relogin (in days)
;LOGIN_REMEMBER_DAYS = 7 ;LOGIN_REMEMBER_DAYS = 31
;; ;;
;; Name of the cookie used to store the current username. ;; Name of the cookie used to store the current username.
;COOKIE_USERNAME = gitea_awesome ;COOKIE_USERNAME = gitea_awesome

View file

@ -528,7 +528,7 @@ And the following unique queues:
- `INSTALL_LOCK`: **false**: Controls access to the installation page. When set to "true", the installation page is not accessible. - `INSTALL_LOCK`: **false**: Controls access to the installation page. When set to "true", the installation page is not accessible.
- `SECRET_KEY`: **\<random at every install\>**: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore. - `SECRET_KEY`: **\<random at every install\>**: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
- `SECRET_KEY_URI`: **_empty_**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY. - `SECRET_KEY_URI`: **_empty_**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY.
- `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days. - `LOGIN_REMEMBER_DAYS`: **31**: How long to remember that a user is logged in before requiring relogin (in days).
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication
information. information.
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: Header name for reverse proxy - `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: Header name for reverse proxy

View file

@ -507,7 +507,7 @@ Gitea 创建以下非唯一队列:
- `INSTALL_LOCK`: **false**:控制是否能够访问安装向导页面,设置为 `true` 则禁止访问安装向导页面。 - `INSTALL_LOCK`: **false**:控制是否能够访问安装向导页面,设置为 `true` 则禁止访问安装向导页面。
- `SECRET_KEY`: **\<每次安装时随机生成\>**:全局服务器安全密钥。这个密钥非常重要,如果丢失将无法解密加密的数据(例如 2FA - `SECRET_KEY`: **\<每次安装时随机生成\>**:全局服务器安全密钥。这个密钥非常重要,如果丢失将无法解密加密的数据(例如 2FA
- `SECRET_KEY_URI`: **_empty_**:与定义 `SECRET_KEY` 不同,此选项可用于使用存储在文件中的密钥(示例值:`file:/etc/gitea/secret_key`)。它不应该像 `SECRET_KEY` 一样容易丢失。 - `SECRET_KEY_URI`: **_empty_**:与定义 `SECRET_KEY` 不同,此选项可用于使用存储在文件中的密钥(示例值:`file:/etc/gitea/secret_key`)。它不应该像 `SECRET_KEY` 一样容易丢失。
- `LOGIN_REMEMBER_DAYS`: **7**Cookie 保存时间,单位为天 - `LOGIN_REMEMBER_DAYS`: **31**:在要求重新登录之前,记住用户的登录状态多长时间(以天为单位)
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**:保存自动登录信息的 Cookie 名称。 - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**:保存自动登录信息的 Cookie 名称。
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**:反向代理认证的 HTTP 头部名称,用于提供用户信息。 - `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**:反向代理认证的 HTTP 头部名称,用于提供用户信息。
- `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**:反向代理认证的 HTTP 头部名称,用于提供邮箱信息。 - `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**:反向代理认证的 HTTP 头部名称,用于提供邮箱信息。

View file

@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided. 9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided.
10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event. 10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event.
11. Custom event names are recommended to use `ce-` prefix. 11. Custom event names are recommended to use `ce-` prefix.
12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-mono`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). 12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-word-break`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided. 13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided.
### Accessibility / ARIA ### Accessibility / ARIA
@ -118,7 +118,7 @@ However, there are still some special cases, so the current guideline is:
### Show/Hide Elements ### Show/Hide Elements
* Vue components are recommended to use `v-if` and `v-show` to show/hide elements. * Vue components are recommended to use `v-if` and `v-show` to show/hide elements.
* Go template code should use Gitea's `.gt-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.gt-hidden`'s comment. * Go template code should use `.tw-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.tw-hidden`'s comment.
### Styles and Attributes in Go HTML Template ### Styles and Attributes in Go HTML Template

View file

@ -47,7 +47,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。 9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。
10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。 10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。
11. 推荐使用自定义事件名称前缀`ce-`。 11. 推荐使用自定义事件名称前缀`ce-`。
12. 建议使用 Tailwind CSS它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-mono`Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。 12. 建议使用 Tailwind CSS它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-word-break`Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。
13. 尽量避免内联脚本和样式建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免请解释无法避免的原因。 13. 尽量避免内联脚本和样式建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免请解释无法避免的原因。
### 可访问性 / ARIA ### 可访问性 / ARIA
@ -117,7 +117,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性参见`aria.js`和`ari
### 显示/隐藏元素 ### 显示/隐藏元素
* 推荐在Vue组件中使用`v-if`和`v-show`来显示/隐藏元素。 * 推荐在Vue组件中使用`v-if`和`v-show`来显示/隐藏元素。
* Go 模板代码应使用 Gitea 的 `.gt-hidden` 和 `showElem()/hideElem()/toggleElem()` 来显示/隐藏元素,请参阅`.gt-hidden`的注释以获取更多详细信息。 * Go 模板代码应使用 `.tw-hidden` 和 `showElem()/hideElem()/toggleElem()` 来显示/隐藏元素,请参阅`.tw-hidden`的注释以获取更多详细信息。
### Go HTML 模板中的样式和属性 ### Go HTML 模板中的样式和属性

View file

@ -120,6 +120,16 @@ func (c *halfCommitter) Close() error {
// TxContext represents a transaction Context, // TxContext represents a transaction Context,
// it will reuse the existing transaction in the parent context or create a new one. // it will reuse the existing transaction in the parent context or create a new one.
// Some tips to use:
//
// 1 It's always recommended to use `WithTx` in new code instead of `TxContext`, since `WithTx` will handle the transaction automatically.
// 2. To maintain the old code which uses `TxContext`:
// a. Always call `Close()` before returning regardless of whether `Commit()` has been called.
// b. Always call `Commit()` before returning if there are no errors, even if the code did not change any data.
// c. Remember the `Committer` will be a halfCommitter when a transaction is being reused.
// So calling `Commit()` will do nothing, but calling `Close()` without calling `Commit()` will rollback the transaction.
// And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function.
// d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback.
func TxContext(parentCtx context.Context) (*Context, Committer, error) { func TxContext(parentCtx context.Context) (*Context, Committer, error) {
if sess, ok := inTransaction(parentCtx); ok { if sess, ok := inTransaction(parentCtx); ok {
return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil

View file

@ -45,3 +45,27 @@
type: 2 type: 2
created_unix: 1688973000 created_unix: 1688973000
updated_unix: 1688973000 updated_unix: 1688973000
-
id: 5
title: project without default column
owner_id: 2
repo_id: 0
is_closed: false
creator_id: 2
board_type: 1
type: 2
created_unix: 1688973000
updated_unix: 1688973000
-
id: 6
title: project with multiple default columns
owner_id: 2
repo_id: 0
is_closed: false
creator_id: 2
board_type: 1
type: 2
created_unix: 1688973000
updated_unix: 1688973000

View file

@ -3,6 +3,7 @@
project_id: 1 project_id: 1
title: To Do title: To Do
creator_id: 2 creator_id: 2
default: true
created_unix: 1588117528 created_unix: 1588117528
updated_unix: 1588117528 updated_unix: 1588117528
@ -29,3 +30,48 @@
creator_id: 2 creator_id: 2
created_unix: 1588117528 created_unix: 1588117528
updated_unix: 1588117528 updated_unix: 1588117528
-
id: 5
project_id: 2
title: Backlog
creator_id: 2
default: true
created_unix: 1588117528
updated_unix: 1588117528
-
id: 6
project_id: 4
title: Backlog
creator_id: 2
default: true
created_unix: 1588117528
updated_unix: 1588117528
-
id: 7
project_id: 5
title: Done
creator_id: 2
default: false
created_unix: 1588117528
updated_unix: 1588117528
-
id: 8
project_id: 6
title: Backlog
creator_id: 2
default: true
created_unix: 1588117528
updated_unix: 1588117528
-
id: 9
project_id: 6
title: Uncategorized
creator_id: 2
default: true
created_unix: 1588117528
updated_unix: 1588117528

View file

@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm"
) )
// CommitStatus holds a single Status of a single Commit // CommitStatus holds a single Status of a single Commit
@ -264,44 +265,48 @@ type CommitStatusIndex struct {
// GetLatestCommitStatus returns all statuses with a unique context for a given commit. // GetLatestCommitStatus returns all statuses with a unique context for a given commit.
func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) { func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) {
ids := make([]int64, 0, 10) getBase := func() *xorm.Session {
sess := db.GetEngine(ctx).Table(&CommitStatus{}). return db.GetEngine(ctx).Table(&CommitStatus{}).
Where("repo_id = ?", repoID).And("sha = ?", sha). Where("repo_id = ?", repoID).And("sha = ?", sha)
Select("max( id ) as id"). }
GroupBy("context_hash").OrderBy("max( id ) desc") indices := make([]int64, 0, 10)
sess := getBase().Select("max( `index` ) as `index`").
GroupBy("context_hash").OrderBy("max( `index` ) desc")
if !listOptions.IsListAll() { if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions) sess = db.SetSessionPagination(sess, &listOptions)
} }
count, err := sess.FindAndCount(&ids) count, err := sess.FindAndCount(&indices)
if err != nil { if err != nil {
return nil, count, err return nil, count, err
} }
statuses := make([]*CommitStatus, 0, len(ids)) statuses := make([]*CommitStatus, 0, len(indices))
if len(ids) == 0 { if len(indices) == 0 {
return statuses, count, nil return statuses, count, nil
} }
return statuses, count, db.GetEngine(ctx).In("id", ids).Find(&statuses) return statuses, count, getBase().And(builder.In("`index`", indices)).Find(&statuses)
} }
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs // GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) { func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) {
type result struct { type result struct {
ID int64 Index int64
RepoID int64 RepoID int64
} }
results := make([]result, 0, len(repoIDsToLatestCommitSHAs)) results := make([]result, 0, len(repoIDsToLatestCommitSHAs))
sess := db.GetEngine(ctx).Table(&CommitStatus{}) getBase := func() *xorm.Session {
return db.GetEngine(ctx).Table(&CommitStatus{})
}
// Create a disjunction of conditions for each repoID and SHA pair // Create a disjunction of conditions for each repoID and SHA pair
conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs)) conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs))
for repoID, sha := range repoIDsToLatestCommitSHAs { for repoID, sha := range repoIDsToLatestCommitSHAs {
conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha}) conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha})
} }
sess = sess.Where(builder.Or(conds...)). sess := getBase().Where(builder.Or(conds...)).
Select("max( id ) as id, repo_id"). Select("max( `index` ) as `index`, repo_id").
GroupBy("context_hash, repo_id").OrderBy("max( id ) desc") GroupBy("context_hash, repo_id").OrderBy("max( `index` ) desc")
if !listOptions.IsListAll() { if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions) sess = db.SetSessionPagination(sess, &listOptions)
@ -312,15 +317,21 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA
return nil, err return nil, err
} }
ids := make([]int64, 0, len(results))
repoStatuses := make(map[int64][]*CommitStatus) repoStatuses := make(map[int64][]*CommitStatus)
for _, result := range results {
ids = append(ids, result.ID)
}
statuses := make([]*CommitStatus, 0, len(ids)) if len(results) > 0 {
if len(ids) > 0 { statuses := make([]*CommitStatus, 0, len(results))
err = db.GetEngine(ctx).In("id", ids).Find(&statuses)
conds = make([]builder.Cond, 0, len(results))
for _, result := range results {
cond := builder.Eq{
"`index`": result.Index,
"repo_id": result.RepoID,
"sha": repoIDsToLatestCommitSHAs[result.RepoID],
}
conds = append(conds, cond)
}
err = getBase().Where(builder.Or(conds...)).Find(&statuses)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -337,42 +348,43 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA
// GetLatestCommitStatusForRepoCommitIDs returns all statuses with a unique context for a given list of repo-sha pairs // GetLatestCommitStatusForRepoCommitIDs returns all statuses with a unique context for a given list of repo-sha pairs
func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, commitIDs []string) (map[string][]*CommitStatus, error) { func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, commitIDs []string) (map[string][]*CommitStatus, error) {
type result struct { type result struct {
ID int64 Index int64
Sha string SHA string
} }
getBase := func() *xorm.Session {
return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID)
}
results := make([]result, 0, len(commitIDs)) results := make([]result, 0, len(commitIDs))
sess := db.GetEngine(ctx).Table(&CommitStatus{})
// Create a disjunction of conditions for each repoID and SHA pair
conds := make([]builder.Cond, 0, len(commitIDs)) conds := make([]builder.Cond, 0, len(commitIDs))
for _, sha := range commitIDs { for _, sha := range commitIDs {
conds = append(conds, builder.Eq{"sha": sha}) conds = append(conds, builder.Eq{"sha": sha})
} }
sess = sess.Where(builder.Eq{"repo_id": repoID}.And(builder.Or(conds...))). sess := getBase().And(builder.Or(conds...)).
Select("max( id ) as id, sha"). Select("max( `index` ) as `index`, sha").
GroupBy("context_hash, sha").OrderBy("max( id ) desc") GroupBy("context_hash, sha").OrderBy("max( `index` ) desc")
err := sess.Find(&results) err := sess.Find(&results)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ids := make([]int64, 0, len(results))
repoStatuses := make(map[string][]*CommitStatus) repoStatuses := make(map[string][]*CommitStatus)
for _, result := range results {
ids = append(ids, result.ID)
}
statuses := make([]*CommitStatus, 0, len(ids)) if len(results) > 0 {
if len(ids) > 0 { statuses := make([]*CommitStatus, 0, len(results))
err = db.GetEngine(ctx).In("id", ids).Find(&statuses)
conds = make([]builder.Cond, 0, len(results))
for _, result := range results {
conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA})
}
err = getBase().And(builder.Or(conds...)).Find(&statuses)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Group the statuses by repo ID // Group the statuses by commit
for _, status := range statuses { for _, status := range statuses {
repoStatuses[status.SHA] = append(repoStatuses[status.SHA], status) repoStatuses[status.SHA] = append(repoStatuses[status.SHA], status)
} }
@ -383,22 +395,36 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) { func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
type result struct {
Index int64
SHA string
}
getBase := func() *xorm.Session {
return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID)
}
start := timeutil.TimeStampNow().AddDuration(-before) start := timeutil.TimeStampNow().AddDuration(-before)
ids := make([]int64, 0, 10) results := make([]result, 0, 10)
if err := db.GetEngine(ctx).Table("commit_status").
Where("repo_id = ?", repoID). sess := getBase().And("updated_unix >= ?", start).
And("updated_unix >= ?", start). Select("max( `index` ) as `index`, sha").
Select("max( id ) as id"). GroupBy("context_hash, sha").OrderBy("max( `index` ) desc")
GroupBy("context_hash").OrderBy("max( id ) desc").
Find(&ids); err != nil { err := sess.Find(&results)
if err != nil {
return nil, err return nil, err
} }
contexts := make([]string, 0, len(ids)) contexts := make([]string, 0, len(results))
if len(ids) == 0 { if len(results) == 0 {
return contexts, nil return contexts, nil
} }
return contexts, db.GetEngine(ctx).Select("context").Table("commit_status").In("id", ids).Find(&contexts)
conds := make([]builder.Cond, 0, len(results))
for _, result := range results {
conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA})
}
return contexts, getBase().And(builder.Or(conds...)).Select("context").Find(&contexts)
} }
// NewCommitStatusOptions holds options for creating a CommitStatus // NewCommitStatusOptions holds options for creating a CommitStatus

View file

@ -117,6 +117,10 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
return nil, err return nil, err
} }
if err := comments.LoadAttachments(ctx); err != nil {
return nil, err
}
// Find all reviews by ReviewID // Find all reviews by ReviewID
reviews := make(map[int64]*Review) reviews := make(map[int64]*Review)
ids := make([]int64, 0, len(comments)) ids := make([]int64, 0, len(comments))

View file

@ -49,10 +49,7 @@ func (issue *Issue) ProjectBoardID(ctx context.Context) int64 {
// LoadIssuesFromBoard load issues assigned to this board // LoadIssuesFromBoard load issues assigned to this board
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) { func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
issueList := make(IssueList, 0, 10) issueList, err := Issues(ctx, &IssuesOptions{
if b.ID > 0 {
issues, err := Issues(ctx, &IssuesOptions{
ProjectBoardID: b.ID, ProjectBoardID: b.ID,
ProjectID: b.ProjectID, ProjectID: b.ProjectID,
SortType: "project-column-sorting", SortType: "project-column-sorting",
@ -60,8 +57,6 @@ func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList
if err != nil { if err != nil {
return nil, err return nil, err
} }
issueList = issues
}
if b.Default { if b.Default {
issues, err := Issues(ctx, &IssuesOptions{ issues, err := Issues(ctx, &IssuesOptions{

View file

@ -21,7 +21,7 @@ import (
// IssuesOptions represents options of an issue. // IssuesOptions represents options of an issue.
type IssuesOptions struct { //nolint type IssuesOptions struct { //nolint
db.Paginator Paginator *db.ListOptions
RepoIDs []int64 // overwrites RepoCond if the length is not 0 RepoIDs []int64 // overwrites RepoCond if the length is not 0
AllPublic bool // include also all public repositories AllPublic bool // include also all public repositories
RepoCond builder.Cond RepoCond builder.Cond
@ -104,23 +104,11 @@ func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
return sess return sess
} }
// Warning: Do not use GetSkipTake() for *db.ListOptions start := 0
// Its implementation could reset the page size with setting.API.MaxResponseItems if opts.Paginator.Page > 1 {
if listOptions, ok := opts.Paginator.(*db.ListOptions); ok { start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
if listOptions.Page >= 0 && listOptions.PageSize > 0 {
var start int
if listOptions.Page == 0 {
start = 0
} else {
start = (listOptions.Page - 1) * listOptions.PageSize
} }
sess.Limit(listOptions.PageSize, start) sess.Limit(opts.Paginator.PageSize, start)
}
return sess
}
start, limit := opts.Paginator.GetSkipTake()
sess.Limit(limit, start)
return sess return sess
} }

View file

@ -68,13 +68,17 @@ func CountIssuesByRepo(ctx context.Context, opts *IssuesOptions) (map[int64]int6
} }
// CountIssues number return of issues by given conditions. // CountIssues number return of issues by given conditions.
func CountIssues(ctx context.Context, opts *IssuesOptions) (int64, error) { func CountIssues(ctx context.Context, opts *IssuesOptions, otherConds ...builder.Cond) (int64, error) {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Select("COUNT(issue.id) AS count"). Select("COUNT(issue.id) AS count").
Table("issue"). Table("issue").
Join("INNER", "repository", "`issue`.repo_id = `repository`.id") Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
applyConditions(sess, opts) applyConditions(sess, opts)
for _, cond := range otherConds {
sess.And(cond)
}
return sess.Count() return sess.Count()
} }

View file

@ -66,6 +66,23 @@ func (err ErrNotValidReviewRequest) Unwrap() error {
return util.ErrInvalidArgument return util.ErrInvalidArgument
} }
// ErrReviewRequestOnClosedPR represents an error when an user tries to request a re-review on a closed or merged PR.
type ErrReviewRequestOnClosedPR struct{}
// IsErrReviewRequestOnClosedPR checks if an error is an ErrReviewRequestOnClosedPR.
func IsErrReviewRequestOnClosedPR(err error) bool {
_, ok := err.(ErrReviewRequestOnClosedPR)
return ok
}
func (err ErrReviewRequestOnClosedPR) Error() string {
return "cannot request a re-review on a closed or merged PR"
}
func (err ErrReviewRequestOnClosedPR) Unwrap() error {
return util.ErrPermissionDenied
}
// ReviewType defines the sort of feedback a review gives // ReviewType defines the sort of feedback a review gives
type ReviewType int type ReviewType int
@ -618,9 +635,24 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
return nil, err return nil, err
} }
if review != nil {
// skip it when reviewer hase been request to review // skip it when reviewer hase been request to review
if review != nil && review.Type == ReviewTypeRequest { if review.Type == ReviewTypeRequest {
return nil, nil return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
}
if issue.IsClosed {
return nil, ErrReviewRequestOnClosedPR{}
}
if issue.IsPull {
if err := issue.LoadPullRequest(ctx); err != nil {
return nil, err
}
if issue.PullRequest.HasMerged {
return nil, ErrReviewRequestOnClosedPR{}
}
}
} }
// if the reviewer is an official reviewer, // if the reviewer is an official reviewer,

View file

@ -288,3 +288,33 @@ func TestDeleteDismissedReview(t *testing.T) {
assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review)) assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review))
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID})
} }
func TestAddReviewRequest(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
assert.NoError(t, pull.LoadIssue(db.DefaultContext))
issue := pull.Issue
assert.NoError(t, issue.LoadRepo(db.DefaultContext))
reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
_, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Issue: issue,
Reviewer: reviewer,
Type: issues_model.ReviewTypeReject,
})
assert.NoError(t, err)
pull.HasMerged = false
assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
issue.IsClosed = true
_, err = issues_model.AddReviewRequest(db.DefaultContext, issue, reviewer, &user_model.User{})
assert.Error(t, err)
assert.True(t, issues_model.IsErrReviewRequestOnClosedPR(err))
pull.HasMerged = true
assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
issue.IsClosed = false
_, err = issues_model.AddReviewRequest(db.DefaultContext, issue, reviewer, &user_model.User{})
assert.Error(t, err)
assert.True(t, issues_model.IsErrReviewRequestOnClosedPR(err))
}

View file

@ -0,0 +1,23 @@
-
id: 1
title: project without default column
owner_id: 2
repo_id: 0
is_closed: false
creator_id: 2
board_type: 1
type: 2
created_unix: 1688973000
updated_unix: 1688973000
-
id: 2
title: project with multiple default columns
owner_id: 2
repo_id: 0
is_closed: false
creator_id: 2
board_type: 1
type: 2
created_unix: 1688973000
updated_unix: 1688973000

View file

@ -0,0 +1,26 @@
-
id: 1
project_id: 1
title: Done
creator_id: 2
default: false
created_unix: 1588117528
updated_unix: 1588117528
-
id: 2
project_id: 2
title: Backlog
creator_id: 2
default: true
created_unix: 1588117528
updated_unix: 1588117528
-
id: 3
project_id: 2
title: Uncategorized
creator_id: 2
default: true
created_unix: 1588117528
updated_unix: 1588117528

View file

@ -568,6 +568,12 @@ var migrations = []Migration{
NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch), NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch),
// v290 -> v291 // v290 -> v291
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable), NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
// v291 -> v292
NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
// v292 -> v293
NewMigration("Ensure every project has exactly one default column - No Op", noopMigration),
// v293 -> v294
NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View file

@ -0,0 +1,14 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import "xorm.io/xorm"
func AddCommentIDIndexofAttachment(x *xorm.Engine) error {
type Attachment struct {
CommentID int64 `xorm:"INDEX"`
}
return x.Sync(&Attachment{})
}

View file

@ -0,0 +1,9 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
// NOTE: noop the original migration has bug which some projects will be skip, so
// these projects will have no default board.
// So that this migration will be skipped and go to v293.go
// This file is a placeholder so that readers can know what happened

View file

@ -0,0 +1,108 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
// CheckProjectColumnsConsistency ensures there is exactly one default board per project present
func CheckProjectColumnsConsistency(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()
limit := setting.Database.IterateBufferSize
if limit <= 0 {
limit = 50
}
type Project struct {
ID int64
CreatorID int64
BoardID int64
}
type ProjectBoard struct {
ID int64 `xorm:"pk autoincr"`
Title string
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
Color string `xorm:"VARCHAR(7)"`
ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
for {
if err := sess.Begin(); err != nil {
return err
}
// all these projects without defaults will be fixed in the same loop, so
// we just need to always get projects without defaults until no such project
var projects []*Project
if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id").
Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true).
Where("project_board.id is NULL OR project_board.id = 0").
Limit(limit).
Find(&projects); err != nil {
return err
}
for _, p := range projects {
if _, err := sess.Insert(ProjectBoard{
ProjectID: p.ID,
Default: true,
Title: "Uncategorized",
CreatorID: p.CreatorID,
}); err != nil {
return err
}
}
if err := sess.Commit(); err != nil {
return err
}
if len(projects) == 0 {
break
}
}
sess.Close()
return removeDuplicatedBoardDefault(x)
}
func removeDuplicatedBoardDefault(x *xorm.Engine) error {
type ProjectInfo struct {
ProjectID int64
DefaultNum int
}
var projects []ProjectInfo
if err := x.Select("project_id, count(*) AS default_num").
Table("project_board").
Where("`default` = ?", true).
GroupBy("project_id").
Having("count(*) > 1").
Find(&projects); err != nil {
return err
}
for _, project := range projects {
if _, err := x.Where("project_id=?", project.ProjectID).
Table("project_board").
Limit(project.DefaultNum - 1).
Update(map[string]bool{
"`default`": false,
}); err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,44 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/models/project"
"github.com/stretchr/testify/assert"
)
func Test_CheckProjectColumnsConsistency(t *testing.T) {
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Board))
defer deferable()
if x == nil || t.Failed() {
return
}
assert.NoError(t, CheckProjectColumnsConsistency(x))
// check if default board was added
var defaultBoard project.Board
has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultBoard)
assert.NoError(t, err)
assert.True(t, has)
assert.Equal(t, int64(1), defaultBoard.ProjectID)
assert.True(t, defaultBoard.Default)
// check if multiple defaults, previous were removed and last will be kept
expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2)
assert.NoError(t, err)
assert.Equal(t, int64(2), expectDefaultBoard.ProjectID)
assert.False(t, expectDefaultBoard.Default)
expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3)
assert.NoError(t, err)
assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID)
assert.True(t, expectNonDefaultBoard.Default)
}

View file

@ -123,6 +123,17 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
return nil return nil
} }
board := Board{
CreatedUnix: timeutil.TimeStampNow(),
CreatorID: project.CreatorID,
Title: "Backlog",
ProjectID: project.ID,
Default: true,
}
if err := db.Insert(ctx, board); err != nil {
return err
}
if len(items) == 0 { if len(items) == 0 {
return nil return nil
} }
@ -176,6 +187,10 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
return err return err
} }
if board.Default {
return fmt.Errorf("deleteBoardByID: cannot delete default board")
}
if err = board.removeIssues(ctx); err != nil { if err = board.removeIssues(ctx); err != nil {
return err return err
} }
@ -194,7 +209,6 @@ func deleteBoardByProjectID(ctx context.Context, projectID int64) error {
// GetBoard fetches the current board of a project // GetBoard fetches the current board of a project
func GetBoard(ctx context.Context, boardID int64) (*Board, error) { func GetBoard(ctx context.Context, boardID int64) (*Board, error) {
board := new(Board) board := new(Board)
has, err := db.GetEngine(ctx).ID(boardID).Get(board) has, err := db.GetEngine(ctx).ID(boardID).Get(board)
if err != nil { if err != nil {
return nil, err return nil, err
@ -228,7 +242,6 @@ func UpdateBoard(ctx context.Context, board *Board) error {
} }
// GetBoards fetches all boards related to a project // GetBoards fetches all boards related to a project
// if no default board set, first board is a temporary "Uncategorized" board
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) { func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
boards := make([]*Board, 0, 5) boards := make([]*Board, 0, 5)
@ -244,53 +257,64 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
return append([]*Board{defaultB}, boards...), nil return append([]*Board{defaultB}, boards...), nil
} }
// getDefaultBoard return default board and create a dummy if none exist // getDefaultBoard return default board and ensure only one exists
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) { func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
var board Board var board Board
exist, err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, true).Get(&board) has, err := db.GetEngine(ctx).
Where("project_id=? AND `default` = ?", p.ID, true).
Desc("id").Get(&board)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if exist {
if has {
return &board, nil return &board, nil
} }
// represents a board for issues not assigned to one // create a default board if none is found
return &Board{ board = Board{
ProjectID: p.ID, ProjectID: p.ID,
Title: "Uncategorized",
Default: true, Default: true,
}, nil Title: "Uncategorized",
CreatorID: p.CreatorID,
}
if _, err := db.GetEngine(ctx).Insert(&board); err != nil {
return nil, err
}
return &board, nil
} }
// SetDefaultBoard represents a board for issues not assigned to one // SetDefaultBoard represents a board for issues not assigned to one
// if boardID is 0 unset default
func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error { func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error {
_, err := db.GetEngine(ctx).Where(builder.Eq{ return db.WithTx(ctx, func(ctx context.Context) error {
if _, err := GetBoard(ctx, boardID); err != nil {
return err
}
if _, err := db.GetEngine(ctx).Where(builder.Eq{
"project_id": projectID, "project_id": projectID,
"`default`": true, "`default`": true,
}).Cols("`default`").Update(&Board{Default: false}) }).Cols("`default`").Update(&Board{Default: false}); err != nil {
if err != nil {
return err return err
} }
if boardID > 0 { _, err := db.GetEngine(ctx).ID(boardID).
_, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}). Where(builder.Eq{"project_id": projectID}).
Cols("`default`").Update(&Board{Default: true}) Cols("`default`").Update(&Board{Default: true})
}
return err return err
})
} }
// UpdateBoardSorting update project board sorting // UpdateBoardSorting update project board sorting
func UpdateBoardSorting(ctx context.Context, bs BoardList) error { func UpdateBoardSorting(ctx context.Context, bs BoardList) error {
return db.WithTx(ctx, func(ctx context.Context) error {
for i := range bs { for i := range bs {
_, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( if _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols(
"sorting", "sorting",
).Update(bs[i]) ).Update(bs[i]); err != nil {
if err != nil {
return err return err
} }
} }
return nil return nil
})
} }

View file

@ -0,0 +1,44 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package project
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestGetDefaultBoard(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5)
assert.NoError(t, err)
// check if default board was added
board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
assert.NoError(t, err)
assert.Equal(t, int64(5), board.ProjectID)
assert.Equal(t, "Uncategorized", board.Title)
projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
assert.NoError(t, err)
// check if multiple defaults were removed
board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
assert.NoError(t, err)
assert.Equal(t, int64(6), board.ProjectID)
assert.Equal(t, int64(9), board.ID)
// set 8 as default board
assert.NoError(t, SetDefaultBoard(db.DefaultContext, board.ProjectID, 8))
// then 9 will become a non-default board
board, err = GetBoard(db.DefaultContext, 9)
assert.NoError(t, err)
assert.Equal(t, int64(6), board.ProjectID)
assert.False(t, board.Default)
}

View file

@ -92,19 +92,19 @@ func TestProjectsSort(t *testing.T) {
}{ }{
{ {
sortType: "default", sortType: "default",
wants: []int64{1, 3, 2, 4}, wants: []int64{1, 3, 2, 6, 5, 4},
}, },
{ {
sortType: "oldest", sortType: "oldest",
wants: []int64{4, 2, 3, 1}, wants: []int64{4, 5, 6, 2, 3, 1},
}, },
{ {
sortType: "recentupdate", sortType: "recentupdate",
wants: []int64{1, 3, 2, 4}, wants: []int64{1, 3, 2, 6, 5, 4},
}, },
{ {
sortType: "leastupdate", sortType: "leastupdate",
wants: []int64{4, 2, 3, 1}, wants: []int64{4, 5, 6, 2, 3, 1},
}, },
} }
@ -113,8 +113,8 @@ func TestProjectsSort(t *testing.T) {
OrderBy: GetSearchOrderByBySortType(tt.sortType), OrderBy: GetSearchOrderByBySortType(tt.sortType),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, int64(4), count) assert.EqualValues(t, int64(6), count)
if assert.Len(t, projects, 4) { if assert.Len(t, projects, 6) {
for i := range projects { for i := range projects {
assert.EqualValues(t, tt.wants[i], projects[i].ID) assert.EqualValues(t, tt.wants[i], projects[i].ID)
} }

View file

@ -10,7 +10,7 @@ import (
) )
// ParsePaginator parses a db.Paginator into a skip and limit // ParsePaginator parses a db.Paginator into a skip and limit
func ParsePaginator(paginator db.Paginator, max ...int) (int, int) { func ParsePaginator(paginator *db.ListOptions, max ...int) (int, int) {
// Use a very large number to indicate no limit // Use a very large number to indicate no limit
unlimited := math.MaxInt32 unlimited := math.MaxInt32
if len(max) > 0 { if len(max) > 0 {
@ -19,22 +19,15 @@ func ParsePaginator(paginator db.Paginator, max ...int) (int, int) {
} }
if paginator == nil || paginator.IsListAll() { if paginator == nil || paginator.IsListAll() {
// It shouldn't happen. In actual usage scenarios, there should not be requests to search all.
// But if it does happen, respect it and return "unlimited".
// And it's also useful for testing.
return 0, unlimited return 0, unlimited
} }
// Warning: Do not use GetSkipTake() for *db.ListOptions if paginator.PageSize == 0 {
// Its implementation could reset the page size with setting.API.MaxResponseItems // Do not return any results when searching, it's used to get the total count only.
if listOptions, ok := paginator.(*db.ListOptions); ok { return 0, 0
if listOptions.Page >= 0 && listOptions.PageSize > 0 {
var start int
if listOptions.Page == 0 {
start = 0
} else {
start = (listOptions.Page - 1) * listOptions.PageSize
}
return start, listOptions.PageSize
}
return 0, unlimited
} }
return paginator.GetSkipTake() return paginator.GetSkipTake()

View file

@ -78,6 +78,17 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
return nil, err return nil, err
} }
// If pagesize == 0, return total count only. It's a special case for search count.
if options.Paginator != nil && options.Paginator.PageSize == 0 {
total, err := issue_model.CountIssues(ctx, opt, cond)
if err != nil {
return nil, err
}
return &internal.SearchResult{
Total: total,
}, nil
}
ids, total, err := issue_model.IssueIDs(ctx, opt, cond) ids, total, err := issue_model.IssueIDs(ctx, opt, cond)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -308,7 +308,7 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err
// CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count. // CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count.
func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) { func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) {
opts = opts.Copy(func(options *SearchOptions) { opts.Paginator = &db_model.ListOptions{PageSize: 0} }) opts = opts.Copy(func(options *SearchOptions) { options.Paginator = &db_model.ListOptions{PageSize: 0} })
_, total, err := SearchIssues(ctx, opts) _, total, err := SearchIssues(ctx, opts)
return total, err return total, err

View file

@ -106,7 +106,7 @@ type SearchOptions struct {
UpdatedAfterUnix optional.Option[int64] UpdatedAfterUnix optional.Option[int64]
UpdatedBeforeUnix optional.Option[int64] UpdatedBeforeUnix optional.Option[int64]
db.Paginator Paginator *db.ListOptions
SortBy SortBy // sort by field SortBy SortBy // sort by field
} }

View file

@ -77,6 +77,13 @@ func TestIndexer(t *testing.T, indexer internal.Indexer) {
assert.Equal(t, c.ExpectedIDs, ids) assert.Equal(t, c.ExpectedIDs, ids)
assert.Equal(t, c.ExpectedTotal, result.Total) assert.Equal(t, c.ExpectedTotal, result.Total)
} }
// test counting
c.SearchOptions.Paginator = &db.ListOptions{PageSize: 0}
countResult, err := indexer.Search(context.Background(), c.SearchOptions)
require.NoError(t, err)
assert.Empty(t, countResult.Hits)
assert.Equal(t, result.Total, countResult.Total)
}) })
} }
} }

View file

@ -218,6 +218,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits) skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
counting := limit == 0
if counting {
// If set limit to 0, it will be 20 by default, and -1 is not allowed.
// See https://www.meilisearch.com/docs/reference/api/search#limit
// So set limit to 1 to make the cost as low as possible, then clear the result before returning.
limit = 1
}
keyword := options.Keyword keyword := options.Keyword
if !options.IsFuzzyKeyword { if !options.IsFuzzyKeyword {
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s) // to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
@ -236,6 +244,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
return nil, err return nil, err
} }
if counting {
searchRes.Hits = nil
}
hits, err := convertHits(searchRes) hits, err := convertHits(searchRes)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -103,7 +103,7 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
func loadSecurityFrom(rootCfg ConfigProvider) { func loadSecurityFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("security") sec := rootCfg.Section("security")
InstallLock = HasInstallLock(rootCfg) InstallLock = HasInstallLock(rootCfg)
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31)
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
if SecretKey == "" { if SecretKey == "" {
// FIXME: https://github.com/go-gitea/gitea/issues/16832 // FIXME: https://github.com/go-gitea/gitea/issues/16832

View file

@ -40,7 +40,7 @@ func RenderCommitMessage(ctx context.Context, msg string, metas map[string]strin
if len(msgLines) == 0 { if len(msgLines) == 0 {
return template.HTML("") return template.HTML("")
} }
return template.HTML(msgLines[0]) return RenderCodeBlock(template.HTML(msgLines[0]))
} }
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to // RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
@ -67,7 +67,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string,
log.Error("RenderCommitMessageSubject: %v", err) log.Error("RenderCommitMessageSubject: %v", err)
return template.HTML("") return template.HTML("")
} }
return template.HTML(renderedMessage) return RenderCodeBlock(template.HTML(renderedMessage))
} }
// RenderCommitBody extracts the body of a commit message without its title. // RenderCommitBody extracts the body of a commit message without its title.

View file

@ -1423,7 +1423,6 @@ projects.type.basic_kanban = Basic Kanban
projects.type.bug_triage = Bug Triage projects.type.bug_triage = Bug Triage
projects.template.desc = Template projects.template.desc = Template
projects.template.desc_helper = Select a project template to get started projects.template.desc_helper = Select a project template to get started
projects.type.uncategorized = Uncategorized
projects.column.edit = Edit Column projects.column.edit = Edit Column
projects.column.edit_title = Name projects.column.edit_title = Name
projects.column.new_title = Name projects.column.new_title = Name
@ -1431,10 +1430,8 @@ projects.column.new_submit = Create Column
projects.column.new = New Column projects.column.new = New Column
projects.column.set_default = Set Default projects.column.set_default = Set Default
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls
projects.column.unset_default = Unset Default
projects.column.unset_default_desc = Unset this column as default
projects.column.delete = Delete Column projects.column.delete = Delete Column
projects.column.deletion_desc = Deleting a project column moves all related issues to "Uncategorized". Continue? projects.column.deletion_desc = Deleting a project column moves all related issues to the default column. Continue?
projects.column.color = Color projects.column.color = Color
projects.open = Open projects.open = Open
projects.close = Close projects.close = Close

409
package-lock.json generated
View file

@ -11,10 +11,10 @@
"@citation-js/plugin-software-formats": "0.6.1", "@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6", "@claviska/jquery-minicolors": "2.3.6",
"@github/markdown-toolbar-element": "2.2.3", "@github/markdown-toolbar-element": "2.2.3",
"@github/relative-time-element": "4.3.1", "@github/relative-time-element": "4.4.0",
"@github/text-expander-element": "2.6.1", "@github/text-expander-element": "2.6.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.8.0", "@primer/octicons": "19.9.0",
"add-asset-webpack-plugin": "2.0.1", "add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2", "ansi_up": "6.0.2",
"asciinema-player": "3.7.1", "asciinema-player": "3.7.1",
@ -40,7 +40,7 @@
"monaco-editor": "0.47.0", "monaco-editor": "0.47.0",
"monaco-editor-webpack-plugin": "7.1.0", "monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0", "pdfobject": "2.3.0",
"postcss": "8.4.35", "postcss": "8.4.38",
"postcss-loader": "8.1.1", "postcss-loader": "8.1.1",
"postcss-nesting": "12.1.0", "postcss-nesting": "12.1.0",
"pretty-ms": "9.0.0", "pretty-ms": "9.0.0",
@ -59,7 +59,7 @@
"vue-chartjs": "5.3.0", "vue-chartjs": "5.3.0",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5", "vue3-calendar-heatmap": "2.0.5",
"webpack": "5.90.3", "webpack": "5.91.0",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0" "wrap-ansi": "9.0.0"
}, },
@ -77,24 +77,24 @@
"eslint-plugin-jquery": "1.5.1", "eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.3.0", "eslint-plugin-regexp": "2.4.0",
"eslint-plugin-sonarjs": "0.24.0", "eslint-plugin-sonarjs": "0.24.0",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "51.0.1",
"eslint-plugin-vitest": "0.3.26", "eslint-plugin-vitest": "0.4.0",
"eslint-plugin-vitest-globals": "1.4.0", "eslint-plugin-vitest-globals": "1.5.0",
"eslint-plugin-vue": "9.23.0", "eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue-scoped-css": "2.7.2", "eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4", "eslint-plugin-wc": "2.0.4",
"happy-dom": "14.2.0", "happy-dom": "14.3.7",
"markdownlint-cli": "0.39.0", "markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0", "postcss-html": "1.6.0",
"stylelint": "16.2.1", "stylelint": "16.3.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.4", "stylelint-declaration-strict-value": "1.10.4",
"svgo": "3.2.0", "svgo": "3.2.0",
"updates": "15.3.1", "updates": "16.0.0",
"vite-string-plugin": "1.1.5", "vite-string-plugin": "1.1.5",
"vitest": "1.3.1" "vitest": "1.4.0"
}, },
"engines": { "engines": {
"node": ">= 18.0.0" "node": ">= 18.0.0"
@ -516,6 +516,16 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/@dual-bundle/import-meta-resolve": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
"integrity": "sha512-ZKXyJeFAzcpKM2kk8ipoGIPUqx9BX52omTGnfwjJvxOCaZTM2wtDK7zN0aIgPRbT9XYAlha0HtmZ+XKteuh0Gw==",
"dev": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
@ -994,9 +1004,9 @@
"integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==" "integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A=="
}, },
"node_modules/@github/relative-time-element": { "node_modules/@github/relative-time-element": {
"version": "4.3.1", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.3.1.tgz", "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.0.tgz",
"integrity": "sha512-zL79nlhZVCg7x2Pf/HT5MB0mowmErE71VXpF10/3Wy8dQwkninNO1M9aOizh2wKC5LkSpDXqNYjDZwbH0/bcSg==" "integrity": "sha512-CrI6oAecoahG7PF5dsgjdvlF5kCtusVMjg810EULD81TvnDsP+k/FRi/ClFubWLgBo4EGpr2EfvmumtqQFo7ow=="
}, },
"node_modules/@github/text-expander-element": { "node_modules/@github/text-expander-element": {
"version": "2.6.1", "version": "2.6.1",
@ -1365,9 +1375,9 @@
} }
}, },
"node_modules/@primer/octicons": { "node_modules/@primer/octicons": {
"version": "19.8.0", "version": "19.9.0",
"resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.8.0.tgz", "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.9.0.tgz",
"integrity": "sha512-Imze/fyW41Io5fN+27T5EAeXJrgBjMbz6nzU+wYbRylXvIAjLPUvaJPVoStiFlgSU+TjTUJqg5A9rgMDzTyMCg==", "integrity": "sha512-uAZa9cMgWkzbEsZnYWB7tg0vt7QprubD7ljtprz2fBJ8CjyqoxFRRsFvH4UiJdjK/3o87ODgDkhiflyJXDh+Lg==",
"dependencies": { "dependencies": {
"object-assign": "^4.1.1" "object-assign": "^4.1.1"
} }
@ -2238,16 +2248,16 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz",
"integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.5.1", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "7.3.1", "@typescript-eslint/scope-manager": "7.4.0",
"@typescript-eslint/type-utils": "7.3.1", "@typescript-eslint/type-utils": "7.4.0",
"@typescript-eslint/utils": "7.3.1", "@typescript-eslint/utils": "7.4.0",
"@typescript-eslint/visitor-keys": "7.3.1", "@typescript-eslint/visitor-keys": "7.4.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.4", "ignore": "^5.2.4",
@ -2273,15 +2283,15 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz",
"integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "7.3.1", "@typescript-eslint/scope-manager": "7.4.0",
"@typescript-eslint/types": "7.3.1", "@typescript-eslint/types": "7.4.0",
"@typescript-eslint/typescript-estree": "7.3.1", "@typescript-eslint/typescript-estree": "7.4.0",
"@typescript-eslint/visitor-keys": "7.3.1", "@typescript-eslint/visitor-keys": "7.4.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -2301,13 +2311,13 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz",
"integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.3.1", "@typescript-eslint/types": "7.4.0",
"@typescript-eslint/visitor-keys": "7.3.1" "@typescript-eslint/visitor-keys": "7.4.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -2318,13 +2328,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz",
"integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "7.3.1", "@typescript-eslint/typescript-estree": "7.4.0",
"@typescript-eslint/utils": "7.3.1", "@typescript-eslint/utils": "7.4.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
@ -2345,9 +2355,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz",
"integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -2358,13 +2368,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz",
"integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.3.1", "@typescript-eslint/types": "7.4.0",
"@typescript-eslint/visitor-keys": "7.3.1", "@typescript-eslint/visitor-keys": "7.4.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -2386,17 +2396,17 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz",
"integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "7.3.1", "@typescript-eslint/scope-manager": "7.4.0",
"@typescript-eslint/types": "7.3.1", "@typescript-eslint/types": "7.4.0",
"@typescript-eslint/typescript-estree": "7.3.1", "@typescript-eslint/typescript-estree": "7.4.0",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
@ -2411,12 +2421,12 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "7.3.1", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz",
"integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.3.1", "@typescript-eslint/types": "7.4.0",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
@ -2447,13 +2457,13 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz",
"integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vitest/spy": "1.3.1", "@vitest/spy": "1.4.0",
"@vitest/utils": "1.3.1", "@vitest/utils": "1.4.0",
"chai": "^4.3.10" "chai": "^4.3.10"
}, },
"funding": { "funding": {
@ -2461,12 +2471,12 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz",
"integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vitest/utils": "1.3.1", "@vitest/utils": "1.4.0",
"p-limit": "^5.0.0", "p-limit": "^5.0.0",
"pathe": "^1.1.1" "pathe": "^1.1.1"
}, },
@ -2502,9 +2512,9 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz",
"integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"magic-string": "^0.30.5", "magic-string": "^0.30.5",
@ -2528,9 +2538,9 @@
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz",
"integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"tinyspy": "^2.2.0" "tinyspy": "^2.2.0"
@ -2540,9 +2550,9 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz",
"integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"diff-sequences": "^29.6.3", "diff-sequences": "^29.6.3",
@ -3475,9 +3485,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001599", "version": "1.0.30001600",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
"integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -4721,9 +4731,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.0.10", "version": "3.0.11",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.10.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.11.tgz",
"integrity": "sha512-WZDL8ZHTliEVP3Lk4phtvjg8SNQ3YMc5WVstxE8cszKZrFjzI4PF4ZTIk9VGAc9vZADO7uGO2V/ZiStcRSAT4Q==" "integrity": "sha512-Fan4uMuyB26gFV3ovPoEoQbxRRPfTu3CvImyZnhGq5fsIEO+gEFLp45ISFt+kQBWsK5ulDdT0oV28jS1UrwQLg=="
}, },
"node_modules/domutils": { "node_modules/domutils": {
"version": "3.1.0", "version": "3.1.0",
@ -4766,9 +4776,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.713", "version": "1.4.716",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.713.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz",
"integrity": "sha512-vDarADhwntXiULEdmWd77g2dV6FrNGa8ecAC29MZ4TwPut2fvosD0/5sJd1qWNNe8HcJFAC+F5Lf9jW1NPtWmw==" "integrity": "sha512-t/MXMzFKQC3UfMDpw7V5wdB/UAB8dWx4hEsy+fpPYJWW3gqh3u5T1uXp6vR+H6dGCPBxkRo+YBcapBLvbGQHRw=="
}, },
"node_modules/elkjs": { "node_modules/elkjs": {
"version": "0.9.2", "version": "0.9.2",
@ -4899,19 +4909,19 @@
} }
}, },
"node_modules/es-aggregate-error": { "node_modules/es-aggregate-error": {
"version": "1.0.12", "version": "1.0.13",
"resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.12.tgz", "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.13.tgz",
"integrity": "sha512-j0PupcmELoVbYS2NNrsn5zcLLEsryQwP02x8fRawh7c2eEaPHwJFAxltZsqV7HJjsF57+SMpYyVRWgbVLfOagg==", "integrity": "sha512-KkzhUUuD2CUMqEc8JEqsXEMDHzDPE8RCjZeUBitsnB1eNcAJWQPiciKsMXe3Yytj4Flw1XLl46Qcf9OxvZha7A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"define-data-property": "^1.1.1", "define-data-property": "^1.1.4",
"define-properties": "^1.2.1", "define-properties": "^1.2.1",
"es-abstract": "^1.22.3", "es-abstract": "^1.23.2",
"es-errors": "^1.1.0", "es-errors": "^1.3.0",
"function-bind": "^1.1.2", "function-bind": "^1.1.2",
"globalthis": "^1.0.3", "globalthis": "^1.0.3",
"has-property-descriptors": "^1.0.1", "has-property-descriptors": "^1.0.2",
"set-function-name": "^2.0.1" "set-function-name": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -4967,9 +4977,9 @@
} }
}, },
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.4.2", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.2.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
"integrity": "sha512-7nOqkomXZEaxUDJw21XZNtRk739QvrPSoZoRtbsEfcii00vdzZUh6zh1CQwHhrib8MdEtJfv5rJiGeb4KuV/vw==" "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw=="
}, },
"node_modules/es-object-atoms": { "node_modules/es-object-atoms": {
"version": "1.0.0", "version": "1.0.0",
@ -5164,9 +5174,9 @@
} }
}, },
"node_modules/eslint-compat-utils": { "node_modules/eslint-compat-utils": {
"version": "0.4.1", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.4.1.tgz", "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz",
"integrity": "sha512-5N7ZaJG5pZxUeNNJfUchurLVrunD1xJvyg5kYOIVF8kg1f3ajTikmAu/5fZ9w100omNPOoMjngRszh/Q/uFGMg==", "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"semver": "^7.5.4" "semver": "^7.5.4"
@ -5598,9 +5608,9 @@
} }
}, },
"node_modules/eslint-plugin-regexp": { "node_modules/eslint-plugin-regexp": {
"version": "2.3.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.4.0.tgz",
"integrity": "sha512-T8JUs7ssRGbuXb+CGfdUJbcxTBMCNOpNqNBLuC8JUKAEIez72J37RaOi5/4dAUsGz92GbWVtqTLPSJZGyP/sQA==", "integrity": "sha512-OL2S6VPjQhs9s/NclQ0qattVq1J0GU8ox70/HIVy5Dxw+qbbdd7KQkyucsez2clEQjvdtDe12DTnPphFFUyXFg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
@ -5664,12 +5674,12 @@
} }
}, },
"node_modules/eslint-plugin-vitest": { "node_modules/eslint-plugin-vitest": {
"version": "0.3.26", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.26.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.4.0.tgz",
"integrity": "sha512-oxe5JSPgRjco8caVLTh7Ti8PxpwJdhSV0hTQAmkFcNcmy/9DnqLB/oNVRA11RmVRP//2+jIIT6JuBEcpW3obYg==", "integrity": "sha512-3oWgZIwdWVBQ5plvkmOBjreIGLQRdYb7x54OP8uIRHeZyRVJIdOn9o/qWVb9292fDMC8jn7H7d9TSFBZqhrykQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/utils": "^7.1.1" "@typescript-eslint/utils": "^7.2.0"
}, },
"engines": { "engines": {
"node": "^18.0.0 || >= 20.0.0" "node": "^18.0.0 || >= 20.0.0"
@ -5688,18 +5698,19 @@
} }
}, },
"node_modules/eslint-plugin-vitest-globals": { "node_modules/eslint-plugin-vitest-globals": {
"version": "1.4.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.4.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.5.0.tgz",
"integrity": "sha512-WE+YlK9X9s4vf5EaYRU0Scw7WItDZStm+PapFSYlg2ABNtaQ4zIG7wEqpoUB3SlfM+SgkhgmzR0TeJOO5k3/Nw==", "integrity": "sha512-ZSsVOaOIig0oVLzRTyk8lUfBfqzWxr/J3/NFMfGGRIkGQPejJYmDH3gXmSJxAojts77uzAGB/UmVrwi2DC4LYA==",
"dev": true "dev": true
}, },
"node_modules/eslint-plugin-vue": { "node_modules/eslint-plugin-vue": {
"version": "9.23.0", "version": "9.24.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.23.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz",
"integrity": "sha512-Bqd/b7hGYGrlV+wP/g77tjyFmp81lh5TMw0be9093X02SyelxRRfCI6/IsGq/J7Um0YwB9s0Ry0wlFyjPdmtUw==", "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"globals": "^13.24.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"nth-check": "^2.1.1", "nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.15", "postcss-selector-parser": "^6.0.15",
@ -5715,13 +5726,13 @@
} }
}, },
"node_modules/eslint-plugin-vue-scoped-css": { "node_modules/eslint-plugin-vue-scoped-css": {
"version": "2.7.2", "version": "2.8.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.7.2.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.8.0.tgz",
"integrity": "sha512-myJ99CJuwmAx5kq1WjgIeaUkxeU6PIEUh7age79Alm30bhN4fVTapOQLSMlvVTgxr36Y3igsZ3BCJM32LbHHig==", "integrity": "sha512-JXb3Um4+AhuDGxSX6FAGCI0p811xF7W8L7yxC8wmAEZEI/teTjlpC09noqQZHXn53RZ/TGQJ8Onaq4teYLxBbg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"eslint-compat-utils": "^0.4.0", "eslint-compat-utils": "^0.5.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"postcss-safe-parser": "^6.0.0", "postcss-safe-parser": "^6.0.0",
@ -6502,9 +6513,9 @@
} }
}, },
"node_modules/happy-dom": { "node_modules/happy-dom": {
"version": "14.2.0", "version": "14.3.7",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.2.0.tgz", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.7.tgz",
"integrity": "sha512-vTqF/9MEkRKgYy5eKq9W0uiNmkgnVAmJhRwn8x8fQBR7lc4C84859jLhgZ1lR4Gi/t70oSdgvtLpxlHjgdJrAw==", "integrity": "sha512-lUfDRGzjrVJF2pnvh13OL+qEJ9eDpcedVLm77a3aMg8gPGKXfG+xFMNk3cOWetjucU8FveJ4qcSC/EX55nJ4fQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"entities": "^4.5.0", "entities": "^4.5.0",
@ -7584,9 +7595,9 @@
} }
}, },
"node_modules/known-css-properties": { "node_modules/known-css-properties": {
"version": "0.29.0", "version": "0.30.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz",
"integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==",
"dev": true "dev": true
}, },
"node_modules/language-subtag-registry": { "node_modules/language-subtag-registry": {
@ -9346,9 +9357,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.35", "version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -9366,7 +9377,7 @@
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.0"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
@ -10676,14 +10687,17 @@
} }
}, },
"node_modules/string.prototype.trimstart": { "node_modules/string.prototype.trimstart": {
"version": "1.0.7", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
"integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.2", "call-bind": "^1.0.7",
"define-properties": "^1.2.0", "define-properties": "^1.2.1",
"es-abstract": "^1.22.1" "es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -10776,15 +10790,16 @@
"dev": true "dev": true
}, },
"node_modules/stylelint": { "node_modules/stylelint": {
"version": "16.2.1", "version": "16.3.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.2.1.tgz", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.3.0.tgz",
"integrity": "sha512-SfIMGFK+4n7XVAyv50CpVfcGYWG4v41y6xG7PqOgQSY8M/PgdK0SQbjWFblxjJZlN9jNq879mB4BCZHJRIJ1hA==", "integrity": "sha512-hqC6xNTbQ5HRGQXfIW4HwXcx09raIFz4W4XFbraeqWqYRVVY/ibYvI0dsu0ORMQY8re2bpDdCAeIa2cm+QJ4Sw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@csstools/css-parser-algorithms": "^2.5.0", "@csstools/css-parser-algorithms": "^2.6.1",
"@csstools/css-tokenizer": "^2.2.3", "@csstools/css-tokenizer": "^2.2.4",
"@csstools/media-query-list-parser": "^2.1.7", "@csstools/media-query-list-parser": "^2.1.9",
"@csstools/selector-specificity": "^3.0.1", "@csstools/selector-specificity": "^3.0.2",
"@dual-bundle/import-meta-resolve": "^4.0.0",
"balanced-match": "^2.0.0", "balanced-match": "^2.0.0",
"colord": "^2.9.3", "colord": "^2.9.3",
"cosmiconfig": "^9.0.0", "cosmiconfig": "^9.0.0",
@ -10798,19 +10813,19 @@
"globby": "^11.1.0", "globby": "^11.1.0",
"globjoin": "^0.1.4", "globjoin": "^0.1.4",
"html-tags": "^3.3.1", "html-tags": "^3.3.1",
"ignore": "^5.3.0", "ignore": "^5.3.1",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0", "is-plain-object": "^5.0.0",
"known-css-properties": "^0.29.0", "known-css-properties": "^0.30.0",
"mathml-tag-names": "^2.1.3", "mathml-tag-names": "^2.1.3",
"meow": "^13.1.0", "meow": "^13.2.0",
"micromatch": "^4.0.5", "micromatch": "^4.0.5",
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"postcss": "^8.4.33", "postcss": "^8.4.38",
"postcss-resolve-nested-selector": "^0.1.1", "postcss-resolve-nested-selector": "^0.1.1",
"postcss-safe-parser": "^7.0.0", "postcss-safe-parser": "^7.0.0",
"postcss-selector-parser": "^6.0.15", "postcss-selector-parser": "^6.0.16",
"postcss-value-parser": "^4.2.0", "postcss-value-parser": "^4.2.0",
"resolve-from": "^5.0.0", "resolve-from": "^5.0.0",
"string-width": "^4.2.3", "string-width": "^4.2.3",
@ -11418,9 +11433,9 @@
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
}, },
"node_modules/tinypool": { "node_modules/tinypool": {
"version": "0.8.2", "version": "0.8.3",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz",
"integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@ -11611,9 +11626,9 @@
} }
}, },
"node_modules/typed-array-length": { "node_modules/typed-array-length": {
"version": "1.0.5", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz",
"integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.7", "call-bind": "^1.0.7",
@ -11737,12 +11752,12 @@
} }
}, },
"node_modules/updates": { "node_modules/updates": {
"version": "15.3.1", "version": "16.0.0",
"resolved": "https://registry.npmjs.org/updates/-/updates-15.3.1.tgz", "resolved": "https://registry.npmjs.org/updates/-/updates-16.0.0.tgz",
"integrity": "sha512-DqHT1aJ6p6jVLWRiAeuVx/TQotvEwUjgrY1Mlc0a2qYk+eKEQVXugQ4M+6QoVMA3X1NFAVsb02d93pmWam4bBA==", "integrity": "sha512-Ra3QUu/rfbSCsG83zNNvoRQt0FVT3qULBSALYTlwTDX6oyz7R5GQAYwqJoIG/RDjYAXpwr3usrInoyHHTP6cag==",
"dev": true, "dev": true,
"bin": { "bin": {
"updates": "bin/updates.js" "updates": "dist/updates.js"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@ -11825,9 +11840,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.2.2", "version": "5.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz",
"integrity": "sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==", "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.20.1", "esbuild": "^0.20.1",
@ -11880,9 +11895,9 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz",
"integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"cac": "^6.7.14", "cac": "^6.7.14",
@ -11927,34 +11942,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/vite/node_modules/postcss": {
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/vite/node_modules/rollup": { "node_modules/vite/node_modules/rollup": {
"version": "4.13.0", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
@ -11988,16 +11975,16 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz",
"integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vitest/expect": "1.3.1", "@vitest/expect": "1.4.0",
"@vitest/runner": "1.3.1", "@vitest/runner": "1.4.0",
"@vitest/snapshot": "1.3.1", "@vitest/snapshot": "1.4.0",
"@vitest/spy": "1.3.1", "@vitest/spy": "1.4.0",
"@vitest/utils": "1.3.1", "@vitest/utils": "1.4.0",
"acorn-walk": "^8.3.2", "acorn-walk": "^8.3.2",
"chai": "^4.3.10", "chai": "^4.3.10",
"debug": "^4.3.4", "debug": "^4.3.4",
@ -12011,7 +11998,7 @@
"tinybench": "^2.5.1", "tinybench": "^2.5.1",
"tinypool": "^0.8.2", "tinypool": "^0.8.2",
"vite": "^5.0.0", "vite": "^5.0.0",
"vite-node": "1.3.1", "vite-node": "1.4.0",
"why-is-node-running": "^2.2.2" "why-is-node-running": "^2.2.2"
}, },
"bin": { "bin": {
@ -12026,8 +12013,8 @@
"peerDependencies": { "peerDependencies": {
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0", "@types/node": "^18.0.0 || >=20.0.0",
"@vitest/browser": "1.3.1", "@vitest/browser": "1.4.0",
"@vitest/ui": "1.3.1", "@vitest/ui": "1.4.0",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
@ -12186,25 +12173,25 @@
} }
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.90.3", "version": "5.91.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz",
"integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==",
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.3", "@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5", "@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.12.1",
"@webassemblyjs/wasm-parser": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.12.1",
"acorn": "^8.7.1", "acorn": "^8.7.1",
"acorn-import-assertions": "^1.9.0", "acorn-import-assertions": "^1.9.0",
"browserslist": "^4.21.10", "browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.15.0", "enhanced-resolve": "^5.16.0",
"es-module-lexer": "^1.2.1", "es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1", "eslint-scope": "5.1.1",
"events": "^3.2.0", "events": "^3.2.0",
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.9", "graceful-fs": "^4.2.11",
"json-parse-even-better-errors": "^2.3.1", "json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0", "loader-runner": "^4.2.0",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
@ -12212,7 +12199,7 @@
"schema-utils": "^3.2.0", "schema-utils": "^3.2.0",
"tapable": "^2.1.1", "tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"watchpack": "^2.4.0", "watchpack": "^2.4.1",
"webpack-sources": "^3.2.3" "webpack-sources": "^3.2.3"
}, },
"bin": { "bin": {

View file

@ -10,10 +10,10 @@
"@citation-js/plugin-software-formats": "0.6.1", "@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6", "@claviska/jquery-minicolors": "2.3.6",
"@github/markdown-toolbar-element": "2.2.3", "@github/markdown-toolbar-element": "2.2.3",
"@github/relative-time-element": "4.3.1", "@github/relative-time-element": "4.4.0",
"@github/text-expander-element": "2.6.1", "@github/text-expander-element": "2.6.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.8.0", "@primer/octicons": "19.9.0",
"add-asset-webpack-plugin": "2.0.1", "add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2", "ansi_up": "6.0.2",
"asciinema-player": "3.7.1", "asciinema-player": "3.7.1",
@ -39,7 +39,7 @@
"monaco-editor": "0.47.0", "monaco-editor": "0.47.0",
"monaco-editor-webpack-plugin": "7.1.0", "monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0", "pdfobject": "2.3.0",
"postcss": "8.4.35", "postcss": "8.4.38",
"postcss-loader": "8.1.1", "postcss-loader": "8.1.1",
"postcss-nesting": "12.1.0", "postcss-nesting": "12.1.0",
"pretty-ms": "9.0.0", "pretty-ms": "9.0.0",
@ -58,7 +58,7 @@
"vue-chartjs": "5.3.0", "vue-chartjs": "5.3.0",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5", "vue3-calendar-heatmap": "2.0.5",
"webpack": "5.90.3", "webpack": "5.91.0",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0" "wrap-ansi": "9.0.0"
}, },
@ -76,24 +76,24 @@
"eslint-plugin-jquery": "1.5.1", "eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.3.0", "eslint-plugin-regexp": "2.4.0",
"eslint-plugin-sonarjs": "0.24.0", "eslint-plugin-sonarjs": "0.24.0",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "51.0.1",
"eslint-plugin-vitest": "0.3.26", "eslint-plugin-vitest": "0.4.0",
"eslint-plugin-vitest-globals": "1.4.0", "eslint-plugin-vitest-globals": "1.5.0",
"eslint-plugin-vue": "9.23.0", "eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue-scoped-css": "2.7.2", "eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4", "eslint-plugin-wc": "2.0.4",
"happy-dom": "14.2.0", "happy-dom": "14.3.7",
"markdownlint-cli": "0.39.0", "markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0", "postcss-html": "1.6.0",
"stylelint": "16.2.1", "stylelint": "16.3.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.4", "stylelint-declaration-strict-value": "1.10.4",
"svgo": "3.2.0", "svgo": "3.2.0",
"updates": "15.3.1", "updates": "16.0.0",
"vite-string-plugin": "1.1.5", "vite-string-plugin": "1.1.5",
"vitest": "1.3.1" "vitest": "1.4.0"
}, },
"browserslist": [ "browserslist": [
"defaults" "defaults"

9
poetry.lock generated
View file

@ -113,18 +113,15 @@ six = ">=1.13.0"
[[package]] [[package]]
name = "json5" name = "json5"
version = "0.9.18" version = "0.9.24"
description = "A Python implementation of the JSON5 data format." description = "A Python implementation of the JSON5 data format."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "json5-0.9.18-py2.py3-none-any.whl", hash = "sha256:3f20193ff8dfdec6ab114b344e7ac5d76fac453c8bab9bdfe1460d1d528ec393"}, {file = "json5-0.9.24-py3-none-any.whl", hash = "sha256:4ca101fd5c7cb47960c055ef8f4d0e31e15a7c6c48c3b6f1473fc83b6c462a13"},
{file = "json5-0.9.18.tar.gz", hash = "sha256:ecb8ac357004e3522fb989da1bf08b146011edbd14fdffae6caad3bd68493467"}, {file = "json5-0.9.24.tar.gz", hash = "sha256:0c638399421da959a20952782800e5c1a78c14e08e1dc9738fa10d8ec14d58c8"},
] ]
[package.extras]
dev = ["hypothesis"]
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.12.1" version = "0.12.1"

View file

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"unicode"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -18,8 +19,8 @@ import (
) )
var ( var (
packageNameRegex = regexp.MustCompile(`\A[A-Za-z0-9\.\_\-\+]+\z`) packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`)
filenameRegex = packageNameRegex filenameRegex = regexp.MustCompile(`\A[-_+=:;.()\[\]{}~!@#$%^& \w]+\z`)
) )
func apiError(ctx *context.Context, status int, obj any) { func apiError(ctx *context.Context, status int, obj any) {
@ -54,20 +55,38 @@ func DownloadPackageFile(ctx *context.Context) {
helper.ServePackageFile(ctx, s, u, pf) helper.ServePackageFile(ctx, s, u, pf)
} }
func isValidPackageName(packageName string) bool {
if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) {
return false
}
return packageNameRegex.MatchString(packageName) && packageName != ".."
}
func isValidFileName(filename string) bool {
return filenameRegex.MatchString(filename) &&
strings.TrimSpace(filename) == filename &&
filename != "." && filename != ".."
}
// UploadPackage uploads the specific generic package. // UploadPackage uploads the specific generic package.
// Duplicated packages get rejected. // Duplicated packages get rejected.
func UploadPackage(ctx *context.Context) { func UploadPackage(ctx *context.Context) {
packageName := ctx.Params("packagename") packageName := ctx.Params("packagename")
filename := ctx.Params("filename") filename := ctx.Params("filename")
if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) { if !isValidPackageName(packageName) {
apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name or filename")) apiError(ctx, http.StatusBadRequest, errors.New("invalid package name"))
return
}
if !isValidFileName(filename) {
apiError(ctx, http.StatusBadRequest, errors.New("invalid filename"))
return return
} }
packageVersion := ctx.Params("packageversion") packageVersion := ctx.Params("packageversion")
if packageVersion != strings.TrimSpace(packageVersion) { if packageVersion != strings.TrimSpace(packageVersion) {
apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version")) apiError(ctx, http.StatusBadRequest, errors.New("invalid package version"))
return return
} }

View file

@ -0,0 +1,65 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package generic
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidatePackageName(t *testing.T) {
bad := []string{
"",
".",
"..",
"-",
"a?b",
"a b",
"a/b",
}
for _, name := range bad {
assert.False(t, isValidPackageName(name), "bad=%q", name)
}
good := []string{
"a",
"1",
"a-",
"a_b",
"c.d+",
}
for _, name := range good {
assert.True(t, isValidPackageName(name), "good=%q", name)
}
}
func TestValidateFileName(t *testing.T) {
bad := []string{
"",
".",
"..",
"a?b",
"a/b",
" a",
"a ",
}
for _, name := range bad {
assert.False(t, isValidFileName(name), "bad=%q", name)
}
good := []string{
"-",
"a",
"1",
"a-",
"a_b",
"a b",
"c.d+",
`-_+=:;.()[]{}~!@#$%^& aA1`,
}
for _, name := range good {
assert.True(t, isValidFileName(name), "good=%q", name)
}
}

View file

@ -150,7 +150,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
return return
} }
// OK, now the blob is known to have at most 1024 bytes we can simply read this in in one go (This saves reading it twice) // OK, now the blob is known to have at most 1024 bytes we can simply read this in one go (This saves reading it twice)
dataRc, err := blob.DataAsync() dataRc, err := blob.DataAsync()
if err != nil { if err != nil {
ctx.ServerError("DataAsync", err) ctx.ServerError("DataAsync", err)

View file

@ -787,6 +787,8 @@ func DeleteReviewRequests(ctx *context.APIContext) {
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
// "403":
// "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
opts := web.GetForm(ctx).(*api.PullReviewRequestOptions) opts := web.GetForm(ctx).(*api.PullReviewRequestOptions)
@ -855,6 +857,10 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
for _, reviewer := range reviewers { for _, reviewer := range reviewers {
comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd) comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd)
if err != nil { if err != nil {
if issues_model.IsErrReviewRequestOnClosedPR(err) {
ctx.Error(http.StatusForbidden, "", err)
return
}
ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
return return
} }
@ -1068,7 +1074,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors
ctx.Error(http.StatusForbidden, "", "Must be repo admin") ctx.Error(http.StatusForbidden, "", "Must be repo admin")
return return
} }
review, pr, isWrong := prepareSingleReview(ctx) review, _, isWrong := prepareSingleReview(ctx)
if isWrong { if isWrong {
return return
} }
@ -1078,13 +1084,12 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors
return return
} }
if pr.Issue.IsClosed {
ctx.Error(http.StatusForbidden, "", "not need to dismiss this review because this pr is closed")
return
}
_, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors) _, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors)
if err != nil { if err != nil {
if pull_service.IsErrDismissRequestOnClosedPR(err) {
ctx.Error(http.StatusForbidden, "", err)
return
}
ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err) ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err)
return return
} }

View file

@ -207,11 +207,7 @@ func ChangeProjectStatus(ctx *context.Context) {
id := ctx.ParamsInt64(":id") id := ctx.ParamsInt64(":id")
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil { if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", err)
} else {
ctx.ServerError("ChangeProjectStatusByRepoIDAndID", err)
}
return return
} }
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action"))) ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action")))
@ -221,11 +217,7 @@ func ChangeProjectStatus(ctx *context.Context) {
func DeleteProject(ctx *context.Context) { func DeleteProject(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return return
} }
if p.OwnerID != ctx.ContextUser.ID { if p.OwnerID != ctx.ContextUser.ID {
@ -254,11 +246,7 @@ func RenderEditProject(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return return
} }
if p.OwnerID != ctx.ContextUser.ID { if p.OwnerID != ctx.ContextUser.ID {
@ -303,11 +291,7 @@ func EditProjectPost(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, projectID) p, err := project_model.GetProjectByID(ctx, projectID)
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return return
} }
if p.OwnerID != ctx.ContextUser.ID { if p.OwnerID != ctx.ContextUser.ID {
@ -335,11 +319,7 @@ func EditProjectPost(ctx *context.Context) {
func ViewProject(ctx *context.Context) { func ViewProject(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return return
} }
if project.OwnerID != ctx.ContextUser.ID { if project.OwnerID != ctx.ContextUser.ID {
@ -353,10 +333,6 @@ func ViewProject(ctx *context.Context) {
return return
} }
if boards[0].ID == 0 {
boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
if err != nil { if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err) ctx.ServerError("LoadIssuesOfBoards", err)
@ -493,11 +469,7 @@ func DeleteProjectBoard(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return return
} }
@ -534,11 +506,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return return
} }
@ -566,11 +534,7 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return nil, nil return nil, nil
} }
@ -636,21 +600,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
ctx.JSONOK() ctx.JSONOK()
} }
// UnsetDefaultProjectBoard unset default board for uncategorized issues/pulls
func UnsetDefaultProjectBoard(ctx *context.Context) {
project, _ := CheckProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
}
if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
ctx.ServerError("SetDefaultBoard", err)
return
}
ctx.JSONOK()
}
// MoveIssues moves or keeps issues in a column and sorts them inside that column // MoveIssues moves or keeps issues in a column and sorts them inside that column
func MoveIssues(ctx *context.Context) { func MoveIssues(ctx *context.Context) {
if ctx.Doer == nil { if ctx.Doer == nil {
@ -662,11 +611,7 @@ func MoveIssues(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if project_model.IsErrProjectNotExist(err) { ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
ctx.NotFound("ProjectNotExist", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
return return
} }
if project.OwnerID != ctx.ContextUser.ID { if project.OwnerID != ctx.ContextUser.ID {
@ -674,29 +619,16 @@ func MoveIssues(ctx *context.Context) {
return return
} }
var board *project_model.Board board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if ctx.ParamsInt64(":boardID") == 0 {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if err != nil { if err != nil {
if project_model.IsErrProjectBoardNotExist(err) { ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectBoardNotExist, err)
ctx.NotFound("ProjectBoardNotExist", nil)
} else {
ctx.ServerError("GetProjectBoard", err)
}
return return
} }
if board.ProjectID != project.ID { if board.ProjectID != project.ID {
ctx.NotFound("BoardNotInProject", nil) ctx.NotFound("BoardNotInProject", nil)
return return
} }
}
type movedIssuesForm struct { type movedIssuesForm struct {
Issues []struct { Issues []struct {
@ -718,11 +650,7 @@ func MoveIssues(ctx *context.Context) {
} }
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs) movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, err)
ctx.NotFound("IssueNotExisting", nil)
} else {
ctx.ServerError("GetIssueByID", err)
}
return return
} }

View file

@ -260,7 +260,7 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames
if commit.User != nil { if commit.User != nil {
avatar = string(avatarUtils.Avatar(commit.User, 18)) avatar = string(avatarUtils.Avatar(commit.User, 18))
} else { } else {
avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "gt-mr-3")) avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "tw-mr-2"))
} }
br.Avatar = gotemplate.HTML(avatar) br.Avatar = gotemplate.HTML(avatar)

View file

@ -1600,7 +1600,7 @@ func ViewIssue(ctx *context.Context) {
} }
marked[issue.PosterID] = issue.ShowRole marked[issue.PosterID] = issue.ShowRole
// Render comments and and fetch participants. // Render comments and fetch participants.
participants[0] = issue.Poster participants[0] = issue.Poster
for _, comment = range issue.Comments { for _, comment = range issue.Comments {
comment.Issue = issue comment.Issue = issue
@ -2494,6 +2494,10 @@ func UpdatePullReviewRequest(ctx *context.Context) {
_, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach") _, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach")
if err != nil { if err != nil {
if issues_model.IsErrReviewRequestOnClosedPR(err) {
ctx.Status(http.StatusForbidden)
return
}
ctx.ServerError("ReviewRequest", err) ctx.ServerError("ReviewRequest", err)
return return
} }

View file

@ -70,7 +70,7 @@ func GetContentHistoryList(ctx *context.Context) {
} }
src := html.EscapeString(item.UserAvatarLink) src := html.EscapeString(item.UserAvatarLink)
class := avatars.DefaultAvatarClass + " gt-mr-3" class := avatars.DefaultAvatarClass + " tw-mr-2"
name := html.EscapeString(username) name := html.EscapeString(username)
avatarHTML := string(templates.AvatarHTML(src, 28, class, username)) avatarHTML := string(templates.AvatarHTML(src, 28, class, username))
timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale)) timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale))

View file

@ -314,10 +314,6 @@ func ViewProject(ctx *context.Context) {
return return
} }
if boards[0].ID == 0 {
boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
if err != nil { if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err) ctx.ServerError("LoadIssuesOfBoards", err)
@ -582,21 +578,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
ctx.JSONOK() ctx.JSONOK()
} }
// UnSetDefaultProjectBoard unset default board for uncategorized issues/pulls
func UnSetDefaultProjectBoard(ctx *context.Context) {
project, _ := checkProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
}
if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
ctx.ServerError("SetDefaultBoard", err)
return
}
ctx.JSONOK()
}
// MoveIssues moves or keeps issues in a column and sorts them inside that column // MoveIssues moves or keeps issues in a column and sorts them inside that column
func MoveIssues(ctx *context.Context) { func MoveIssues(ctx *context.Context) {
if ctx.Doer == nil { if ctx.Doer == nil {
@ -627,16 +608,7 @@ func MoveIssues(ctx *context.Context) {
return return
} }
var board *project_model.Board board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if ctx.ParamsInt64(":boardID") == 0 {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if err != nil { if err != nil {
if project_model.IsErrProjectBoardNotExist(err) { if project_model.IsErrProjectBoardNotExist(err) {
ctx.NotFound("ProjectBoardNotExist", nil) ctx.NotFound("ProjectBoardNotExist", nil)
@ -645,11 +617,11 @@ func MoveIssues(ctx *context.Context) {
} }
return return
} }
if board.ProjectID != project.ID { if board.ProjectID != project.ID {
ctx.NotFound("BoardNotInProject", nil) ctx.NotFound("BoardNotInProject", nil)
return return
} }
}
type movedIssuesForm struct { type movedIssuesForm struct {
Issues []struct { Issues []struct {

View file

@ -263,6 +263,10 @@ func DismissReview(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.DismissReviewForm) form := web.GetForm(ctx).(*forms.DismissReviewForm)
comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true, true) comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true, true)
if err != nil { if err != nil {
if pull_service.IsErrDismissRequestOnClosedPR(err) {
ctx.Status(http.StatusForbidden)
return
}
ctx.ServerError("pull_service.DismissReview", err) ctx.ServerError("pull_service.DismissReview", err)
return return
} }

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -44,20 +45,17 @@ func RenderFile(ctx *context.Context) {
isTextFile := st.IsText() isTextFile := st.IsText()
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{}) rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
if markupType := markup.Type(blob.Name()); markupType == "" { if markupType := markup.Type(blob.Name()); markupType == "" {
if isTextFile { if isTextFile {
_, err = io.Copy(ctx.Resp, rd) _, _ = io.Copy(ctx.Resp, rd)
if err != nil { } else {
ctx.ServerError("Copy", err) http.Error(ctx.Resp, "Unsupported file type render", http.StatusInternalServerError)
} }
return return
} }
ctx.Error(http.StatusInternalServerError, "Unsupported file type render")
return
}
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
err = markup.Render(&markup.RenderContext{ err = markup.Render(&markup.RenderContext{
Ctx: ctx, Ctx: ctx,
RelativePath: ctx.Repo.TreePath, RelativePath: ctx.Repo.TreePath,
@ -71,7 +69,8 @@ func RenderFile(ctx *context.Context) {
InStandalonePage: true, InStandalonePage: true,
}, rd, ctx.Resp) }, rd, ctx.Resp)
if err != nil { if err != nil {
ctx.ServerError("Render", err) log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
return return
} }
} }

View file

@ -938,9 +938,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
schema, _, _ := strings.Cut(app.OpenURL, ":") schema, _, _ := strings.Cut(app.OpenURL, ":")
var iconHTML template.HTML var iconHTML template.HTML
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" { if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "gt-mr-3") iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "tw-mr-2")
} else { } else {
iconHTML = svg.RenderHTML("gitea-git", 16, "gt-mr-3") // TODO: it could support user's customized icon in the future iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
} }
tmplApps = append(tmplApps, map[string]any{ tmplApps = append(tmplApps, map[string]any{
"DisplayName": app.DisplayName, "DisplayName": app.DisplayName,

View file

@ -986,7 +986,6 @@ func registerRoutes(m *web.Route) {
m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard) m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard)
m.Delete("", org.DeleteProjectBoard) m.Delete("", org.DeleteProjectBoard)
m.Post("/default", org.SetDefaultProjectBoard) m.Post("/default", org.SetDefaultProjectBoard)
m.Post("/unsetdefault", org.UnsetDefaultProjectBoard)
m.Post("/move", org.MoveIssues) m.Post("/move", org.MoveIssues)
}) })
@ -1360,7 +1359,6 @@ func registerRoutes(m *web.Route) {
m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard) m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard)
m.Delete("", repo.DeleteProjectBoard) m.Delete("", repo.DeleteProjectBoard)
m.Post("/default", repo.SetDefaultProjectBoard) m.Post("/default", repo.SetDefaultProjectBoard)
m.Post("/unsetdefault", repo.UnSetDefaultProjectBoard)
m.Post("/move", repo.MoveIssues) m.Post("/move", repo.MoveIssues)
}) })

View file

@ -59,7 +59,7 @@ func (p *AuthSourceProvider) DisplayName() string {
func (p *AuthSourceProvider) IconHTML(size int) template.HTML { func (p *AuthSourceProvider) IconHTML(size int) template.HTML {
if p.iconURL != "" { if p.iconURL != "" {
img := fmt.Sprintf(`<img class="tw-object-contain gt-mr-3" width="%d" height="%d" src="%s" alt="%s">`, img := fmt.Sprintf(`<img class="tw-object-contain tw-mr-2" width="%d" height="%d" src="%s" alt="%s">`,
size, size,
size, size,
html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()), html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),

View file

@ -35,10 +35,10 @@ func (b *BaseProvider) IconHTML(size int) template.HTML {
case "github": case "github":
svgName = "octicon-mark-github" svgName = "octicon-mark-github"
} }
svgHTML := svg.RenderHTML(svgName, size, "gt-mr-3") svgHTML := svg.RenderHTML(svgName, size, "tw-mr-2")
if svgHTML == "" { if svgHTML == "" {
log.Error("No SVG icon for oauth2 provider %q", b.name) log.Error("No SVG icon for oauth2 provider %q", b.name)
svgHTML = svg.RenderHTML("gitea-openid", size, "gt-mr-3") svgHTML = svg.RenderHTML("gitea-openid", size, "tw-mr-2")
} }
return svgHTML return svgHTML
} }

View file

@ -29,7 +29,7 @@ func (o *OpenIDProvider) DisplayName() string {
// IconHTML returns icon HTML for this provider // IconHTML returns icon HTML for this provider
func (o *OpenIDProvider) IconHTML(size int) template.HTML { func (o *OpenIDProvider) IconHTML(size int) template.HTML {
return svg.RenderHTML("gitea-openid", size, "gt-mr-3") return svg.RenderHTML("gitea-openid", size, "tw-mr-2")
} }
// CreateGothProvider creates a GothProvider from this Provider // CreateGothProvider creates a GothProvider from this Provider

View file

@ -216,6 +216,12 @@ func fixBrokenRepoUnit16961(repoUnit *repo_model.RepoUnit, bs []byte) (fixed boo
return false, nil return false, nil
} }
var cfg any
err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
if err == nil {
return false, nil
}
switch repoUnit.Type { switch repoUnit.Type {
case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects: case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects:
cfg := &repo_model.UnitConfig{} cfg := &repo_model.UnitConfig{}

View file

@ -162,7 +162,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
if opts.RepoArchives || opts.All { if opts.RepoArchives || opts.All {
if err := commonCheckStorage(ctx, logger, autofix, if err := commonCheckStorage(ctx, logger, autofix,
&commonStorageCheckOptions{ &commonStorageCheckOptions{
storer: storage.RepoAvatars, storer: storage.RepoArchives,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
exists, err := repo.ExistsRepoArchiverWithStoragePath(ctx, path) exists, err := repo.ExistsRepoArchiverWithStoragePath(ctx, path)
if err == nil || errors.Is(err, util.ErrInvalidArgument) { if err == nil || errors.Is(err, util.ErrInvalidArgument) {

View file

@ -257,14 +257,13 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
} }
log.Warn("migrating milestones is not supported, ignored") log.Warn("migrating milestones is not supported, ignored")
} }
msBatchSize := uploader.MaxBatchInsertSize("milestone") msBatchSize := uploader.MaxBatchInsertSize("milestone")
for len(milestones) > 0 { for len(milestones) > 0 {
if len(milestones) < msBatchSize { if len(milestones) < msBatchSize {
msBatchSize = len(milestones) msBatchSize = len(milestones)
} }
if err := uploader.CreateMilestones(milestones...); err != nil { if err := uploader.CreateMilestones(milestones[:msBatchSize]...); err != nil {
return err return err
} }
milestones = milestones[msBatchSize:] milestones = milestones[msBatchSize:]

View file

@ -20,11 +20,29 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
) )
var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`) var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`)
// ErrDismissRequestOnClosedPR represents an error when an user tries to dismiss a review associated to a closed or merged PR.
type ErrDismissRequestOnClosedPR struct{}
// IsErrDismissRequestOnClosedPR checks if an error is an ErrDismissRequestOnClosedPR.
func IsErrDismissRequestOnClosedPR(err error) bool {
_, ok := err.(ErrDismissRequestOnClosedPR)
return ok
}
func (err ErrDismissRequestOnClosedPR) Error() string {
return "can't dismiss a review associated to a closed or merged PR"
}
func (err ErrDismissRequestOnClosedPR) Unwrap() error {
return util.ErrPermissionDenied
}
// checkInvalidation checks if the line of code comment got changed by another commit. // checkInvalidation checks if the line of code comment got changed by another commit.
// If the line got changed the comment is going to be invalidated. // If the line got changed the comment is going to be invalidated.
func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error { func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
@ -382,6 +400,21 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string,
return nil, fmt.Errorf("reviews's repository is not the same as the one we expect") return nil, fmt.Errorf("reviews's repository is not the same as the one we expect")
} }
issue := review.Issue
if issue.IsClosed {
return nil, ErrDismissRequestOnClosedPR{}
}
if issue.IsPull {
if err := issue.LoadPullRequest(ctx); err != nil {
return nil, err
}
if issue.PullRequest.HasMerged {
return nil, ErrDismissRequestOnClosedPR{}
}
}
if err := issues_model.DismissReview(ctx, review, isDismiss); err != nil { if err := issues_model.DismissReview(ctx, review, isDismiss); err != nil {
return nil, err return nil, err
} }

View file

@ -0,0 +1,48 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package pull_test
import (
"testing"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
pull_service "code.gitea.io/gitea/services/pull"
"github.com/stretchr/testify/assert"
)
func TestDismissReview(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{})
assert.NoError(t, pull.LoadIssue(db.DefaultContext))
issue := pull.Issue
assert.NoError(t, issue.LoadRepo(db.DefaultContext))
reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Issue: issue,
Reviewer: reviewer,
Type: issues_model.ReviewTypeReject,
})
assert.NoError(t, err)
issue.IsClosed = true
pull.HasMerged = false
assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "is_closed"))
assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
_, err = pull_service.DismissReview(db.DefaultContext, review.ID, issue.RepoID, "", &user_model.User{}, false, false)
assert.Error(t, err)
assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err))
pull.HasMerged = true
pull.Issue.IsClosed = false
assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "is_closed"))
assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
_, err = pull_service.DismissReview(db.DefaultContext, review.ID, issue.RepoID, "", &user_model.User{}, false, false)
assert.Error(t, err)
assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err))
}

View file

@ -113,7 +113,6 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) {
err := hook.SetHeaderAuthorization("Bearer s3cr3t-t0ken") err := hook.SetHeaderAuthorization("Bearer s3cr3t-t0ken")
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
db.GetEngine(db.DefaultContext).NoAutoTime().DB().Logger.ShowSQL(true)
hookTask := &webhook_model.HookTask{ hookTask := &webhook_model.HookTask{
HookID: hook.ID, HookID: hook.ID,

View file

@ -41,6 +41,8 @@ export default {
// classes that don't work without CSS variables from "@tailwind base" which we don't use // classes that don't work without CSS variables from "@tailwind base" which we don't use
'transform', 'shadow', 'ring', 'blur', 'grayscale', 'invert', '!invert', 'filter', '!filter', 'transform', 'shadow', 'ring', 'blur', 'grayscale', 'invert', '!invert', 'filter', '!filter',
'backdrop-filter', 'backdrop-filter',
// we use double-class tw-hidden defined in web_src/css/helpers.css for increased specificity
'hidden',
// unneeded classes // unneeded classes
'[-a-zA-Z:0-9_.]', '[-a-zA-Z:0-9_.]',
], ],
@ -66,6 +68,10 @@ export default {
'3xl': '24px', '3xl': '24px',
'full': 'var(--border-radius-circle)', // 50% 'full': 'var(--border-radius-circle)', // 50%
}, },
fontFamily: {
sans: 'var(--fonts-regular)',
mono: 'var(--fonts-monospace)',
},
fontWeight: { fontWeight: {
light: 'var(--font-weight-light)', light: 'var(--font-weight-light)',
normal: 'var(--font-weight-normal)', normal: 'var(--font-weight-normal)',

View file

@ -42,7 +42,7 @@
<label for="port">{{ctx.Locale.Tr "admin.auths.port"}}</label> <label for="port">{{ctx.Locale.Tr "admin.auths.port"}}</label>
<input id="port" name="port" value="{{$cfg.Port}}" placeholder="636" required> <input id="port" name="port" value="{{$cfg.Port}}" placeholder="636" required>
</div> </div>
<div class="has-tls inline field {{if not .HasTLS}}gt-hidden{{end}}"> <div class="has-tls inline field {{if not .HasTLS}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label> <label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}> <input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}>
@ -113,7 +113,7 @@
<input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if $cfg.GroupsEnabled}}checked{{end}}> <input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if $cfg.GroupsEnabled}}checked{{end}}>
</div> </div>
</div> </div>
<div id="ldap-group-options" class="ui segment secondary {{if not $cfg.GroupsEnabled}}gt-hidden{{end}}"> <div id="ldap-group-options" class="ui segment secondary {{if not $cfg.GroupsEnabled}}tw-hidden{{end}}">
<div class="field"> <div class="field">
<label>{{ctx.Locale.Tr "admin.auths.group_search_base"}}</label> <label>{{ctx.Locale.Tr "admin.auths.group_search_base"}}</label>
<input name="group_dn" value="{{$cfg.GroupDN}}" placeholder="ou=group,dc=mydomain,dc=com"> <input name="group_dn" value="{{$cfg.GroupDN}}" placeholder="ou=group,dc=mydomain,dc=com">
@ -148,7 +148,7 @@
<input id="use_paged_search" name="use_paged_search" type="checkbox" {{if $cfg.UsePagedSearch}}checked{{end}}> <input id="use_paged_search" name="use_paged_search" type="checkbox" {{if $cfg.UsePagedSearch}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field required search-page-size{{if not $cfg.UsePagedSearch}} gt-hidden{{end}}"> <div class="field required search-page-size{{if not $cfg.UsePagedSearch}} tw-hidden{{end}}">
<label for="search_page_size">{{ctx.Locale.Tr "admin.auths.search_page_size"}}</label> <label for="search_page_size">{{ctx.Locale.Tr "admin.auths.search_page_size"}}</label>
<input id="search_page_size" name="search_page_size" value="{{if $cfg.UsePagedSearch}}{{$cfg.SearchPageSize}}{{end}}"> <input id="search_page_size" name="search_page_size" value="{{if $cfg.UsePagedSearch}}{{$cfg.SearchPageSize}}{{end}}">
</div> </div>
@ -205,7 +205,7 @@
</div> </div>
<p class="help">{{ctx.Locale.Tr "admin.auths.force_smtps_helper"}}</p> <p class="help">{{ctx.Locale.Tr "admin.auths.force_smtps_helper"}}</p>
</div> </div>
<div class="has-tls inline field {{if not .HasTLS}}gt-hidden{{end}}"> <div class="has-tls inline field {{if not .HasTLS}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label> <label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if $cfg.SkipVerify}}checked{{end}}> <input name="skip_verify" type="checkbox" {{if $cfg.SkipVerify}}checked{{end}}>

View file

@ -33,13 +33,13 @@
{{template "admin/auth/source/smtp" .}} {{template "admin/auth/source/smtp" .}}
<!-- PAM --> <!-- PAM -->
<div class="pam required field {{if not (eq .type 4)}}gt-hidden{{end}}"> <div class="pam required field {{if not (eq .type 4)}}tw-hidden{{end}}">
<label for="pam_service_name">{{ctx.Locale.Tr "admin.auths.pam_service_name"}}</label> <label for="pam_service_name">{{ctx.Locale.Tr "admin.auths.pam_service_name"}}</label>
<input id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}"> <input id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}">
<label for="pam_email_domain">{{ctx.Locale.Tr "admin.auths.pam_email_domain"}}</label> <label for="pam_email_domain">{{ctx.Locale.Tr "admin.auths.pam_email_domain"}}</label>
<input id="pam_email_domain" name="pam_email_domain" value="{{.pam_email_domain}}"> <input id="pam_email_domain" name="pam_email_domain" value="{{.pam_email_domain}}">
</div> </div>
<div class="pam optional field {{if not (eq .type 4)}}gt-hidden{{end}}"> <div class="pam optional field {{if not (eq .type 4)}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}>
@ -59,7 +59,7 @@
<input name="attributes_in_bind" type="checkbox" {{if .attributes_in_bind}}checked{{end}}> <input name="attributes_in_bind" type="checkbox" {{if .attributes_in_bind}}checked{{end}}>
</div> </div>
</div> </div>
<div class="ldap inline field {{if not (eq .type 2)}}gt-hidden{{end}}"> <div class="ldap inline field {{if not (eq .type 2)}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.syncenabled"}}</strong></label> <label><strong>{{ctx.Locale.Tr "admin.auths.syncenabled"}}</strong></label>
<input name="is_sync_enabled" type="checkbox" {{if .is_sync_enabled}}checked{{end}}> <input name="is_sync_enabled" type="checkbox" {{if .is_sync_enabled}}checked{{end}}>

View file

@ -1,4 +1,4 @@
<div class="ldap dldap field {{if not (or (eq .type 2) (eq .type 5))}}gt-hidden{{end}}"> <div class="ldap dldap field {{if not (or (eq .type 2) (eq .type 5))}}tw-hidden{{end}}">
<div class="inline required field {{if .Err_SecurityProtocol}}error{{end}}"> <div class="inline required field {{if .Err_SecurityProtocol}}error{{end}}">
<label>{{ctx.Locale.Tr "admin.auths.security_protocol"}}</label> <label>{{ctx.Locale.Tr "admin.auths.security_protocol"}}</label>
<div class="ui selection security-protocol dropdown"> <div class="ui selection security-protocol dropdown">
@ -20,17 +20,17 @@
<label for="port">{{ctx.Locale.Tr "admin.auths.port"}}</label> <label for="port">{{ctx.Locale.Tr "admin.auths.port"}}</label>
<input id="port" name="port" value="{{.port}}" placeholder="636"> <input id="port" name="port" value="{{.port}}" placeholder="636">
</div> </div>
<div class="has-tls inline field {{if not .HasTLS}}gt-hidden{{end}}"> <div class="has-tls inline field {{if not .HasTLS}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label> <label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}> <input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}>
</div> </div>
</div> </div>
<div class="ldap field {{if not (eq .type 2)}}gt-hidden{{end}}"> <div class="ldap field {{if not (eq .type 2)}}tw-hidden{{end}}">
<label for="bind_dn">{{ctx.Locale.Tr "admin.auths.bind_dn"}}</label> <label for="bind_dn">{{ctx.Locale.Tr "admin.auths.bind_dn"}}</label>
<input id="bind_dn" name="bind_dn" value="{{.bind_dn}}" placeholder="cn=Search,dc=mydomain,dc=com"> <input id="bind_dn" name="bind_dn" value="{{.bind_dn}}" placeholder="cn=Search,dc=mydomain,dc=com">
</div> </div>
<div class="ldap field {{if not (eq .type 2)}}gt-hidden{{end}}"> <div class="ldap field {{if not (eq .type 2)}}tw-hidden{{end}}">
<label for="bind_password">{{ctx.Locale.Tr "admin.auths.bind_password"}}</label> <label for="bind_password">{{ctx.Locale.Tr "admin.auths.bind_password"}}</label>
<input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}"> <input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}">
</div> </div>
@ -38,7 +38,7 @@
<label for="user_base">{{ctx.Locale.Tr "admin.auths.user_base"}}</label> <label for="user_base">{{ctx.Locale.Tr "admin.auths.user_base"}}</label>
<input id="user_base" name="user_base" value="{{.user_base}}" placeholder="ou=Users,dc=mydomain,dc=com"> <input id="user_base" name="user_base" value="{{.user_base}}" placeholder="ou=Users,dc=mydomain,dc=com">
</div> </div>
<div class="dldap required field {{if not (eq .type 5)}}gt-hidden{{end}}"> <div class="dldap required field {{if not (eq .type 5)}}tw-hidden{{end}}">
<label for="user_dn">{{ctx.Locale.Tr "admin.auths.user_dn"}}</label> <label for="user_dn">{{ctx.Locale.Tr "admin.auths.user_dn"}}</label>
<input id="user_dn" name="user_dn" value="{{.user_dn}}" placeholder="uid=%s,ou=Users,dc=mydomain,dc=com"> <input id="user_dn" name="user_dn" value="{{.user_dn}}" placeholder="uid=%s,ou=Users,dc=mydomain,dc=com">
</div> </div>
@ -115,13 +115,13 @@
</div> </div>
<!-- ldap group end --> <!-- ldap group end -->
<div class="ldap inline field {{if not (eq .type 2)}}gt-hidden{{end}}"> <div class="ldap inline field {{if not (eq .type 2)}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="use_paged_search"><strong>{{ctx.Locale.Tr "admin.auths.use_paged_search"}}</strong></label> <label for="use_paged_search"><strong>{{ctx.Locale.Tr "admin.auths.use_paged_search"}}</strong></label>
<input id="use_paged_search" name="use_paged_search" class="use-paged-search" type="checkbox" {{if .use_paged_search}}checked{{end}}> <input id="use_paged_search" name="use_paged_search" class="use-paged-search" type="checkbox" {{if .use_paged_search}}checked{{end}}>
</div> </div>
</div> </div>
<div class="ldap field search-page-size required {{if or (not (eq .type 2)) (not .use_paged_search)}}gt-hidden{{end}}"> <div class="ldap field search-page-size required {{if or (not (eq .type 2)) (not .use_paged_search)}}tw-hidden{{end}}">
<label for="search_page_size">{{ctx.Locale.Tr "admin.auths.search_page_size"}}</label> <label for="search_page_size">{{ctx.Locale.Tr "admin.auths.search_page_size"}}</label>
<input id="search_page_size" name="search_page_size" value="{{.search_page_size}}"> <input id="search_page_size" name="search_page_size" value="{{.search_page_size}}">
</div> </div>

View file

@ -1,4 +1,4 @@
<div class="oauth2 field {{if not (eq .type 6)}}gt-hidden{{end}}"> <div class="oauth2 field {{if not (eq .type 6)}}tw-hidden{{end}}">
<div class="inline required field"> <div class="inline required field">
<label>{{ctx.Locale.Tr "admin.auths.oauth2_provider"}}</label> <label>{{ctx.Locale.Tr "admin.auths.oauth2_provider"}}</label>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">

View file

@ -1,4 +1,4 @@
<div class="smtp field {{if not (eq .type 3)}}gt-hidden{{end}}"> <div class="smtp field {{if not (eq .type 3)}}tw-hidden{{end}}">
<div class="inline required field"> <div class="inline required field">
<label>{{ctx.Locale.Tr "admin.auths.smtp_auth"}}</label> <label>{{ctx.Locale.Tr "admin.auths.smtp_auth"}}</label>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">

View file

@ -1,4 +1,4 @@
<div class="sspi field {{if not (eq .type 7)}}gt-hidden{{end}}"> <div class="sspi field {{if not (eq .type 7)}}tw-hidden{{end}}">
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="sspi_auto_create_users"><strong>{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users"}}</strong></label> <label for="sspi_auto_create_users"><strong>{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users"}}</strong></label>

View file

@ -231,7 +231,7 @@
<dt>{{ctx.Locale.Tr "admin.config.mailer_user"}}</dt> <dt>{{ctx.Locale.Tr "admin.config.mailer_user"}}</dt>
<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd> <dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd>
<div class="divider"></div> <div class="divider"></div>
<dt class="gt-py-2">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt> <dt class="tw-py-1">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt>
<dd> <dd>
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post"> <form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
@ -334,7 +334,7 @@
{{range $loggerName, $loggerDetail := .Loggers}} {{range $loggerName, $loggerDetail := .Loggers}}
<dt>{{ctx.Locale.Tr "admin.config.logger_name_fmt" $loggerName}}</dt> <dt>{{ctx.Locale.Tr "admin.config.logger_name_fmt" $loggerName}}</dt>
{{if $loggerDetail.IsEnabled}} {{if $loggerDetail.IsEnabled}}
<dd><pre class="gt-m-0">{{$loggerDetail.EventWriters | JsonUtils.EncodeToString | JsonUtils.PrettyIndent}}</pre></dd> <dd><pre class="tw-m-0">{{$loggerDetail.EventWriters | JsonUtils.EncodeToString | JsonUtils.PrettyIndent}}</pre></dd>
{{else}} {{else}}
<dd>{{ctx.Locale.Tr "admin.config.disabled_logger"}}</dd> <dd>{{ctx.Locale.Tr "admin.config.disabled_logger"}}</dd>
{{end}} {{end}}

View file

@ -28,7 +28,7 @@
<div class="field"> <div class="field">
<details> <details>
<summary>{{ctx.Locale.Tr "admin.config.open_with_editor_app_help"}}</summary> <summary>{{ctx.Locale.Tr "admin.config.open_with_editor_app_help"}}</summary>
<pre class="gt-px-4">{{.DefaultOpenWithEditorAppsString}}</pre> <pre class="tw-px-4">{{.DefaultOpenWithEditorAppsString}}</pre>
</details> </details>
</div> </div>
<div class="field"> <div class="field">

View file

@ -5,7 +5,7 @@
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin"> <form method="post" action="{{AppSubUrl}}/admin">
<table class="ui very basic striped table unstackable gt-mb-0"> <table class="ui very basic striped table unstackable tw-mb-0">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>

View file

@ -11,7 +11,7 @@
<div class="ui attached table segment"> <div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin"> <form method="post" action="{{AppSubUrl}}/admin">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<table class="ui very basic table gt-mt-0 gt-px-4"> <table class="ui very basic table tw-mt-0 tw-px-4">
<tbody> <tbody>
<tr> <tr>
<td>{{ctx.Locale.Tr "admin.dashboard.delete_inactive_accounts"}}</td> <td>{{ctx.Locale.Tr "admin.dashboard.delete_inactive_accounts"}}</td>

View file

@ -4,12 +4,12 @@
{{ctx.Locale.Tr "admin.emails.email_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}}) {{ctx.Locale.Tr "admin.emails.email_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="ui secondary filter menu tw-items-center gt-mx-0"> <div class="ui secondary filter menu tw-items-center tw-mx-0">
<form class="ui form ignore-dirty tw-flex-1"> <form class="ui form ignore-dirty tw-flex-1">
{{template "shared/search/combo" dict "Value" .Keyword}} {{template "shared/search/combo" dict "Value" .Keyword}}
</form> </form>
<!-- Sort --> <!-- Sort -->
<div class="ui dropdown type jump item gt-mr-0"> <div class="ui dropdown type jump item tw-mr-0">
<span class="text"> <span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}} {{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span> </span>

View file

@ -7,12 +7,12 @@
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="ui secondary filter menu tw-items-center gt-mx-0"> <div class="ui secondary filter menu tw-items-center tw-mx-0">
<form class="ui form ignore-dirty tw-flex-1"> <form class="ui form ignore-dirty tw-flex-1">
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.org_kind")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.org_kind")}}
</form> </form>
<!-- Sort --> <!-- Sort -->
<div class="ui dropdown type jump item gt-mr-0"> <div class="ui dropdown type jump item tw-mr-0">
<span class="text"> <span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}} {{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span> </span>

View file

@ -30,7 +30,7 @@
- -
{{else}} {{else}}
{{$sum}} {{$sum}}
<form action="{{$.Link}}/remove-all-items" method="post" class="tw-inline-block gt-ml-4"> <form action="{{$.Link}}/remove-all-items" method="post" class="tw-inline-block tw-ml-4">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button class="ui tiny basic red button">{{ctx.Locale.Tr "admin.monitor.queue.settings.remove_all_items"}}</button> <button class="ui tiny basic red button">{{ctx.Locale.Tr "admin.monitor.queue.settings.remove_all_items"}}</button>
</form> </form>

View file

@ -23,7 +23,7 @@
<div class="item tw-flex tw-items-center"> <div class="item tw-flex tw-items-center">
<span class="tw-flex-1"> {{svg "octicon-file-directory-fill"}} {{$dir}}</span> <span class="tw-flex-1"> {{svg "octicon-file-directory-fill"}} {{$dir}}</span>
<div> <div>
<button class="ui button primary show-modal gt-p-3" data-modal="#adopt-unadopted-modal-{{$dirI}}">{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</button> <button class="ui button primary show-modal tw-p-2" data-modal="#adopt-unadopted-modal-{{$dirI}}">{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</button>
<div class="ui g-modal-confirm modal" id="adopt-unadopted-modal-{{$dirI}}"> <div class="ui g-modal-confirm modal" id="adopt-unadopted-modal-{{$dirI}}">
<div class="header"> <div class="header">
<span class="label">{{ctx.Locale.Tr "repo.adopt_preexisting"}}</span> <span class="label">{{ctx.Locale.Tr "repo.adopt_preexisting"}}</span>
@ -40,7 +40,7 @@
{{template "base/modal_actions_confirm"}} {{template "base/modal_actions_confirm"}}
</form> </form>
</div> </div>
<button class="ui button red show-modal gt-p-3" data-modal="#delete-unadopted-modal-{{$dirI}}">{{svg "octicon-x"}} {{ctx.Locale.Tr "repo.delete_preexisting_label"}}</button> <button class="ui button red show-modal tw-p-2" data-modal="#delete-unadopted-modal-{{$dirI}}">{{svg "octicon-x"}} {{ctx.Locale.Tr "repo.delete_preexisting_label"}}</button>
<div class="ui g-modal-confirm modal" id="delete-unadopted-modal-{{$dirI}}"> <div class="ui g-modal-confirm modal" id="delete-unadopted-modal-{{$dirI}}">
<div class="header"> <div class="header">
<span class="label">{{ctx.Locale.Tr "repo.delete_preexisting"}}</span> <span class="label">{{ctx.Locale.Tr "repo.delete_preexisting"}}</span>

View file

@ -7,9 +7,9 @@
<div class="ui attached segment"> <div class="ui attached segment">
{{if .DatabaseCheckHasProblems}} {{if .DatabaseCheckHasProblems}}
{{if .DatabaseType.IsMySQL}} {{if .DatabaseType.IsMySQL}}
<div class="gt-p-3">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div> <div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
{{else if .DatabaseType.IsMSSQL}} {{else if .DatabaseType.IsMSSQL}}
<div class="gt-p-3">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div> <div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div>
{{end}} {{end}}
{{if .DatabaseCheckCollationMismatch}} {{if .DatabaseCheckCollationMismatch}}
<div class="ui red message">{{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}</div> <div class="ui red message">{{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}</div>
@ -28,7 +28,7 @@
</div> </div>
{{end}} {{end}}
{{else}} {{else}}
<div class="gt-p-3">{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}</div> <div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}</div>
{{end}} {{end}}
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<div class="item"> <div class="item">
<div class="tw-flex tw-items-center"> <div class="tw-flex tw-items-center">
<div class="icon gt-ml-3 gt-mr-3"> <div class="icon tw-ml-2 tw-mr-2">
{{if eq .Process.Type "request"}} {{if eq .Process.Type "request"}}
{{svg "octicon-globe" 16}} {{svg "octicon-globe" 16}}
{{else if eq .Process.Type "system"}} {{else if eq .Process.Type "system"}}
@ -22,14 +22,14 @@
</div> </div>
</div> </div>
{{if .Process.Stacks}} {{if .Process.Stacks}}
<div class="divided list gt-ml-3"> <div class="divided list tw-ml-2">
{{range .Process.Stacks}} {{range .Process.Stacks}}
<div class="item"> <div class="item">
<details> <details>
<summary> <summary>
<div class="flex-text-inline"> <div class="flex-text-inline">
<div class="header gt-ml-3"> <div class="header tw-ml-2">
<span class="icon gt-mr-3">{{svg "octicon-code" 16}}</span>{{.Description}}{{if gt .Count 1}} * {{.Count}}{{end}} <span class="icon tw-mr-2">{{svg "octicon-code" 16}}</span>{{.Description}}{{if gt .Count 1}} * {{.Count}}{{end}}
</div> </div>
<div class="description"> <div class="description">
{{range .Labels}} {{range .Labels}}
@ -41,7 +41,7 @@
<div class="list"> <div class="list">
{{range .Entry}} {{range .Entry}}
<div class="item tw-flex tw-items-center"> <div class="item tw-flex tw-items-center">
<span class="icon gt-mr-4">{{svg "octicon-dot-fill" 16}}</span> <span class="icon tw-mr-4">{{svg "octicon-dot-fill" 16}}</span>
<div class="content tw-flex-1"> <div class="content tw-flex-1">
<div class="header"><code>{{.Function}}</code></div> <div class="header"><code>{{.Function}}</code></div>
<div class="description"><code>{{.File}}:{{.Line}}</code></div> <div class="description"><code>{{.File}}:{{.Line}}</code></div>

View file

@ -53,7 +53,7 @@
</div> </div>
</div> </div>
<div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .User.LoginSource 0}}gt-hidden{{end}}"> <div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .User.LoginSource 0}}tw-hidden{{end}}">
<label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label> <label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label>
<input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus> <input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus>
</div> </div>
@ -65,7 +65,7 @@
<label for="email">{{ctx.Locale.Tr "email"}}</label> <label for="email">{{ctx.Locale.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required> <input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required>
</div> </div>
<div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}gt-hidden{{end}}"> <div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}tw-hidden{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label> <label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password"> <input id="password" name="password" type="password" autocomplete="new-password">
<p class="help">{{ctx.Locale.Tr "admin.users.password_helper"}}</p> <p class="help">{{ctx.Locale.Tr "admin.users.password_helper"}}</p>
@ -128,13 +128,13 @@
<input name="restricted" type="checkbox" {{if .User.IsRestricted}}checked{{end}}> <input name="restricted" type="checkbox" {{if .User.IsRestricted}}checked{{end}}>
</div> </div>
</div> </div>
<div class="inline field {{if DisableGitHooks}}gt-hidden{{end}}"> <div class="inline field {{if DisableGitHooks}}tw-hidden{{end}}">
<div class="ui checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.users.allow_git_hook_tooltip"}}"> <div class="ui checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.users.allow_git_hook_tooltip"}}">
<label><strong>{{ctx.Locale.Tr "admin.users.allow_git_hook"}}</strong></label> <label><strong>{{ctx.Locale.Tr "admin.users.allow_git_hook"}}</strong></label>
<input name="allow_git_hook" type="checkbox" {{if .User.CanEditGitHook}}checked{{end}} {{if DisableGitHooks}}disabled{{end}}> <input name="allow_git_hook" type="checkbox" {{if .User.CanEditGitHook}}checked{{end}} {{if DisableGitHooks}}disabled{{end}}>
</div> </div>
</div> </div>
<div class="inline field {{if or (DisableImportLocal) (.DisableMigrations)}}gt-hidden{{end}}"> <div class="inline field {{if or (DisableImportLocal) (.DisableMigrations)}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.users.allow_import_local"}}</strong></label> <label><strong>{{ctx.Locale.Tr "admin.users.allow_import_local"}}</strong></label>
<input name="allow_import_local" type="checkbox" {{if .User.CanImportLocal}}checked{{end}} {{if DisableImportLocal}}disabled{{end}}> <input name="allow_import_local" type="checkbox" {{if .User.CanImportLocal}}checked{{end}} {{if DisableImportLocal}}disabled{{end}}>
@ -181,7 +181,7 @@
<label>{{ctx.Locale.Tr "settings.lookup_avatar_by_mail"}}</label> <label>{{ctx.Locale.Tr "settings.lookup_avatar_by_mail"}}</label>
</div> </div>
</div> </div>
<div class="field gt-pl-4 {{if .Err_Gravatar}}error{{end}}"> <div class="field tw-pl-4 {{if .Err_Gravatar}}error{{end}}">
<label for="gravatar">Avatar {{ctx.Locale.Tr "email"}}</label> <label for="gravatar">Avatar {{ctx.Locale.Tr "email"}}</label>
<input id="gravatar" name="gravatar" value="{{.User.AvatarEmail}}"> <input id="gravatar" name="gravatar" value="{{.User.AvatarEmail}}">
</div> </div>
@ -194,7 +194,7 @@
</div> </div>
</div> </div>
<div class="inline field gt-pl-4"> <div class="inline field tw-pl-4">
<label for="avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label> <label for="avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label>
<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> <input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
</div> </div>

View file

@ -47,7 +47,7 @@
</div> </div>
</div> </div>
<div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .login_type "0-0"}}gt-hidden{{end}}"> <div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .login_type "0-0"}}tw-hidden{{end}}">
<label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label> <label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label>
<input id="login_name" name="login_name" value="{{.login_name}}"> <input id="login_name" name="login_name" value="{{.login_name}}">
</div> </div>
@ -59,12 +59,12 @@
<label for="email">{{ctx.Locale.Tr "email"}}</label> <label for="email">{{ctx.Locale.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.email}}" required> <input id="email" name="email" type="email" value="{{.email}}" required>
</div> </div>
<div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}gt-hidden{{end}}"> <div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}tw-hidden{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label> <label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}> <input id="password" name="password" type="password" autocomplete="new-password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}>
</div> </div>
<div class="inline field local {{if ne .login_type "0-0"}}gt-hidden{{end}}"> <div class="inline field local {{if ne .login_type "0-0"}}tw-hidden{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "auth.allow_password_change"}}</strong></label> <label><strong>{{ctx.Locale.Tr "auth.allow_password_change"}}</strong></label>
<input name="must_change_password" type="checkbox" checked> <input name="must_change_password" type="checkbox" checked>

View file

@ -13,14 +13,14 @@
<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column --> <!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
<div class="ui secondary menu item navbar-mobile-right only-mobile"> <div class="ui secondary menu item navbar-mobile-right only-mobile">
{{if .IsSigned}} {{if .IsSigned}}
<a id="mobile-notifications-icon" class="item tw-w-auto gt-p-3" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}"> <a id="mobile-notifications-icon" class="item tw-w-auto tw-p-2" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
<div class="tw-relative"> <div class="tw-relative">
{{svg "octicon-bell"}} {{svg "octicon-bell"}}
<span class="notification_count{{if not $notificationUnreadCount}} gt-hidden{{end}}">{{$notificationUnreadCount}}</span> <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
</div> </div>
</a> </a>
{{end}} {{end}}
<button class="item tw-w-auto ui icon mini button gt-p-3 gt-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "toggle_menu"}}">{{svg "octicon-three-bars"}}</button> <button class="item tw-w-auto ui icon mini button tw-p-2 tw-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "toggle_menu"}}">{{svg "octicon-three-bars"}}</button>
</div> </div>
<!-- navbar links non-mobile --> <!-- navbar links non-mobile -->
@ -57,8 +57,8 @@
{{if and .IsSigned .MustChangePassword}} {{if and .IsSigned .MustChangePassword}}
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}"> <div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text tw-flex tw-items-center"> <span class="text tw-flex tw-items-center">
{{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}} {{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
<span class="only-mobile gt-ml-3">{{.SignedUser.Name}}</span> <span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span> <span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span> </span>
<div class="menu user-menu"> <div class="menu user-menu">
@ -75,19 +75,19 @@
</div><!-- end dropdown avatar menu --> </div><!-- end dropdown avatar menu -->
{{else if .IsSigned}} {{else if .IsSigned}}
{{if EnableTimetracking}} {{if EnableTimetracking}}
<a class="active-stopwatch-trigger item gt-mx-0{{if not .ActiveStopwatch}} gt-hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}"> <a class="active-stopwatch-trigger item tw-mx-0{{if not .ActiveStopwatch}} tw-hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}">
<div class="tw-relative"> <div class="tw-relative">
{{svg "octicon-stopwatch"}} {{svg "octicon-stopwatch"}}
<span class="header-stopwatch-dot"></span> <span class="header-stopwatch-dot"></span>
</div> </div>
<span class="only-mobile gt-ml-3">{{ctx.Locale.Tr "active_stopwatch"}}</span> <span class="only-mobile tw-ml-2">{{ctx.Locale.Tr "active_stopwatch"}}</span>
</a> </a>
<div class="active-stopwatch-popup item tippy-target gt-p-3"> <div class="active-stopwatch-popup item tippy-target tw-p-2">
<div class="tw-flex tw-items-center"> <div class="tw-flex tw-items-center">
<a class="stopwatch-link tw-flex tw-items-center" href="{{.ActiveStopwatch.IssueLink}}"> <a class="stopwatch-link tw-flex tw-items-center" href="{{.ActiveStopwatch.IssueLink}}">
{{svg "octicon-issue-opened" 16 "gt-mr-3"}} {{svg "octicon-issue-opened" 16 "tw-mr-2"}}
<span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span> <span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span>
<span class="ui primary label stopwatch-time gt-my-0 gt-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}"> <span class="ui primary label stopwatch-time tw-my-0 tw-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}">
{{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}} {{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}}
</span> </span>
</a> </a>
@ -111,14 +111,14 @@
</div> </div>
{{end}} {{end}}
<a class="item not-mobile gt-mx-0" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}"> <a class="item not-mobile tw-mx-0" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
<div class="tw-relative"> <div class="tw-relative">
{{svg "octicon-bell"}} {{svg "octicon-bell"}}
<span class="notification_count{{if not $notificationUnreadCount}} gt-hidden{{end}}">{{$notificationUnreadCount}}</span> <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
</div> </div>
</a> </a>
<div class="ui dropdown jump item gt-mx-0 gt-pr-3" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}"> <div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}">
<span class="text"> <span class="text">
{{svg "octicon-plus"}} {{svg "octicon-plus"}}
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span> <span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
@ -141,10 +141,10 @@
</div><!-- end content create new menu --> </div><!-- end content create new menu -->
</div><!-- end dropdown menu create new --> </div><!-- end dropdown menu create new -->
<div class="ui dropdown jump item gt-mx-0 gt-pr-3" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}"> <div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text tw-flex tw-items-center"> <span class="text tw-flex tw-items-center">
{{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}} {{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
<span class="only-mobile gt-ml-3">{{.SignedUser.Name}}</span> <span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span> <span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span> </span>
<div class="menu user-menu"> <div class="menu user-menu">

View file

@ -6,11 +6,11 @@
<div class="center page buttons"> <div class="center page buttons">
<div class="ui borderless pagination menu"> <div class="ui borderless pagination menu">
<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$paginationLink}}{{if $paginationParams}}?{{$paginationParams}}{{end}}"{{end}}> <a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$paginationLink}}{{if $paginationParams}}?{{$paginationParams}}{{end}}"{{end}}>
{{svg "gitea-double-chevron-left" 16 "gt-mr-2"}} {{svg "gitea-double-chevron-left" 16 "tw-mr-1"}}
<span class="navigation_label">{{ctx.Locale.Tr "admin.first_page"}}</span> <span class="navigation_label">{{ctx.Locale.Tr "admin.first_page"}}</span>
</a> </a>
<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$paginationLink}}?page={{.Previous}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}> <a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$paginationLink}}?page={{.Previous}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
{{svg "octicon-chevron-left" 16 "gt-mr-2"}} {{svg "octicon-chevron-left" 16 "tw-mr-1"}}
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.previous"}}</span> <span class="navigation_label">{{ctx.Locale.Tr "repo.issues.previous"}}</span>
</a> </a>
{{range .Pages}} {{range .Pages}}
@ -22,11 +22,11 @@
{{end}} {{end}}
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}> <a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.next"}}</span> <span class="navigation_label">{{ctx.Locale.Tr "repo.issues.next"}}</span>
{{svg "octicon-chevron-right" 16 "gt-ml-2"}} {{svg "octicon-chevron-right" 16 "tw-ml-1"}}
</a> </a>
<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$paginationLink}}?page={{.TotalPages}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}> <a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$paginationLink}}?page={{.TotalPages}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
<span class="navigation_label">{{ctx.Locale.Tr "admin.last_page"}}</span> <span class="navigation_label">{{ctx.Locale.Tr "admin.last_page"}}</span>
{{svg "gitea-double-chevron-right" 16 "gt-ml-2"}} {{svg "gitea-double-chevron-right" 16 "tw-ml-1"}}
</a> </a>
</div> </div>
</div> </div>

View file

@ -26,7 +26,7 @@
<div><button name="btn">submit post</button></div> <div><button name="btn">submit post</button></div>
</form> </form>
<form method="post" action="/no-such-uri" class="form-fetch-action"> <form method="post" action="/no-such-uri" class="form-fetch-action">
<div class="gt-py-5">bad action url</div> <div class="tw-py-8">bad action url</div>
<div><button name="btn">submit test</button></div> <div><button name="btn">submit test</button></div>
</form> </form>
</div> </div>

View file

@ -25,7 +25,7 @@
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
<button class="ui tiny red button"> <button class="ui tiny red button">
{{svg "octicon-warning" 14}} CJK文本测试 {{svg "octicon-alert" 14}} CJK文本测试
</button> </button>
<button class="ui tiny primary button"> <button class="ui tiny primary button">
{{svg "octicon-info" 14}} Button {{svg "octicon-info" 14}} Button
@ -54,7 +54,7 @@
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
<button class="ui tiny red button"> <button class="ui tiny red button">
{{svg "octicon-warning" 12}} CJK文本测试 <!-- single CJK text test, it shouldn't be horizontal --> {{svg "octicon-alert" 12}} CJK文本测试 <!-- single CJK text test, it shouldn't be horizontal -->
</button> </button>
</div> </div>
</div> </div>
@ -73,7 +73,7 @@
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
<a class="muted" href="{{$.Link}}"> <a class="muted" href="{{$.Link}}">
<span class="flex-text-inline"><i class="color-icon gt-mr-3 tw-bg-blue"></i>Go</span> <span class="flex-text-inline"><i class="color-icon tw-mr-2 tw-bg-blue"></i>Go</span>
</a> </a>
<a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-star" 16}}45000</a> <a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-star" 16}}45000</a>
<a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-git-branch" 16}}1234</a> <a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-git-branch" 16}}1234</a>
@ -104,7 +104,7 @@
</div> </div>
<h1>If parent provides the padding/margin space:</h1> <h1>If parent provides the padding/margin space:</h1>
<div class="tw-border tw-border-secondary gt-py-4"> <div class="tw-border tw-border-secondary tw-py-4">
<div class="flex-list flex-space-fitted"> <div class="flex-list flex-space-fitted">
<div class="flex-item">item 1 (no padding top)</div> <div class="flex-item">item 1 (no padding top)</div>
<div class="flex-item">item 2 (no padding bottom)</div> <div class="flex-item">item 2 (no padding bottom)</div>

View file

@ -67,10 +67,10 @@
</li> </li>
<li class="sample-group"> <li class="sample-group">
<h2>Inline / Plain:</h2> <h2>Inline / Plain:</h2>
<div class="gt-my-2"> <div class="tw-my-1">
<button class="btn gt-p-3">Plain button</button> <button class="btn tw-p-2">Plain button</button>
<button class="btn interact-fg gt-p-3">Plain button with interact fg</button> <button class="btn interact-fg tw-p-2">Plain button with interact fg</button>
<button class="btn interact-bg gt-p-3">Plain button with interact bg</button> <button class="btn interact-bg tw-p-2">Plain button with interact bg</button>
</div> </div>
</li> </li>
</ul> </ul>
@ -102,8 +102,8 @@
<div> <div>
<h1>Loading</h1> <h1>Loading</h1>
<div class="is-loading small-loading-icon tw-border tw-border-secondary gt-py-2"><span>loading ...</span></div> <div class="is-loading small-loading-icon tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>
<div class="is-loading tw-border tw-border-secondary gt-py-4"> <div class="is-loading tw-border tw-border-secondary tw-py-4">
<p>loading ...</p> <p>loading ...</p>
<p>loading ...</p> <p>loading ...</p>
<p>loading ...</p> <p>loading ...</p>

View file

@ -33,7 +33,7 @@
<div class="flex-item-trailing muted-links"> <div class="flex-item-trailing muted-links">
{{if .PrimaryLanguage}} {{if .PrimaryLanguage}}
<a class="flex-text-inline" href="?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}{{if $.TabName}}&tab={{$.TabName}}{{end}}"> <a class="flex-text-inline" href="?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}{{if $.TabName}}&tab={{$.TabName}}{{end}}">
<i class="color-icon gt-mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i> <i class="color-icon tw-mr-2" style="background-color: {{.PrimaryLanguage.Color}}"></i>
{{.PrimaryLanguage.Language}} {{.PrimaryLanguage.Language}}
</a> </a>
{{end}} {{end}}

View file

@ -1,4 +1,4 @@
<div class="ui small secondary filter menu tw-items-center gt-mx-0"> <div class="ui small secondary filter menu tw-items-center tw-mx-0">
<form class="ui form ignore-dirty tw-flex-1"> <form class="ui form ignore-dirty tw-flex-1">
{{if .PageIsExploreUsers}} {{if .PageIsExploreUsers}}
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.user_kind")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.user_kind")}}
@ -7,7 +7,7 @@
{{end}} {{end}}
</form> </form>
<!-- Sort --> <!-- Sort -->
<div class="ui small dropdown type jump item gt-mr-0"> <div class="ui small dropdown type jump item tw-mr-0">
<span class="text"> <span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}} {{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span> </span>

View file

@ -1,6 +1,6 @@
{{template "base/head" .}} {{template "base/head" .}}
<div role="main" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}" class="page-content home"> <div role="main" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}" class="page-content home">
<div class="gt-mb-5 gt-px-5"> <div class="tw-mb-8 tw-px-8">
<div class="center"> <div class="center">
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}"> <img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
<div class="hero"> <div class="hero">

View file

@ -28,7 +28,7 @@
</div> </div>
</div> </div>
<div class="gt-mt-4 gt-hidden" data-db-setting-for="common-host"> <div class="tw-mt-4 tw-hidden" data-db-setting-for="common-host">
<div class="inline required field {{if .Err_DbSetting}}error{{end}}"> <div class="inline required field {{if .Err_DbSetting}}error{{end}}">
<label for="db_host">{{ctx.Locale.Tr "install.host"}}</label> <label for="db_host">{{ctx.Locale.Tr "install.host"}}</label>
<input id="db_host" name="db_host" value="{{.db_host}}"> <input id="db_host" name="db_host" value="{{.db_host}}">
@ -47,7 +47,7 @@
</div> </div>
</div> </div>
<div class="gt-mt-4 gt-hidden" data-db-setting-for="postgres"> <div class="tw-mt-4 tw-hidden" data-db-setting-for="postgres">
<div class="inline required field"> <div class="inline required field">
<label>{{ctx.Locale.Tr "install.ssl_mode"}}</label> <label>{{ctx.Locale.Tr "install.ssl_mode"}}</label>
<div class="ui selection database type dropdown"> <div class="ui selection database type dropdown">
@ -68,7 +68,7 @@
</div> </div>
</div> </div>
<div class="gt-mt-4 gt-hidden" data-db-setting-for="sqlite3"> <div class="tw-mt-4 tw-hidden" data-db-setting-for="sqlite3">
<div class="inline required field {{if or .Err_DbPath .Err_DbSetting}}error{{end}}"> <div class="inline required field {{if or .Err_DbPath .Err_DbSetting}}error{{end}}">
<label for="db_path">{{ctx.Locale.Tr "install.path"}}</label> <label for="db_path">{{ctx.Locale.Tr "install.path"}}</label>
<input id="db_path" name="db_path" value="{{.db_path}}"> <input id="db_path" name="db_path" value="{{.db_path}}">
@ -160,7 +160,7 @@
<!-- Email --> <!-- Email -->
<details class="optional field"> <details class="optional field">
<summary class="right-content gt-py-3{{if .Err_SMTP}} text red{{end}}"> <summary class="right-content tw-py-2{{if .Err_SMTP}} text red{{end}}">
{{ctx.Locale.Tr "install.email_title"}} {{ctx.Locale.Tr "install.email_title"}}
</summary> </summary>
<div class="inline field"> <div class="inline field">
@ -200,7 +200,7 @@
<!-- Server and other services --> <!-- Server and other services -->
<details class="optional field"> <details class="optional field">
<summary class="right-content gt-py-3{{if .Err_Services}} text red{{end}}"> <summary class="right-content tw-py-2{{if .Err_Services}} text red{{end}}">
{{ctx.Locale.Tr "install.server_service_title"}} {{ctx.Locale.Tr "install.server_service_title"}}
</summary> </summary>
<div class="inline field"> <div class="inline field">
@ -298,7 +298,7 @@
<!-- Admin --> <!-- Admin -->
<details class="optional field"> <details class="optional field">
<summary class="right-content gt-py-3{{if .Err_Admin}} text red{{end}}"> <summary class="right-content tw-py-2{{if .Err_Admin}} text red{{end}}">
{{ctx.Locale.Tr "install.admin_title"}} {{ctx.Locale.Tr "install.admin_title"}}
</summary> </summary>
<p class="center">{{ctx.Locale.Tr "install.admin_setting_desc"}}</p> <p class="center">{{ctx.Locale.Tr "install.admin_setting_desc"}}</p>
@ -327,7 +327,7 @@
<div class="right-content"> <div class="right-content">
{{ctx.Locale.Tr "install.env_config_keys_prompt"}} {{ctx.Locale.Tr "install.env_config_keys_prompt"}}
</div> </div>
<div class="right-content gt-mt-3"> <div class="right-content tw-mt-2">
{{range .EnvConfigKeys}}<span class="ui label">{{.}}</span>{{end}} {{range .EnvConfigKeys}}<span class="ui label">{{.}}</span>{{end}}
</div> </div>
</div> </div>
@ -338,7 +338,7 @@
<div class="right-content"> <div class="right-content">
{{ctx.Locale.Tr "install.config_location_hint"}} {{.CustomConfFile}} {{ctx.Locale.Tr "install.config_location_hint"}} {{.CustomConfFile}}
</div> </div>
<div class="right-content gt-mt-3"> <div class="right-content tw-mt-2">
<button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button>
</div> </div>
</div> </div>
@ -347,5 +347,5 @@
</div> </div>
</div> </div>
</div> </div>
<img class="gt-hidden" src="{{AssetUrlPrefix}}/img/forgejo-loading.svg" width="256" height="256"> <img class="tw-hidden" src="{{AssetUrlPrefix}}/img/forgejo-loading.svg" width="256" height="256">
{{template "base/footer" .}} {{template "base/footer" .}}

View file

@ -1,4 +1,4 @@
<button class="ui basic button gt-mr-0" hx-post="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}"> <button class="ui basic button tw-mr-0" hx-post="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}">
{{if $.IsFollowing}} {{if $.IsFollowing}}
{{ctx.Locale.Tr "user.unfollow"}} {{ctx.Locale.Tr "user.unfollow"}}
{{else}} {{else}}

View file

@ -9,7 +9,7 @@
</span> </span>
<span class="tw-flex tw-items-center tw-gap-1 tw-ml-auto tw-text-16 tw-whitespace-nowrap"> <span class="tw-flex tw-items-center tw-gap-1 tw-ml-auto tw-text-16 tw-whitespace-nowrap">
{{if .EnableFeed}} {{if .EnableFeed}}
<a class="ui basic label button gt-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}"> <a class="ui basic label button tw-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
{{svg "octicon-rss" 24}} {{svg "octicon-rss" 24}}
</a> </a>
{{end}} {{end}}
@ -19,7 +19,7 @@
</span> </span>
</div> </div>
{{if .RenderedDescription}}<div class="render-content markup">{{.RenderedDescription}}</div>{{end}} {{if .RenderedDescription}}<div class="render-content markup">{{.RenderedDescription}}</div>{{end}}
<div class="text light meta gt-mt-2"> <div class="text light meta tw-mt-1">
{{if .Org.Location}}<div class="flex-text-block">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}} {{if .Org.Location}}<div class="flex-text-block">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}}
{{if .Org.Website}}<div class="flex-text-block">{{svg "octicon-link"}} <a class="muted" target="_blank" rel="noopener noreferrer me" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}} {{if .Org.Website}}<div class="flex-text-block">{{svg "octicon-link"}} <a class="muted" target="_blank" rel="noopener noreferrer me" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}}
{{if .IsSigned}} {{if .IsSigned}}

View file

@ -1,7 +1,7 @@
{{template "base/head" .}} {{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content organization profile"> <div role="main" aria-label="{{.Title}}" class="page-content organization profile">
{{if .Flash}} {{if .Flash}}
<div class="ui container gt-mb-5"> <div class="ui container tw-mb-8">
{{template "base/alert" .}} {{template "base/alert" .}}
</div> </div>
{{end}} {{end}}
@ -21,7 +21,7 @@
{{if .ShowMemberAndTeamTab}} {{if .ShowMemberAndTeamTab}}
<div class="ui five wide column"> <div class="ui five wide column">
{{if .CanCreateOrgRepo}} {{if .CanCreateOrgRepo}}
<div class="center aligned"> <div class="center aligned tw-mb-4">
<a class="ui primary button" href="{{AppSubUrl}}/repo/create?org={{.Org.ID}}">{{ctx.Locale.Tr "new_repo"}}</a> <a class="ui primary button" href="{{AppSubUrl}}/repo/create?org={{.Org.ID}}">{{ctx.Locale.Tr "new_repo"}}</a>
{{if not .DisableNewPullMirrors}} {{if not .DisableNewPullMirrors}}
<a class="ui primary button" href="{{AppSubUrl}}/repo/migrate?org={{.Org.ID}}&mirror=1">{{ctx.Locale.Tr "new_migrate"}}</a> <a class="ui primary button" href="{{AppSubUrl}}/repo/migrate?org={{.Org.ID}}&mirror=1">{{ctx.Locale.Tr "new_migrate"}}</a>

View file

@ -1,5 +1,5 @@
<div class="ui container"> <div class="ui container">
<overflow-menu class="ui secondary pointing tabular borderless menu"> <overflow-menu class="ui secondary pointing tabular borderless menu tw-mb-4">
<div class="overflow-menu-items"> <div class="overflow-menu-items">
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}"> <a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}">
{{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}} {{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}}

View file

@ -14,7 +14,7 @@
{{template "shared/user/profile_big_avatar" .}} {{template "shared/user/profile_big_avatar" .}}
</div> </div>
<div class="ui twelve wide column"> <div class="ui twelve wide column">
<div class="gt-mb-4"> <div class="tw-mb-4">
{{template "user/overview/header" .}} {{template "user/overview/header" .}}
</div> </div>
{{template "projects/list" .}} {{template "projects/list" .}}

View file

@ -8,7 +8,7 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_Name}}error{{end}}"> <div class="required field {{if .Err_Name}}error{{end}}">
<label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}} <label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}}
<span class="text red gt-hidden" id="org-name-change-prompt"> <span class="text red tw-hidden" id="org-name-change-prompt">
<br>{{ctx.Locale.Tr "org.settings.change_orgname_prompt"}}<br>{{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}} <br>{{ctx.Locale.Tr "org.settings.change_orgname_prompt"}}<br>{{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}}
</span> </span>
</label> </label>

View file

@ -12,7 +12,7 @@
<form class="ui form ignore-dirty tw-flex tw-flex-wrap tw-gap-2" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post"> <form class="ui form ignore-dirty tw-flex tw-flex-wrap tw-gap-2" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="uid" value="{{.SignedUser.ID}}"> <input type="hidden" name="uid" value="{{.SignedUser.ID}}">
<div id="search-user-box" class="ui search gt-mr-3"{{if .IsEmailInviteEnabled}} data-allow-email="true" data-allow-email-description="{{ctx.Locale.Tr "org.teams.invite_team_member" $.Team.Name}}"{{end}}> <div id="search-user-box" class="ui search tw-mr-2"{{if .IsEmailInviteEnabled}} data-allow-email="true" data-allow-email-description="{{ctx.Locale.Tr "org.teams.invite_team_member" $.Team.Name}}"{{end}}>
<div class="ui input"> <div class="ui input">
<input class="prompt" name="uname" placeholder="{{ctx.Locale.Tr "search.user_kind"}}" autocomplete="off" required> <input class="prompt" name="uname" placeholder="{{ctx.Locale.Tr "search.user_kind"}}" autocomplete="off" required>
</div> </div>

View file

@ -71,18 +71,18 @@
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<div class="team-units required grouped field {{if eq .Team.AccessMode 3}}gt-hidden{{end}}"> <div class="team-units required grouped field {{if eq .Team.AccessMode 3}}tw-hidden{{end}}">
<label>{{ctx.Locale.Tr "org.team_unit_desc"}}</label> <label>{{ctx.Locale.Tr "org.team_unit_desc"}}</label>
<table class="ui celled table"> <table class="ui celled table">
<thead> <thead>
<tr> <tr>
<th>{{ctx.Locale.Tr "units.unit"}}</th> <th>{{ctx.Locale.Tr "units.unit"}}</th>
<th class="center aligned">{{ctx.Locale.Tr "org.teams.none_access"}} <th class="center aligned">{{ctx.Locale.Tr "org.teams.none_access"}}
<span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.none_access_helper"}}">{{svg "octicon-question" 16 "gt-ml-2"}}</span></th> <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.none_access_helper"}}">{{svg "octicon-question" 16 "tw-ml-1"}}</span></th>
<th class="center aligned">{{ctx.Locale.Tr "org.teams.read_access"}} <th class="center aligned">{{ctx.Locale.Tr "org.teams.read_access"}}
<span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.read_access_helper"}}">{{svg "octicon-question" 16 "gt-ml-2"}}</span></th> <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.read_access_helper"}}">{{svg "octicon-question" 16 "tw-ml-1"}}</span></th>
<th class="center aligned">{{ctx.Locale.Tr "org.teams.write_access"}} <th class="center aligned">{{ctx.Locale.Tr "org.teams.write_access"}}
<span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.write_access_helper"}}">{{svg "octicon-question" 16 "gt-ml-2"}}</span></th> <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.write_access_helper"}}">{{svg "octicon-question" 16 "tw-ml-1"}}</span></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -17,7 +17,7 @@
<input class="prompt" name="repo_name" placeholder="{{ctx.Locale.Tr "search.repo_kind"}}" autocomplete="off" required> <input class="prompt" name="repo_name" placeholder="{{ctx.Locale.Tr "search.repo_kind"}}" autocomplete="off" required>
</div> </div>
</div> </div>
<button class="ui primary button gt-ml-3">{{ctx.Locale.Tr "add"}}</button> <button class="ui primary button tw-ml-2">{{ctx.Locale.Tr "add"}}</button>
</form> </form>
<div class="tw-inline-block"> <div class="tw-inline-block">
<button class="ui primary button link-action" data-modal-confirm="{{ctx.Locale.Tr "org.teams.add_all_repos_desc"}}" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/addall">{{ctx.Locale.Tr "add_all"}}</button> <button class="ui primary button link-action" data-modal-confirm="{{ctx.Locale.Tr "org.teams.add_all_repos_desc"}}" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/addall">{{ctx.Locale.Tr "add_all"}}</button>

View file

@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "alpine"}} {{if eq .PackageDescriptor.Package.Type "alpine"}}
{{if .PackageDescriptor.Metadata.Maintainer}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Maintainer}}</div>{{end}} {{if .PackageDescriptor.Metadata.Maintainer}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Maintainer}}</div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}} {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}} {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{end}} {{end}}

Some files were not shown because too many files have changed in this diff Show more