Compare commits

...

4 Commits

Author SHA1 Message Date
Giteabot
b301cb17a3
Fix and move "Use this template" button (#23398) (#23408)
Backport #23398

Before:
<img width="1157" alt="Screenshot 2023-03-09 at 23 21 25"
src="https://user-images.githubusercontent.com/115237/224174168-869966cc-fa59-4231-b449-23bd9db12862.png">

After:
<img width="1145" alt="Screenshot 2023-03-09 at 23 24 34"
src="https://user-images.githubusercontent.com/115237/224174173-7f5b9c22-44c4-4eed-990c-da49d749eb0e.png">

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-03-11 01:41:04 +08:00
Giteabot
e259daeff8
Add missing tabs to org projects page (#22705) (#23412)
Backport #22705 by @yp05327

Fixes https://github.com/go-gitea/gitea/issues/22676

Context Data `IsOrganizationMember` and `IsOrganizationOwner` is used to
control the visibility of `people` and `team` tab.

2871ea0809/templates/org/menu.tmpl (L19-L40)

And because of the reuse of user projects page, User Context is changed
to Organization Context. But the value of `IsOrganizationMember` and
`IsOrganizationOwner` are not being given.

I reused func `HandleOrgAssignment` to add them to the ctx, but may have
some unnecessary variables, idk whether it is ok.

I found there is a missing `PageIsViewProjects` at create project page.

Co-authored-by: yp05327 <576951401@qq.com>
2023-03-10 10:08:28 -06:00
Giteabot
edb618c136
Handle OpenID discovery URL errors a little nicer when creating/editing sources (#23397) (#23403)
Backport #23397

When there is an error creating a new openIDConnect authentication
source try to handle the error a little better.

Close #23283

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
2023-03-10 08:29:28 -06:00
Giteabot
43cf04c031
Fix broken Chroma CSS styles (#23174) (#23402)
Backport #23174

The CSS styles in Gitea themes are out-of-sync of Chroma's styles.

This PR introduces a `chroma-style-diff.go` tool to compare the diff.
The missing CSS styles have been added manually. They are left as empty
to reduce arguments because there was no color for them before.

And this PR fixes #22348, with just 2 lines changed: `.chroma .kt & .n`,
these colors are taken from GitHub.

It's good enough for #22348


![image](https://user-images.githubusercontent.com/2114189/221551941-0d27d11d-e71e-498f-8e88-92b558fe4a18.png)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-03-10 19:45:07 +08:00
25 changed files with 288 additions and 84 deletions

View File

@ -1,12 +1,18 @@
plugins: plugins:
- stylelint-declaration-strict-value - stylelint-declaration-strict-value
ignoreFiles:
- "**/*.go"
overrides: overrides:
- files: ["**/*.less"] - files: ["**/*.less"]
customSyntax: postcss-less customSyntax: postcss-less
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"] - files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"]
rules: rules:
scale-unlimited/declaration-strict-value: null scale-unlimited/declaration-strict-value: null
- files: ["**/chroma/*", "**/codemirror/*"]
rules:
block-no-empty: null
rules: rules:
alpha-value-notation: null alpha-value-notation: null

View File

@ -7,6 +7,7 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/url"
"os" "os"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
@ -469,11 +470,19 @@ func runAddOauth(c *cli.Context) error {
return err return err
} }
config := parseOAuth2Config(c)
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL)
}
}
return auth_model.CreateSource(&auth_model.Source{ return auth_model.CreateSource(&auth_model.Source{
Type: auth_model.OAuth2, Type: auth_model.OAuth2,
Name: c.String("name"), Name: c.String("name"),
IsActive: true, IsActive: true,
Cfg: parseOAuth2Config(c), Cfg: config,
}) })
} }

View File

