1
0
mirror of https://github.com/go-gitea/gitea.git synced 2024-10-26 07:55:34 +03:00

Compare commits

...

20 Commits

Author SHA1 Message Date
Kemal Zebari
c1bdd99df1
Merge 17205fa1d3 into 7cf611d197 2024-10-25 21:48:59 -07:00
Kemal Zebari
17205fa1d3
Merge branch 'main' into respect-co-authors-in-contributors-graph 2024-10-25 21:48:56 -07:00
yp05327
7cf611d197
Fix broken image when editing comment with non-image attachments (#32319)
Fix #32316
2024-10-23 17:39:10 +02:00
Lunny Xiao
2abdbe88b5
Fix disable 2fa bug (#32320) 2024-10-23 06:41:00 +00:00
Lunny Xiao
d46d34a655
Upgrade rollup to 4.24.0 (#32312) 2024-10-23 05:00:32 +00:00
Lunny Xiao
500774277c
Upgrade vue to 3.5.12 (#32311) 2024-10-23 04:55:17 +00:00
Tim
de2ad2e1b1
Make admins adhere to branch protection rules (#32248)
This introduces a new flag `BlockAdminMergeOverride` on the branch
protection rules that prevents admins/repo owners from bypassing branch
protection rules and merging without approvals or failing status checks.

Fixes #17131

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2024-10-23 12:39:43 +08:00
박상철
620f19610e
Prevent from submitting issue/comment on uploading (#32263)
fix #32262

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2024-10-23 10:48:04 +08:00
Lunny Xiao
a264c46fb0
Add warn log when deleting inactive users (#32318)
Add log for the problem #31480
2024-10-23 09:28:28 +08:00
Zettat123
9206fbb55f
Add DISABLE_ORGANIZATIONS_PAGE and DISABLE_CODE_PAGE settings for explore pages and fix an issue related to user search (#32288)
These settings can allow users to only display the repositories explore page.

Thanks to yp05327 and wxiaoguang !

---------

Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-10-22 13:09:19 +08:00
wangjingcun
3d6ccbac3f
chore: fix some function names in comment (#32300)
fix some function names in comment
2024-10-22 08:41:05 +08:00
Kemal Zebari
78fecfc32e Add a space between license comment and package keyword 2024-05-13 23:17:00 -07:00
Kemal Zebari
6c713da4b1 Add missing license comment 2024-05-13 23:15:48 -07:00
Kemal Zebari
fd0bf3b563 Handle duplicated Co-authored-by entries 2024-05-13 23:12:19 -07:00
Kemal Zebari
8e3209b09a Make commit trailer parsing public and add parameterized tests 2024-05-13 22:35:17 -07:00
Kemal Zebari
25a9f121b5 Shrink openEmailBracketIdx -> openBracketIdx 2024-05-11 16:09:40 -07:00
Kemal Zebari
cce78121ff Have co-author parsing be more robust and some refactoring 2024-05-11 16:02:08 -07:00
Kemal Zebari
9ac9327c57 Fix lint problem in test code 2024-05-09 14:01:57 -07:00
Kemal Zebari
99953fa21c CI feedback 2024-05-09 13:52:11 -07:00
Kemal Zebari
ea4099918d Respect Co-authored-by trailers in the contributors graph 2024-05-09 12:29:20 -07:00
52 changed files with 912 additions and 419 deletions

View File

@ -386,7 +386,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
return a.createAuthSource(ctx, authSource)
}
// updateLdapBindDn updates a new LDAP (simple auth) authentication source.
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()

View File

@ -907,6 +907,24 @@ LEVEL = Info
;; Valid site url schemes for user profiles
;VALID_SITE_URL_SCHEMES=http,https
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[service.explore]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Only allow signed in users to view the explore pages.
;REQUIRE_SIGNIN_VIEW = false
;;
;; Disable the users explore page.
;DISABLE_USERS_PAGE = false
;;
;; Disable the organizations explore page.
;DISABLE_ORGANIZATIONS_PAGE = false
;;
;; Disable the code explore page.
;DISABLE_CODE_PAGE = false
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -63,6 +63,7 @@ type ProtectedBranch struct {
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"`
UnprotectedFilePatterns string `xorm:"TEXT"`
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`

View File

@ -872,7 +872,7 @@ func GetPinnedIssues(ctx context.Context, repoID int64, isPull bool) (IssueList,
return issues, nil
}
// IsNewPinnedAllowed returns if a new Issue or Pull request can be pinned
// IsNewPinAllowed returns if a new Issue or Pull request can be pinned
func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) {
var maxPin int
_, err := db.GetEngine(ctx).SQL("SELECT COUNT(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ? AND pin_order > 0", repoID, isPull).Get(&maxPin)

View File

@ -701,7 +701,7 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest,
return pr, pr.LoadAttributes(ctx)
}
// GetPullRequestsByBaseHeadInfo returns the pull request by given base and head
// GetPullRequestByBaseHeadInfo returns the pull request by given base and head
func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) {
pr := &PullRequest{}
sess := db.GetEngine(ctx).

View File

@ -603,6 +603,8 @@ var migrations = []Migration{
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
// v305 -> v306
NewMigration("Add Repository Licenses", v1_23.AddRepositoryLicenses),
// v306 -> v307
NewMigration("Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
}
// GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,13 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import "xorm.io/xorm"
func AddBlockAdminMergeOverrideBranchProtection(x *xorm.Engine) error {
type ProtectedBranch struct {
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(new(ProtectedBranch))
}

View File

@ -90,8 +90,10 @@ var Service = struct {
// Explore page settings
Explore struct {
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`
DisableCodePage bool `ini:"DISABLE_CODE_PAGE"`
} `ini:"service.explore"`
}{
AllowedUserVisibilityModesSlice: []bool{true, true, true},

View File

@ -52,6 +52,7 @@ type BranchProtection struct {
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
BlockAdminMergeOverride bool `json:"block_admin_merge_override"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
@ -90,6 +91,7 @@ type CreateBranchProtectionOption struct {
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
BlockAdminMergeOverride bool `json:"block_admin_merge_override"`
}
// EditBranchProtectionOption options for editing a branch protection
@ -121,4 +123,5 @@ type EditBranchProtectionOption struct {
RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"`
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
BlockAdminMergeOverride *bool `json:"block_admin_merge_override"`
}

View File

@ -0,0 +1,42 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package util
import (
"errors"
"net/mail"
"strings"
)
var ErrInvalidCommitTrailerValueSyntax = errors.New("syntax error occurred while parsing a commit trailer value")
// ParseCommitTrailerValueWithAuthor parses a commit trailer value that contains author data.
// Note that it only parses the value and does not consider the trailer key i.e. we just
// parse something like the following:
//
// Foo Bar <foobar@example.com>
func ParseCommitTrailerValueWithAuthor(value string) (name, email string, err error) {
value = strings.TrimSpace(value)
if !strings.HasSuffix(value, ">") {
return "", "", ErrInvalidCommitTrailerValueSyntax
}
closedBracketIdx := len(value) - 1
openBracketIdx := strings.LastIndex(value, "<")
if openBracketIdx == -1 {
return "", "", ErrInvalidCommitTrailerValueSyntax
}
email = value[openBracketIdx+1 : closedBracketIdx]
if _, err := mail.ParseAddress(email); err != nil {
return "", "", ErrInvalidCommitTrailerValueSyntax
}
name = strings.TrimSpace(value[:openBracketIdx])
if len(name) == 0 {
return "", "", ErrInvalidCommitTrailerValueSyntax
}
return name, email, nil
}

View File

@ -0,0 +1,40 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseCommitTrailerValueWithAuthor(t *testing.T) {
cases := []struct {
input string
shouldBeError bool
expectedName string
expectedEmail string
}{
{"Foo Bar <foobar@example.com", true, "", ""},
{"Foo Bar foobar@example.com>", true, "", ""},
{"Foo Bar <>", true, "", ""},
{"Foo Bar <invalid-email-address>", true, "", ""},
{"<foobar@example.com>", true, "", ""},
{" <foobar@example.com>", true, "", ""},
{"Foo Bar <foobar@example.com>", false, "Foo Bar", "foobar@example.com"},
{" Foo Bar <foobar@example.com>", false, "Foo Bar", "foobar@example.com"},
// Account for edge case where name contains an open bracket.
{" Foo < Bar <foobar@example.com>", false, "Foo < Bar", "foobar@example.com"},
}
for n, c := range cases {
name, email, err := ParseCommitTrailerValueWithAuthor(c.input)
if c.shouldBeError {
assert.Error(t, err, "case %d should be a syntax error", n)
} else {
assert.Equal(t, c.expectedName, name, "case %d should have correct name", n)
assert.Equal(t, c.expectedEmail, email, "case %d should have correct email", n)
}
}
}

View File

@ -2461,6 +2461,8 @@ settings.block_on_official_review_requests = Block merge on official review requ
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
settings.block_outdated_branch = Block merge if pull request is outdated
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
settings.block_admin_merge_override = Administrators must follow branch protection rules
settings.block_admin_merge_override_desc = Administrators must follow branch protection rules and can not circumvent it.
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
settings.merge_style_desc = Merge Styles
settings.default_merge_style_desc = Default Merge Style

345
package-lock.json generated
View File

@ -55,7 +55,7 @@
"typescript": "5.5.4",
"uint8-to-base64": "0.2.0",
"vanilla-colorful": "0.7.2",
"vue": "3.4.38",
"vue": "3.5.12",
"vue-bar-graph": "2.1.0",
"vue-chartjs": "5.3.1",
"vue-loader": "17.4.2",
@ -3618,224 +3618,208 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz",
"integrity": "sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
"integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz",
"integrity": "sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
"integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz",
"integrity": "sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
"integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz",
"integrity": "sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
"integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz",
"integrity": "sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
"integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz",
"integrity": "sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
"integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz",
"integrity": "sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
"integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz",
"integrity": "sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
"integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz",
"integrity": "sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
"integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz",
"integrity": "sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
"integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz",
"integrity": "sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
"integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz",
"integrity": "sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
"integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz",
"integrity": "sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
"integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz",
"integrity": "sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
"integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz",
"integrity": "sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
"integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz",
"integrity": "sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
"integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
@ -5202,113 +5186,130 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz",
"integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz",
"integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==",
"dependencies": {
"@babel/parser": "^7.24.7",
"@vue/shared": "3.4.38",
"@babel/parser": "^7.25.3",
"@vue/shared": "3.5.12",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz",
"integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz",
"integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==",
"dependencies": {
"@vue/compiler-core": "3.4.38",
"@vue/shared": "3.4.38"
"@vue/compiler-core": "3.5.12",
"@vue/shared": "3.5.12"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz",
"integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz",
"integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==",
"dependencies": {
"@babel/parser": "^7.24.7",
"@vue/compiler-core": "3.4.38",
"@vue/compiler-dom": "3.4.38",
"@vue/compiler-ssr": "3.4.38",
"@vue/shared": "3.4.38",
"@babel/parser": "^7.25.3",
"@vue/compiler-core": "3.5.12",
"@vue/compiler-dom": "3.5.12",
"@vue/compiler-ssr": "3.5.12",
"@vue/shared": "3.5.12",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.10",
"postcss": "^8.4.40",
"magic-string": "^0.30.11",
"postcss": "^8.4.47",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-sfc/node_modules/magic-string": {
"version": "0.30.11",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
"integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
"license": "MIT",
"version": "0.30.12",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
"integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz",
"integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==",
"license": "MIT",
"node_modules/@vue/compiler-sfc/node_modules/postcss": {
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"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": {
"@vue/compiler-dom": "3.4.38",
"@vue/shared": "3.4.38"
"nanoid": "^3.3.7",
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz",
"integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==",
"dependencies": {
"@vue/compiler-dom": "3.5.12",
"@vue/shared": "3.5.12"
}
},
"node_modules/@vue/reactivity": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.38.tgz",
"integrity": "sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz",
"integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==",
"dependencies": {
"@vue/shared": "3.4.38"
"@vue/shared": "3.5.12"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.38.tgz",
"integrity": "sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz",
"integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==",
"dependencies": {
"@vue/reactivity": "3.4.38",
"@vue/shared": "3.4.38"
"@vue/reactivity": "3.5.12",
"@vue/shared": "3.5.12"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.38.tgz",
"integrity": "sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz",
"integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==",
"dependencies": {
"@vue/reactivity": "3.4.38",
"@vue/runtime-core": "3.4.38",
"@vue/shared": "3.4.38",
"@vue/reactivity": "3.5.12",
"@vue/runtime-core": "3.5.12",
"@vue/shared": "3.5.12",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.38.tgz",
"integrity": "sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz",
"integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==",
"dependencies": {
"@vue/compiler-ssr": "3.4.38",
"@vue/shared": "3.4.38"
"@vue/compiler-ssr": "3.5.12",
"@vue/shared": "3.5.12"
},
"peerDependencies": {
"vue": "3.4.38"
"vue": "3.5.12"
}
},
"node_modules/@vue/shared": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz",
"integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==",
"license": "MIT"
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz",
"integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg=="
},
"node_modules/@webassemblyjs/ast": {
"version": "1.12.1",
@ -12460,10 +12461,9 @@
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"license": "ISC"
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@ -13512,11 +13512,10 @@
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"version": "2.79.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true,
"license": "MIT",
"bin": {
"rollup": "dist/bin/rollup"
},
@ -13847,10 +13846,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"license": "BSD-3-Clause",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"engines": {
"node": ">=0.10.0"
}
@ -15378,11 +15376,10 @@
"license": "BSD-2-Clause"
},
"node_modules/vite/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true,
"license": "MIT"
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true
},
"node_modules/vite/node_modules/fsevents": {
"version": "2.3.3",
@ -15400,13 +15397,12 @@
}
},
"node_modules/vite/node_modules/rollup": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.1.tgz",
"integrity": "sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
"integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.5"
"@types/estree": "1.0.6"
},
"bin": {
"rollup": "dist/bin/rollup"
@ -15416,22 +15412,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.21.1",
"@rollup/rollup-android-arm64": "4.21.1",
"@rollup/rollup-darwin-arm64": "4.21.1",
"@rollup/rollup-darwin-x64": "4.21.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.21.1",
"@rollup/rollup-linux-arm-musleabihf": "4.21.1",
"@rollup/rollup-linux-arm64-gnu": "4.21.1",
"@rollup/rollup-linux-arm64-musl": "4.21.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.1",
"@rollup/rollup-linux-riscv64-gnu": "4.21.1",
"@rollup/rollup-linux-s390x-gnu": "4.21.1",
"@rollup/rollup-linux-x64-gnu": "4.21.1",
"@rollup/rollup-linux-x64-musl": "4.21.1",
"@rollup/rollup-win32-arm64-msvc": "4.21.1",
"@rollup/rollup-win32-ia32-msvc": "4.21.1",
"@rollup/rollup-win32-x64-msvc": "4.21.1",
"@rollup/rollup-android-arm-eabi": "4.24.0",
"@rollup/rollup-android-arm64": "4.24.0",
"@rollup/rollup-darwin-arm64": "4.24.0",
"@rollup/rollup-darwin-x64": "4.24.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
"@rollup/rollup-linux-arm-musleabihf": "4.24.0",
"@rollup/rollup-linux-arm64-gnu": "4.24.0",
"@rollup/rollup-linux-arm64-musl": "4.24.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
"@rollup/rollup-linux-riscv64-gnu": "4.24.0",
"@rollup/rollup-linux-s390x-gnu": "4.24.0",
"@rollup/rollup-linux-x64-gnu": "4.24.0",
"@rollup/rollup-linux-x64-musl": "4.24.0",
"@rollup/rollup-win32-arm64-msvc": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.24.0",
"fsevents": "~2.3.2"
}
},
@ -15560,16 +15556,15 @@
"license": "MIT"
},
"node_modules/vue": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",
"integrity": "sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==",
"license": "MIT",
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz",
"integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==",
"dependencies": {
"@vue/compiler-dom": "3.4.38",
"@vue/compiler-sfc": "3.4.38",
"@vue/runtime-dom": "3.4.38",
"@vue/server-renderer": "3.4.38",
"@vue/shared": "3.4.38"
"@vue/compiler-dom": "3.5.12",
"@vue/compiler-sfc": "3.5.12",
"@vue/runtime-dom": "3.5.12",
"@vue/server-renderer": "3.5.12",
"@vue/shared": "3.5.12"
},
"peerDependencies": {
"typescript": "*"

View File

@ -54,7 +54,7 @@
"typescript": "5.5.4",
"uint8-to-base64": "0.2.0",
"vanilla-colorful": "0.7.2",
"vue": "3.4.38",
"vue": "3.5.12",
"vue-bar-graph": "2.1.0",
"vue-chartjs": "5.3.1",
"vue-loader": "17.4.2",

View File

@ -356,12 +356,20 @@ func reqToken() func(ctx *context.APIContext) {
func reqExploreSignIn() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
}
}
}
func reqUsersExploreEnabled() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if setting.Service.Explore.DisableUsersPage {
ctx.NotFound()
}
}
}
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
@ -955,7 +963,7 @@ func Routes() *web.Router {
// Users (requires user scope)
m.Group("/users", func() {
m.Get("/search", reqExploreSignIn(), user.Search)
m.Get("/search", reqExploreSignIn(), reqUsersExploreEnabled(), user.Search)
m.Group("/{username}", func() {
m.Get("", reqExploreSignIn(), user.GetInfo)

View File

@ -642,6 +642,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
ProtectedFilePatterns: form.ProtectedFilePatterns,
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
BlockAdminMergeOverride: form.BlockAdminMergeOverride,
}
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
@ -852,6 +853,10 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
}
if form.BlockAdminMergeOverride != nil {
protectBranch.BlockAdminMergeOverride = *form.BlockAdminMergeOverride
}
var whitelistUsers, forcePushAllowlistUsers, mergeWhitelistUsers, approvalsWhitelistUsers []int64
if form.PushWhitelistUsernames != nil {
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)

View File

@ -21,12 +21,13 @@ const (
// Code render explore code page
func Code(ctx *context.Context) {
if !setting.Indexer.RepoIndexerEnabled {
if !setting.Indexer.RepoIndexerEnabled || setting.Service.Explore.DisableCodePage {
ctx.Redirect(setting.AppSubURL + "/explore")
return
}
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true

View File

@ -14,7 +14,13 @@ import (
// Organizations render explore organizations page
func Organizations(ctx *context.Context) {
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
if setting.Service.Explore.DisableOrganizationsPage {
ctx.Redirect(setting.AppSubURL + "/explore")
return
}
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreOrganizations"] = true

View File

@ -165,7 +165,9 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
// Repos render explore repositories page
func Repos(ctx *context.Context) {
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreRepositories"] = true

View File

@ -131,9 +131,11 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
// Users render explore users page
func Users(ctx *context.Context) {
if setting.Service.Explore.DisableUsersPage {
ctx.Redirect(setting.AppSubURL + "/explore/repos")
ctx.Redirect(setting.AppSubURL + "/explore")
return
}
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreUsers"] = true

View File

@ -256,6 +256,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
protectBranch.BlockAdminMergeOverride = f.BlockAdminMergeOverride
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,

View File

@ -8,37 +8,24 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
// Search search users
func Search(ctx *context.Context) {
listOptions := db.ListOptions{
Page: ctx.FormInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
}
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
// SearchCandidates searches candidate users for dropdown list
func SearchCandidates(ctx *context.Context) {
users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: ctx.FormInt64("uid"),
Type: user_model.UserTypeIndividual,
IsActive: ctx.FormOptionalBool("active"),
ListOptions: listOptions,
IsActive: optional.Some(true),
ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum},
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]any{
"ok": false,
"error": err.Error(),
})
ctx.ServerError("Unable to search users", err)
return
}
ctx.SetTotalCountHeader(maxResults)
ctx.JSON(http.StatusOK, map[string]any{
"ok": true,
"data": convert.ToUsers(ctx, ctx.Doer, users),
})
ctx.JSON(http.StatusOK, map[string]any{"data": convert.ToUsers(ctx, ctx.Doer, users)})
}

View File

@ -39,8 +39,9 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
if auth.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
} else {
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
}
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
return
}
@ -74,8 +75,9 @@ func DisableTwoFactor(ctx *context.Context) {
if auth.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
} else {
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
}
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
return
}
@ -84,8 +86,9 @@ func DisableTwoFactor(ctx *context.Context) {
// There is a potential DB race here - we must have been disabled by another request in the intervening period
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
} else {
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
}
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
return
}

View File

@ -670,7 +670,7 @@ func registerRoutes(m *web.Router) {
m.Post("/forgot_password", auth.ForgotPasswdPost)
m.Post("/logout", auth.SignOut)
m.Get("/stopwatches", reqSignIn, user.GetStopwatches)
m.Get("/search", ignExploreSignIn, user.Search)
m.Get("/search_candidates", ignExploreSignIn, user.SearchCandidates)
m.Group("/oauth2", func() {
m.Get("/{provider}", auth.SignInOAuth)
m.Get("/{provider}/callback", auth.SignInOAuthCallback)

View File

@ -185,6 +185,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
RequireSignedCommits: bp.RequireSignedCommits,
ProtectedFilePatterns: bp.ProtectedFilePatterns,
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
BlockAdminMergeOverride: bp.BlockAdminMergeOverride,
Created: bp.CreatedUnix.AsTime(),
Updated: bp.UpdatedUnix.AsTime(),
}

View File

@ -219,6 +219,7 @@ type ProtectBranchForm struct {
RequireSignedCommits bool
ProtectedFilePatterns string
UnprotectedFilePatterns string
BlockAdminMergeOverride bool
}
// Validate validates the fields

View File

@ -68,7 +68,7 @@ const (
)
// CheckPullMergeable check if the pull mergeable based on all conditions (branch protection, merge options, ...)
func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error {
func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminForceMerge bool) error {
return db.WithTx(stdCtx, func(ctx context.Context) error {
if pr.HasMerged {
return ErrHasMerged
@ -118,13 +118,22 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc
err = nil
}
// * if the doer is admin, they could skip the branch protection check
if adminSkipProtectionCheck {
if isRepoAdmin, errCheckAdmin := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); errCheckAdmin != nil {
log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, errCheckAdmin)
return errCheckAdmin
} else if isRepoAdmin {
err = nil // repo admin can skip the check, so clear the error
// * if admin tries to "Force Merge", they could sometimes skip the branch protection check
if adminForceMerge {
isRepoAdmin, errForceMerge := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer)
if errForceMerge != nil {
return fmt.Errorf("IsUserRepoAdmin failed, repo: %v, doer: %v, err: %w", pr.BaseRepoID, doer.ID, errForceMerge)
}
protectedBranchRule, errForceMerge := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
if errForceMerge != nil {
return fmt.Errorf("GetFirstMatchProtectedBranchRule failed, repo: %v, base branch: %v, err: %w", pr.BaseRepoID, pr.BaseBranch, errForceMerge)
}
// if doer is admin and the "Force Merge" is not blocked, then clear the branch protection check error
blockAdminForceMerge := protectedBranchRule != nil && protectedBranchRule.BlockAdminMergeOverride
if isRepoAdmin && !blockAdminForceMerge {
err = nil
}
}

View File

@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
)
const (
@ -53,10 +54,11 @@ type ContributorData struct {
Weeks map[int64]*WeekData `json:"weeks"`
}
// ExtendedCommitStats contains information for commit stats with author data
// ExtendedCommitStats contains information for commit stats with both author and coauthors data
type ExtendedCommitStats struct {
Author *api.CommitUser `json:"author"`
Stats *api.CommitStats `json:"stats"`
Author *api.CommitUser `json:"author"`
CoAuthors []*api.CommitUser `json:"co_authors"`
Stats *api.CommitStats `json:"stats"`
}
const layout = time.DateOnly
@ -125,8 +127,7 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
_ = stdoutWriter.Close()
}()
gitCmd := git.NewCommand(repo.Ctx, "log", "--shortstat", "--no-merges", "--pretty=format:---%n%aN%n%aE%n%as", "--reverse")
// AddOptionFormat("--max-count=%d", limit)
gitCmd := git.NewCommand(repo.Ctx, "log", "--shortstat", "--no-merges", "--pretty=format:---%n%aN%n%aE%n%as%n%(trailers:key=Co-authored-by,valueonly=true)", "--reverse")
gitCmd.AddDynamicArguments(baseCommit.ID.String())
var extendedCommitStats []*ExtendedCommitStats
@ -150,6 +151,30 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
authorEmail := strings.TrimSpace(scanner.Text())
scanner.Scan()
date := strings.TrimSpace(scanner.Text())
var coAuthors []*api.CommitUser
emailSet := map[string]bool{}
for scanner.Scan() {
line := scanner.Text()
if line == "" {
// There should be an empty line before we read the commit stats line.
break
}
coAuthorName, coAuthorEmail, err := util.ParseCommitTrailerValueWithAuthor(line)
if err != nil {
continue
}
if _, exists := emailSet[coAuthorEmail]; exists {
continue
}
emailSet[coAuthorEmail] = true
coAuthor := &api.CommitUser{
Identity: api.Identity{Name: coAuthorName, Email: coAuthorEmail},
Date: date,
}
coAuthors = append(coAuthors, coAuthor)
}
scanner.Scan()
stats := strings.TrimSpace(scanner.Text())
if authorName == "" || authorEmail == "" || date == "" || stats == "" {
@ -184,7 +209,8 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
},
Date: date,
},
Stats: &commitStats,
CoAuthors: coAuthors,
Stats: &commitStats,
}
extendedCommitStats = append(extendedCommitStats, res)
}
@ -222,8 +248,6 @@ func generateContributorStats(genDone chan struct{}, cache cache.StringCache, ca
return
}
layout := time.DateOnly
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLinkWithSize(ctx, 0)
contributorsCommitStats := make(map[string]*ContributorData)
contributorsCommitStats["total"] = &ContributorData{
@ -237,67 +261,16 @@ func generateContributorStats(genDone chan struct{}, cache cache.StringCache, ca
if len(userEmail) == 0 {
continue
}
u, _ := user_model.GetUserByEmail(ctx, userEmail)
if u != nil {
// update userEmail with user's primary email address so
// that different mail addresses will linked to same account
userEmail = u.GetEmail()
}
// duplicated logic
if _, ok := contributorsCommitStats[userEmail]; !ok {
if u == nil {
avatarLink := avatars.GenerateEmailAvatarFastLink(ctx, userEmail, 0)
if avatarLink == "" {
avatarLink = unknownUserAvatarLink
}
contributorsCommitStats[userEmail] = &ContributorData{
Name: v.Author.Name,
AvatarLink: avatarLink,
Weeks: make(map[int64]*WeekData),
}
} else {
contributorsCommitStats[userEmail] = &ContributorData{
Name: u.DisplayName(),
Login: u.LowerName,
AvatarLink: u.AvatarLinkWithSize(ctx, 0),
HomeLink: u.HomeLink(),
Weeks: make(map[int64]*WeekData),
}
}
}
// Update user statistics
user := contributorsCommitStats[userEmail]
startingOfWeek, _ := findLastSundayBeforeDate(v.Author.Date)
val, _ := time.Parse(layout, startingOfWeek)
week := val.UnixMilli()
authorData := getContributorData(ctx, contributorsCommitStats, v.Author, unknownUserAvatarLink)
date := v.Author.Date
stats := v.Stats
updateUserAndOverallStats(stats, date, authorData, total, false)
if user.Weeks[week] == nil {
user.Weeks[week] = &WeekData{
Additions: 0,
Deletions: 0,
Commits: 0,
Week: week,
}
for _, coAuthor := range v.CoAuthors {
coAuthorData := getContributorData(ctx, contributorsCommitStats, coAuthor, unknownUserAvatarLink)
updateUserAndOverallStats(stats, date, coAuthorData, total, true)
}
if total.Weeks[week] == nil {
total.Weeks[week] = &WeekData{
Additions: 0,
Deletions: 0,
Commits: 0,
Week: week,
}
}
user.Weeks[week].Additions += v.Stats.Additions
user.Weeks[week].Deletions += v.Stats.Deletions
user.Weeks[week].Commits++
user.TotalCommits++
// Update overall statistics
total.Weeks[week].Additions += v.Stats.Additions
total.Weeks[week].Deletions += v.Stats.Deletions
total.Weeks[week].Commits++
total.TotalCommits++
}
_ = cache.PutJSON(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
@ -306,3 +279,77 @@ func generateContributorStats(genDone chan struct{}, cache cache.StringCache, ca
genDone <- struct{}{}
}
}
func getContributorData(ctx context.Context, contributorsCommitStats map[string]*ContributorData, user *api.CommitUser, defaultUserAvatarLink string) *ContributorData {
userEmail := user.Email
u, _ := user_model.GetUserByEmail(ctx, userEmail)
if u != nil {
// update userEmail with user's primary email address so
// that different mail addresses will linked to same account
userEmail = u.GetEmail()
}
if _, ok := contributorsCommitStats[userEmail]; !ok {
if u == nil {
avatarLink := avatars.GenerateEmailAvatarFastLink(ctx, userEmail, 0)
if avatarLink == "" {
avatarLink = defaultUserAvatarLink
}
contributorsCommitStats[userEmail] = &ContributorData{
Name: user.Name,
AvatarLink: avatarLink,
Weeks: make(map[int64]*WeekData),
}
} else {
contributorsCommitStats[userEmail] = &ContributorData{
Name: u.DisplayName(),
Login: u.LowerName,
AvatarLink: u.AvatarLinkWithSize(ctx, 0),
HomeLink: u.HomeLink(),
Weeks: make(map[int64]*WeekData),
}
}
}
return contributorsCommitStats[userEmail]
}
func updateUserAndOverallStats(stats *api.CommitStats, commitDate string, user, total *ContributorData, isCoAuthor bool) {
startingOfWeek, _ := findLastSundayBeforeDate(commitDate)
val, _ := time.Parse(layout, startingOfWeek)
week := val.UnixMilli()
if user.Weeks[week] == nil {
user.Weeks[week] = &WeekData{
Additions: 0,
Deletions: 0,
Commits: 0,
Week: week,
}
}
if total.Weeks[week] == nil {
total.Weeks[week] = &WeekData{
Additions: 0,
Deletions: 0,
Commits: 0,
Week: week,
}
}
// Update user statistics
user.Weeks[week].Additions += stats.Additions
user.Weeks[week].Deletions += stats.Deletions
user.Weeks[week].Commits++
user.TotalCommits++
if isCoAuthor {
// We would have or will count these additions/deletions/commits already when we encounter the original
// author of the commit. Let's avoid this duplication.
return
}
// Update overall statistics
total.Weeks[week].Additions += stats.Additions
total.Weeks[week].Deletions += stats.Deletions
total.Weeks[week].Commits++
total.TotalCommits++
}

View File

@ -20,66 +20,240 @@ func TestRepository_ContributorsGraph(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
mockCache, err := cache.NewStringCache(setting.Cache{})
assert.NoError(t, err)
generateContributorStats(nil, mockCache, "key", repo, "404ref")
var data map[string]*ContributorData
_, getErr := mockCache.GetJSON("key", &data)
assert.NotNil(t, getErr)
assert.ErrorContains(t, getErr.ToError(), "object does not exist")
t.Run("non-existent revision", func(t *testing.T) {
mockCache, err := cache.NewStringCache(setting.Cache{})
assert.NoError(t, err)
generateContributorStats(nil, mockCache, "key", repo, "404ref")
var data map[string]*ContributorData
_, getErr := mockCache.GetJSON("key", &data)
assert.NotNil(t, getErr)
assert.ErrorContains(t, getErr.ToError(), "object does not exist")
})
t.Run("generate contributor stats", func(t *testing.T) {
mockCache, err := cache.NewStringCache(setting.Cache{})
assert.NoError(t, err)
generateContributorStats(nil, mockCache, "key", repo, "master")
var data map[string]*ContributorData
exist, _ := mockCache.GetJSON("key", &data)
assert.True(t, exist)
var keys []string
for k := range data {
keys = append(keys, k)
}
slices.Sort(keys)
assert.EqualValues(t, []string{
"ethantkoenig@gmail.com",
"jimmy.praet@telenet.be",
"jon@allspice.io",
"total", // generated summary
}, keys)
generateContributorStats(nil, mockCache, "key2", repo, "master")
exist, _ := mockCache.GetJSON("key2", &data)
assert.True(t, exist)
var keys []string
for k := range data {
keys = append(keys, k)
}
slices.Sort(keys)
assert.EqualValues(t, []string{
"ethantkoenig@gmail.com",
"jimmy.praet@telenet.be",
"jon@allspice.io",
"total", // generated summary
}, keys)
assert.EqualValues(t, &ContributorData{
Name: "Ethan Koenig",
AvatarLink: "https://secure.gravatar.com/avatar/b42fb195faa8c61b8d88abfefe30e9e3?d=identicon",
TotalCommits: 1,
Weeks: map[int64]*WeekData{
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26
Additions: 3,
Deletions: 0,
Commits: 1,
},
},
}, data["ethantkoenig@gmail.com"])
assert.EqualValues(t, &ContributorData{
Name: "Total",
AvatarLink: "",
TotalCommits: 3,
Weeks: map[int64]*WeekData{
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26 (2017-11-26 20:31:18 -0800)
Additions: 3,
Deletions: 0,
Commits: 1,
},
1607817600000: {
Week: 1607817600000, // sunday 2020-12-13 (2020-12-15 15:23:11 -0500)
Additions: 10,
Deletions: 0,
Commits: 1,
},
1624752000000: {
Week: 1624752000000, // sunday 2021-06-27 (2021-06-29 21:54:09 +0200)
Additions: 2,
Deletions: 0,
Commits: 1,
},
},
}, data["total"])
})
t.Run("generate contributor stats with co-authored commit", func(t *testing.T) {
mockCache, err := cache.NewStringCache(setting.Cache{})
assert.NoError(t, err)
generateContributorStats(nil, mockCache, "key", repo, "branch-with-co-author")
var data map[string]*ContributorData
exist, _ := mockCache.GetJSON("key", &data)
assert.True(t, exist)
var keys []string
for k := range data {
keys = append(keys, k)
}
slices.Sort(keys)
assert.EqualValues(t, []string{
"ethantkoenig@gmail.com",
"fizzbuzz@example.com",
"foobar@example.com",
"jimmy.praet@telenet.be",
"jon@allspice.io",
"total",
}, keys)
assert.EqualValues(t, &ContributorData{
Name: "Ethan Koenig",
AvatarLink: "https://secure.gravatar.com/avatar/b42fb195faa8c61b8d88abfefe30e9e3?d=identicon",
TotalCommits: 1,
Weeks: map[int64]*WeekData{
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26
Additions: 3,
Deletions: 0,
Commits: 1,
// make sure we can see the author of the commit
assert.EqualValues(t, &ContributorData{
Name: "Foo Bar",
AvatarLink: "https://secure.gravatar.com/avatar/0d4907cea9d97688aa7a5e722d742f71?d=identicon",
TotalCommits: 1,
Weeks: map[int64]*WeekData{
1714867200000: {
Week: 1714867200000, // sunday 2024-05-05
Additions: 1,
Deletions: 1,
Commits: 1,
},
},
},
}, data["ethantkoenig@gmail.com"])
assert.EqualValues(t, &ContributorData{
Name: "Total",
AvatarLink: "",
TotalCommits: 3,
Weeks: map[int64]*WeekData{
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26 (2017-11-26 20:31:18 -0800)
Additions: 3,
Deletions: 0,
Commits: 1,
}, data["foobar@example.com"])
// make sure that we can also see the co-author
assert.EqualValues(t, &ContributorData{
Name: "Fizz Buzz",
AvatarLink: "https://secure.gravatar.com/avatar/474e3516254f43b2337011c4ac4de421?d=identicon",
TotalCommits: 1,
Weeks: map[int64]*WeekData{
1714867200000: {
Week: 1714867200000, // sunday 2024-05-05
Additions: 1,
Deletions: 1,
Commits: 1,
},
},
1607817600000: {
Week: 1607817600000, // sunday 2020-12-13 (2020-12-15 15:23:11 -0500)
Additions: 10,
Deletions: 0,
Commits: 1,
}, data["fizzbuzz@example.com"])
// let's also make sure we don't duplicate the additions/deletions/commits counts in the overall stats that week
assert.EqualValues(t, &ContributorData{
Name: "Total",
AvatarLink: "",
TotalCommits: 4,
Weeks: map[int64]*WeekData{
1714867200000: {
Week: 1714867200000, // sunday 2024-05-05
Additions: 1,
Deletions: 1,
Commits: 1,
},
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26
Additions: 3,
Deletions: 0,
Commits: 1,
},
1607817600000: {
Week: 1607817600000, // sunday 2020-12-13
Additions: 10,
Deletions: 0,
Commits: 1,
},
1624752000000: {
Week: 1624752000000, // sunday 2021-06-27
Additions: 2,
Deletions: 0,
Commits: 1,
},
},
1624752000000: {
Week: 1624752000000, // sunday 2021-06-27 (2021-06-29 21:54:09 +0200)
Additions: 2,
Deletions: 0,
Commits: 1,
}, data["total"])
})
t.Run("generate contributor stats with commit that has duplicate co-authored lines", func(t *testing.T) {
mockCache, err := cache.NewStringCache(setting.Cache{})
assert.NoError(t, err)
generateContributorStats(nil, mockCache, "key", repo, "branch-with-duplicated-co-author-entries")
var data map[string]*ContributorData
exist, _ := mockCache.GetJSON("key", &data)
assert.True(t, exist)
var keys []string
for k := range data {
keys = append(keys, k)
}
slices.Sort(keys)
assert.EqualValues(t, []string{
"ethantkoenig@gmail.com",
"fizzbuzz@example.com",
"foobar@example.com",
"jimmy.praet@telenet.be",
"jon@allspice.io",
"total",
}, keys)
// make sure we can see the author of the commit
assert.EqualValues(t, &ContributorData{
Name: "Foo Bar",
AvatarLink: "https://secure.gravatar.com/avatar/0d4907cea9d97688aa7a5e722d742f71?d=identicon",
TotalCommits: 1,
Weeks: map[int64]*WeekData{
1715472000000: {
Week: 1715472000000, // sunday 2024-05-12
Additions: 1,
Deletions: 0,
Commits: 1,
},
},
},
}, data["total"])
}, data["foobar@example.com"])
// make sure that we can also see the co-author and that we don't see duplicated additions/deletions/commits
assert.EqualValues(t, &ContributorData{
Name: "Fizz Buzz",
AvatarLink: "https://secure.gravatar.com/avatar/474e3516254f43b2337011c4ac4de421?d=identicon",
TotalCommits: 1,
Weeks: map[int64]*WeekData{
1715472000000: {
Week: 1715472000000, // sunday 2024-05-12
Additions: 1,
Deletions: 0,
Commits: 1,
},
},
}, data["fizzbuzz@example.com"])
// let's also make sure we don't duplicate the additions/deletions/commits counts in the overall stats that week
assert.EqualValues(t, &ContributorData{
Name: "Total",
AvatarLink: "",
TotalCommits: 4,
Weeks: map[int64]*WeekData{
1715472000000: {
Week: 1715472000000, // sunday 2024-05-12
Additions: 1,
Deletions: 0,
Commits: 1,
},
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26
Additions: 3,
Deletions: 0,
Commits: 1,
},
1607817600000: {
Week: 1607817600000, // sunday 2020-12-13
Additions: 10,
Deletions: 0,
Commits: 1,
},
1624752000000: {
Week: 1624752000000, // sunday 2021-06-27
Additions: 2,
Deletions: 0,
Commits: 1,
},
},
}, data["total"])
})
}

View File

@ -289,6 +289,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
if err = DeleteUser(ctx, u, false); err != nil {
// Ignore inactive users that were ever active but then were set inactive by admin
if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) {
log.Warn("Inactive user %q has repositories, organizations or packages, skipping deletion: %v", u.Name, err)
continue
}
select {

View File

@ -3,15 +3,17 @@
<a class="{{if .PageIsExploreRepositories}}active {{end}}item" href="{{AppSubUrl}}/explore/repos">
{{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}}
</a>
{{if not .UsersIsDisabled}}
{{if not .UsersPageIsDisabled}}
<a class="{{if .PageIsExploreUsers}}active {{end}}item" href="{{AppSubUrl}}/explore/users">
{{svg "octicon-person"}} {{ctx.Locale.Tr "explore.users"}}
</a>
{{end}}
{{if not .OrganizationsPageIsDisabled}}
<a class="{{if .PageIsExploreOrganizations}}active {{end}}item" href="{{AppSubUrl}}/explore/organizations">
{{svg "octicon-organization"}} {{ctx.Locale.Tr "explore.organizations"}}
</a>
{{if and (not ctx.Consts.RepoUnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled}}
{{end}}
{{if and (not ctx.Consts.RepoUnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled (not .CodePageIsDisabled)}}
<a class="{{if .PageIsExploreCode}}active {{end}}item" href="{{AppSubUrl}}/explore/code">
{{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}}
</a>

View File

@ -164,7 +164,7 @@
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
{{/* admin can merge without checks, writer can merge when checks succeed */}}
{{$canMergeNow := and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
{{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
{{/* admin and writer both can make an auto merge schedule */}}
{{if $canMergeNow}}

View File

@ -323,6 +323,13 @@
<p class="help">{{ctx.Locale.Tr "repo.settings.block_outdated_branch_desc"}}</p>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input name="block_admin_merge_override" type="checkbox" {{if .Rule.BlockAdminMergeOverride}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.block_admin_merge_override"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.block_admin_merge_override_desc"}}</p>
</div>
</div>
<div class="divider"></div>
<div class="field">

View File

@ -18771,6 +18771,10 @@
},
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
@ -19466,6 +19470,10 @@
},
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
@ -20685,6 +20693,10 @@
},
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"

View File

@ -0,0 +1 @@
xuŽÁJ1E]ç+ÞLIÒfÈPJ…"¸vãî½äERÐùzS\¹ps¹÷Â<C3B7>JÎsmLJV™A…„ÌnTí8*<2A>":oùE¶„{«G%}+V¾6Pr¯‰²‰Œ>xCdÊE$vÉïµuVJ<56>·öQ*\J<>3V˜R)„õÄ_˜×…w¡ä#(«Œ¶Æi ƒ¼Cýív<C3AD>+¼pÆÞ¸33LŸ÷µ1U<½gœ—pñºFl Ï%ó.G!žÊðëÁq ïG¸ÌÛç[<5B>)õJ½ý1?øçYa

View File

@ -0,0 +1 @@
xм▌мJд0┘]В)Н tHзФO┼ # ┌k7ННMnфbcJх─ЖИмЮй┘{7┤s|ПЫ°рRaф╩Z≤│)■▐йЙ╖╗▒╫fIч#] я &╚╖nцб╒(M$/┌СN╘Iз─д6ZОэ`╛╒цk}кн9ц л1gбrДOLшй÷сH#∙жй9 ╫╦AМmv∙ <sб^╧1 лО╥╣3<^.КxВ O9Я!┘╝{лЩ▐┤·╬НА╪Л;°╝-Фь*╣ЖкХ?ъМ╩▐

View File

@ -0,0 +1 @@
x+)JMU040g040031QðÈÏMÕËMa˜”*¼8<C2BC>_±—wŠÛÿøú*O¹UMÅP%9™y©zÅeé ÉS·õÉ¿Û-ç“Zí??{Qz† 7TMIjq‰^EnƒÚ§=kÍ9,Ümrzë;Åy<C385>cÛl˸*Ú

View File

@ -0,0 +1,2 @@
x5х1 PgNЯ┴ЁЁ╝nю h( P#╞/▀и⌡·кЙ╟╜с▄C Ц╒хф°▓├. Ышb╞aтЬ°*Ц╔/T#
┌в┘·.z▐ПZJЙж|╝.W

View File

@ -0,0 +1 @@
26442ad16af268ef4768a5a30db377e860f504a3

View File

@ -0,0 +1 @@
79a64f10ca2ea21fa2a94102751f0dd9f2c15ce8

View File

@ -976,3 +976,50 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
})
}
func TestPullNonMergeForAdminWithBranchProtection(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
// create a pull request
session := loginUser(t, "user1")
forkedName := "repo1-1"
testRepoFork(t, session, "user2", "repo1", "user1", forkedName, "")
defer testDeleteRepository(t, session, "user1", forkedName)
testEditFile(t, session, "user1", forkedName, "master", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", forkedName, false, "master", "master", "Indexer notifier test pull")
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: forkedName})
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
BaseRepoID: baseRepo.ID,
BaseBranch: "master",
HeadRepoID: forkedRepo.ID,
HeadBranch: "master",
})
// add protected branch for commit status
csrf := GetUserCSRFToken(t, session)
// Change master branch to protected
pbCreateReq := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
"_csrf": csrf,
"rule_name": "master",
"enable_push": "true",
"enable_status_check": "true",
"status_check_contexts": "gitea/actions",
"block_admin_merge_override": "true",
})
session.MakeRequest(t, pbCreateReq, http.StatusSeeOther)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
mergeReq := NewRequestWithValues(t, "POST", "/api/v1/repos/user2/repo1/pulls/6/merge", map[string]string{
"_csrf": csrf,
"head_commit_id": "",
"merge_when_checks_succeed": "false",
"force_merge": "true",
"do": "rebase",
}).AddTokenAuth(token)
session.MakeRequest(t, mergeReq, http.StatusMethodNotAllowed)
})
}

