mirror of
https://github.com/go-gitea/gitea.git
synced 2025-10-19 02:32:33 +02:00
Merge branch 'feat-32257-add-comments-unchanged-lines-and-show' of github.com:rajesh-jonnalagadda/gitea into feat-32257-add-comments-unchanged-lines-and-show
This commit is contained in:
commit
17deb2cdc4
@ -41,6 +41,8 @@ func SplitStringAtByteN(input string, n int) (left, right string) {
|
|||||||
|
|
||||||
// SplitTrimSpace splits the string at given separator and trims leading and trailing space
|
// SplitTrimSpace splits the string at given separator and trims leading and trailing space
|
||||||
func SplitTrimSpace(input, sep string) []string {
|
func SplitTrimSpace(input, sep string) []string {
|
||||||
|
// Trim initial leading & trailing space
|
||||||
|
input = strings.TrimSpace(input)
|
||||||
// replace CRLF with LF
|
// replace CRLF with LF
|
||||||
input = strings.ReplaceAll(input, "\r\n", "\n")
|
input = strings.ReplaceAll(input, "\r\n", "\n")
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/modules/auth"
|
"code.gitea.io/gitea/modules/auth"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"gitea.com/go-chi/binding"
|
"gitea.com/go-chi/binding"
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
@ -31,6 +32,7 @@ const (
|
|||||||
// AddBindingRules adds additional binding rules
|
// AddBindingRules adds additional binding rules
|
||||||
func AddBindingRules() {
|
func AddBindingRules() {
|
||||||
addGitRefNameBindingRule()
|
addGitRefNameBindingRule()
|
||||||
|
addValidURLListBindingRule()
|
||||||
addValidURLBindingRule()
|
addValidURLBindingRule()
|
||||||
addValidSiteURLBindingRule()
|
addValidSiteURLBindingRule()
|
||||||
addGlobPatternRule()
|
addGlobPatternRule()
|
||||||
@ -44,7 +46,7 @@ func addGitRefNameBindingRule() {
|
|||||||
// Git refname validation rule
|
// Git refname validation rule
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "GitRefName")
|
return rule == "GitRefName"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
str := fmt.Sprintf("%v", val)
|
str := fmt.Sprintf("%v", val)
|
||||||
@ -58,11 +60,38 @@ func addGitRefNameBindingRule() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addValidURLListBindingRule() {
|
||||||
|
// URL validation rule
|
||||||
|
binding.AddRule(&binding.Rule{
|
||||||
|
IsMatch: func(rule string) bool {
|
||||||
|
return rule == "ValidUrlList"
|
||||||
|
},
|
||||||
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
|
str := fmt.Sprintf("%v", val)
|
||||||
|
if len(str) == 0 {
|
||||||
|
errs.Add([]string{name}, binding.ERR_URL, "Url")
|
||||||
|
return false, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
urls := util.SplitTrimSpace(str, "\n")
|
||||||
|
for _, u := range urls {
|
||||||
|
if !IsValidURL(u) {
|
||||||
|
ok = false
|
||||||
|
errs.Add([]string{name}, binding.ERR_URL, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok, errs
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func addValidURLBindingRule() {
|
func addValidURLBindingRule() {
|
||||||
// URL validation rule
|
// URL validation rule
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "ValidUrl")
|
return rule == "ValidUrl"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
str := fmt.Sprintf("%v", val)
|
str := fmt.Sprintf("%v", val)
|
||||||
@ -80,7 +109,7 @@ func addValidSiteURLBindingRule() {
|
|||||||
// URL validation rule
|
// URL validation rule
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "ValidSiteUrl")
|
return rule == "ValidSiteUrl"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
str := fmt.Sprintf("%v", val)
|
str := fmt.Sprintf("%v", val)
|
||||||
@ -171,7 +200,7 @@ func addUsernamePatternRule() {
|
|||||||
func addValidGroupTeamMapRule() {
|
func addValidGroupTeamMapRule() {
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "ValidGroupTeamMap")
|
return rule == "ValidGroupTeamMap"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
_, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val))
|
_, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val))
|
||||||
|
@ -27,6 +27,7 @@ type (
|
|||||||
TestForm struct {
|
TestForm struct {
|
||||||
BranchName string `form:"BranchName" binding:"GitRefName"`
|
BranchName string `form:"BranchName" binding:"GitRefName"`
|
||||||
URL string `form:"ValidUrl" binding:"ValidUrl"`
|
URL string `form:"ValidUrl" binding:"ValidUrl"`
|
||||||
|
URLs string `form:"ValidUrls" binding:"ValidUrlList"`
|
||||||
GlobPattern string `form:"GlobPattern" binding:"GlobPattern"`
|
GlobPattern string `form:"GlobPattern" binding:"GlobPattern"`
|
||||||
RegexPattern string `form:"RegexPattern" binding:"RegexPattern"`
|
RegexPattern string `form:"RegexPattern" binding:"RegexPattern"`
|
||||||
}
|
}
|
||||||
|
157
modules/validation/validurllist_test.go
Normal file
157
modules/validation/validurllist_test.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.com/go-chi/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a copy of all the URL tests cases, plus additional ones to
|
||||||
|
// account for multiple URLs
|
||||||
|
var urlListValidationTestCases = []validationTestCase{
|
||||||
|
{
|
||||||
|
description: "Empty URL",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL without port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL with port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL with IPv6 address without port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://[::1]/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL with IPv6 address with port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://[::1]:3000/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid URL",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http//test.lan/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http//test.lan/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid schema",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "ftp://test.lan/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "ftp://test.lan/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3x4/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://test.lan:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid port with IPv6 address",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Multi URLs",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/\nhttp://test.local/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Multi URLs with newline",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/\nhttp://test.local/\n",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "List with invalid entry",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/\nhttp://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "List with two invalid entries",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "ftp://test.lan:3000/\nhttp://[::1]:3x4/\n",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "ftp://test.lan:3000/",
|
||||||
|
},
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ValidURLListValidation(t *testing.T) {
|
||||||
|
AddBindingRules()
|
||||||
|
|
||||||
|
for _, testCase := range urlListValidationTestCases {
|
||||||
|
t.Run(testCase.description, func(t *testing.T) {
|
||||||
|
performValidationTest(t, testCase)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -765,6 +765,7 @@ uploaded_avatar_not_a_image = The uploaded file is not an image.
|
|||||||
uploaded_avatar_is_too_big = The uploaded file size (%d KiB) exceeds the maximum size (%d KiB).
|
uploaded_avatar_is_too_big = The uploaded file size (%d KiB) exceeds the maximum size (%d KiB).
|
||||||
update_avatar_success = Your avatar has been updated.
|
update_avatar_success = Your avatar has been updated.
|
||||||
update_user_avatar_success = The user's avatar has been updated.
|
update_user_avatar_success = The user's avatar has been updated.
|
||||||
|
cropper_prompt = You can edit the image before saving. The edited image will be saved as PNG.
|
||||||
|
|
||||||
change_password = Update Password
|
change_password = Update Password
|
||||||
old_password = Current Password
|
old_password = Current Password
|
||||||
|
7
package-lock.json
generated
7
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"clippie": "4.1.3",
|
"clippie": "4.1.3",
|
||||||
|
"cropperjs": "1.6.2",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"dropzone": "6.0.0-beta.2",
|
"dropzone": "6.0.0-beta.2",
|
||||||
@ -6876,6 +6877,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cropperjs": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.5",
|
"version": "7.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"clippie": "4.1.3",
|
"clippie": "4.1.3",
|
||||||
|
"cropperjs": "1.6.2",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"dropzone": "6.0.0-beta.2",
|
"dropzone": "6.0.0-beta.2",
|
||||||
|
@ -168,8 +168,8 @@ func List(ctx *context.Context) {
|
|||||||
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||||
ctx.Data["ActionsConfig"] = actionsConfig
|
ctx.Data["ActionsConfig"] = actionsConfig
|
||||||
|
|
||||||
if len(workflowID) > 0 && ctx.Repo.IsAdmin() {
|
if len(workflowID) > 0 && ctx.Repo.CanWrite(unit.TypeActions) {
|
||||||
ctx.Data["AllowDisableOrEnableWorkflow"] = true
|
ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.IsAdmin()
|
||||||
isWorkflowDisabled := actionsConfig.IsWorkflowDisabled(workflowID)
|
isWorkflowDisabled := actionsConfig.IsWorkflowDisabled(workflowID)
|
||||||
ctx.Data["CurWorkflowDisabled"] = isWorkflowDisabled
|
ctx.Data["CurWorkflowDisabled"] = isWorkflowDisabled
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO validate redirect URI
|
|
||||||
app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
|
app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
|
||||||
Name: form.Name,
|
Name: form.Name,
|
||||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||||
@ -96,11 +95,25 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) {
|
|||||||
form := web.GetForm(ctx).(*forms.EditOAuth2ApplicationForm)
|
form := web.GetForm(ctx).(*forms.EditOAuth2ApplicationForm)
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
|
app, err := auth.GetOAuth2ApplicationByID(ctx, ctx.PathParamInt64("id"))
|
||||||
|
if err != nil {
|
||||||
|
if auth.IsErrOAuthApplicationNotFound(err) {
|
||||||
|
ctx.NotFound("Application not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.ServerError("GetOAuth2ApplicationByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if app.UID != oa.OwnerID {
|
||||||
|
ctx.NotFound("Application not found", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["App"] = app
|
||||||
|
|
||||||
oa.renderEditPage(ctx)
|
oa.renderEditPage(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO validate redirect URI
|
|
||||||
var err error
|
var err error
|
||||||
if ctx.Data["App"], err = auth.UpdateOAuth2Application(ctx, auth.UpdateOAuth2ApplicationOptions{
|
if ctx.Data["App"], err = auth.UpdateOAuth2Application(ctx, auth.UpdateOAuth2ApplicationOptions{
|
||||||
ID: ctx.PathParamInt64("id"),
|
ID: ctx.PathParamInt64("id"),
|
||||||
|
@ -1406,7 +1406,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get("", actions.List)
|
m.Get("", actions.List)
|
||||||
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
|
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
|
||||||
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
|
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
|
||||||
m.Post("/run", reqRepoAdmin, actions.Run)
|
m.Post("/run", reqRepoActionsWriter, actions.Run)
|
||||||
|
|
||||||
m.Group("/runs/{run}", func() {
|
m.Group("/runs/{run}", func() {
|
||||||
m.Combo("").
|
m.Combo("").
|
||||||
|
@ -390,6 +390,12 @@ func (a *actionNotifier) DeleteRef(ctx context.Context, doer *user_model.User, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *actionNotifier) SyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
|
func (a *actionNotifier) SyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
|
||||||
|
// ignore pull sync message for pull requests refs
|
||||||
|
// TODO: it's better to have a UI to let users chose
|
||||||
|
if opts.RefFullName.IsPull() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(commits)
|
data, err := json.Marshal(commits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("json.Marshal: %v", err)
|
log.Error("json.Marshal: %v", err)
|
||||||
|
@ -366,7 +366,7 @@ func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) {
|
|||||||
// EditOAuth2ApplicationForm form for editing oauth2 applications
|
// EditOAuth2ApplicationForm form for editing oauth2 applications
|
||||||
type EditOAuth2ApplicationForm struct {
|
type EditOAuth2ApplicationForm struct {
|
||||||
Name string `binding:"Required;MaxSize(255)" form:"application_name"`
|
Name string `binding:"Required;MaxSize(255)" form:"application_name"`
|
||||||
RedirectURIs string `binding:"Required" form:"redirect_uris"`
|
RedirectURIs string `binding:"Required;ValidUrlList" form:"redirect_uris"`
|
||||||
ConfidentialClient bool `form:"confidential_client"`
|
ConfidentialClient bool `form:"confidential_client"`
|
||||||
SkipSecondaryAuthorization bool `form:"skip_secondary_authorization"`
|
SkipSecondaryAuthorization bool `form:"skip_secondary_authorization"`
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,11 @@
|
|||||||
<input id="new-avatar" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
|
<input id="new-avatar" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field tw-pl-4 cropper-panel tw-hidden">
|
||||||
|
<div>{{ctx.Locale.Tr "settings.cropper_prompt"}}</div>
|
||||||
|
<div class="cropper-wrapper"><img class="cropper-source" src alt></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button>
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button>
|
||||||
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button>
|
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button>
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate that each navbar setting is correct. This checks that the
|
// Validate that each navbar setting is correct. This checks that the
|
||||||
@ -51,9 +53,11 @@ func WithDisabledFeatures(t *testing.T, features ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserSettingsAccount(t *testing.T) {
|
func TestUserSettingsAccount(t *testing.T) {
|
||||||
t.Run("all features enabled", func(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("all features enabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
req := NewRequest(t, "GET", "/user/settings/account")
|
req := NewRequest(t, "GET", "/user/settings/account")
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
@ -68,7 +72,7 @@ func TestUserSettingsAccount(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("credentials disabled", func(t *testing.T) {
|
t.Run("credentials disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
||||||
|
|
||||||
@ -85,7 +89,7 @@ func TestUserSettingsAccount(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("deletion disabled", func(t *testing.T) {
|
t.Run("deletion disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureDeletion)
|
WithDisabledFeatures(t, setting.UserFeatureDeletion)
|
||||||
|
|
||||||
@ -102,7 +106,7 @@ func TestUserSettingsAccount(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("deletion, credentials and email notifications are disabled", func(t *testing.T) {
|
t.Run("deletion, credentials and email notifications are disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
mail := setting.Service.EnableNotifyMail
|
mail := setting.Service.EnableNotifyMail
|
||||||
setting.Service.EnableNotifyMail = false
|
setting.Service.EnableNotifyMail = false
|
||||||
@ -119,9 +123,11 @@ func TestUserSettingsAccount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserSettingsUpdatePassword(t *testing.T) {
|
func TestUserSettingsUpdatePassword(t *testing.T) {
|
||||||
t.Run("enabled", func(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("enabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
req := NewRequest(t, "GET", "/user/settings/account")
|
req := NewRequest(t, "GET", "/user/settings/account")
|
||||||
@ -138,7 +144,7 @@ func TestUserSettingsUpdatePassword(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("credentials disabled", func(t *testing.T) {
|
t.Run("credentials disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
||||||
|
|
||||||
@ -156,9 +162,11 @@ func TestUserSettingsUpdatePassword(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserSettingsUpdateEmail(t *testing.T) {
|
func TestUserSettingsUpdateEmail(t *testing.T) {
|
||||||
t.Run("credentials disabled", func(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("credentials disabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
@ -175,9 +183,11 @@ func TestUserSettingsUpdateEmail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserSettingsDeleteEmail(t *testing.T) {
|
func TestUserSettingsDeleteEmail(t *testing.T) {
|
||||||
t.Run("credentials disabled", func(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("credentials disabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
@ -194,9 +204,11 @@ func TestUserSettingsDeleteEmail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserSettingsDelete(t *testing.T) {
|
func TestUserSettingsDelete(t *testing.T) {
|
||||||
t.Run("deletion disabled", func(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("deletion disabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureDeletion)
|
WithDisabledFeatures(t, setting.UserFeatureDeletion)
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
@ -224,9 +236,10 @@ func TestUserSettingsAppearance(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserSettingsSecurity(t *testing.T) {
|
func TestUserSettingsSecurity(t *testing.T) {
|
||||||
t.Run("credentials disabled", func(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("credentials disabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
WithDisabledFeatures(t, setting.UserFeatureManageCredentials)
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
@ -240,8 +253,7 @@ func TestUserSettingsSecurity(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("mfa disabled", func(t *testing.T) {
|
t.Run("mfa disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageMFA)
|
WithDisabledFeatures(t, setting.UserFeatureManageMFA)
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
@ -255,8 +267,7 @@ func TestUserSettingsSecurity(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("credentials and mfa disabled", func(t *testing.T) {
|
t.Run("credentials and mfa disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageCredentials, setting.UserFeatureManageMFA)
|
WithDisabledFeatures(t, setting.UserFeatureManageCredentials, setting.UserFeatureManageMFA)
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
@ -268,18 +279,76 @@ func TestUserSettingsSecurity(t *testing.T) {
|
|||||||
func TestUserSettingsApplications(t *testing.T) {
|
func TestUserSettingsApplications(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("Applications", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
req := NewRequest(t, "GET", "/user/settings/applications")
|
req := NewRequest(t, "GET", "/user/settings/applications")
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
doc := NewHTMLParser(t, resp.Body)
|
doc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
assertNavbar(t, doc)
|
assertNavbar(t, doc)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OAuth2", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
|
t.Run("OAuth2ApplicationShow", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user/settings/applications/oauth2/2")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
doc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
assertNavbar(t, doc)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OAuthApplicationsEdit", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user/settings/applications/oauth2/2")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
doc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
t.Run("Invalid URL", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/2", map[string]string{
|
||||||
|
"_csrf": doc.GetCSRF(),
|
||||||
|
"application_name": "Test native app",
|
||||||
|
"redirect_uris": "ftp://127.0.0.1",
|
||||||
|
"confidential_client": "false",
|
||||||
|
})
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
doc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
msg := doc.Find(".flash-error p").Text()
|
||||||
|
assert.Equal(t, `form.RedirectURIs"ftp://127.0.0.1" is not a valid URL.`, msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/2", map[string]string{
|
||||||
|
"_csrf": doc.GetCSRF(),
|
||||||
|
"application_name": "Test native app",
|
||||||
|
"redirect_uris": "http://127.0.0.1",
|
||||||
|
"confidential_client": "false",
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserSettingsKeys(t *testing.T) {
|
func TestUserSettingsKeys(t *testing.T) {
|
||||||
t.Run("all enabled", func(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("all enabled", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
req := NewRequest(t, "GET", "/user/settings/keys")
|
req := NewRequest(t, "GET", "/user/settings/keys")
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
@ -292,7 +361,7 @@ func TestUserSettingsKeys(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ssh keys disabled", func(t *testing.T) {
|
t.Run("ssh keys disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageSSHKeys)
|
WithDisabledFeatures(t, setting.UserFeatureManageSSHKeys)
|
||||||
|
|
||||||
@ -308,7 +377,7 @@ func TestUserSettingsKeys(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("gpg keys disabled", func(t *testing.T) {
|
t.Run("gpg keys disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageGPGKeys)
|
WithDisabledFeatures(t, setting.UserFeatureManageGPGKeys)
|
||||||
|
|
||||||
@ -324,7 +393,7 @@ func TestUserSettingsKeys(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ssh & gpg keys disabled", func(t *testing.T) {
|
t.Run("ssh & gpg keys disabled", func(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
WithDisabledFeatures(t, setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys)
|
WithDisabledFeatures(t, setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys)
|
||||||
|
|
||||||
|
6
web_src/css/features/cropper.css
Normal file
6
web_src/css/features/cropper.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@import "cropperjs/dist/cropper.css";
|
||||||
|
|
||||||
|
.page-content.user.profile .cropper-panel .cropper-wrapper {
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
@ -40,6 +40,7 @@
|
|||||||
@import "./features/codeeditor.css";
|
@import "./features/codeeditor.css";
|
||||||
@import "./features/projects.css";
|
@import "./features/projects.css";
|
||||||
@import "./features/tribute.css";
|
@import "./features/tribute.css";
|
||||||
|
@import "./features/cropper.css";
|
||||||
@import "./features/console.css";
|
@import "./features/console.css";
|
||||||
|
|
||||||
@import "./markup/content.css";
|
@import "./markup/content.css";
|
||||||
|
40
web_src/js/features/comp/Cropper.ts
Normal file
40
web_src/js/features/comp/Cropper.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import {showElem} from '../../utils/dom.ts';
|
||||||
|
|
||||||
|
type CropperOpts = {
|
||||||
|
container: HTMLElement,
|
||||||
|
imageSource: HTMLImageElement,
|
||||||
|
fileInput: HTMLInputElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initCompCropper({container, fileInput, imageSource}: CropperOpts) {
|
||||||
|
const {default: Cropper} = await import(/* webpackChunkName: "cropperjs" */'cropperjs');
|
||||||
|
let currentFileName = '';
|
||||||
|
let currentFileLastModified = 0;
|
||||||
|
const cropper = new Cropper(imageSource, {
|
||||||
|
aspectRatio: 1,
|
||||||
|
viewMode: 2,
|
||||||
|
autoCrop: false,
|
||||||
|
crop() {
|
||||||
|
const canvas = cropper.getCroppedCanvas();
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
const croppedFileName = currentFileName.replace(/\.[^.]{3,4}$/, '.png');
|
||||||
|
const croppedFile = new File([blob], croppedFileName, {type: 'image/png', lastModified: currentFileLastModified});
|
||||||
|
const dataTransfer = new DataTransfer();
|
||||||
|
dataTransfer.items.add(croppedFile);
|
||||||
|
fileInput.files = dataTransfer.files;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('input', (e: Event & {target: HTMLInputElement}) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (files?.length > 0) {
|
||||||
|
currentFileName = files[0].name;
|
||||||
|
currentFileLastModified = files[0].lastModified;
|
||||||
|
const fileURL = URL.createObjectURL(files[0]);
|
||||||
|
imageSource.src = fileURL;
|
||||||
|
cropper.replace(fileURL);
|
||||||
|
showElem(container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import {beforeEach, describe, expect, test, vi} from 'vitest';
|
import {beforeEach, describe, expect, test, vi} from 'vitest';
|
||||||
import {initRepoBranchesSettings} from './repo-settings-branches.ts';
|
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
import {POST} from '../modules/fetch.ts';
|
||||||
import {createSortable} from '../modules/sortable.ts';
|
import {createSortable} from '../modules/sortable.ts';
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ describe('Repository Branch Settings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should initialize sortable for protected branches list', () => {
|
test('should initialize sortable for protected branches list', () => {
|
||||||
initRepoBranchesSettings();
|
initRepoSettingsBranchesDrag();
|
||||||
|
|
||||||
expect(createSortable).toHaveBeenCalledWith(
|
expect(createSortable).toHaveBeenCalledWith(
|
||||||
document.querySelector('#protected-branches-list'),
|
document.querySelector('#protected-branches-list'),
|
||||||
@ -45,7 +45,7 @@ describe('Repository Branch Settings', () => {
|
|||||||
test('should not initialize if protected branches list is not present', () => {
|
test('should not initialize if protected branches list is not present', () => {
|
||||||
document.body.innerHTML = '';
|
document.body.innerHTML = '';
|
||||||
|
|
||||||
initRepoBranchesSettings();
|
initRepoSettingsBranchesDrag();
|
||||||
|
|
||||||
expect(createSortable).not.toHaveBeenCalled();
|
expect(createSortable).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -59,7 +59,7 @@ describe('Repository Branch Settings', () => {
|
|||||||
return {destroy: vi.fn()};
|
return {destroy: vi.fn()};
|
||||||
});
|
});
|
||||||
|
|
||||||
initRepoBranchesSettings();
|
initRepoSettingsBranchesDrag();
|
||||||
|
|
||||||
expect(POST).toHaveBeenCalledWith(
|
expect(POST).toHaveBeenCalledWith(
|
||||||
'some/repo/branches/priority',
|
'some/repo/branches/priority',
|
||||||
|
@ -3,7 +3,7 @@ import {POST} from '../modules/fetch.ts';
|
|||||||
import {showErrorToast} from '../modules/toast.ts';
|
import {showErrorToast} from '../modules/toast.ts';
|
||||||
import {queryElemChildren} from '../utils/dom.ts';
|
import {queryElemChildren} from '../utils/dom.ts';
|
||||||
|
|
||||||
export function initRepoBranchesSettings() {
|
export function initRepoSettingsBranchesDrag() {
|
||||||
const protectedBranchesList = document.querySelector('#protected-branches-list');
|
const protectedBranchesList = document.querySelector('#protected-branches-list');
|
||||||
if (!protectedBranchesList) return;
|
if (!protectedBranchesList) return;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import {minimatch} from 'minimatch';
|
|||||||
import {createMonaco} from './codeeditor.ts';
|
import {createMonaco} from './codeeditor.ts';
|
||||||
import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts';
|
import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
import {POST} from '../modules/fetch.ts';
|
||||||
import {initRepoBranchesSettings} from './repo-settings-branches.ts';
|
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
|
||||||
|
|
||||||
const {appSubUrl, csrfToken} = window.config;
|
const {appSubUrl, csrfToken} = window.config;
|
||||||
|
|
||||||
@ -155,5 +155,5 @@ export function initRepoSettings() {
|
|||||||
initRepoSettingsCollaboration();
|
initRepoSettingsCollaboration();
|
||||||
initRepoSettingsSearchTeamBox();
|
initRepoSettingsSearchTeamBox();
|
||||||
initRepoSettingsGitHook();
|
initRepoSettingsGitHook();
|
||||||
initRepoBranchesSettings();
|
initRepoSettingsBranchesDrag();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
import {hideElem, showElem} from '../utils/dom.ts';
|
import {hideElem, showElem} from '../utils/dom.ts';
|
||||||
|
import {initCompCropper} from './comp/Cropper.ts';
|
||||||
|
|
||||||
|
function initUserSettingsAvatarCropper() {
|
||||||
|
const fileInput = document.querySelector<HTMLInputElement>('#new-avatar');
|
||||||
|
const container = document.querySelector<HTMLElement>('.user.settings.profile .cropper-panel');
|
||||||
|
const imageSource = container.querySelector<HTMLImageElement>('.cropper-source');
|
||||||
|
initCompCropper({container, fileInput, imageSource});
|
||||||
|
}
|
||||||
|
|
||||||
export function initUserSettings() {
|
export function initUserSettings() {
|
||||||
if (!document.querySelectorAll('.user.settings.profile').length) return;
|
if (!document.querySelector('.user.settings.profile')) return;
|
||||||
|
|
||||||
|
initUserSettingsAvatarCropper();
|
||||||
|
|
||||||
const usernameInput = document.querySelector('#username');
|
const usernameInput = document.querySelector('#username');
|
||||||
if (!usernameInput) return;
|
if (!usernameInput) return;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type {SortableOptions, SortableEvent} from 'sortablejs';
|
import type {SortableOptions, SortableEvent} from 'sortablejs';
|
||||||
|
|
||||||
export async function createSortable(el: HTMLElement, opts: {handle?: string} & SortableOptions = {}) {
|
export async function createSortable(el: Element, opts: {handle?: string} & SortableOptions = {}) {
|
||||||
// @ts-expect-error: wrong type derived by typescript
|
// @ts-expect-error: wrong type derived by typescript
|
||||||
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');
|
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import tailwindcssNesting from 'tailwindcss/nesting/index.js';
|
|||||||
import postcssNesting from 'postcss-nesting';
|
import postcssNesting from 'postcss-nesting';
|
||||||
|
|
||||||
const {EsbuildPlugin} = EsBuildLoader;
|
const {EsbuildPlugin} = EsBuildLoader;
|
||||||
const {SourceMapDevToolPlugin, DefinePlugin} = webpack;
|
const {SourceMapDevToolPlugin, DefinePlugin, EnvironmentPlugin} = webpack;
|
||||||
const formatLicenseText = (licenseText) => wrapAnsi(licenseText || '', 80).trim();
|
const formatLicenseText = (licenseText) => wrapAnsi(licenseText || '', 80).trim();
|
||||||
|
|
||||||
const glob = (pattern) => fastGlob.sync(pattern, {
|
const glob = (pattern) => fastGlob.sync(pattern, {
|
||||||
@ -213,6 +213,10 @@ export default {
|
|||||||
__VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production
|
__VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production
|
||||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, // https://github.com/vuejs/vue-cli/pull/7443
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, // https://github.com/vuejs/vue-cli/pull/7443
|
||||||
}),
|
}),
|
||||||
|
// all environment variables used in bundled js via process.env must be declared here
|
||||||
|
new EnvironmentPlugin({
|
||||||
|
TEST: 'false',
|
||||||
|
}),
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'css/[name].css',
|
filename: 'css/[name].css',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user