@ -239,6 +239,32 @@ func (org *Organization) CustomAvatarRelativePath() string {
return org.Avatar return org.Avatar
} }
// UnitPermission returns unit permission
func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.User, unitType unit.Type) perm.AccessMode {
if doer != nil {
teams, err := GetUserOrgTeams(ctx, org.ID, doer.ID)
if err != nil {
log.Error("GetUserOrgTeams: %v", err)
return perm.AccessModeNone
}
if err := teams.LoadUnits(ctx); err != nil {
log.Error("LoadUnits: %v", err)
return perm.AccessModeNone
}
if len(teams) > 0 {
return teams.UnitMaxAccess(unitType)
}
}
if org.Visibility.IsPublic() {
return perm.AccessModeRead
}
return perm.AccessModeNone
}
// CreateOrganization creates record of a new organization. // CreateOrganization creates record of a new organization.
func CreateOrganization(org *Organization, owner *user_model.User) (err error) { func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
if !owner.CanCreateOrganization() { if !owner.CanCreateOrganization() {

View File

@ -393,6 +393,11 @@ func (u *User) IsOrganization() bool {
return u.Type == UserTypeOrganization return u.Type == UserTypeOrganization
} }
// IsIndividual returns true if user is actually a individual user.
func (u *User) IsIndividual() bool {
return u.Type == UserTypeIndividual
}
// DisplayName returns full name if it's not empty, // DisplayName returns full name if it's not empty,
// returns username otherwise. // returns username otherwise.
func (u *User) DisplayName() string { func (u *User) DisplayName() string {

View File

@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"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/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
) )
@ -31,29 +30,34 @@ type Organization struct {
} }
func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool { func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool {
if ctx.Doer == nil { return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeWrite
return false
}
return org.UnitPermission(ctx, ctx.Doer.ID, unitType) >= perm.AccessModeWrite
} }
func (org *Organization) UnitPermission(ctx *Context, doerID int64, unitType unit.Type) perm.AccessMode { func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
if doerID > 0 { return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeRead
teams, err := organization.GetUserOrgTeams(ctx, org.Organization.ID, doerID) }
if err != nil {
log.Error("GetUserOrgTeams: %v", err)
return perm.AccessModeNone
}
if len(teams) > 0 {
return teams.UnitMaxAccess(unitType)
}
}
if org.Organization.Visibility == structs.VisibleTypePublic { func GetOrganizationByParams(ctx *Context) {
return perm.AccessModeRead orgName := ctx.Params(":org")
}
return perm.AccessModeNone var err error
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
if err != nil {
if organization.IsErrOrgNotExist(err) {
redirectUserID, err := user_model.LookupUserRedirect(orgName)
if err == nil {
RedirectToUser(ctx, orgName, redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
ctx.NotFound("GetUserByName", err)
} else {
ctx.ServerError("LookupUserRedirect", err)
}
} else {
ctx.ServerError("GetUserByName", err)
}
return
}
} }
// HandleOrgAssignment handles organization assignment // HandleOrgAssignment handles organization assignment
@ -77,25 +81,26 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
requireTeamAdmin = args[3] requireTeamAdmin = args[3]
} }
orgName := ctx.Params(":org")
var err error var err error
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
if err != nil { if ctx.ContextUser == nil {
if organization.IsErrOrgNotExist(err) { // if Organization is not defined, get it from params
redirectUserID, err := user_model.LookupUserRedirect(orgName) if ctx.Org.Organization == nil {
if err == nil { GetOrganizationByParams(ctx)
RedirectToUser(ctx, orgName, redirectUserID) if ctx.Written() {
} else if user_model.IsErrUserRedirectNotExist(err) { return
ctx.NotFound("GetUserByName", err)
} else {
ctx.ServerError("LookupUserRedirect", err)
} }
} else {
ctx.ServerError("GetUserByName", err)
} }
} else if ctx.ContextUser.IsOrganization() {
if ctx.Org == nil {
ctx.Org = &Organization{}
}
ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser)
} else {
// ContextUser is an individual User
return return
} }
org := ctx.Org.Organization org := ctx.Org.Organization
// Handle Visibility // Handle Visibility
@ -156,6 +161,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
} }
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["IsPublicMember"] = func(uid int64) bool { ctx.Data["IsPublicMember"] = func(uid int64) bool {
@ -231,6 +237,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
return return
} }
} }
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
} }
// OrgAssignment returns a middleware to handle organization assignment // OrgAssignment returns a middleware to handle organization assignment

View File