View File

@ -3,14 +3,19 @@ import '@github/text-expander-element';
import $ from 'jquery';
import {attachTribute} from '../tribute.ts';
import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.ts';
import {initEasyMDEPaste, initTextareaEvents} from './EditorUpload.ts';
import {
EventUploadStateChanged,
initEasyMDEPaste,
initTextareaEvents,
triggerUploadStateChanged,
} from './EditorUpload.ts';
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.ts';
import {renderPreviewPanelContent} from '../repo-editor.ts';
import {easyMDEToolbarActions} from './EasyMDEToolbarActions.ts';
import {initTextExpander} from './TextExpander.ts';
import {showErrorToast} from '../../modules/toast.ts';
import {POST} from '../../modules/fetch.ts';
import {initTextareaMarkdown} from './EditorMarkdown.ts';
import {EventEditorContentChanged, initTextareaMarkdown, triggerEditorContentChanged} from './EditorMarkdown.ts';
import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
let elementIdCounter = 0;
@ -37,7 +42,34 @@ export function validateTextareaNonEmpty(textarea) {
return true;
}
class ComboMarkdownEditor {
export class ComboMarkdownEditor {
static EventEditorContentChanged = EventEditorContentChanged;
static EventUploadStateChanged = EventUploadStateChanged;
public container : HTMLElement;
// TODO: use correct types to replace these "any" types
options: any;
tabEditor: HTMLElement;
tabPreviewer: HTMLElement;
easyMDE: any;
easyMDEToolbarActions: any;
easyMDEToolbarDefault: any;
textarea: HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
textareaMarkdownToolbar: HTMLElement;
textareaAutosize: any;
dropzone: HTMLElement;
attachedDropzoneInst: any;
previewUrl: string;
previewContext: string;
previewMode: string;
previewWiki: boolean;
constructor(container, options = {}) {
container._giteaComboMarkdownEditor = this;
this.options = options;
@ -63,14 +95,13 @@ class ComboMarkdownEditor {
setupContainer() {
initTextExpander(this.container.querySelector('text-expander'));
this.container.addEventListener('ce-editor-content-changed', (e) => this.options?.onContentChanged?.(this, e));
}
setupTextarea() {
this.textarea = this.container.querySelector('.markdown-text-editor');
this.textarea._giteaComboMarkdownEditor = this;
this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`;
this.textarea.addEventListener('input', (e) => this.options?.onContentChanged?.(this, e));
this.textarea.addEventListener('input', () => triggerEditorContentChanged(this.container));
this.applyEditorHeights(this.textarea, this.options.editorHeights);
if (this.textarea.getAttribute('data-disable-autosize') !== 'true') {
@ -115,15 +146,21 @@ class ComboMarkdownEditor {
async setupDropzone() {
const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
if (dropzoneParentContainer) {
this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone');
if (this.dropzone) this.attachedDropzoneInst = await initDropzone(this.dropzone);
}
if (!dropzoneParentContainer) return;
this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone');
if (!this.dropzone) return;
this.attachedDropzoneInst = await initDropzone(this.dropzone);
// dropzone events
// * "processing" means a file is being uploaded
// * "queuecomplete" means all files have been uploaded
this.attachedDropzoneInst.on('processing', () => triggerUploadStateChanged(this.container));
this.attachedDropzoneInst.on('queuecomplete', () => triggerUploadStateChanged(this.container));
}
dropzoneGetFiles() {
if (!this.dropzone) return null;
return Array.from(this.dropzone.querySelectorAll('.files [name=files]'), (el) => el.value);
return Array.from(this.dropzone.querySelectorAll<HTMLInputElement>('.files [name=files]'), (el) => el.value);
}
dropzoneReloadFiles() {
@ -137,8 +174,13 @@ class ComboMarkdownEditor {
this.attachedDropzoneInst.emit(DropzoneCustomEventReloadFiles);
}
isUploading() {
if (!this.dropzone) return false;
return this.attachedDropzoneInst.getQueuedFiles().length || this.attachedDropzoneInst.getUploadingFiles().length;
}
setupTab() {
const tabs = this.container.querySelectorAll('.tabular.menu > .item');
const tabs = this.container.querySelectorAll<HTMLElement>('.tabular.menu > .item');
// Fomantic Tab requires the "data-tab" to be globally unique.
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
@ -170,7 +212,7 @@ class ComboMarkdownEditor {
formData.append('mode', this.previewMode);
formData.append('context', this.previewContext);
formData.append('text', this.value());
formData.append('wiki', this.previewWiki);
formData.append('wiki', String(this.previewWiki));
const response = await POST(this.previewUrl, {data: formData});
const data = await response.text();
renderPreviewPanelContent($(panelPreviewer), data);
@ -237,24 +279,24 @@ class ComboMarkdownEditor {
easyMDEOpt.toolbar = this.parseEasyMDEToolbar(EasyMDE, easyMDEOpt.toolbar ?? this.easyMDEToolbarDefault);
this.easyMDE = new EasyMDE(easyMDEOpt);
this.easyMDE.codemirror.on('change', (...args) => {this.options?.onContentChanged?.(this, ...args)});
this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container));
this.easyMDE.codemirror.setOption('extraKeys', {
'Cmd-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
'Ctrl-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
Enter: (cm) => {
const tributeContainer = document.querySelector('.tribute-container');
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
if (!tributeContainer || tributeContainer.style.display === 'none') {
cm.execCommand('newlineAndIndent');
}
},
Up: (cm) => {
const tributeContainer = document.querySelector('.tribute-container');
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
if (!tributeContainer || tributeContainer.style.display === 'none') {
return cm.execCommand('goLineUp');
}
},
Down: (cm) => {
const tributeContainer = document.querySelector('.tribute-container');
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
if (!tributeContainer || tributeContainer.style.display === 'none') {
return cm.execCommand('goLineDown');
}
@ -314,13 +356,7 @@ export function getComboMarkdownEditor(el) {
return el?._giteaComboMarkdownEditor;
}
export async function initComboMarkdownEditor(container, options = {}) {
if (container instanceof $) {
if (container.length !== 1) {
throw new Error('initComboMarkdownEditor: container must be a single element');
}
container = container[0];
}
export async function initComboMarkdownEditor(container: HTMLElement, options = {}) {
if (!container) {
throw new Error('initComboMarkdownEditor: container is null');
}

View File

@ -1,5 +1,7 @@
export const EventEditorContentChanged = 'ce-editor-content-changed';
export function triggerEditorContentChanged(target) {
target.dispatchEvent(new CustomEvent('ce-editor-content-changed', {bubbles: true}));
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
}
function handleIndentSelection(textarea, e) {

View File

@ -7,9 +7,16 @@ import {
DropzoneCustomEventUploadDone,
generateMarkdownLinkForAttachment,
} from '../dropzone.ts';
import type CodeMirror from 'codemirror';
let uploadIdCounter = 0;
export const EventUploadStateChanged = 'ce-upload-state-changed';
export function triggerUploadStateChanged(target) {
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true}));
}
function uploadFile(dropzoneEl, file) {
return new Promise((resolve) => {
const curUploadId = uploadIdCounter++;
@ -18,7 +25,7 @@ function uploadFile(dropzoneEl, file) {
const onUploadDone = ({file}) => {
if (file._giteaUploadId === curUploadId) {
dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone);
resolve();
resolve(file);
}
};
dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone);
@ -27,6 +34,8 @@ function uploadFile(dropzoneEl, file) {
}
class TextareaEditor {
editor : HTMLTextAreaElement;
constructor(editor) {
this.editor = editor;
}
@ -61,6 +70,8 @@ class TextareaEditor {
}
class CodeMirrorEditor {
editor: CodeMirror.EditorFromTextArea;
constructor(editor) {
this.editor = editor;
}

View File

@ -8,41 +8,38 @@ export function initCompSearchUserBox() {
const searchUserBox = document.querySelector('#search-user-box');
if (!searchUserBox) return;
const $searchUserBox = $(searchUserBox);
const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true';
const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined;
$searchUserBox.search({
$(searchUserBox).search({
minCharacters: 2,
apiSettings: {
url: `${appSubUrl}/user/search?active=1&q={query}`,
url: `${appSubUrl}/user/search_candidates?q={query}`,
onResponse(response) {
const items = [];
const searchQuery = $searchUserBox.find('input').val();
const resultItems = [];
const searchQuery = searchUserBox.querySelector('input').value;
const searchQueryUppercase = searchQuery.toUpperCase();
$.each(response.data, (_i, item) => {
for (const item of response.data) {
const resultItem = {
title: item.login,
image: item.avatar_url,
description: htmlEscape(item.full_name),
};
if (item.full_name) {
resultItem.description = htmlEscape(item.full_name);
}
if (searchQueryUppercase === item.login.toUpperCase()) {
items.unshift(resultItem);
resultItems.unshift(resultItem); // add the exact match to the top
} else {
items.push(resultItem);
resultItems.push(resultItem);
}
});
}
if (allowEmailInput && !items.length && looksLikeEmailAddressCheck.test(searchQuery)) {
if (allowEmailInput && !resultItems.length && looksLikeEmailAddressCheck.test(searchQuery)) {
const resultItem = {
title: searchQuery,
description: allowEmailDescription,
};
items.push(resultItem);
resultItems.push(resultItem);
}
return {results: items};
return {results: resultItems};
},
},
searchFields: ['login', 'full_name'],

View File

@ -128,10 +128,12 @@ export async function initDropzone(dropzoneEl) {
fileUuidDict = {};
for (const attachment of respData) {
const file = {name: attachment.name, uuid: attachment.uuid, size: attachment.size};
const imgSrc = `${attachmentBaseLinkUrl}/${file.uuid}`;
dzInst.emit('addedfile', file);
dzInst.emit('thumbnail', file, imgSrc);
dzInst.emit('complete', file);
if (isImageFile(file.name)) {
const imgSrc = `${attachmentBaseLinkUrl}/${file.uuid}`;
dzInst.emit('thumbnail', file, imgSrc);
}
addCopyLink(file); // it is from server response, so no "type"
fileUuidDict[file.uuid] = {submitted: true};
const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${file.uuid}`, value: file.uuid});

View File

@ -1,11 +1,12 @@
import $ from 'jquery';
import {handleReply} from './repo-issue.ts';
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {hideElem, showElem} from '../utils/dom.ts';
import {attachRefIssueContextPopup} from './contextpopup.ts';
import {initCommentContent, initMarkupContent} from '../markup/content.ts';
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
async function onEditContent(event) {
event.preventDefault();
@ -15,7 +16,7 @@ async function onEditContent(event) {
const renderContent = segment.querySelector('.render-content');
const rawContent = segment.querySelector('.raw-content');
let comboMarkdownEditor;
let comboMarkdownEditor : ComboMarkdownEditor;
const cancelAndReset = (e) => {
e.preventDefault();
@ -79,9 +80,12 @@ async function onEditContent(event) {
comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
if (!comboMarkdownEditor) {
editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML;
const saveButton = editContentZone.querySelector('.ui.primary.button');
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading();
comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
editContentZone.querySelector('.ui.cancel.button').addEventListener('click', cancelAndReset);
editContentZone.querySelector('.ui.primary.button').addEventListener('click', saveAndRefresh);
saveButton.addEventListener('click', saveAndRefresh);
}
// Show write/preview tab and copy raw content as needed
@ -93,6 +97,7 @@ async function onEditContent(event) {
}
comboMarkdownEditor.switchTabToEditor();
comboMarkdownEditor.focus();
triggerUploadStateChanged(comboMarkdownEditor.container);
}
export function initRepoIssueCommentEdit() {

View File

@ -3,7 +3,7 @@ import {htmlEscape} from 'escape-goat';
import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts';
import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
import {setFileFolding} from './file-fold.ts';
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
import {toAbsoluteUrl} from '../utils.ts';
import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
@ -483,9 +483,9 @@ export function initRepoPullRequestReview() {
await handleReply(this);
});
const $reviewBox = $('.review-box-panel');
if ($reviewBox.length === 1) {
const _promise = initComboMarkdownEditor($reviewBox.find('.combo-markdown-editor'));
const elReviewBox = document.querySelector('.review-box-panel');
if (elReviewBox) {
initComboMarkdownEditor(elReviewBox.querySelector('.combo-markdown-editor'));
}
// The following part is only for diff views
@ -548,7 +548,7 @@ export function initRepoPullRequestReview() {
$td.find("input[name='line']").val(idx);
$td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
$td.find("input[name='path']").val(path);
const editor = await initComboMarkdownEditor($td.find('.combo-markdown-editor'));
const editor = await initComboMarkdownEditor($td[0].querySelector('.combo-markdown-editor'));
editor.focus();
} catch (error) {
console.error(error);
@ -669,20 +669,22 @@ export async function initSingleCommentEditor($commentForm) {
// pages:
// * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
// * issue/pr view page: with comment form, has status-button and comment-button
const opts = {};
const statusButton = document.querySelector('#status-button');
const commentButton = document.querySelector('#comment-button');
opts.onContentChanged = (editor) => {
const editorText = editor.value().trim();
const editor = await initComboMarkdownEditor($commentForm[0].querySelector('.combo-markdown-editor'));
const statusButton = document.querySelector<HTMLButtonElement>('#status-button');
const commentButton = document.querySelector<HTMLButtonElement>('#comment-button');
const syncUiState = () => {
const editorText = editor.value().trim(), isUploading = editor.isUploading();
if (statusButton) {
statusButton.textContent = statusButton.getAttribute(editorText ? 'data-status-and-comment' : 'data-status');
statusButton.disabled = isUploading;
}
if (commentButton) {
commentButton.disabled = !editorText;
commentButton.disabled = !editorText || isUploading;
}
};
const editor = await initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts);
opts.onContentChanged(editor); // sync state of buttons with the initial content
editor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, syncUiState);
syncUiState();
}
export function initIssueTemplateCommentEditors($commentForm) {
@ -690,16 +692,13 @@ export function initIssueTemplateCommentEditors($commentForm) {
// * new issue with issue template
const $comboFields = $commentForm.find('.combo-editor-dropzone');
const initCombo = async ($combo) => {
const $dropzoneContainer = $combo.find('.form-field-dropzone');
const $formField = $combo.find('.form-field-real');
const $markdownEditor = $combo.find('.combo-markdown-editor');
const initCombo = async (elCombo) => {
const $formField = $(elCombo.querySelector('.form-field-real'));
const dropzoneContainer = elCombo.querySelector('.form-field-dropzone');
const markdownEditor = elCombo.querySelector('.combo-markdown-editor');
const editor = await initComboMarkdownEditor($markdownEditor, {
onContentChanged: (editor) => {
$formField.val(editor.value());
},
});
const editor = await initComboMarkdownEditor(markdownEditor);
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value()));
$formField.on('focus', async () => {
// deactivate all markdown editors
@ -709,8 +708,8 @@ export function initIssueTemplateCommentEditors($commentForm) {
// activate this markdown editor
hideElem($formField);
showElem($markdownEditor);
showElem($dropzoneContainer);
showElem(markdownEditor);
showElem(dropzoneContainer);
await editor.switchToUserPreference();
editor.focus();
@ -718,7 +717,7 @@ export function initIssueTemplateCommentEditors($commentForm) {
};
for (const el of $comboFields) {
initCombo($(el));
initCombo(el);
}
}

View File

@ -50,7 +50,7 @@ function initTagNameEditor() {
}
function initRepoReleaseEditor() {
const editor = document.querySelector('.repository.new.release .combo-markdown-editor');
const editor = document.querySelector<HTMLElement>('.repository.new.release .combo-markdown-editor');
if (!editor) {
return;
}

View File

@ -4,11 +4,11 @@ import {fomanticMobileScreen} from '../modules/fomantic.ts';
import {POST} from '../modules/fetch.ts';
async function initRepoWikiFormEditor() {
const editArea = document.querySelector('.repository.wiki .combo-markdown-editor textarea');
const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea');
if (!editArea) return;
const form = document.querySelector('.repository.wiki.new .ui.form');
const editorContainer = form.querySelector('.combo-markdown-editor');
const editorContainer = form.querySelector<HTMLElement>('.combo-markdown-editor');
let editor;
let renderRequesting = false;