diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 2db4a9296f..595fd8bbb0 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -441,6 +441,9 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa // all acts conditions should be satisfied for cond, vals := range acts { switch cond { + case "types": + // types have been checked + continue case "branches": refName := git.RefName(prPayload.PullRequest.Base.Ref) patterns, err := workflowpattern.CompilePatterns(vals...) diff --git a/modules/repository/init.go b/modules/repository/init.go index b90b234a73..5f500c5233 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -6,22 +6,18 @@ package repository import ( "context" "fmt" - "os" "path/filepath" "sort" "strings" - "time" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - asymkey_service "code.gitea.io/gitea/services/asymkey" ) type OptionFile struct { @@ -124,70 +120,6 @@ func LoadRepoConfig() error { return nil } -// InitRepoCommit temporarily changes with work directory. -func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) { - commitTimeStr := time.Now().Format(time.RFC3339) - - sig := u.NewGitSig() - // Because this may call hooks we should pass in the environment - env := append(os.Environ(), - "GIT_AUTHOR_NAME="+sig.Name, - "GIT_AUTHOR_EMAIL="+sig.Email, - "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_DATE="+commitTimeStr, - ) - committerName := sig.Name - committerEmail := sig.Email - - if stdout, _, err := git.NewCommand(ctx, "add", "--all"). - SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). - RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil { - log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) - return fmt.Errorf("git add --all: %w", err) - } - - cmd := git.NewCommand(ctx, "commit", "--message=Initial commit"). - AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email) - - sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) - if sign { - cmd.AddOptionFormat("-S%s", keyID) - - if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { - // need to set the committer to the KeyID owner - committerName = signer.Name - committerEmail = signer.Email - } - } else { - cmd.AddArguments("--no-gpg-sign") - } - - env = append(env, - "GIT_COMMITTER_NAME="+committerName, - "GIT_COMMITTER_EMAIL="+committerEmail, - ) - - if stdout, _, err := cmd. - SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). - RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil { - log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.String(), stdout, err) - return fmt.Errorf("git commit: %w", err) - } - - if len(defaultBranch) == 0 { - defaultBranch = setting.Repository.DefaultBranch - } - - if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch). - SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)). - RunStdString(&git.RunOpts{Dir: tmpPath, Env: InternalPushingEnvironment(u, repo)}); err != nil { - log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err) - return fmt.Errorf("git push: %w", err) - } - - return nil -} - func CheckInitRepository(ctx context.Context, owner, name, objectFormatName string) (err error) { // Somehow the directory could exist. repoPath := repo_model.RepoPath(owner, name) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 00a291500c..ff4e0ddfba 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -359,7 +359,7 @@ func Generate(ctx *context.APIContext) { return } - opts := repo_module.GenerateRepoOptions{ + opts := repo_service.GenerateRepoOptions{ Name: form.Name, DefaultBranch: form.DefaultBranch, Description: form.Description, diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index a9e1a0e9ec..9891ecee92 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -244,7 +244,7 @@ func CreatePost(ctx *context.Context) { var repo *repo_model.Repository var err error if form.RepoTemplate > 0 { - opts := repo_module.GenerateRepoOptions{ + opts := repo_service.GenerateRepoOptions{ Name: form.RepoName, Description: form.Description, Private: form.Private, diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index eb108268ae..2253b8840d 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -35,8 +35,9 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) { prepareContextForCommonProfile(ctx) ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate - ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location) - + if setting.Service.UserLocationMapURL != "" { + ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location) + } // Show OpenID URIs openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID) if err != nil { diff --git a/services/actions/job_emitter.go b/services/actions/job_emitter.go index fe39312386..d2bbbd9a7c 100644 --- a/services/actions/job_emitter.go +++ b/services/actions/job_emitter.go @@ -7,12 +7,14 @@ import ( "context" "errors" "fmt" + "strings" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/queue" + "github.com/nektos/act/pkg/jobparser" "xorm.io/builder" ) @@ -76,12 +78,15 @@ func checkJobsOfRun(ctx context.Context, runID int64) error { type jobStatusResolver struct { statuses map[int64]actions_model.Status needs map[int64][]int64 + jobMap map[int64]*actions_model.ActionRunJob } func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver { idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs)) + jobMap := make(map[int64]*actions_model.ActionRunJob) for _, job := range jobs { idToJobs[job.JobID] = append(idToJobs[job.JobID], job) + jobMap[job.ID] = job } statuses := make(map[int64]actions_model.Status, len(jobs)) @@ -97,6 +102,7 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver { return &jobStatusResolver{ statuses: statuses, needs: needs, + jobMap: jobMap, } } @@ -135,7 +141,20 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status { if allSucceed { ret[id] = actions_model.StatusWaiting } else { - ret[id] = actions_model.StatusSkipped + // If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed. + // See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds + always := false + if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 { + _, wfJob := wfJobs[0].Job() + expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}")) + always = expr == "always()" + } + + if always { + ret[id] = actions_model.StatusWaiting + } else { + ret[id] = actions_model.StatusSkipped + } } } } diff --git a/services/actions/job_emitter_test.go b/services/actions/job_emitter_test.go index e81aa61d80..038df7d4f8 100644 --- a/services/actions/job_emitter_test.go +++ b/services/actions/job_emitter_test.go @@ -70,6 +70,62 @@ func Test_jobStatusResolver_Resolve(t *testing.T) { }, want: map[int64]actions_model.Status{}, }, + { + name: "with ${{ always() }} condition", + jobs: actions_model.ActionJobList{ + {ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}}, + {ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte( + ` +name: test +on: push +jobs: + job2: + runs-on: ubuntu-latest + needs: job1 + if: ${{ always() }} + steps: + - run: echo "always run" +`)}, + }, + want: map[int64]actions_model.Status{2: actions_model.StatusWaiting}, + }, + { + name: "with always() condition", + jobs: actions_model.ActionJobList{ + {ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}}, + {ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte( + ` +name: test +on: push +jobs: + job2: + runs-on: ubuntu-latest + needs: job1 + if: always() + steps: + - run: echo "always run" +`)}, + }, + want: map[int64]actions_model.Status{2: actions_model.StatusWaiting}, + }, + { + name: "without always() condition", + jobs: actions_model.ActionJobList{ + {ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}}, + {ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte( + ` +name: test +on: push +jobs: + job2: + runs-on: ubuntu-latest + needs: job1 + steps: + - run: echo "not always run" +`)}, + }, + want: map[int64]actions_model.Status{2: actions_model.StatusSkipped}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/services/actions/notifier.go b/services/actions/notifier.go index e144484dab..1e99c51a8b 100644 --- a/services/actions/notifier.go +++ b/services/actions/notifier.go @@ -152,7 +152,13 @@ func (n *actionsNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo } else { action = api.HookIssueAssigned } - notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestAssign, action) + + hookEvent := webhook_module.HookEventIssueAssign + if issue.IsPull { + hookEvent = webhook_module.HookEventPullRequestAssign + } + + notifyIssueChange(ctx, doer, issue, hookEvent, action) } // IssueChangeMilestone notifies assignee to notifiers diff --git a/services/repository/create.go b/services/repository/create.go index 52d9f1af6f..862c88d627 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -158,7 +158,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re } // Apply changes and commit. - if err = repo_module.InitRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil { + if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil { return fmt.Errorf("initRepoCommit: %w", err) } } diff --git a/modules/repository/generate.go b/services/repository/generate.go similarity index 94% rename from modules/repository/generate.go rename to services/repository/generate.go index f622383bb5..c444b60b2c 100644 --- a/modules/repository/generate.go +++ b/services/repository/generate.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" @@ -242,7 +243,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r defaultBranch = templateRepo.DefaultBranch } - return InitRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch) + return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch) } func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { @@ -292,7 +293,7 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo return err } - if err := UpdateRepoSize(ctx, generateRepo); err != nil { + if err := repo_module.UpdateRepoSize(ctx, generateRepo); err != nil { return fmt.Errorf("failed to update size for repository: %w", err) } @@ -323,8 +324,8 @@ func (gro GenerateRepoOptions) IsValid() bool { gro.IssueLabels || gro.ProtectedBranch // or other items as they are added } -// GenerateRepository generates a repository from a template -func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { +// generateRepository generates a repository from a template +func generateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { generateRepo := &repo_model.Repository{ OwnerID: owner.ID, Owner: owner, @@ -341,7 +342,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ ObjectFormatName: templateRepo.ObjectFormatName, } - if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil { + if err = repo_module.CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil { return nil, err } @@ -358,11 +359,11 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ } } - if err = CheckInitRepository(ctx, owner.Name, generateRepo.Name, generateRepo.ObjectFormatName); err != nil { + if err = repo_module.CheckInitRepository(ctx, owner.Name, generateRepo.Name, generateRepo.ObjectFormatName); err != nil { return generateRepo, err } - if err = CheckDaemonExportOK(ctx, generateRepo); err != nil { + if err = repo_module.CheckDaemonExportOK(ctx, generateRepo); err != nil { return generateRepo, fmt.Errorf("checkDaemonExportOK: %w", err) } diff --git a/modules/repository/generate_test.go b/services/repository/generate_test.go similarity index 100% rename from modules/repository/generate_test.go rename to services/repository/generate_test.go diff --git a/services/repository/init.go b/services/repository/init.go new file mode 100644 index 0000000000..817fa4abd7 --- /dev/null +++ b/services/repository/init.go @@ -0,0 +1,83 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "context" + "fmt" + "os" + "time" + + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + asymkey_service "code.gitea.io/gitea/services/asymkey" +) + +// initRepoCommit temporarily changes with work directory. +func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) { + commitTimeStr := time.Now().Format(time.RFC3339) + + sig := u.NewGitSig() + // Because this may call hooks we should pass in the environment + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+sig.Name, + "GIT_AUTHOR_EMAIL="+sig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + committerName := sig.Name + committerEmail := sig.Email + + if stdout, _, err := git.NewCommand(ctx, "add", "--all"). + SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). + RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil { + log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) + return fmt.Errorf("git add --all: %w", err) + } + + cmd := git.NewCommand(ctx, "commit", "--message=Initial commit"). + AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email) + + sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) + if sign { + cmd.AddOptionFormat("-S%s", keyID) + + if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { + // need to set the committer to the KeyID owner + committerName = signer.Name + committerEmail = signer.Email + } + } else { + cmd.AddArguments("--no-gpg-sign") + } + + env = append(env, + "GIT_COMMITTER_NAME="+committerName, + "GIT_COMMITTER_EMAIL="+committerEmail, + ) + + if stdout, _, err := cmd. + SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). + RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil { + log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.String(), stdout, err) + return fmt.Errorf("git commit: %w", err) + } + + if len(defaultBranch) == 0 { + defaultBranch = setting.Repository.DefaultBranch + } + + if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch). + SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)). + RunStdString(&git.RunOpts{Dir: tmpPath, Env: repo_module.InternalPushingEnvironment(u, repo)}); err != nil { + log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err) + return fmt.Errorf("git push: %w", err) + } + + return nil +} diff --git a/services/repository/template.go b/services/repository/template.go index 06cf05026f..36a680c8e2 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -11,7 +11,6 @@ import ( issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - repo_module "code.gitea.io/gitea/modules/repository" notify_service "code.gitea.io/gitea/services/notify" ) @@ -63,7 +62,7 @@ func GenerateProtectedBranch(ctx context.Context, templateRepo, generateRepo *re } // GenerateRepository generates a repository from a template -func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts repo_module.GenerateRepoOptions) (_ *repo_model.Repository, err error) { +func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { if !doer.IsAdmin && !owner.CanCreateRepo() { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, @@ -72,14 +71,14 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ var generateRepo *repo_model.Repository if err = db.WithTx(ctx, func(ctx context.Context) error { - generateRepo, err = repo_module.GenerateRepository(ctx, doer, owner, templateRepo, opts) + generateRepo, err = generateRepository(ctx, doer, owner, templateRepo, opts) if err != nil { return err } // Git Content if opts.GitContent && !templateRepo.IsEmpty { - if err = repo_module.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { + if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { return err } } diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl index 0376da4a71..40dae76dc7 100644 --- a/templates/repo/clone_script.tmpl +++ b/templates/repo/clone_script.tmpl @@ -24,14 +24,22 @@ const btn = isSSH ? sshBtn : httpsBtn; if (!btn) return; - let link = btn.getAttribute('data-link'); - if (link.startsWith('http://') || link.startsWith('https://')) { - // use current protocol/host as the clone link - const url = new URL(link); - url.protocol = window.location.protocol; - url.host = window.location.host; - link = url.toString(); + // NOTE: Keep this function in sync with the one in the js folder + function toOriginUrl(urlStr) { + try { + if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) { + const {origin, protocol, hostname, port} = window.location; + const url = new URL(urlStr, origin); + url.protocol = protocol; + url.hostname = hostname; + url.port = port || (protocol === 'https:' ? '443' : '80'); + return url.toString(); + } + } catch {} + return urlStr; } + const link = toOriginUrl(btn.getAttribute('data-link')); + for (const el of document.getElementsByClassName('js-clone-url')) { el[el.nodeName === 'INPUT' ? 'value' : 'textContent'] = link; } diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css index bac002e3db..9cc2a656cb 100644 --- a/web_src/css/themes/theme-gitea-dark.css +++ b/web_src/css/themes/theme-gitea-dark.css @@ -3,72 +3,72 @@ :root { --is-dark-theme: true; - --color-primary: #87ab63; + --color-primary: #4183c4; --color-primary-contrast: #ffffff; - --color-primary-dark-1: #93b373; - --color-primary-dark-2: #9fbc82; - --color-primary-dark-3: #abc492; - --color-primary-dark-4: #b7cda1; - --color-primary-dark-5: #cfddc1; - --color-primary-dark-6: #e7eee0; - --color-primary-dark-7: #f8faf6; - --color-primary-light-1: #7a9e55; - --color-primary-light-2: #6c8c4c; - --color-primary-light-3: #5f7b42; - --color-primary-light-4: #516939; - --color-primary-light-5: #364626; - --color-primary-light-6: #1b2313; - --color-primary-light-7: #080b06; - --color-primary-alpha-10: #87ab6319; - --color-primary-alpha-20: #87ab6333; - --color-primary-alpha-30: #87ab634b; - --color-primary-alpha-40: #87ab6366; - --color-primary-alpha-50: #87ab6380; - --color-primary-alpha-60: #87ab6399; - --color-primary-alpha-70: #87ab63b3; - --color-primary-alpha-80: #87ab63cc; - --color-primary-alpha-90: #87ab63e1; + --color-primary-dark-1: #548fca; + --color-primary-dark-2: #679cd0; + --color-primary-dark-3: #7aa8d6; + --color-primary-dark-4: #8db5dc; + --color-primary-dark-5: #b3cde7; + --color-primary-dark-6: #d9e6f3; + --color-primary-dark-7: #f4f8fb; + --color-primary-light-1: #3876b3; + --color-primary-light-2: #31699f; + --color-primary-light-3: #2b5c8b; + --color-primary-light-4: #254f77; + --color-primary-light-5: #193450; + --color-primary-light-6: #0c1a28; + --color-primary-light-7: #04080c; + --color-primary-alpha-10: #4183c419; + --color-primary-alpha-20: #4183c433; + --color-primary-alpha-30: #4183c44b; + --color-primary-alpha-40: #4183c466; + --color-primary-alpha-50: #4183c480; + --color-primary-alpha-60: #4183c499; + --color-primary-alpha-70: #4183c4b3; + --color-primary-alpha-80: #4183c4cc; + --color-primary-alpha-90: #4183c4e1; --color-primary-hover: var(--color-primary-light-1); --color-primary-active: var(--color-primary-light-2); - --color-secondary: #525767; - --color-secondary-dark-1: #5c6374; - --color-secondary-dark-2: #666e81; - --color-secondary-dark-3: #7c8497; - --color-secondary-dark-4: #8990a1; - --color-secondary-dark-5: #959cab; - --color-secondary-dark-6: #a2a8b5; - --color-secondary-dark-7: #afb4c0; - --color-secondary-dark-8: #bcc0ca; - --color-secondary-dark-9: #c9cbd4; - --color-secondary-dark-10: #d6d7de; - --color-secondary-dark-11: #e2e3e8; - --color-secondary-dark-12: #eeeff2; - --color-secondary-dark-13: #fbfbfc; - --color-secondary-light-1: #454a57; - --color-secondary-light-2: #383c47; - --color-secondary-light-3: #2c2f37; - --color-secondary-light-4: #1f2226; - --color-secondary-alpha-10: #52576719; - --color-secondary-alpha-20: #52576733; - --color-secondary-alpha-30: #5257674b; - --color-secondary-alpha-40: #52576766; - --color-secondary-alpha-50: #52576780; - --color-secondary-alpha-60: #52576799; - --color-secondary-alpha-70: #525767b3; - --color-secondary-alpha-80: #525767cc; - --color-secondary-alpha-90: #525767e1; + --color-secondary: #3f4346; + --color-secondary-dark-1: #464a4d; + --color-secondary-dark-2: #4f5356; + --color-secondary-dark-3: #5f6366; + --color-secondary-dark-4: #72767a; + --color-secondary-dark-5: #7f8488; + --color-secondary-dark-6: #8d9297; + --color-secondary-dark-7: #999ea3; + --color-secondary-dark-8: #a6abaf; + --color-secondary-dark-9: #aeb3b8; + --color-secondary-dark-10: #babfc4; + --color-secondary-dark-11: #c5cbd0; + --color-secondary-dark-12: #ced4da; + --color-secondary-dark-13: #d1d7dd; + --color-secondary-light-1: #313538; + --color-secondary-light-2: #272b2e; + --color-secondary-light-3: #1e2225; + --color-secondary-light-4: #171b1e; + --color-secondary-alpha-10: #3f434619; + --color-secondary-alpha-20: #3f434633; + --color-secondary-alpha-30: #3f43464b; + --color-secondary-alpha-40: #3f434666; + --color-secondary-alpha-50: #3f434680; + --color-secondary-alpha-60: #3f434699; + --color-secondary-alpha-70: #3f4346b3; + --color-secondary-alpha-80: #3f4346cc; + --color-secondary-alpha-90: #3f4346e1; --color-secondary-button: var(--color-secondary-dark-4); --color-secondary-hover: var(--color-secondary-dark-3); --color-secondary-active: var(--color-secondary-dark-2); /* console colors - used for actions console and console files */ - --color-console-fg: #eeeff2; - --color-console-fg-subtle: #959cab; - --color-console-bg: #262936; - --color-console-border: #383c47; + --color-console-fg: #ced4da; + --color-console-fg-subtle: #7f8488; + --color-console-bg: #1c2023; + --color-console-border: #272b2e; --color-console-hover-bg: #ffffff16; - --color-console-active-bg: #454a57; - --color-console-menu-bg: #383c47; - --color-console-menu-border: #5c6374; + --color-console-active-bg: #313538; + --color-console-menu-bg: #272b2e; + --color-console-menu-border: #464a4d; /* named colors */ --color-red: #cc4848; --color-orange: #cc580c; @@ -81,7 +81,7 @@ --color-purple: #b259d0; --color-pink: #d22e8b; --color-brown: #a47252; - --color-black: #2e323e; + --color-black: #1f2326; /* light variants - produced via Sass scale-color(color, $lightness: +10%) */ --color-red-light: #d15a5a; --color-orange-light: #f6a066; @@ -94,7 +94,7 @@ --color-purple-light: #ba6ad5; --color-pink-light: #d74397; --color-brown-light: #b08061; - --color-black-light: #3f4555; + --color-black-light: #46494d; /* dark 1 variants - produced via Sass scale-color(color, $lightness: -10%) */ --color-red-dark-1: #c23636; --color-orange-dark-1: #f38236; @@ -107,7 +107,7 @@ --color-purple-dark-1: #a742c9; --color-pink-dark-1: #be297d; --color-brown-dark-1: #94674a; - --color-black-dark-1: #292d38; + --color-black-dark-1: #2c2f35; /* dark 2 variants - produced via Sass scale-color(color, $lightness: -20%) */ --color-red-dark-2: #ad3030; --color-orange-dark-2: #f16e17; @@ -120,7 +120,7 @@ --color-purple-dark-2: #9834b9; --color-pink-dark-2: #a9246f; --color-brown-dark-2: #835b42; - --color-black-dark-2: #252832; + --color-black-dark-2: #292a2e; /* ansi colors used for actions console and console files */ --color-ansi-black: var(--color-black); --color-ansi-red: var(--color-red); @@ -139,8 +139,8 @@ --color-ansi-bright-cyan: var(--color-teal-light); --color-ansi-bright-white: var(--color-console-fg); /* other colors */ - --color-grey: #505665; - --color-grey-light: #a1a6b7; + --color-grey: #3c4043; + --color-grey-light: #898e92; --color-gold: #b1983b; --color-white: #ffffff; --color-diff-removed-word-bg: #6f3333; @@ -151,7 +151,7 @@ --color-diff-removed-row-border: #634343; --color-diff-moved-row-border: #bcca6f; --color-diff-added-row-border: #314a37; - --color-diff-inactive: #353846; + --color-diff-inactive: #24282b; --color-error-border: #a04141; --color-error-bg: #522; --color-error-bg-active: #744; @@ -180,40 +180,40 @@ --color-orange-badge-hover-bg: #f2711c4d; --color-git: #f05133; /* target-based colors */ - --color-body: #2e323e; - --color-box-header: #303340; - --color-box-body: #222733; - --color-box-body-highlight: #262b36; - --color-text-dark: #dbe0ea; - --color-text: #cbd0da; - --color-text-light: #bbbfca; - --color-text-light-1: #aaafb9; - --color-text-light-2: #9a9ea9; - --color-text-light-3: #8a8e99; - --color-footer: #232834; - --color-timeline: #4c525e; - --color-input-text: #dfe3ec; - --color-input-background: #1e252e; - --color-input-toggle-background: #454a57; + --color-body: #1f2326; + --color-box-header: #202427; + --color-box-body: #191d20; + --color-box-body-highlight: #1d2124; + --color-text-dark: #c4cace; + --color-text: #babfc3; + --color-text-light: #a8acb0; + --color-text-light-1: #9ca0a5; + --color-text-light-2: #8f9397; + --color-text-light-3: #828689; + --color-footer: #1b1f22; + --color-timeline: #383c3f; + --color-input-text: #c7ccd1; + --color-input-background: #161a1d; + --color-input-toggle-background: #313538; --color-input-border: var(--color-secondary); --color-input-border-hover: var(--color-secondary-dark-1); - --color-header-wrapper: #202430; + --color-header-wrapper: #191d20; --color-light: #00000028; --color-light-mimic-enabled: rgba(0, 0, 0, calc(40 / 255 * 222 / 255 / var(--opacity-disabled))); --color-light-border: #ffffff28; --color-hover: #ffffff19; --color-active: #ffffff24; - --color-menu: #1e252e; - --color-card: #1e252e; + --color-menu: #161a1d; + --color-card: #161a1d; --color-markup-table-row: #ffffff06; --color-markup-code-block: #ffffff16; - --color-button: #1e252e; - --color-code-bg: #222733; - --color-code-sidebar-bg: #232834; + --color-button: #161a1d; + --color-code-bg: #191d20; + --color-code-sidebar-bg: #1b1f22; --color-shadow: #00000058; - --color-secondary-bg: #2a2e3a; - --color-expand-button: #3c404d; - --color-placeholder-text: #8a8e99; + --color-secondary-bg: #2f3135; + --color-expand-button: #414348; + --color-placeholder-text: #777b7f; --color-editor-line-highlight: var(--color-primary-light-5); --color-project-board-bg: var(--color-secondary-light-2); --color-project-board-dark-label: #111111; @@ -224,13 +224,13 @@ --color-reaction-active-bg: var(--color-primary-light-5); --color-tooltip-text: #ffffff; --color-tooltip-bg: #000000f0; - --color-nav-bg: #232834; - --color-nav-hover-bg: #383c47; + --color-nav-bg: #1b1f22; + --color-nav-hover-bg: #272b2e; --color-nav-text: var(--color-text); - --color-label-text: #dfe3ec; - --color-label-bg: #7c84974b; - --color-label-hover-bg: #7c8497a0; - --color-label-active-bg: #7c8497ff; + --color-label-text: #ced2d7; + --color-label-bg: #7a7f834b; + --color-label-hover-bg: #7a7f83a0; + --color-label-active-bg: #7a7f83ff; --color-accent: var(--color-primary-light-1); --color-small-accent: var(--color-primary-light-5); --color-active-line: #534d1b; diff --git a/web_src/js/features/repo-findfile.js b/web_src/js/features/repo-findfile.js index 158732acc2..cb03d9e803 100644 --- a/web_src/js/features/repo-findfile.js +++ b/web_src/js/features/repo-findfile.js @@ -1,13 +1,11 @@ -import $ from 'jquery'; import {svg} from '../svg.js'; import {toggleElem} from '../utils/dom.js'; import {pathEscapeSegments} from '../utils/url.js'; - -const {csrf} = window.config; +import {GET} from '../modules/fetch.js'; const threshold = 50; let files = []; -let $repoFindFileInput, $repoFindFileTableBody, $repoFindFileNoResult; +let repoFindFileInput, repoFindFileTableBody, repoFindFileNoResult; // return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...] // res[even] is unmatched, res[odd] is matched, see unit tests for examples @@ -74,46 +72,46 @@ export function filterRepoFilesWeighted(files, filter) { } function filterRepoFiles(filter) { - const treeLink = $repoFindFileInput.attr('data-url-tree-link'); - $repoFindFileTableBody.empty(); + const treeLink = repoFindFileInput.getAttribute('data-url-tree-link'); + repoFindFileTableBody.innerHTML = ''; const filterResult = filterRepoFilesWeighted(files, filter); - const tmplRow = ``; - toggleElem($repoFindFileNoResult, filterResult.length === 0); + toggleElem(repoFindFileNoResult, filterResult.length === 0); for (const r of filterResult) { - const $row = $(tmplRow); - const $a = $row.find('a'); - $a.attr('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`); - const $octiconFile = $(svg('octicon-file')).addClass('gt-mr-3'); - $a.append($octiconFile); - // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz'] - // the matchResult[odd] is matched and highlighted to red. - for (let j = 0; j < r.matchResult.length; j++) { - if (!r.matchResult[j]) continue; - const $span = $('').text(r.matchResult[j]); - if (j % 2 === 1) $span.addClass('ui text red'); - $a.append($span); + const row = document.createElement('tr'); + const cell = document.createElement('td'); + const a = document.createElement('a'); + a.setAttribute('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`); + a.innerHTML = svg('octicon-file', 16, 'gt-mr-3'); + row.append(cell); + cell.append(a); + for (const [index, part] of r.matchResult.entries()) { + const span = document.createElement('span'); + // safely escape by using textContent + span.textContent = part; + // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz'] + // the matchResult[odd] is matched and highlighted to red. + if (index % 2 === 1) span.classList.add('ui', 'text', 'red'); + a.append(span); } - $repoFindFileTableBody.append($row); + repoFindFileTableBody.append(row); } } async function loadRepoFiles() { - files = await $.ajax({ - url: $repoFindFileInput.attr('data-url-data-link'), - headers: {'X-Csrf-Token': csrf} - }); - filterRepoFiles($repoFindFileInput.val()); + const response = await GET(repoFindFileInput.getAttribute('data-url-data-link')); + files = await response.json(); + filterRepoFiles(repoFindFileInput.value); } export function initFindFileInRepo() { - $repoFindFileInput = $('#repo-file-find-input'); - if (!$repoFindFileInput.length) return; + repoFindFileInput = document.getElementById('repo-file-find-input'); + if (!repoFindFileInput) return; - $repoFindFileTableBody = $('#repo-find-file-table tbody'); - $repoFindFileNoResult = $('#repo-find-file-no-result'); - $repoFindFileInput.on('input', () => filterRepoFiles($repoFindFileInput.val())); + repoFindFileTableBody = document.querySelector('#repo-find-file-table tbody'); + repoFindFileNoResult = document.getElementById('repo-find-file-no-result'); + repoFindFileInput.addEventListener('input', () => filterRepoFiles(repoFindFileInput.value)); loadRepoFiles(); } diff --git a/web_src/js/webcomponents/GiteaOriginUrl.js b/web_src/js/webcomponents/GiteaOriginUrl.js index 5d71d95c60..6e6f84d739 100644 --- a/web_src/js/webcomponents/GiteaOriginUrl.js +++ b/web_src/js/webcomponents/GiteaOriginUrl.js @@ -1,7 +1,8 @@ -// Convert an absolute or relative URL to an absolute URL with the current origin +// Convert an absolute or relative URL to an absolute URL with the current origin. It only +// processes absolute HTTP/HTTPS URLs or relative URLs like '/xxx' or '//host/xxx'. +// NOTE: Keep this function in sync with clone_script.tmpl export function toOriginUrl(urlStr) { try { - // only process absolute HTTP/HTTPS URL or relative URLs ('/xxx' or '//host/xxx') if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) { const {origin, protocol, hostname, port} = window.location; const url = new URL(urlStr, origin);