@ -2806,6 +2806,8 @@ auths.still_in_used = The authentication source is still in use. Convert or dele
auths.deletion_success = The authentication source has been deleted. auths.deletion_success = The authentication source has been deleted.
auths.login_source_exist = The authentication source '%s' already exists. auths.login_source_exist = The authentication source '%s' already exists.
auths.login_source_of_type_exist = An authentication source of this type already exists. auths.login_source_of_type_exist = An authentication source of this type already exists.
auths.unable_to_initialize_openid = Unable to initialize OpenID Connect Provider: %s
auths.invalid_openIdConnectAutoDiscoveryURL = Invalid Auto Discovery URL (this must be a valid URL starting with http:// or https://)
config.server_config = Server Configuration config.server_config = Server Configuration
config.app_name = Site Title config.app_name = Site Title

View File

@ -271,6 +271,15 @@ func NewAuthSourcePost(ctx *context.Context) {
} }
case auth.OAuth2: case auth.OAuth2:
config = parseOAuth2Config(form) config = parseOAuth2Config(form)
oauth2Config := config.(*oauth2.Source)
if oauth2Config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
ctx.Data["Err_DiscoveryURL"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthNew, form)
return
}
}
case auth.SSPI: case auth.SSPI:
var err error var err error
config, err = parseSSPIConfig(ctx, form) config, err = parseSSPIConfig(ctx, form)
@ -305,6 +314,10 @@ func NewAuthSourcePost(ctx *context.Context) {
if auth.IsErrSourceAlreadyExist(err) { if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true ctx.Data["Err_Name"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form) ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form)
} else if oauth2.IsErrOpenIDConnectInitialize(err) {
ctx.Data["Err_DiscoveryURL"] = true
unwrapped := err.(oauth2.ErrOpenIDConnectInitialize).Unwrap()
ctx.RenderWithErr(ctx.Tr("admin.auths.unable_to_initialize_openid", unwrapped), tplAuthNew, form)
} else { } else {
ctx.ServerError("auth.CreateSource", err) ctx.ServerError("auth.CreateSource", err)
} }
@ -389,6 +402,15 @@ func EditAuthSourcePost(ctx *context.Context) {
} }
case auth.OAuth2: case auth.OAuth2:
config = parseOAuth2Config(form) config = parseOAuth2Config(form)
oauth2Config := config.(*oauth2.Source)
if oauth2Config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
ctx.Data["Err_DiscoveryURL"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthEdit, form)
return
}
}
case auth.SSPI: case auth.SSPI:
config, err = parseSSPIConfig(ctx, form) config, err = parseSSPIConfig(ctx, form)
if err != nil { if err != nil {
@ -408,6 +430,7 @@ func EditAuthSourcePost(ctx *context.Context) {
if err := auth.UpdateSource(source); err != nil { if err := auth.UpdateSource(source); err != nil {
if oauth2.IsErrOpenIDConnectInitialize(err) { if oauth2.IsErrOpenIDConnectInitialize(err) {
ctx.Flash.Error(err.Error(), true) ctx.Flash.Error(err.Error(), true)
ctx.Data["Err_DiscoveryURL"] = true
ctx.HTML(http.StatusOK, tplAuthEdit) ctx.HTML(http.StatusOK, tplAuthEdit)
} else { } else {
ctx.ServerError("UpdateSource", err) ctx.ServerError("UpdateSource", err)

View File

@ -156,6 +156,7 @@ func Home(ctx *context.Context) {
pager.SetDefaultParams(ctx) pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "language", "Language") pager.AddParam(ctx, "language", "Language")
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.HTML(http.StatusOK, tplOrgHome) ctx.HTML(http.StatusOK, tplOrgHome)
} }

View File

@ -123,6 +123,7 @@ func NewProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new") ctx.Data["Title"] = ctx.Tr("repo.projects.new")
ctx.Data["BoardTypes"] = project_model.GetBoardConfig() ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
ctx.Data["PageIsViewProjects"] = true
ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink() ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink()
shared_user.RenderUserHeader(ctx) shared_user.RenderUserHeader(ctx)
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)

View File

@ -9,6 +9,8 @@ import (
) )
func RenderUserHeader(ctx *context.Context) { func RenderUserHeader(ctx *context.Context) {
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["ContextUser"] = ctx.ContextUser
} }

View File

@ -24,6 +24,7 @@ func CodeSearch(ctx *context.Context) {
return return
} }
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore.code") ctx.Data["Title"] = ctx.Tr("explore.code")

View File

@ -288,6 +288,7 @@ func Profile(ctx *context.Context) {
pager.AddParam(ctx, "language", "Language") pager.AddParam(ctx, "language", "Language")
} }
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

View File

@ -690,6 +690,21 @@ func RegisterRoutes(m *web.Route) {
} }
} }
reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode) func(ctx *context.Context) {
return func(ctx *context.Context) {
if ctx.ContextUser == nil {
ctx.NotFound(unitType.String(), nil)
return
}
if ctx.ContextUser.IsOrganization() {
if ctx.Org.Organization.UnitPermission(ctx, ctx.Doer, unitType) < accessMode {
ctx.NotFound(unitType.String(), nil)
return
}
}
}
}
// ***** START: Organization ***** // ***** START: Organization *****
m.Group("/org", func() { m.Group("/org", func() {
m.Group("/{org}", func() { m.Group("/{org}", func() {
@ -869,8 +884,10 @@ func RegisterRoutes(m *web.Route) {
} }
m.Group("/projects", func() { m.Group("/projects", func() {
m.Get("", org.Projects) m.Group("", func() {
m.Get("/{id}", org.ViewProject) m.Get("", org.Projects)
m.Get("/{id}", org.ViewProject)
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead))
m.Group("", func() { //nolint:dupl m.Group("", func() { //nolint:dupl
m.Get("/new", org.NewProject) m.Get("/new", org.NewProject)
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost) m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
@ -890,25 +907,18 @@ func RegisterRoutes(m *web.Route) {
m.Post("/move", org.MoveIssues) m.Post("/move", org.MoveIssues)
}) })
}) })
}, reqSignIn, func(ctx *context.Context) { }, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite), func(ctx *context.Context) {
if ctx.ContextUser == nil { if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID {
ctx.NotFound("NewProject", nil)
return
}
if ctx.ContextUser.IsOrganization() {
if !ctx.Org.CanWriteUnit(ctx, unit.TypeProjects) {
ctx.NotFound("NewProject", nil)
return
}
} else if ctx.ContextUser.ID != ctx.Doer.ID {
ctx.NotFound("NewProject", nil) ctx.NotFound("NewProject", nil)
return return
} }
}) })
}, repo.MustEnableProjects) }, repo.MustEnableProjects)
m.Get("/code", user.CodeSearch) m.Group("", func() {
}, context_service.UserAssignmentWeb()) m.Get("/code", user.CodeSearch)
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead))
}, context_service.UserAssignmentWeb(), context.OrgAssignment())
// ***** Release Attachment Download without Signin // ***** Release Attachment Download without Signin
m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment, repo.MustBeNotEmpty, repo.RedirectDownload) m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment, repo.MustBeNotEmpty, repo.RedirectDownload)

