mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-13 02:02:53 +09:00
Compare commits
14 Commits
v1.25.1
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d9ae7ac23 | ||
|
|
01873a99c1 | ||
|
|
ce70863793 | ||
|
|
327f2207dc | ||
|
|
db876d8f17 | ||
|
|
2b71bf283b | ||
|
|
1ca4fef611 | ||
|
|
70ee6b9029 | ||
|
|
e5b404ec53 | ||
|
|
5842cd23a6 | ||
|
|
289bd9694b | ||
|
|
154d7521a5 | ||
|
|
24189dcced | ||
|
|
f84bf259ad |
@@ -567,6 +567,11 @@ ENABLED = true
|
|||||||
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
|
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
|
||||||
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
|
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
|
||||||
;;
|
;;
|
||||||
|
;; The "issuer" claim identifies the principal that issued the JWT.
|
||||||
|
;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
|
||||||
|
;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
|
||||||
|
;JWT_CLAIM_ISSUER =
|
||||||
|
;;
|
||||||
;; Lifetime of an OAuth2 access token in seconds
|
;; Lifetime of an OAuth2 access token in seconds
|
||||||
;ACCESS_TOKEN_EXPIRATION_TIME = 3600
|
;ACCESS_TOKEN_EXPIRATION_TIME = 3600
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -466,11 +466,13 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c
|
|||||||
return currentWhitelist, nil
|
return currentWhitelist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prUserIDs, err := access_model.GetUserIDsWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
whitelist = make([]int64, 0, len(newWhitelist))
|
whitelist = make([]int64, 0, len(newWhitelist))
|
||||||
for _, userID := range newWhitelist {
|
for _, userID := range newWhitelist {
|
||||||
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
|
if !prUserIDs.Contains(userID) {
|
||||||
return nil, err
|
|
||||||
} else if !reader {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
whitelist = append(whitelist, userID)
|
whitelist = append(whitelist, userID)
|
||||||
|
|||||||
@@ -53,24 +53,45 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
|
|||||||
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
||||||
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
|
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
|
||||||
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
|
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
|
||||||
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) {
|
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teams []*Team, err error) {
|
||||||
teams := make([]*Team, 0, 5)
|
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(teamIDs) == 0 {
|
||||||
|
return teams, nil
|
||||||
|
}
|
||||||
|
err = db.GetEngine(ctx).Where(builder.In("id", teamIDs)).OrderBy("team.name").Find(&teams)
|
||||||
|
return teams, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTeamIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teamIDs []int64, err error) {
|
||||||
sub := builder.Select("team_id").From("team_unit").
|
sub := builder.Select("team_id").From("team_unit").
|
||||||
Where(builder.Expr("team_unit.team_id = team.id")).
|
Where(builder.Expr("team_unit.team_id = team.id")).
|
||||||
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
|
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
|
||||||
And(builder.Expr("team_unit.access_mode >= ?", mode))
|
And(builder.Expr("team_unit.access_mode >= ?", mode))
|
||||||
|
|
||||||
err := db.GetEngine(ctx).
|
err = db.GetEngine(ctx).
|
||||||
|
Select("team.id").
|
||||||
|
Table("team").
|
||||||
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||||
And("team_repo.org_id = ?", orgID).
|
And("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID).
|
||||||
And("team_repo.repo_id = ?", repoID).
|
|
||||||
And(builder.Or(
|
And(builder.Or(
|
||||||
builder.Expr("team.authorize >= ?", mode),
|
builder.Expr("team.authorize >= ?", mode),
|
||||||
builder.In("team.id", sub),
|
builder.In("team.id", sub),
|
||||||
)).
|
)).
|
||||||
OrderBy("name").
|
Find(&teamIDs)
|
||||||
Find(&teams)
|
return teamIDs, err
|
||||||
|
}
|
||||||
return teams, err
|
|
||||||
|
func GetTeamUserIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (userIDs []int64, err error) {
|
||||||
|
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(teamIDs) == 0 {
|
||||||
|
return userIDs, nil
|
||||||
|
}
|
||||||
|
err = db.GetEngine(ctx).Table("team_user").Select("uid").Where(builder.In("team_id", teamIDs)).Find(&userIDs)
|
||||||
|
return userIDs, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@@ -458,54 +459,44 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi
|
|||||||
return perm.HasAnyUnitAccess(), nil
|
return perm.HasAnyUnitAccess(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
|
func GetUsersWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (users []*user_model.User, err error) {
|
||||||
func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
|
userIDs, err := GetUserIDsWithUnitAccess(ctx, repo, mode, unitType)
|
||||||
if err = repo.LoadOwner(ctx); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(userIDs) == 0 {
|
||||||
e := db.GetEngine(ctx)
|
return users, nil
|
||||||
accesses := make([]*Access, 0, 10)
|
}
|
||||||
if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
|
if err = db.GetEngine(ctx).In("id", userIDs.Values()).OrderBy("`name`").Find(&users); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
|
||||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
|
||||||
users := make([]*user_model.User, 0, len(accesses)+1)
|
|
||||||
if len(accesses) > 0 {
|
|
||||||
userIDs := make([]int64, len(accesses))
|
|
||||||
for i := 0; i < len(accesses); i++ {
|
|
||||||
userIDs[i] = accesses[i].UserID
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = e.In("id", userIDs).Find(&users); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !repo.Owner.IsOrganization() {
|
|
||||||
users = append(users, repo.Owner)
|
|
||||||
}
|
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepoReaders returns all users that have explicit read access or higher to the repository.
|
func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (container.Set[int64], error) {
|
||||||
func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
|
userIDs := container.Set[int64]{}
|
||||||
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead)
|
e := db.GetEngine(ctx)
|
||||||
|
accesses := make([]*Access, 0, 10)
|
||||||
|
if err := e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, a := range accesses {
|
||||||
|
userIDs.Add(a.UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepoWriters returns all users that have write access to the repository.
|
if err := repo.LoadOwner(ctx); err != nil {
|
||||||
func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
|
return nil, err
|
||||||
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite)
|
|
||||||
}
|
}
|
||||||
|
if !repo.Owner.IsOrganization() {
|
||||||
// IsRepoReader returns true if user has explicit read access or higher to the repository.
|
userIDs.Add(repo.Owner.ID)
|
||||||
func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
|
} else {
|
||||||
if repo.OwnerID == userID {
|
teamUserIDs, err := organization.GetTeamUserIDsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, mode, unitType)
|
||||||
return true, nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
|
userIDs.AddMultiple(teamUserIDs...)
|
||||||
|
}
|
||||||
|
return userIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRepoUnitUser check whether user could visit the unit of this repository
|
// CheckRepoUnitUser check whether user could visit the unit of this repository
|
||||||
|
|||||||
@@ -169,9 +169,9 @@ func TestGetUserRepoPermission(t *testing.T) {
|
|||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
|
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
|
||||||
require.NoError(t, db.Insert(ctx, team))
|
require.NoError(t, db.Insert(ctx, team))
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
|
||||||
|
|
||||||
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
|
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
|
||||||
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
|
|
||||||
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||||
@@ -219,6 +219,15 @@ func TestGetUserRepoPermission(t *testing.T) {
|
|||||||
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
|
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
|
||||||
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
|
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
|
||||||
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
||||||
|
|
||||||
|
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeRead, unit.TypeIssues)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, users, 1)
|
||||||
|
assert.Equal(t, user.ID, users[0].ID)
|
||||||
|
|
||||||
|
users, err = GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
|
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
|
||||||
@@ -229,5 +238,10 @@ func TestGetUserRepoPermission(t *testing.T) {
|
|||||||
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
|
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
|
||||||
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
|
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
|
||||||
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
|
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
|
||||||
|
|
||||||
|
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, users, 1)
|
||||||
|
assert.Equal(t, user.ID, users[0].ID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,17 @@ type TreeEntry struct {
|
|||||||
gogitTreeEntry *object.TreeEntry
|
gogitTreeEntry *object.TreeEntry
|
||||||
ptree *Tree
|
ptree *Tree
|
||||||
|
|
||||||
|
fullName string
|
||||||
|
|
||||||
size int64
|
size int64
|
||||||
sized bool
|
sized bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the name of the entry
|
// Name returns the name of the entry
|
||||||
func (te *TreeEntry) Name() string {
|
func (te *TreeEntry) Name() string {
|
||||||
|
if te.fullName != "" {
|
||||||
|
return te.fullName
|
||||||
|
}
|
||||||
return te.gogitTreeEntry.Name
|
return te.gogitTreeEntry.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
|||||||
seen := map[plumbing.Hash]bool{}
|
seen := map[plumbing.Hash]bool{}
|
||||||
walker := object.NewTreeWalker(t.gogitTree, true, seen)
|
walker := object.NewTreeWalker(t.gogitTree, true, seen)
|
||||||
for {
|
for {
|
||||||
_, entry, err := walker.Next()
|
fullName, entry, err := walker.Next()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -84,6 +84,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
|||||||
ID: ParseGogitHash(entry.Hash),
|
ID: ParseGogitHash(entry.Hash),
|
||||||
gogitTreeEntry: &entry,
|
gogitTreeEntry: &entry,
|
||||||
ptree: t,
|
ptree: t,
|
||||||
|
fullName: fullName,
|
||||||
}
|
}
|
||||||
entries = append(entries, convertedEntry)
|
entries = append(entries, convertedEntry)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ var OAuth2 = struct {
|
|||||||
InvalidateRefreshTokens bool
|
InvalidateRefreshTokens bool
|
||||||
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
|
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
|
||||||
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
|
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
|
||||||
|
JWTClaimIssuer string `ini:"JWT_CLAIM_ISSUER"`
|
||||||
MaxTokenLength int
|
MaxTokenLength int
|
||||||
DefaultApplications []string
|
DefaultApplications []string
|
||||||
}{
|
}{
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ func (a *AzureBlobStorage) Delete(path string) error {
|
|||||||
func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
|
func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
|
||||||
blobClient := a.getBlobClient(path)
|
blobClient := a.getBlobClient(path)
|
||||||
|
|
||||||
|
// TODO: OBJECT-STORAGE-CONTENT-TYPE: "browser inline rendering images/PDF" needs proper Content-Type header from storage
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
u, err := blobClient.GetSASURL(sas.BlobPermissions{
|
u, err := blobClient.GetSASURL(sas.BlobPermissions{
|
||||||
Read: true,
|
Read: true,
|
||||||
|
|||||||
@@ -279,20 +279,44 @@ func (m *MinioStorage) Delete(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
|
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
|
||||||
func (m *MinioStorage) URL(path, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
|
func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
|
||||||
// copy serveDirectReqParams
|
// copy serveDirectReqParams
|
||||||
reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
|
reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we?
|
|
||||||
reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"")
|
// Here we might not know the real filename, and it's quite inefficient to detect the mine type by pre-fetching the object head.
|
||||||
|
// So we just do a quick detection by extension name, at least if works for the "View Raw File" for an LFS file on the Web UI.
|
||||||
|
// Detect content type by extension name, only support the well-known safe types for inline rendering.
|
||||||
|
// TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future
|
||||||
|
ext := path.Ext(name)
|
||||||
|
inlineExtMimeTypes := map[string]string{
|
||||||
|
".png": "image/png",
|
||||||
|
".jpg": "image/jpeg",
|
||||||
|
".jpeg": "image/jpeg",
|
||||||
|
".gif": "image/gif",
|
||||||
|
".webp": "image/webp",
|
||||||
|
".avif": "image/avif",
|
||||||
|
// ATTENTION! Don't support unsafe types like HTML/SVG due to security concerns: they can contain JS code, and maybe they need proper Content-Security-Policy
|
||||||
|
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context, it seems fine to render it inline
|
||||||
|
".pdf": "application/pdf",
|
||||||
|
|
||||||
|
// TODO: refactor with "modules/public/mime_types.go", for example: "DetectWellKnownSafeInlineMimeType"
|
||||||
|
}
|
||||||
|
if mimeType, ok := inlineExtMimeTypes[ext]; ok {
|
||||||
|
reqParams.Set("response-content-type", mimeType)
|
||||||
|
reqParams.Set("response-content-disposition", "inline")
|
||||||
|
} else {
|
||||||
|
reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name)))
|
||||||
|
}
|
||||||
|
|
||||||
expires := 5 * time.Minute
|
expires := 5 * time.Minute
|
||||||
if method == http.MethodHead {
|
if method == http.MethodHead {
|
||||||
u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
|
u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
|
||||||
return u, convertMinioErr(err)
|
return u, convertMinioErr(err)
|
||||||
}
|
}
|
||||||
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
|
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
|
||||||
return u, convertMinioErr(err)
|
return u, convertMinioErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ func EnumeratePackages(ctx *context.Context) {
|
|||||||
Timestamp: fileMetadata.Timestamp,
|
Timestamp: fileMetadata.Timestamp,
|
||||||
Build: fileMetadata.Build,
|
Build: fileMetadata.Build,
|
||||||
BuildNumber: fileMetadata.BuildNumber,
|
BuildNumber: fileMetadata.BuildNumber,
|
||||||
Dependencies: fileMetadata.Dependencies,
|
Dependencies: util.SliceNilAsEmpty(fileMetadata.Dependencies),
|
||||||
License: versionMetadata.License,
|
License: versionMetadata.License,
|
||||||
LicenseFamily: versionMetadata.LicenseFamily,
|
LicenseFamily: versionMetadata.LicenseFamily,
|
||||||
HashMD5: pfd.Blob.HashMD5,
|
HashMD5: pfd.Blob.HashMD5,
|
||||||
|
|||||||
@@ -897,7 +897,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
|||||||
} else {
|
} else {
|
||||||
whitelistUsers = protectBranch.WhitelistUserIDs
|
whitelistUsers = protectBranch.WhitelistUserIDs
|
||||||
}
|
}
|
||||||
if form.ForcePushAllowlistDeployKeys != nil {
|
if form.ForcePushAllowlistUsernames != nil {
|
||||||
forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
|
forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if user_model.IsErrUserNotExist(err) {
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
|||||||
@@ -369,11 +369,11 @@ func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
|
|||||||
},
|
},
|
||||||
Signoff: commonOpts.Signoff,
|
Signoff: commonOpts.Signoff,
|
||||||
}
|
}
|
||||||
if commonOpts.Dates.Author.IsZero() {
|
if changeFileOpts.Dates.Author.IsZero() {
|
||||||
commonOpts.Dates.Author = time.Now()
|
changeFileOpts.Dates.Author = time.Now()
|
||||||
}
|
}
|
||||||
if commonOpts.Dates.Committer.IsZero() {
|
if changeFileOpts.Dates.Committer.IsZero() {
|
||||||
commonOpts.Dates.Committer = time.Now()
|
changeFileOpts.Dates.Committer = time.Now()
|
||||||
}
|
}
|
||||||
ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
|
ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,6 +436,7 @@ func ViewProject(ctx *context.Context) {
|
|||||||
ctx.Data["Project"] = project
|
ctx.Data["Project"] = project
|
||||||
ctx.Data["IssuesMap"] = issuesMap
|
ctx.Data["IssuesMap"] = issuesMap
|
||||||
ctx.Data["Columns"] = columns
|
ctx.Data["Columns"] = columns
|
||||||
|
ctx.Data["Title"] = fmt.Sprintf("%s - %s", project.Title, ctx.ContextUser.DisplayName())
|
||||||
|
|
||||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||||
ctx.ServerError("RenderUserOrgHeader", err)
|
ctx.ServerError("RenderUserOrgHeader", err)
|
||||||
|
|||||||
@@ -73,10 +73,9 @@ func SettingsProtectedBranch(c *context.Context) {
|
|||||||
|
|
||||||
c.Data["PageIsSettingsBranches"] = true
|
c.Data["PageIsSettingsBranches"] = true
|
||||||
c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName
|
c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName
|
||||||
|
users, err := access_model.GetUsersWithUnitAccess(c, c.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
|
||||||
users, err := access_model.GetRepoReaders(c, c.Repo.Repository)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ServerError("Repo.Repository.GetReaders", err)
|
c.ServerError("GetUsersWithUnitAccess", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Data["Users"] = users
|
c.Data["Users"] = users
|
||||||
|
|||||||
@@ -149,9 +149,9 @@ func setTagsContext(ctx *context.Context) error {
|
|||||||
}
|
}
|
||||||
ctx.Data["ProtectedTags"] = protectedTags
|
ctx.Data["ProtectedTags"] = protectedTags
|
||||||
|
|
||||||
users, err := access_model.GetRepoReaders(ctx, ctx.Repo.Repository)
|
users, err := access_model.GetUsersWithUnitAccess(ctx, ctx.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Repo.Repository.GetReaders", err)
|
ctx.ServerError("GetUsersWithUnitAccess", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Data["Users"] = users
|
ctx.Data["Users"] = users
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) (buf []b
|
|||||||
|
|
||||||
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
|
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
|
||||||
if err != nil { // fallback to a plain file
|
if err != nil { // fallback to a plain file
|
||||||
|
fi.lfsMeta = &pointer
|
||||||
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
|
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
|
||||||
return buf, dataRc, fi, nil
|
return buf, dataRc, fi, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,9 +105,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if source.AttributeAvatar != "" {
|
if source.AttributeAvatar != "" {
|
||||||
if err := user_service.UploadAvatar(ctx, user, sr.Avatar); err != nil {
|
_ = user_service.UploadAvatar(ctx, user, sr.Avatar)
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T,
|
|||||||
|
|
||||||
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
|
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
|
||||||
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
|
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
|
||||||
readers, err := access_model.GetRepoReaders(ctx, repo)
|
readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetRepoReaders: %v", err)
|
log.Error("GetRepoReaders: %v", err)
|
||||||
}
|
}
|
||||||
@@ -720,7 +720,7 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.
|
|||||||
|
|
||||||
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection
|
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection
|
||||||
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
|
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
|
||||||
readers, err := access_model.GetRepoReaders(ctx, repo)
|
readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetRepoReaders: %v", err)
|
log.Error("GetRepoReaders: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,8 +112,12 @@ func NewJwtRegisteredClaimsFromUser(clientID string, grantUserID int64, exp *jwt
|
|||||||
// to retrieve the configuration information. This MUST also be identical to the "iss" Claim value in ID Tokens issued from this Issuer.
|
// to retrieve the configuration information. This MUST also be identical to the "iss" Claim value in ID Tokens issued from this Issuer.
|
||||||
// * https://accounts.google.com/.well-known/openid-configuration
|
// * https://accounts.google.com/.well-known/openid-configuration
|
||||||
// * https://github.com/login/oauth/.well-known/openid-configuration
|
// * https://github.com/login/oauth/.well-known/openid-configuration
|
||||||
|
issuer := setting.OAuth2.JWTClaimIssuer
|
||||||
|
if issuer == "" {
|
||||||
|
issuer = strings.TrimSuffix(setting.AppURL, "/")
|
||||||
|
}
|
||||||
return jwt.RegisteredClaims{
|
return jwt.RegisteredClaims{
|
||||||
Issuer: strings.TrimSuffix(setting.AppURL, "/"),
|
Issuer: issuer,
|
||||||
Audience: []string{clientID},
|
Audience: []string{clientID},
|
||||||
Subject: strconv.FormatInt(grantUserID, 10),
|
Subject: strconv.FormatInt(grantUserID, 10),
|
||||||
ExpiresAt: exp,
|
ExpiresAt: exp,
|
||||||
|
|||||||
@@ -78,18 +78,6 @@
|
|||||||
{{ctx.Locale.Tr "repo.release.downloads"}}
|
{{ctx.Locale.Tr "repo.release.downloads"}}
|
||||||
</summary>
|
</summary>
|
||||||
<ul class="ui divided list attachment-list">
|
<ul class="ui divided list attachment-list">
|
||||||
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
|
|
||||||
<li class="item">
|
|
||||||
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow">
|
|
||||||
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="item">
|
|
||||||
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">
|
|
||||||
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{range $att := $release.Attachments}}
|
{{range $att := $release.Attachments}}
|
||||||
<li class="item">
|
<li class="item">
|
||||||
<a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}">
|
<a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}">
|
||||||
@@ -105,6 +93,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
|
||||||
|
<li class="item">
|
||||||
|
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow">
|
||||||
|
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="item">
|
||||||
|
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">
|
||||||
|
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{{if .HeatmapData}}
|
{{if .HeatmapData}}
|
||||||
|
<div class="activity-heatmap-container">
|
||||||
<div id="user-heatmap" class="is-loading"
|
<div id="user-heatmap" class="is-loading"
|
||||||
data-heatmap-data="{{JsonUtils.EncodeToString .HeatmapData}}"
|
data-heatmap-data="{{JsonUtils.EncodeToString .HeatmapData}}"
|
||||||
data-locale-total-contributions="{{ctx.Locale.Tr "heatmap.number_of_contributions_in_the_last_12_months" (ctx.Locale.PrettyNumber .HeatmapTotalContributions)}}"
|
data-locale-total-contributions="{{ctx.Locale.Tr "heatmap.number_of_contributions_in_the_last_12_months" (ctx.Locale.PrettyNumber .HeatmapTotalContributions)}}"
|
||||||
@@ -6,5 +7,6 @@
|
|||||||
data-locale-more="{{ctx.Locale.Tr "heatmap.more"}}"
|
data-locale-more="{{ctx.Locale.Tr "heatmap.more"}}"
|
||||||
data-locale-less="{{ctx.Locale.Tr "heatmap.less"}}"
|
data-locale-less="{{ctx.Locale.Tr "heatmap.less"}}"
|
||||||
></div>
|
></div>
|
||||||
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -237,6 +237,8 @@ func TestPackageConda(t *testing.T) {
|
|||||||
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
|
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
|
||||||
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
|
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
|
||||||
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
|
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
|
||||||
|
assert.NotNil(t, packageInfo.Dependencies)
|
||||||
|
assert.Empty(t, packageInfo.Dependencies)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(".conda", func(t *testing.T) {
|
t.Run(".conda", func(t *testing.T) {
|
||||||
@@ -268,6 +270,8 @@ func TestPackageConda(t *testing.T) {
|
|||||||
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
|
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
|
||||||
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
|
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
|
||||||
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
|
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
|
||||||
|
assert.NotNil(t, packageInfo.Dependencies)
|
||||||
|
assert.Empty(t, packageInfo.Dependencies)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -919,9 +919,10 @@ func TestOAuth_GrantScopesClaimAllGroups(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testOAuth2WellKnown(t *testing.T) {
|
func testOAuth2WellKnown(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
|
||||||
urlOpenidConfiguration := "/.well-known/openid-configuration"
|
urlOpenidConfiguration := "/.well-known/openid-configuration"
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
|
t.Run("WellKnown", func(t *testing.T) {
|
||||||
req := NewRequest(t, "GET", urlOpenidConfiguration)
|
req := NewRequest(t, "GET", urlOpenidConfiguration)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
var respMap map[string]any
|
var respMap map[string]any
|
||||||
@@ -933,6 +934,17 @@ func testOAuth2WellKnown(t *testing.T) {
|
|||||||
assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"])
|
assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"])
|
||||||
assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"])
|
assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"])
|
||||||
assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"])
|
assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WellKnownWithIssuer", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.OAuth2.JWTClaimIssuer, "https://try.gitea.io/")()
|
||||||
|
req := NewRequest(t, "GET", urlOpenidConfiguration)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var respMap map[string]any
|
||||||
|
DecodeJSON(t, resp, &respMap)
|
||||||
|
assert.Equal(t, "https://try.gitea.io/", respMap["issuer"]) // has trailing by JWTClaimIssuer
|
||||||
|
assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"])
|
||||||
|
})
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.OAuth2.Enabled, false)()
|
defer test.MockVariableValue(&setting.OAuth2.Enabled, false)()
|
||||||
MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound)
|
MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound)
|
||||||
|
|||||||
@@ -626,7 +626,6 @@ img.ui.avatar,
|
|||||||
font-family: var(--fonts-monospace);
|
font-family: var(--fonts-monospace);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
padding: 3px 5px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,44 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* before the Vue component is mounted, show a loading indicator with dummy size */
|
.activity-heatmap-container {
|
||||||
/* the ratio is guesswork, see https://github.com/razorness/vue3-calendar-heatmap/issues/26 */
|
container-type: inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (width > 0) {
|
||||||
|
#user-heatmap {
|
||||||
|
/* Set element to fixed height so that it does not resize after load. The calculation is complex
|
||||||
|
because the element does not scale with a fixed aspect ratio. */
|
||||||
|
height: calc((100cqw / 5) - (100cqw / 25) + 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback height adjustment above for browsers that don't support container queries */
|
||||||
|
@supports not (container-type: inline-size) {
|
||||||
|
/* Before the Vue component is mounted, show a loading indicator with dummy size */
|
||||||
|
/* The ratio is guesswork for legacy browsers, new browsers use the "@container" approach above */
|
||||||
#user-heatmap.is-loading {
|
#user-heatmap.is-loading {
|
||||||
aspect-ratio: 5.415; /* the size is about 790 x 145 */
|
aspect-ratio: 5.4823972051; /* the size is about 816 x 148.84 */
|
||||||
}
|
}
|
||||||
.user.profile #user-heatmap.is-loading {
|
.user.profile #user-heatmap.is-loading {
|
||||||
aspect-ratio: 5.645; /* the size is about 953 x 169 */
|
aspect-ratio: 5.6290608387; /* the size is about 953 x 169.3 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-heatmap text {
|
#user-heatmap text {
|
||||||
fill: currentcolor !important;
|
fill: currentcolor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* root legend */
|
||||||
|
#user-heatmap .vch__container > .vch__legend {
|
||||||
|
display: flex;
|
||||||
|
font-size: 11px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
/* for the "Less" and "More" legend */
|
/* for the "Less" and "More" legend */
|
||||||
#user-heatmap .vch__legend .vch__legend {
|
#user-heatmap .vch__legend .vch__legend {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 11px;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
}
|
}
|
||||||
@@ -34,25 +55,3 @@
|
|||||||
#user-heatmap .vch__day__square:hover {
|
#user-heatmap .vch__day__square:hover {
|
||||||
outline: 1.5px solid var(--color-text);
|
outline: 1.5px solid var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* move the "? contributions in the last ? months" text from top to bottom */
|
|
||||||
#user-heatmap .total-contributions {
|
|
||||||
font-size: 11px;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
#user-heatmap .total-contributions {
|
|
||||||
left: 21px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
#user-heatmap .total-contributions {
|
|
||||||
font-size: 10px;
|
|
||||||
left: 17px;
|
|
||||||
bottom: -4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ td .commit-summary {
|
|||||||
|
|
||||||
.repository.view.issue .pull-desc code {
|
.repository.view.issue .pull-desc code {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.view.issue .pull-desc a[data-clipboard-text] {
|
.repository.view.issue .pull-desc a[data-clipboard-text] {
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ function handleDayClick(e: Event & {date: Date}) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="total-contributions">
|
|
||||||
{{ locale.textTotalContributions }}
|
|
||||||
</div>
|
|
||||||
<calendar-heatmap
|
<calendar-heatmap
|
||||||
:locale="locale.heatMapLocale"
|
:locale="locale.heatMapLocale"
|
||||||
:no-data-text="locale.noDataText"
|
:no-data-text="locale.noDataText"
|
||||||
@@ -65,5 +62,7 @@ function handleDayClick(e: Event & {date: Date}) {
|
|||||||
:range-color="colorRange"
|
:range-color="colorRange"
|
||||||
@day-click="handleDayClick($event)"
|
@day-click="handleDayClick($event)"
|
||||||
:tippy-props="{theme: 'tooltip'}"
|
:tippy-props="{theme: 'tooltip'}"
|
||||||
/>
|
>
|
||||||
|
<template #vch__legend-left>{{ locale.textTotalContributions }}</template>
|
||||||
|
</calendar-heatmap>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import './globals.ts';
|
|
||||||
import '../fomantic/build/fomantic.js';
|
import '../fomantic/build/fomantic.js';
|
||||||
import '../../node_modules/easymde/dist/easymde.min.css'; // TODO: lazy load in "switchToEasyMDE"
|
import '../../node_modules/easymde/dist/easymde.min.css'; // TODO: lazy load in "switchToEasyMDE"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
// bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
|
// bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
|
||||||
import './bootstrap.ts';
|
import './bootstrap.ts';
|
||||||
|
|
||||||
|
// many users expect to use jQuery in their custom scripts (https://docs.gitea.com/administration/customizing-gitea#example-plantuml)
|
||||||
|
// so load globals (including jQuery) as early as possible
|
||||||
|
import './globals.ts';
|
||||||
|
|
||||||
import './webcomponents/index.ts';
|
import './webcomponents/index.ts';
|
||||||
import {onDomReady} from './utils/dom.ts';
|
import {onDomReady} from './utils/dom.ts';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user