View File

@ -36,6 +36,10 @@ func (err ErrOpenIDConnectInitialize) Error() string {
return fmt.Sprintf("Failed to initialize OpenID Connect Provider with name '%s' with url '%s': %v", err.ProviderName, err.OpenIDConnectAutoDiscoveryURL, err.Cause) return fmt.Sprintf("Failed to initialize OpenID Connect Provider with name '%s' with url '%s': %v", err.ProviderName, err.OpenIDConnectAutoDiscoveryURL, err.Cause)
} }
func (err ErrOpenIDConnectInitialize) Unwrap() error {
return err.Cause
}
// wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2 // wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2
// inside oauth2: import cycle not allowed models -> modules/auth/oauth2 -> models // inside oauth2: import cycle not allowed models -> modules/auth/oauth2 -> models
func wrapOpenIDConnectInitializeError(err error, providerName string, source *Source) error { func wrapOpenIDConnectInitializeError(err error, providerName string, source *Source) error {

View File

@ -8,7 +8,6 @@ import (
"net/http" "net/http"
"strings" "strings"
org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
) )
@ -57,14 +56,6 @@ func userAssignment(ctx *context.Context, errCb func(int, string, interface{}))
} else { } else {
errCb(http.StatusInternalServerError, "GetUserByName", err) errCb(http.StatusInternalServerError, "GetUserByName", err)
} }
} else {
if ctx.ContextUser.IsOrganization() {
if ctx.Org == nil {
ctx.Org = &context.Organization{}
}
ctx.Org.Organization = (*org_model.Organization)(ctx.ContextUser)
ctx.Data["Org"] = ctx.Org.Organization
}
} }
} }
} }

View File

@ -24,7 +24,7 @@
<label for="oauth2_icon_url">{{.locale.Tr "admin.auths.oauth2_icon_url"}}</label> <label for="oauth2_icon_url">{{.locale.Tr "admin.auths.oauth2_icon_url"}}</label>
<input id="oauth2_icon_url" name="oauth2_icon_url" value="{{.oauth2_icon_url}}"> <input id="oauth2_icon_url" name="oauth2_icon_url" value="{{.oauth2_icon_url}}">
</div> </div>
<div class="open_id_connect_auto_discovery_url required field"> <div class="open_id_connect_auto_discovery_url required field{{if .Err_DiscoveryURL}} error{{end}}">
<label for="open_id_connect_auto_discovery_url">{{.locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label> <label for="open_id_connect_auto_discovery_url">{{.locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label>
<input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{.open_id_connect_auto_discovery_url}}"> <input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{.open_id_connect_auto_discovery_url}}">
</div> </div>

View File

@ -3,16 +3,18 @@
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}"> <a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}">
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}} {{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
</a> </a>
{{if and .IsProjectEnabled .CanReadProjects}}
<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects"> <a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}} {{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a> </a>
{{if .IsPackageEnabled}} {{end}}
{{if and .IsPackageEnabled .CanReadPackages}}
<a class="item" href="{{$.Org.HomeLink}}/-/packages"> <a class="item" href="{{$.Org.HomeLink}}/-/packages">
{{svg "octicon-package"}} {{.locale.Tr "packages.title"}} {{svg "octicon-package"}} {{.locale.Tr "packages.title"}}
</a> </a>
{{end}} {{end}}
{{if .IsRepoIndexerEnabled}} {{if and .IsRepoIndexerEnabled .CanReadCode}}
<a class="{{if $.PageIsOrgCode}}active {{end}}item" href="{{$.Org.HomeLink}}/-/code"> <a class="item" href="{{$.Org.HomeLink}}/-/code">
{{svg "octicon-code"}}&nbsp;{{$.locale.Tr "org.code"}} {{svg "octicon-code"}}&nbsp;{{$.locale.Tr "org.code"}}
</a> </a>
{{end}} {{end}}

View File

@ -63,7 +63,7 @@
{{end}} {{end}}
{{template "repo/sub_menu" .}} {{template "repo/sub_menu" .}}
<div class="repo-button-row gt-df gt-ac gt-sb gt-fw"> <div class="repo-button-row gt-df gt-ac gt-sb gt-fw">
<div class="gt-df gt-ac"> <div class="gt-df gt-ac gt-fw gt-gap-y-3">
{{template "repo/branch_dropdown" dict "root" .}} {{template "repo/branch_dropdown" dict "root" .}}
{{$n := len .TreeNames}} {{$n := len .TreeNames}}
{{$l := Subtract $n 1}} {{$l := Subtract $n 1}}
@ -99,20 +99,16 @@
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
</button> </button>
{{end}} {{end}}
{{if and (eq $n 0) (.Repository.IsTemplate)}}
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
{{.locale.Tr "repo.use_template"}}
</a>
{{end}}
{{if ne $n 0}} {{if ne $n 0}}
<span class="ui breadcrumb repo-path gt-ml-2"><a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span class="divider">/</span>{{if eq $i $l}}<span class="active section" title="{{$v}}">{{EllipsisString $v 30}}</span>{{else}}{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span> <span class="ui breadcrumb repo-path gt-ml-2"><a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span class="divider">/</span>{{if eq $i $l}}<span class="active section" title="{{$v}}">{{EllipsisString $v 30}}</span>{{else}}{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span>
{{end}} {{end}}
</div> </div>
<div class="gt-df gt-ac"> <div class="gt-df gt-ac">
{{if eq $n 0}}
{{if .Repository.IsTemplate}}
<div class="ui tiny primary buttons">
<a href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}" class="ui button">
{{.locale.Tr "repo.use_template"}}
</a>
</div>
{{end}}
{{end}}
<!-- Only show clone panel in repository home page --> <!-- Only show clone panel in repository home page -->
{{if eq $n 0}} {{if eq $n 0}}
<div class="ui action tiny input" id="clone-panel"> <div class="ui action tiny input" id="clone-panel">

View File

@ -22,15 +22,17 @@
<a class="item" href="{{.ContextUser.HomeLink}}"> <a class="item" href="{{.ContextUser.HomeLink}}">
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}} {{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
</a> </a>
{{if and .IsProjectEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadProjects))}}
<a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item"> <a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item">
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}} {{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a> </a>
{{if (not .UnitPackagesGlobalDisabled)}} {{end}}
{{if and .IsPackageEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadPackages))}}
<a href="{{.ContextUser.HomeLink}}/-/packages" class="{{if .IsPackagesPage}}active {{end}}item"> <a href="{{.ContextUser.HomeLink}}/-/packages" class="{{if .IsPackagesPage}}active {{end}}item">
{{svg "octicon-package"}} {{.locale.Tr "packages.title"}} {{svg "octicon-package"}} {{.locale.Tr "packages.title"}}
</a> </a>
{{end}} {{end}}
{{if .IsRepoIndexerEnabled}} {{if and .IsRepoIndexerEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadCode))}}
<a href="{{.ContextUser.HomeLink}}/-/code" class="{{if .IsCodePage}}active {{end}}item"> <a href="{{.ContextUser.HomeLink}}/-/code" class="{{if .IsCodePage}}active {{end}}item">
{{svg "octicon-code"}} {{.locale.Tr "user.code"}} {{svg "octicon-code"}} {{.locale.Tr "user.code"}}
</a> </a>

View File

@ -2869,7 +2869,7 @@
} }
.repo-button-row > * { .repo-button-row > * {
margin-top: 10px; margin-top: 8px;
} }
.wiki .repo-button-row { .wiki .repo-button-row {

View File

@ -0,0 +1,79 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build ignore
/*
This tool is used to compare the CSS names in a chroma builtin styles with the Gitea theme CSS names.
It outputs the difference between the two sets of CSS names, eg:
```
CSS names not in builtin:
.chroma .ln
----
Builtin CSS names not in file:
.chroma .vm
```
Developers could use this tool to re-sync the CSS names in the Gitea theme.
*/
package main
import (
"os"
"regexp"
"strings"
"github.com/alecthomas/chroma/v2"
)
func main() {
if len(os.Args) != 2 {
println("Usage: chroma-style-diff css-or-less-file")
os.Exit(1)
}
data, err := os.ReadFile(os.Args[1])
if err != nil {
println(err.Error())
os.Exit(1)
}
content := string(data)
// a simple CSS parser to collect CSS names
content = regexp.MustCompile("//.*\r?\n").ReplaceAllString(content, "\n")
content = regexp.MustCompile("/\\*.*?\\*/").ReplaceAllString(content, "")
matches := regexp.MustCompile("\\s*([-.#:\\w\\s]+)\\s*\\{[^}]*}").FindAllStringSubmatch(content, -1)
cssNames := map[string]bool{}
for _, matchGroup := range matches {
cssName := strings.TrimSpace(matchGroup[1])
cssNames[cssName] = true
}
// collect Chroma builtin CSS names
builtin := map[string]bool{}
for tokenType, cssName := range chroma.StandardTypes {
if tokenType > 0 && cssName != "" {
builtin[".chroma ."+cssName] = true
}
}
// show the diff
println("CSS names not in builtin:")
for cssName := range cssNames {
if !builtin[cssName] {
println(cssName)
}
}
println("----")
println("Builtin CSS names not in file:")
for cssName := range builtin {
if !cssNames[cssName] {
println(cssName)
}
}
}

View File

@ -7,17 +7,19 @@
.chroma .cpf { color: #649bc4; } /* CommentPreprocFile */ .chroma .cpf { color: #649bc4; } /* CommentPreprocFile */
.chroma .cs { color: #9075cd; } /* CommentSpecial */ .chroma .cs { color: #9075cd; } /* CommentSpecial */
.chroma .dl { color: #649bc4; } /* LiteralStringDelimiter */ .chroma .dl { color: #649bc4; } /* LiteralStringDelimiter */
.chroma .fm {} /* NameFunctionMagic */
.chroma .g {} /* Generic */
.chroma .gd { color: #ffffff; background-color: #5f3737; } /* GenericDeleted */ .chroma .gd { color: #ffffff; background-color: #5f3737; } /* GenericDeleted */
.chroma .ge { color: #ddee30; } /* GenericEmph */ .chroma .ge { color: #ddee30; } /* GenericEmph */
.chroma .gh { color: #ffaa10; } /* GenericHeading */ .chroma .gh { color: #ffaa10; } /* GenericHeading */
.chroma .gi { color: #ffffff; background-color: #3a523a; } /* GenericInserted */ .chroma .gi { color: #ffffff; background-color: #3a523a; } /* GenericInserted */
.chroma .gl {} /* GenericUnderline */
.chroma .go { color: #777e94; } /* GenericOutput */ .chroma .go { color: #777e94; } /* GenericOutput */
.chroma .gp { color: #ebdbb2; } /* GenericPrompt */ .chroma .gp { color: #ebdbb2; } /* GenericPrompt */
.chroma .gr { color: #ff4433; } /* GenericError */ .chroma .gr { color: #ff4433; } /* GenericError */
.chroma .gs { color: #ebdbb2; } /* GenericStrong */ .chroma .gs { color: #ebdbb2; } /* GenericStrong */
.chroma .gt { color: #ff7540; } /* GenericTraceback */ .chroma .gt { color: #ff7540; } /* GenericTraceback */
.chroma .gu { color: #b8bb26; } /* GenericSubheading */ .chroma .gu { color: #b8bb26; } /* GenericSubheading */
.chroma .hl { background-color: #3f424d; } /* LineHighlight */
.chroma .il { color: #649bc4; } /* LiteralNumberIntegerLong */ .chroma .il { color: #649bc4; } /* LiteralNumberIntegerLong */
.chroma .k { color: #ff7540; } /* Keyword */ .chroma .k { color: #ff7540; } /* Keyword */
.chroma .kc { color: #649bc4; } /* KeywordConstant */ .chroma .kc { color: #649bc4; } /* KeywordConstant */
@ -25,16 +27,16 @@
.chroma .kn { color: #ffaa10; } /* KeywordNamespace */ .chroma .kn { color: #ffaa10; } /* KeywordNamespace */
.chroma .kp { color: #5f8700; } /* KeywordPseudo */ .chroma .kp { color: #5f8700; } /* KeywordPseudo */
.chroma .kr { color: #ff7540; } /* KeywordReserved */ .chroma .kr { color: #ff7540; } /* KeywordReserved */
.chroma .kt { color: #fabd2f; } /* KeywordType */ .chroma .kt { color: #ff7b72; } /* KeywordType */
.chroma .ln { color: #7f8699; } /* LineNumbers */ .chroma .l {} /* Literal */
.chroma .lnt { color: #7f8699; } /* LineNumbersTable */ .chroma .ld {} /* LiteralDate */
.chroma .m { color: #649bc4; } /* LiteralNumber */ .chroma .m { color: #649bc4; } /* LiteralNumber */
.chroma .mb { color: #649bc4; } /* LiteralNumberBin */ .chroma .mb { color: #649bc4; } /* LiteralNumberBin */
.chroma .mf { color: #649bc4; } /* LiteralNumberFloat */ .chroma .mf { color: #649bc4; } /* LiteralNumberFloat */
.chroma .mh { color: #649bc4; } /* LiteralNumberHex */ .chroma .mh { color: #649bc4; } /* LiteralNumberHex */
.chroma .mi { color: #649bc4; } /* LiteralNumberInteger */ .chroma .mi { color: #649bc4; } /* LiteralNumberInteger */
.chroma .mo { color: #649bc4; } /* LiteralNumberOct */ .chroma .mo { color: #649bc4; } /* LiteralNumberOct */
.chroma .n { color: #fabd2f; } /* Name */ .chroma .n { color: #c9d1d9; } /* Name */
.chroma .na { color: #b8bb26; } /* NameAttribute */ .chroma .na { color: #b8bb26; } /* NameAttribute */
.chroma .nb { color: #fabd2f; } /* NameBuiltin */ .chroma .nb { color: #fabd2f; } /* NameBuiltin */
.chroma .nc { color: #ffaa10; } /* NameClass */ .chroma .nc { color: #ffaa10; } /* NameClass */
@ -51,6 +53,7 @@
.chroma .o { color: #ff7540; } /* Operator */ .chroma .o { color: #ff7540; } /* Operator */
.chroma .ow { color: #5f8700; } /* OperatorWord */ .chroma .ow { color: #5f8700; } /* OperatorWord */
.chroma .p { color: #d2d4db; } /* Punctuation */ .chroma .p { color: #d2d4db; } /* Punctuation */
.chroma .py {} /* NameProperty */
.chroma .s { color: #b8bb26; } /* LiteralString */ .chroma .s { color: #b8bb26; } /* LiteralString */
.chroma .s1 { color: #b8bb26; } /* LiteralStringSingle */ .chroma .s1 { color: #b8bb26; } /* LiteralStringSingle */
.chroma .s2 { color: #b8bb26; } /* LiteralStringDouble */ .chroma .s2 { color: #b8bb26; } /* LiteralStringDouble */
@ -67,4 +70,5 @@
.chroma .vc { color: #ff7540; } /* NameVariableClass */ .chroma .vc { color: #ff7540; } /* NameVariableClass */
.chroma .vg { color: #ffaa10; } /* NameVariableGlobal */ .chroma .vg { color: #ffaa10; } /* NameVariableGlobal */
.chroma .vi { color: #ffaa10; } /* NameVariableInstance */ .chroma .vi { color: #ffaa10; } /* NameVariableInstance */
.chroma .vm {} /* NameVariableMagic */
.chroma .w { color: #7f8699; } /* TextWhitespace */ .chroma .w { color: #7f8699; } /* TextWhitespace */

View File

@ -7,16 +7,19 @@
.chroma .cpf { color: #4c4dbc; } /* CommentPreprocFile */ .chroma .cpf { color: #4c4dbc; } /* CommentPreprocFile */
.chroma .cs { color: #999999; } /* CommentSpecial */ .chroma .cs { color: #999999; } /* CommentSpecial */
.chroma .dl { color: #106303; } /* LiteralStringDelimiter */ .chroma .dl { color: #106303; } /* LiteralStringDelimiter */
.chroma .fm {} /* NameFunctionMagic */
.chroma .g {} /* Generic */
.chroma .gd { color: #000000; background-color: #ffdddd; } /* GenericDeleted */ .chroma .gd { color: #000000; background-color: #ffdddd; } /* GenericDeleted */
.chroma .ge { color: #000000; } /* GenericEmph */ .chroma .ge { color: #000000; } /* GenericEmph */
.chroma .gh { color: #999999; } /* GenericHeading */ .chroma .gh { color: #999999; } /* GenericHeading */
.chroma .gi { color: #000000; background-color: #ddffdd; } /* GenericInserted */ .chroma .gi { color: #000000; background-color: #ddffdd; } /* GenericInserted */
.chroma .gl {} /* GenericUnderline */
.chroma .go { color: #888888; } /* GenericOutput */ .chroma .go { color: #888888; } /* GenericOutput */
.chroma .gp { color: #555555; } /* GenericPrompt */ .chroma .gp { color: #555555; } /* GenericPrompt */
.chroma .gr { color: #aa0000; } /* GenericError */ .chroma .gr { color: #aa0000; } /* GenericError */
.chroma .gs {} /* GenericStrong */
.chroma .gt { color: #aa0000; } /* GenericTraceback */ .chroma .gt { color: #aa0000; } /* GenericTraceback */
.chroma .gu { color: #aaaaaa; } /* GenericSubheading */ .chroma .gu { color: #aaaaaa; } /* GenericSubheading */
.chroma .hl { background-color: #e5e5e5; } /* LineHighlight */
.chroma .il { color: #009999; } /* LiteralNumberIntegerLong */ .chroma .il { color: #009999; } /* LiteralNumberIntegerLong */
.chroma .k { color: #d73a49; } /* Keyword */ .chroma .k { color: #d73a49; } /* Keyword */
.chroma .kc { color: #d73a49; } /* KeywordConstant */ .chroma .kc { color: #d73a49; } /* KeywordConstant */
@ -25,14 +28,15 @@
.chroma .kp { color: #d73a49; } /* KeywordPseudo */ .chroma .kp { color: #d73a49; } /* KeywordPseudo */
.chroma .kr { color: #d73a49; } /* KeywordReserved */ .chroma .kr { color: #d73a49; } /* KeywordReserved */
.chroma .kt { color: #445588; } /* KeywordType */ .chroma .kt { color: #445588; } /* KeywordType */
.chroma .ln { color: #7f7f7f; } /* LineNumbers */ .chroma .l {} /* Literal */
.chroma .lnt { color: #7f7f7f; } /* LineNumbersTable */ .chroma .ld {} /* LiteralDate */
.chroma .m { color: #009999; } /* LiteralNumber */ .chroma .m { color: #009999; } /* LiteralNumber */
.chroma .mb { color: #009999; } /* LiteralNumberBin */ .chroma .mb { color: #009999; } /* LiteralNumberBin */
.chroma .mf { color: #009999; } /* LiteralNumberFloat */ .chroma .mf { color: #009999; } /* LiteralNumberFloat */
.chroma .mh { color: #009999; } /* LiteralNumberHex */ .chroma .mh { color: #009999; } /* LiteralNumberHex */
.chroma .mi { color: #009999; } /* LiteralNumberInteger */ .chroma .mi { color: #009999; } /* LiteralNumberInteger */
.chroma .mo { color: #009999; } /* LiteralNumberOct */ .chroma .mo { color: #009999; } /* LiteralNumberOct */
.chroma .n {} /* Name */
.chroma .na { color: #d73a49; } /* NameAttribute */ .chroma .na { color: #d73a49; } /* NameAttribute */
.chroma .nb { color: #005cc5; } /* NameBuiltin */ .chroma .nb { color: #005cc5; } /* NameBuiltin */
.chroma .nc { color: #445588; } /* NameClass */ .chroma .nc { color: #445588; } /* NameClass */
@ -48,6 +52,8 @@
.chroma .nx { color: #24292e; } /* NameOther */ .chroma .nx { color: #24292e; } /* NameOther */
.chroma .o { color: #d73a49; } /* Operator */ .chroma .o { color: #d73a49; } /* Operator */
.chroma .ow { color: #d73a49; } /* OperatorWord */ .chroma .ow { color: #d73a49; } /* OperatorWord */
.chroma .p {} /* Punctuation */
.chroma .py {} /* NameProperty */
.chroma .s { color: #106303; } /* LiteralString */ .chroma .s { color: #106303; } /* LiteralString */
.chroma .s1 { color: #cc7a00; } /* LiteralStringSingle */ .chroma .s1 { color: #cc7a00; } /* LiteralStringSingle */
.chroma .s2 { color: #106303; } /* LiteralStringDouble */ .chroma .s2 { color: #106303; } /* LiteralStringDouble */
@ -64,4 +70,5 @@
.chroma .vc { color: #008080; } /* NameVariableClass */ .chroma .vc { color: #008080; } /* NameVariableClass */
.chroma .vg { color: #008080; } /* NameVariableGlobal */ .chroma .vg { color: #008080; } /* NameVariableGlobal */
.chroma .vi { color: #008080; } /* NameVariableInstance */ .chroma .vi { color: #008080; } /* NameVariableInstance */
.chroma .vm {} /* NameVariableMagic */
.chroma .w { color: #bbbbbb; } /* TextWhitespace */ .chroma .w { color: #bbbbbb; } /* TextWhitespace */

View File

@ -172,6 +172,27 @@
.gt-py-4 { padding-top: 1rem !important; padding-bottom: 1rem !important; } .gt-py-4 { padding-top: 1rem !important; padding-bottom: 1rem !important; }
.gt-py-5 { padding-top: 2rem !important; padding-bottom: 2rem !important; } .gt-py-5 { padding-top: 2rem !important; padding-bottom: 2rem !important; }
.gt-gap-0 { gap: 0 !important; }
.gt-gap-1 { gap: .125rem !important; }
.gt-gap-2 { gap: .25rem !important; }
.gt-gap-3 { gap: .5rem !important; }
.gt-gap-4 { gap: 1rem !important; }
.gt-gap-5 { gap: 2rem !important; }
.gt-gap-x-0 { column-gap: 0 !important; }
.gt-gap-x-1 { column-gap: .125rem !important; }
.gt-gap-x-2 { column-gap: .25rem !important; }
.gt-gap-x-3 { column-gap: .5rem !important; }
.gt-gap-x-4 { column-gap: 1rem !important; }
.gt-gap-x-5 { column-gap: 2rem !important; }
.gt-gap-y-0 { row-gap: 0 !important; }
.gt-gap-y-1 { row-gap: .125rem !important; }
.gt-gap-y-2 { row-gap: .25rem !important; }
.gt-gap-y-3 { row-gap: .5rem !important; }
.gt-gap-y-4 { row-gap: 1rem !important; }
.gt-gap-y-5 { row-gap: 2rem !important; }
.gt-content-center { align-content: center !important; } .gt-content-center { align-content: center !important; }
@media @mediaSm { @media @mediaSm {

View File

@ -1,3 +1,4 @@
@import "../chroma/base.less";
@import "../chroma/dark.less"; @import "../chroma/dark.less";
@import "../codemirror/dark.less"; @import "../codemirror/dark.less";