From b667634b32e240f3f3260ab7e304f72f4ff75659 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 14 Apr 2023 19:27:11 +0200 Subject: [PATCH 01/25] Fix meilisearch not working when searching across multiple repositories (#24109) This would happen in the issue and pull request dashboards, while the per repository lists worked fine. Use OR instead of AND for repo IDs. --- modules/indexer/issues/meilisearch.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/indexer/issues/meilisearch.go b/modules/indexer/issues/meilisearch.go index 5c45236e66006..319dc3e30b2fa 100644 --- a/modules/indexer/issues/meilisearch.go +++ b/modules/indexer/issues/meilisearch.go @@ -6,6 +6,7 @@ package issues import ( "context" "strconv" + "strings" "sync" "time" @@ -120,10 +121,11 @@ func (b *MeilisearchIndexer) Delete(ids ...int64) error { // Search searches for issues by given conditions. // Returns the matching issue IDs func (b *MeilisearchIndexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*SearchResult, error) { - filter := make([][]string, 0, len(repoIDs)) + repoFilters := make([]string, 0, len(repoIDs)) for _, repoID := range repoIDs { - filter = append(filter, []string{"repo_id = " + strconv.FormatInt(repoID, 10)}) + repoFilters = append(repoFilters, "repo_id = "+strconv.FormatInt(repoID, 10)) } + filter := strings.Join(repoFilters, " OR ") searchRes, err := b.client.Index(b.indexerName).Search(keyword, &meilisearch.SearchRequest{ Filter: filter, Limit: int64(limit), From cfe3d6e9b507a9331328e55ff98a1f582abae185 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Apr 2023 02:18:28 +0800 Subject: [PATCH 02/25] Make more functions use ctx instead of db.DefaultContext (#24068) Continue the "ctx refactoring" work. There are still a lot db.DefaultContext, incorrect context could cause database deadlock errors. --- models/issues/assignees.go | 4 +-- models/issues/assignees_test.go | 6 ++-- models/issues/issue.go | 4 +-- models/issues/issue_xref_test.go | 2 +- routers/api/v1/repo/issue.go | 8 ++--- routers/api/v1/repo/pull.go | 2 +- routers/api/v1/repo/pull_review.go | 4 +-- routers/web/repo/issue.go | 16 ++++----- services/issue/assignee.go | 25 +++++++------ services/issue/assignee_test.go | 2 +- services/issue/comments.go | 8 ++--- services/issue/issue.go | 57 +++++++++++++++--------------- services/issue/issue_test.go | 6 ++-- services/pull/comment.go | 2 +- services/pull/pull.go | 6 ++-- services/pull/review.go | 4 +-- tests/integration/api_pull_test.go | 2 +- 17 files changed, 79 insertions(+), 79 deletions(-) diff --git a/models/issues/assignees.go b/models/issues/assignees.go index 16f675a83f875..fdd0d6f2274b6 100644 --- a/models/issues/assignees.go +++ b/models/issues/assignees.go @@ -63,8 +63,8 @@ func IsUserAssignedToIssue(ctx context.Context, issue *Issue, user *user_model.U } // ToggleIssueAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. -func ToggleIssueAssignee(issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ToggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return false, nil, err } diff --git a/models/issues/assignees_test.go b/models/issues/assignees_test.go index 8a2cef8acd8e6..2185f6fc4244f 100644 --- a/models/issues/assignees_test.go +++ b/models/issues/assignees_test.go @@ -24,17 +24,17 @@ func TestUpdateAssignee(t *testing.T) { // Assign multiple users user2, err := user_model.GetUserByID(db.DefaultContext, 2) assert.NoError(t, err) - _, _, err = issues_model.ToggleIssueAssignee(issue, &user_model.User{ID: 1}, user2.ID) + _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user2.ID) assert.NoError(t, err) user3, err := user_model.GetUserByID(db.DefaultContext, 3) assert.NoError(t, err) - _, _, err = issues_model.ToggleIssueAssignee(issue, &user_model.User{ID: 1}, user3.ID) + _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user3.ID) assert.NoError(t, err) user1, err := user_model.GetUserByID(db.DefaultContext, 1) // This user is already assigned (see the definition in fixtures), so running UpdateAssignee should unassign him assert.NoError(t, err) - _, _, err = issues_model.ToggleIssueAssignee(issue, &user_model.User{ID: 1}, user1.ID) + _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user1.ID) assert.NoError(t, err) // Check if he got removed diff --git a/models/issues/issue.go b/models/issues/issue.go index 64b0edd3e7bac..583603047661e 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -743,8 +743,8 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, } // ChangeIssueTitle changes the title of this issue, as the given user. -func ChangeIssueTitle(issue *Issue, doer *user_model.User, oldTitle string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User, oldTitle string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_xref_test.go b/models/issues/issue_xref_test.go index 6d96c398d0524..6e94c262723eb 100644 --- a/models/issues/issue_xref_test.go +++ b/models/issues/issue_xref_test.go @@ -83,7 +83,7 @@ func TestXRef_NeuterCrossReferences(t *testing.T) { d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) i.Title = "title2, no mentions" - assert.NoError(t, issues_model.ChangeIssueTitle(i, d, title)) + assert.NoError(t, issues_model.ChangeIssueTitle(db.DefaultContext, i, d, title)) ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}) assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type) diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 06bf06b4e8472..5bf5fc8c8bcca 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -651,7 +651,7 @@ func CreateIssue(ctx *context.APIContext) { form.Labels = make([]int64, 0) } - if err := issue_service.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil { + if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err) return @@ -752,7 +752,7 @@ func EditIssue(ctx *context.APIContext) { issue.Content = *form.Body } if form.Ref != nil { - err = issue_service.ChangeIssueRef(issue, ctx.Doer, *form.Ref) + err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref) if err != nil { ctx.Error(http.StatusInternalServerError, "UpdateRef", err) return @@ -790,7 +790,7 @@ func EditIssue(ctx *context.APIContext) { oneAssignee = *form.Assignee } - err = issue_service.UpdateAssignees(issue, oneAssignee, form.Assignees, ctx.Doer) + err = issue_service.UpdateAssignees(ctx, issue, oneAssignee, form.Assignees, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err) return @@ -887,7 +887,7 @@ func DeleteIssue(ctx *context.APIContext) { return } - if err = issue_service.DeleteIssue(ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { + if err = issue_service.DeleteIssue(ctx, ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { ctx.Error(http.StatusInternalServerError, "DeleteIssueByID", err) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 9b5ec0b3f8ed6..f4e2969d7d1c2 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -534,7 +534,7 @@ func EditPullRequest(ctx *context.APIContext) { // Send an empty array ([]) to clear all assignees from the Issue. if ctx.Repo.CanWrite(unit.TypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) { - err = issue_service.UpdateAssignees(issue, form.Assignee, form.Assignees, ctx.Doer) + err = issue_service.UpdateAssignees(ctx, issue, form.Assignee, form.Assignees, ctx.Doer) if err != nil { if user_model.IsErrUserNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err)) diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 8f4b9dafec67d..a568cd565a768 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -706,7 +706,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } for _, reviewer := range reviewers { - comment, err := issue_service.ReviewRequest(pr.Issue, ctx.Doer, reviewer, isAdd) + comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd) if err != nil { ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) return @@ -750,7 +750,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } for _, teamReviewer := range teamReviewers { - comment, err := issue_service.TeamReviewRequest(pr.Issue, ctx.Doer, teamReviewer, isAdd) + comment, err := issue_service.TeamReviewRequest(ctx, pr.Issue, ctx.Doer, teamReviewer, isAdd) if err != nil { ctx.ServerError("TeamReviewRequest", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index d251f2043c043..fb61ec00d127c 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -964,7 +964,7 @@ func DeleteIssue(ctx *context.Context) { return } - if err := issue_service.DeleteIssue(ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { + if err := issue_service.DeleteIssue(ctx, ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { ctx.ServerError("DeleteIssueByID", err) return } @@ -1132,7 +1132,7 @@ func NewIssuePost(ctx *context.Context) { Ref: form.Ref, } - if err := issue_service.NewIssue(repo, issue, labelIDs, attachments, assigneeIDs); err != nil { + if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) return @@ -2013,7 +2013,7 @@ func UpdateIssueTitle(ctx *context.Context) { return } - if err := issue_service.ChangeTitle(issue, ctx.Doer, title); err != nil { + if err := issue_service.ChangeTitle(ctx, issue, ctx.Doer, title); err != nil { ctx.ServerError("ChangeTitle", err) return } @@ -2037,7 +2037,7 @@ func UpdateIssueRef(ctx *context.Context) { ref := ctx.FormTrim("ref") - if err := issue_service.ChangeIssueRef(issue, ctx.Doer, ref); err != nil { + if err := issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, ref); err != nil { ctx.ServerError("ChangeRef", err) return } @@ -2161,7 +2161,7 @@ func UpdateIssueAssignee(ctx *context.Context) { for _, issue := range issues { switch action { case "clear": - if err := issue_service.DeleteNotPassedAssignee(issue, ctx.Doer, []*user_model.User{}); err != nil { + if err := issue_service.DeleteNotPassedAssignee(ctx, issue, ctx.Doer, []*user_model.User{}); err != nil { ctx.ServerError("ClearAssignees", err) return } @@ -2182,7 +2182,7 @@ func UpdateIssueAssignee(ctx *context.Context) { return } - _, _, err = issue_service.ToggleAssignee(issue, ctx.Doer, assigneeID) + _, _, err = issue_service.ToggleAssignee(ctx, issue, ctx.Doer, assigneeID) if err != nil { ctx.ServerError("ToggleAssignee", err) return @@ -2269,7 +2269,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - _, err = issue_service.TeamReviewRequest(issue, ctx.Doer, team, action == "attach") + _, err = issue_service.TeamReviewRequest(ctx, issue, ctx.Doer, team, action == "attach") if err != nil { ctx.ServerError("TeamReviewRequest", err) return @@ -2307,7 +2307,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - _, err = issue_service.ReviewRequest(issue, ctx.Doer, reviewer, action == "attach") + _, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach") if err != nil { ctx.ServerError("ReviewRequest", err) return diff --git a/services/issue/assignee.go b/services/issue/assignee.go index e5e1456c3f12e..4d0224d4bff10 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -6,7 +6,6 @@ package issue import ( "context" - "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -18,7 +17,7 @@ import ( ) // DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array -func DeleteNotPassedAssignee(issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) { +func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) { var found bool oriAssignes := make([]*user_model.User, len(issue.Assignees)) _ = copy(oriAssignes, issue.Assignees) @@ -34,7 +33,7 @@ func DeleteNotPassedAssignee(issue *issues_model.Issue, doer *user_model.User, a if !found { // This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here - if _, _, err := ToggleAssignee(issue, doer, assignee.ID); err != nil { + if _, _, err := ToggleAssignee(ctx, issue, doer, assignee.ID); err != nil { return err } } @@ -44,25 +43,25 @@ func DeleteNotPassedAssignee(issue *issues_model.Issue, doer *user_model.User, a } // ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. -func ToggleAssignee(issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { - removed, comment, err = issues_model.ToggleIssueAssignee(issue, doer, assigneeID) +func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { + removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID) if err != nil { return } - assignee, err1 := user_model.GetUserByID(db.DefaultContext, assigneeID) + assignee, err1 := user_model.GetUserByID(ctx, assigneeID) if err1 != nil { err = err1 return } - notification.NotifyIssueChangeAssignee(db.DefaultContext, doer, issue, assignee, removed, comment) + notification.NotifyIssueChangeAssignee(ctx, doer, issue, assignee, removed, comment) return removed, comment, err } // ReviewRequest add or remove a review request from a user for this PR, and make comment for it. -func ReviewRequest(issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { +func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { if isAdd { comment, err = issues_model.AddReviewRequest(issue, reviewer, doer) } else { @@ -74,7 +73,7 @@ func ReviewRequest(issue *issues_model.Issue, doer, reviewer *user_model.User, i } if comment != nil { - notification.NotifyPullReviewRequest(db.DefaultContext, doer, issue, reviewer, isAdd, comment) + notification.NotifyPullReviewRequest(ctx, doer, issue, reviewer, isAdd, comment) } return comment, err @@ -229,7 +228,7 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, } // TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it. -func TeamReviewRequest(issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) { +func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) { if isAdd { comment, err = issues_model.AddTeamReviewRequest(issue, reviewer, doer) } else { @@ -245,11 +244,11 @@ func TeamReviewRequest(issue *issues_model.Issue, doer *user_model.User, reviewe } // notify all user in this team - if err = comment.LoadIssue(db.DefaultContext); err != nil { + if err = comment.LoadIssue(ctx); err != nil { return } - members, err := organization.GetTeamMembers(db.DefaultContext, &organization.SearchMembersOptions{ + members, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ TeamID: reviewer.ID, }) if err != nil { @@ -261,7 +260,7 @@ func TeamReviewRequest(issue *issues_model.Issue, doer *user_model.User, reviewe continue } comment.AssigneeID = member.ID - notification.NotifyPullReviewRequest(db.DefaultContext, doer, issue, member, isAdd, comment) + notification.NotifyPullReviewRequest(ctx, doer, issue, member, isAdd, comment) } return comment, err diff --git a/services/issue/assignee_test.go b/services/issue/assignee_test.go index 114ace078edd1..43b24e1d1fc4f 100644 --- a/services/issue/assignee_test.go +++ b/services/issue/assignee_test.go @@ -31,7 +31,7 @@ func TestDeleteNotPassedAssignee(t *testing.T) { assert.True(t, isAssigned) // Clean everyone - err = DeleteNotPassedAssignee(issue, user1, []*user_model.User{}) + err = DeleteNotPassedAssignee(db.DefaultContext, issue, user1, []*user_model.User{}) assert.NoError(t, err) assert.EqualValues(t, 0, len(issue.Assignees)) diff --git a/services/issue/comments.go b/services/issue/comments.go index 1323fb47aa440..4fe07c17b9d37 100644 --- a/services/issue/comments.go +++ b/services/issue/comments.go @@ -16,8 +16,8 @@ import ( ) // CreateComment creates comment of issue or commit. -func CreateComment(opts *issues_model.CreateCommentOptions) (comment *issues_model.Comment, err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CreateComment(ctx context.Context, opts *issues_model.CreateCommentOptions) (comment *issues_model.Comment, err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -53,7 +53,7 @@ func CreateRefComment(doer *user_model.User, repo *repo_model.Repository, issue return nil } - _, err = CreateComment(&issues_model.CreateCommentOptions{ + _, err = CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeCommitRef, Doer: doer, Repo: repo, @@ -66,7 +66,7 @@ func CreateRefComment(doer *user_model.User, repo *repo_model.Repository, issue // CreateIssueComment creates a plain issue comment. func CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content string, attachments []string) (*issues_model.Comment, error) { - comment, err := CreateComment(&issues_model.CreateCommentOptions{ + comment, err := CreateComment(ctx, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeComment, Doer: doer, Repo: repo, diff --git a/services/issue/issue.go b/services/issue/issue.go index b91ee4fc18b07..d4f827e99af56 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -4,6 +4,7 @@ package issue import ( + "context" "fmt" activities_model "code.gitea.io/gitea/models/activities" @@ -20,49 +21,49 @@ import ( ) // NewIssue creates new issue with labels for repository. -func NewIssue(repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64) error { +func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64) error { if err := issues_model.NewIssue(repo, issue, labelIDs, uuids); err != nil { return err } for _, assigneeID := range assigneeIDs { - if err := AddAssigneeIfNotAssigned(issue, issue.Poster, assigneeID); err != nil { + if err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID); err != nil { return err } } - mentions, err := issues_model.FindAndUpdateIssueMentions(db.DefaultContext, issue, issue.Poster, issue.Content) + mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content) if err != nil { return err } - notification.NotifyNewIssue(db.DefaultContext, issue, mentions) + notification.NotifyNewIssue(ctx, issue, mentions) if len(issue.Labels) > 0 { - notification.NotifyIssueChangeLabels(db.DefaultContext, issue.Poster, issue, issue.Labels, nil) + notification.NotifyIssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil) } if issue.Milestone != nil { - notification.NotifyIssueChangeMilestone(db.DefaultContext, issue.Poster, issue, 0) + notification.NotifyIssueChangeMilestone(ctx, issue.Poster, issue, 0) } return nil } // ChangeTitle changes the title of this issue, as the given user. -func ChangeTitle(issue *issues_model.Issue, doer *user_model.User, title string) (err error) { +func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, title string) (err error) { oldTitle := issue.Title issue.Title = title - if err = issues_model.ChangeIssueTitle(issue, doer, oldTitle); err != nil { + if err = issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil { return } - notification.NotifyIssueChangeTitle(db.DefaultContext, doer, issue, oldTitle) + notification.NotifyIssueChangeTitle(ctx, doer, issue, oldTitle) return nil } // ChangeIssueRef changes the branch of this issue, as the given user. -func ChangeIssueRef(issue *issues_model.Issue, doer *user_model.User, ref string) error { +func ChangeIssueRef(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, ref string) error { oldRef := issue.Ref issue.Ref = ref @@ -70,7 +71,7 @@ func ChangeIssueRef(issue *issues_model.Issue, doer *user_model.User, ref string return err } - notification.NotifyIssueChangeRef(db.DefaultContext, doer, issue, oldRef) + notification.NotifyIssueChangeRef(ctx, doer, issue, oldRef) return nil } @@ -81,7 +82,7 @@ func ChangeIssueRef(issue *issues_model.Issue, doer *user_model.User, ref string // "assignees" (array): Logins for Users to assign to this issue. // Pass one or more user logins to replace the set of assignees on this Issue. // Send an empty array ([]) to clear all assignees from the Issue. -func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssignees []string, doer *user_model.User) (err error) { +func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee string, multipleAssignees []string, doer *user_model.User) (err error) { var allNewAssignees []*user_model.User // Keep the old assignee thingy for compatibility reasons @@ -102,7 +103,7 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi // Loop through all assignees to add them for _, assigneeName := range multipleAssignees { - assignee, err := user_model.GetUserByName(db.DefaultContext, assigneeName) + assignee, err := user_model.GetUserByName(ctx, assigneeName) if err != nil { return err } @@ -111,7 +112,7 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi } // Delete all old assignees not passed - if err = DeleteNotPassedAssignee(issue, doer, allNewAssignees); err != nil { + if err = DeleteNotPassedAssignee(ctx, issue, doer, allNewAssignees); err != nil { return err } @@ -121,7 +122,7 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi // has access to the repo. for _, assignee := range allNewAssignees { // Extra method to prevent double adding (which would result in removing) - err = AddAssigneeIfNotAssigned(issue, doer, assignee.ID) + err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID) if err != nil { return err } @@ -131,42 +132,42 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi } // DeleteIssue deletes an issue -func DeleteIssue(doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue) error { +func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue) error { // load issue before deleting it - if err := issue.LoadAttributes(gitRepo.Ctx); err != nil { + if err := issue.LoadAttributes(ctx); err != nil { return err } - if err := issue.LoadPullRequest(gitRepo.Ctx); err != nil { + if err := issue.LoadPullRequest(ctx); err != nil { return err } // delete entries in database - if err := deleteIssue(issue); err != nil { + if err := deleteIssue(ctx, issue); err != nil { return err } // delete pull request related git data - if issue.IsPull { + if issue.IsPull && gitRepo != nil { if err := gitRepo.RemoveReference(fmt.Sprintf("%s%d/head", git.PullPrefix, issue.PullRequest.Index)); err != nil { return err } } - notification.NotifyDeleteIssue(gitRepo.Ctx, doer, issue) + notification.NotifyDeleteIssue(ctx, doer, issue) return nil } // AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue. // Also checks for access of assigned user -func AddAssigneeIfNotAssigned(issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) { - assignee, err := user_model.GetUserByID(db.DefaultContext, assigneeID) +func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) { + assignee, err := user_model.GetUserByID(ctx, assigneeID) if err != nil { return err } // Check if the user is already assigned - isAssigned, err := issues_model.IsUserAssignedToIssue(db.DefaultContext, issue, assignee) + isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee) if err != nil { return err } @@ -175,7 +176,7 @@ func AddAssigneeIfNotAssigned(issue *issues_model.Issue, doer *user_model.User, return nil } - valid, err := access_model.CanBeAssigned(db.DefaultContext, assignee, issue.Repo, issue.IsPull) + valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull) if err != nil { return err } @@ -183,7 +184,7 @@ func AddAssigneeIfNotAssigned(issue *issues_model.Issue, doer *user_model.User, return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name} } - _, _, err = ToggleAssignee(issue, doer, assigneeID) + _, _, err = ToggleAssignee(ctx, issue, doer, assigneeID) if err != nil { return err } @@ -206,8 +207,8 @@ func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[i } // deleteIssue deletes the issue -func deleteIssue(issue *issues_model.Issue) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func deleteIssue(ctx context.Context, issue *issues_model.Issue) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/services/issue/issue_test.go b/services/issue/issue_test.go index b67d2e2e79a25..da0e97c23c258 100644 --- a/services/issue/issue_test.go +++ b/services/issue/issue_test.go @@ -44,7 +44,7 @@ func TestIssue_DeleteIssue(t *testing.T) { ID: issueIDs[2], } - err = deleteIssue(issue) + err = deleteIssue(db.DefaultContext, issue) assert.NoError(t, err) issueIDs, err = issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1) assert.NoError(t, err) @@ -55,7 +55,7 @@ func TestIssue_DeleteIssue(t *testing.T) { assert.NoError(t, err) issue, err = issues_model.GetIssueByID(db.DefaultContext, 4) assert.NoError(t, err) - err = deleteIssue(issue) + err = deleteIssue(db.DefaultContext, issue) assert.NoError(t, err) assert.EqualValues(t, 2, len(attachments)) for i := range attachments { @@ -78,7 +78,7 @@ func TestIssue_DeleteIssue(t *testing.T) { assert.NoError(t, err) assert.False(t, left) - err = deleteIssue(issue2) + err = deleteIssue(db.DefaultContext, issue2) assert.NoError(t, err) left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1) assert.NoError(t, err) diff --git a/services/pull/comment.go b/services/pull/comment.go index 933ad09a85e9a..24dfd8af0ce00 100644 --- a/services/pull/comment.go +++ b/services/pull/comment.go @@ -90,7 +90,7 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *iss ops.Content = string(dataJSON) - comment, err = issue_service.CreateComment(ops) + comment, err = issue_service.CreateComment(ctx, ops) return comment, err } diff --git a/services/pull/pull.go b/services/pull/pull.go index fe2f002010b1d..55dfd3c180297 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -52,7 +52,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu } for _, assigneeID := range assigneeIDs { - if err := issue_service.AddAssigneeIfNotAssigned(pull, pull.Poster, assigneeID); err != nil { + if err := issue_service.AddAssigneeIfNotAssigned(ctx, pull, pull.Poster, assigneeID); err != nil { return err } } @@ -122,7 +122,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu Content: string(dataJSON), } - _, _ = issue_service.CreateComment(ops) + _, _ = issue_service.CreateComment(ctx, ops) } return nil @@ -221,7 +221,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer OldRef: oldBranch, NewRef: targetBranch, } - if _, err = issue_service.CreateComment(options); err != nil { + if _, err = issue_service.CreateComment(ctx, options); err != nil { return fmt.Errorf("CreateChangeTargetBranchComment: %w", err) } diff --git a/services/pull/review.go b/services/pull/review.go index ba93b5e2f540c..6feffe4ec4e7a 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -248,7 +248,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo return nil, err } } - return issue_service.CreateComment(&issues_model.CreateCommentOptions{ + return issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeCode, Doer: doer, Repo: repo, @@ -368,7 +368,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, return } - comment, err = issue_service.CreateComment(&issues_model.CreateCommentOptions{ + comment, err = issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{ Doer: doer, Content: message, Type: issues_model.CommentTypeDismissReview, diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index 4427c610bfa4d..4b25f97dddb29 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -67,7 +67,7 @@ func TestAPIMergePullWIP(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{Status: issues_model.PullRequestStatusMergeable}, unittest.Cond("has_merged = ?", false)) pr.LoadIssue(db.DefaultContext) - issue_service.ChangeTitle(pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title) + issue_service.ChangeTitle(db.DefaultContext, pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title) // force reload pr.LoadAttributes(db.DefaultContext) From ed81b608cb5bd94ef518393cdd724c4fac1215d4 Mon Sep 17 00:00:00 2001 From: Tyrone Yeh Date: Sat, 15 Apr 2023 02:48:36 +0800 Subject: [PATCH 03/25] Add option to search for users is active join a team (#24093) Adding a user in a team to enter a username gives a list of no active users --------- Co-authored-by: Jason Song --- routers/web/user/search.go | 1 + web_src/js/features/comp/SearchUserBox.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/routers/web/user/search.go b/routers/web/user/search.go index c5c3aa75f084e..bdc4116e3776b 100644 --- a/routers/web/user/search.go +++ b/routers/web/user/search.go @@ -24,6 +24,7 @@ func Search(ctx *context.Context) { Keyword: ctx.FormTrim("q"), UID: ctx.FormInt64("uid"), Type: user_model.UserTypeIndividual, + IsActive: ctx.FormOptionalBool("active"), ListOptions: listOptions, }) if err != nil { diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js index 0e9a005acf6e1..960b787fea6b6 100644 --- a/web_src/js/features/comp/SearchUserBox.js +++ b/web_src/js/features/comp/SearchUserBox.js @@ -11,7 +11,7 @@ export function initCompSearchUserBox() { $searchUserBox.search({ minCharacters: 2, apiSettings: { - url: `${appSubUrl}/user/search?q={query}`, + url: `${appSubUrl}/user/search?active=1&q={query}`, onResponse(response) { const items = []; const searchQuery = $searchUserBox.find('input').val(); From 2902d1e9d193a157bccd46cdda449de5443a2fd8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Apr 2023 03:29:05 +0800 Subject: [PATCH 04/25] Sort repo topic labels by name (#24123) Close #24077 --- models/repo/topic.go | 4 +++- routers/web/repo/blame.go | 7 +------ web_src/js/features/repo-home.js | 5 +++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/models/repo/topic.go b/models/repo/topic.go index 05f50cfe46353..88fe532be9a70 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -194,14 +194,16 @@ func (opts *FindTopicOptions) toConds() builder.Cond { // FindTopics retrieves the topics via FindTopicOptions func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { sess := db.GetEngine(db.DefaultContext).Select("topic.*").Where(opts.toConds()) + orderBy := "topic.repo_count DESC" if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") + orderBy = "topic.name" // when render topics for a repo, it's better to sort them by name, to get consistent result } if opts.PageSize != 0 && opts.Page != 0 { sess = db.SetSessionPagination(sess, opts) } topics := make([]*Topic, 0, 10) - total, err := sess.Desc("topic.repo_count").FindAndCount(&topics) + total, err := sess.OrderBy(orderBy).FindAndCount(&topics) return topics, total, err } diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 3546334ed6479..0e232c194c2e1 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -12,7 +12,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -23,10 +22,6 @@ import ( "code.gitea.io/gitea/modules/util" ) -const ( - tplBlame base.TplName = "repo/home" -) - type blameRow struct { RowNumber int Avatar gotemplate.HTML @@ -140,7 +135,7 @@ func RefBlame(ctx *context.Context) { renderBlame(ctx, blameParts, commitNames, previousCommits) - ctx.HTML(http.StatusOK, tplBlame) + ctx.HTML(http.StatusOK, tplRepoHome) } func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[string]*user_model.UserCommit, map[string]string) { diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js index e08e84b393cb3..55a27710542c0 100644 --- a/web_src/js/features/repo-home.js +++ b/web_src/js/features/repo-home.js @@ -41,6 +41,7 @@ export function initRepoTopicBar() { viewDiv.children('.topic').remove(); if (topics.length) { const topicArray = topics.split(','); + topicArray.sort(); for (let i = 0; i < topicArray.length; i++) { const link = $(''); link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`); @@ -57,12 +58,12 @@ export function initRepoTopicBar() { topicPrompts.formatPrompt = xhr.responseJSON.message; const {invalidTopics} = xhr.responseJSON; - const topicLables = topicDropdown.children('a.ui.label'); + const topicLabels = topicDropdown.children('a.ui.label'); for (const [index, value] of topics.split(',').entries()) { for (let i = 0; i < invalidTopics.length; i++) { if (invalidTopics[i] === value) { - topicLables.eq(index).removeClass('green').addClass('red'); + topicLabels.eq(index).removeClass('green').addClass('red'); } } } From 35e562d7bd5e984a5cb97f74f5753d2a9998c1d3 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sat, 15 Apr 2023 00:07:26 +0000 Subject: [PATCH 05/25] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-PT.ini | 2 + options/locale/locale_ru-RU.ini | 81 +++++++++++++++++++++++++++------ options/locale/locale_zh-TW.ini | 4 ++ 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 4339b3d19ac64..5649352254c81 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -133,6 +133,8 @@ buttons.list.task.tooltip=Adicionar uma lista de tarefas buttons.mention.tooltip=Mencionar um utilizador ou uma equipa buttons.ref.tooltip=Referenciar uma questão ou um pedido de integração buttons.switch_to_legacy.tooltip=Usar o editor clássico +buttons.enable_monospace_font=Habilitar tipo de letra mono-espaçado +buttons.disable_monospace_font=Desabilitar tipo de letra mono-espaçado [filter] string.asc=A - Z diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index b74ed1609453b..da5a739c5b650 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -4,7 +4,7 @@ explore=Обзор help=Помощь logo=Логотип sign_in=Вход -sign_in_with=Войдите с помощью +sign_in_with=Войти с помощью sign_out=Выход sign_up=Регистрация link_account=Привязать аккаунт @@ -25,14 +25,14 @@ licenses=Лицензии return_to_gitea=Вернуться к Gitea username=Имя пользователя -email=Адрес эл. почты +email=Адрес электронной почты password=Пароль access_token=Токен доступа re_type=Введите пароль еще раз captcha=CAPTCHA twofa=Двухфакторная аутентификация twofa_scratch=Двухфакторный scratch-код -passcode=Пароль +passcode=Код webauthn_insert_key=Вставьте ваш ключ безопасности webauthn_sign_in=Нажмите кнопку на ключе безопасности. Если ваш ключ безопасности не имеет кнопки, вставьте его снова. @@ -41,11 +41,11 @@ webauthn_use_twofa=Используйте двухфакторный код с webauthn_error=Не удалось прочитать ваш ключ безопасности. webauthn_unsupported_browser=Ваш браузер в настоящее время не поддерживает WebAuthn. webauthn_error_unknown=Произошла неизвестная ошибка. Повторите попытку. -webauthn_error_insecure=`WebAuthn поддерживает только безопасные соединения. Для тестирования по HTTP, вы можете использовать "localhost" или "127.0.0.1"` +webauthn_error_insecure=`WebAuthn поддерживает только безопасные соединения. Для тестирования по HTTP можно использовать "localhost" или "127.0.0.1"` webauthn_error_unable_to_process=Сервер не смог обработать ваш запрос. -webauthn_error_duplicated=Представленный ключ не подходит для этого запроса. Если вы пытаетесь зарегистрировать его, убедитесь, что ключ ещё не зарегистрирован. -webauthn_error_empty=Вы должны указать имя для этого ключа. -webauthn_error_timeout=Тайм-аут достигнут до того, как ваш ключ был прочитан. Перезагрузите эту страницу и повторите попытку. +webauthn_error_duplicated=Данный ключ безопасности не разрешен для этого запроса. Пожалуйста, убедитесь, что ключ не регистрировался ранее. +webauthn_error_empty=Необходимо задать имя для этого ключа. +webauthn_error_timeout=Время истекло раньше, чем ключ был прочитан. Перезагрузите эту страницу и повторите попытку. webauthn_reload=Обновить repository=Репозиторий @@ -91,7 +91,7 @@ enabled=Включено disabled=Отключен copy=Скопировать -copy_url=Копировать URL +copy_url=Скопировать URL copy_content=Скопировать содержимое copy_branch=Скопировать имя ветки copy_success=Скопировано! @@ -106,7 +106,7 @@ step1=Шаг 1: step2=Шаг 2: error=Ошибка -error404=Страница, которую вы пытаетесь открыть, либо не существует, либо недостаточно прав для ее просмотра. +error404=Либо страница, которую вы пытаетесь открыть, не существует, либо у вас недостаточно прав для ее просмотра. never=Никогда @@ -128,8 +128,8 @@ string.desc=Я - А [error] occurred=Произошла ошибка report_message=Если вы уверены, что это баг Gitea, пожалуйста, поищите задачу на GitHub или создайте новую при необходимости. -missing_csrf=Некорректный запрос: CSRF токен отсутствует -invalid_csrf=Некорректный запрос: неверный CSRF токен +missing_csrf=Некорректный запрос: отсутствует токен CSRF +invalid_csrf=Некорректный запрос: неверный токен CSRF network_error=Ошибка сети [startpage] @@ -137,7 +137,7 @@ app_desc=Удобный сервис собственного хостинга install=Простой в установке install_desc=Просто запустите исполняемый файл для вашей платформы, разверните через Docker, или установите с помощью менеджера пакетов. platform=Кроссплатформенный -platform_desc=Gitea работает на любой операционной системе, которая может компилировать Go: Windows, macOS, Linux, ARM и т. д. Выбирайте, что вам больше нравится! +platform_desc=Gitea работает на любой платформе, поддерживаемой Go: Windows, macOS, Linux, ARM и т. д. Выбирайте, что вам больше нравится! lightweight=Легковесный lightweight_desc=Gitea имеет низкие системные требования и может работать на недорогом Raspberry Pi. Экономьте энергию вашей машины! license=Открытый исходный код @@ -154,7 +154,7 @@ host=Хост user=Имя пользователя password=Пароль db_name=Имя базы данных -db_helper=Для пользователей MySQL: пожалуйста, используйте движок InnoDB, и если вы используете "utf8mb4" - ваша версия InnoDB должна быть старше 5.6 . +db_helper=Для пользователей MySQL: пожалуйста, используйте хранилище InnoDB, и если вы используете "utf8mb4", версия InnoDB должна быть выше 5.6 . db_schema=Схема db_schema_helper=Оставьте пустым для значения по умолчанию ("public"). ssl_mode=SSL @@ -179,8 +179,8 @@ app_name=Название сайта app_name_helper=Здесь вы можете ввести название своей компании. repo_path=Путь до корня репозитория repo_path_helper=Все удалённые Git репозитории будут сохранены в эту директорию. -lfs_path=Корневой путь Git LFS -lfs_path_helper=В этой директории будут храниться файлы Git LFS. Оставьте пустым, чтобы отключить LFS. +lfs_path=Путь к корневому каталогу Git LFS +lfs_path_helper=В этом каталоге будут храниться файлы Git LFS. Оставьте пустым, чтобы отключить LFS. run_user=Запуск от имени пользователя run_user_helper=Введите имя пользователя операционной системы, под которым работает Gitea. Обратите внимание, что этот пользователь должен иметь доступ к корневому пути репозиториев. domain=Домен сервера @@ -287,6 +287,7 @@ search=Поиск code=Код search.type.tooltip=Тип поиска search.fuzzy=Неточный +search.fuzzy.tooltip=Включать результаты, которые не полностью соответствуют поисковому запросу search.match=Соответствие search.match.tooltip=Включать только результаты, которые точно соответствуют поисковому запросу code_search_unavailable=В настоящее время поиск по коду недоступен. Обратитесь к администратору сайта. @@ -1029,6 +1030,7 @@ issues=Задачи pulls=Запросы на слияние project_board=Проекты packages=Пакеты +actions=Действия labels=Метки org_labels_desc=Метки уровня организации, которые можно использовать с всеми репозиториями< / strong> в этой организации org_labels_desc_manage=управлять @@ -1247,6 +1249,7 @@ issues.choose.blank=По умолчанию issues.choose.blank_about=Создать запрос из шаблона по умолчанию. issues.choose.ignore_invalid_templates=Некорректные шаблоны были проигнорированы issues.choose.invalid_templates=Найден(ы) %v неверный(х) шаблон(ов) +issues.choose.invalid_config=Конфигурация задачи содержит ошибки: issues.no_ref=Не указана ветка или тэг issues.create=Добавить задачу issues.new_label=Новая метка @@ -1525,6 +1528,8 @@ pulls.allow_edits_from_maintainers=Разрешить редактировани pulls.allow_edits_from_maintainers_desc=Пользователи с доступом на запись в основную ветку могут отправлять изменения и в эту ветку pulls.allow_edits_from_maintainers_err=Не удалось обновить pulls.compare_changes_desc=Сравнить две ветки и создать запрос на слияние для изменений. +pulls.has_viewed_file=Просмотрено +pulls.viewed_files_label=%[1]d из %[2]d файлов просмотрено pulls.expand_files=Показать все файлы pulls.collapse_files=Свернуть все файлы pulls.compare_base=базовая ветка @@ -1572,6 +1577,7 @@ pulls.can_auto_merge_desc=Этот запрос на слияние может pulls.cannot_auto_merge_desc=Этот запрос на слияние не может быть объединён автоматически. pulls.cannot_auto_merge_helper=Пожалуйста, совершите слияние вручную для урегулирования конфликтов. pulls.num_conflicting_files_1=%d конфликтующий файл +pulls.num_conflicting_files_n=%d конфликтующих файлов pulls.approve_count_1=%d одобрение pulls.reject_count_1=%d запрос на изменение pulls.reject_count_n=%d запросов на изменение @@ -1620,10 +1626,23 @@ pulls.reopened_at=`переоткрыл этот запрос на слияни pulls.merge_instruction_hint=`Вы также можете просмотреть инструкции командной строки.` pulls.merge_instruction_step1_desc=В репозитории вашего проекта посмотрите новую ветку и протестируйте изменения. pulls.merge_instruction_step2_desc=Объединить изменения и обновить на Gitea. +pulls.clear_merge_message=Очистить сообщение о слиянии +pulls.clear_merge_message_hint=Очистка сообщения о слиянии удалит только содержимое сообщения коммита, но сохранит сгенерированные git добавки, такие как "Co-Authored-By …". +pulls.auto_merge_button_when_succeed=(При успешных проверках) +pulls.auto_merge_when_succeed=Слить автоматически после прохождения всех проверок +pulls.auto_merge_newly_scheduled=Запрос был запланирован для слияния после прохождения всех проверок. +pulls.auto_merge_has_pending_schedule=%[1]s запланировал этот запрос для автоматического слияния, когда все проверки пройдены %[2]s. +pulls.auto_merge_cancel_schedule=Отменить автоматическое слияние +pulls.auto_merge_not_scheduled=Этот запрос не запланирован для автоматического слияния. +pulls.auto_merge_canceled_schedule=Автоматическое слияние для этого запроса было отменено. +pulls.auto_merge_newly_scheduled_comment=`запланировал этот запрос для автоматического слияния после прохождения всех проверок %[1]s` +pulls.auto_merge_canceled_schedule_comment=`отменил автоматическое слияние этого запроса после прохождения всех проверок %[1]s` +pulls.delete.title=Удалить этот запрос на слияние? +pulls.delete.text=Вы действительно хотите удалить этот запрос на слияние? (Это навсегда удалит всё содержимое. Возможно, лучше закрыть запрос в архивных целях.) milestones.new=Новый этап milestones.closed=Закрыт %s @@ -1669,6 +1688,7 @@ signing.wont_sign.commitssigned=Слияние не будет подписан signing.wont_sign.approved=Слияние не будет подписано, так как PR не одобрен signing.wont_sign.not_signed_in=Вы не авторизовались +ext_wiki=Доступ к внешней вики ext_wiki.desc=Ссылка на внешнюю вики. wiki=Вики @@ -1765,6 +1785,7 @@ search=Поиск search.search_repo=Поиск по репозиторию search.type.tooltip=Тип поиска search.fuzzy=Неточный +search.fuzzy.tooltip=Включать результаты, которые не полностью соответствуют поисковому запросу search.match=Соответствие search.match.tooltip=Включать только результаты, которые точно соответствуют поисковому запросу search.results=Результаты поиска "%s" в %s @@ -1798,6 +1819,7 @@ settings.mirror_sync_in_progress=Синхронизируются репозит settings.site=Сайт settings.update_settings=Обновить настройки settings.branches.update_default_branch=Обновить ветку по умолчанию +settings.branches.add_new_rule=Добавить новое правило settings.advanced_settings=Расширенные настройки settings.wiki_desc=Включить Вики для репозитория settings.use_internal_wiki=Использовать встроенную вики-систему @@ -1816,15 +1838,22 @@ settings.tracker_url_format_error=Формат URL внешнего баг-тр settings.tracker_issue_style=Формат нумерации для внешней системы учета задач settings.tracker_issue_style.numeric=Цифровой settings.tracker_issue_style.alphanumeric=Буквенноцифровой +settings.tracker_issue_style.regexp=Регулярное выражение +settings.tracker_issue_style.regexp_pattern=Шаблон регулярного выражения +settings.tracker_issue_style.regexp_pattern_desc=Вместо {index} будет использоваться первая захваченная группа. settings.tracker_url_format_desc=Вы можете использовать шаблоны {user}, {repo} и {index} для имени пользователя, репозитория и номера задачи. settings.enable_timetracker=Включить отслеживание времени settings.allow_only_contributors_to_track_time=Учитывать только участников разработки в подсчёте времени settings.pulls_desc=Включить запросы на слияние settings.pulls.ignore_whitespace=Игнорировать незначащие изменения (пробелы, табуляция) при проверке на конфликты слияния settings.pulls.enable_autodetect_manual_merge=Включить автоопределение ручного слияния (Примечание: в некоторых особых случаях могут возникнуть ошибки) +settings.pulls.allow_rebase_update=Включить обновление ветки из запроса на слияние путём rebase settings.pulls.default_delete_branch_after_merge=Удалить ветку запроса после его слияния по умолчанию settings.pulls.default_allow_edits_from_maintainers=По умолчанию разрешать редактирование сопровождающими +settings.releases_desc=Включить релизы +settings.packages_desc=Включить реестр пакетов settings.projects_desc=Включить проекты репозитория +settings.actions_desc=Включить действия репозитория settings.admin_settings=Настройки администратора settings.admin_enable_health_check=Выполнять проверки целостности этого репозитория (git fsck) settings.admin_code_indexer=Индексатор кода @@ -1886,6 +1915,7 @@ settings.confirm_delete=Удалить репозиторий settings.add_collaborator=Добавить соавтора settings.add_collaborator_success=Соавтор добавлен. settings.add_collaborator_inactive_user=Невозможно добавить неактивного пользователя как соавтора. +settings.add_collaborator_owner=Невозможно добавить владельца в качестве соавтора. settings.add_collaborator_duplicate=Соавтор уже добавлен в этот репозиторий. settings.delete_collaborator=Удалить settings.collaborator_deletion=Удалить соавтора @@ -1917,6 +1947,7 @@ settings.webhook.headers=Заголовки settings.webhook.payload=Содержимое settings.webhook.body=Тело ответа settings.webhook.replay.description=Повторить этот веб-хук. +settings.webhook.delivery.success=Событие было добавлено в очередь доставки. Может пройти несколько секунд, прежде чем оно отобразится в истории. settings.githooks_desc=Git-хуки предоставляются самим Git. Вы можете изменять файлы хуков из списка ниже, чтобы настроить собственные операции. settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведёт к отключению хука. settings.githook_name=Название хукa @@ -1981,6 +2012,7 @@ settings.event_package_desc=Пакет создан или удален в ре settings.branch_filter=Фильтр веток settings.branch_filter_desc=Белый список ветвей для событий Push, создания ветвей и удаления ветвей, указанных в виде глоб-шаблона. Если пустой или *, то все событий для всех ветвей будут зарегистрированы. Перейдите по ссылке github.com/gobwas/glob на документацию по синтаксису. Примеры: master, {master,release*}. settings.authorization_header=Заголовок Authorization +settings.authorization_header_desc=Будет включён в качестве заголовка авторизации для запросов. Примеры: %s. settings.active=Активный settings.active_helper=Информация о происходящих событиях будет отправляться на URL этого веб-хука. settings.add_hook_success=Веб-хук был добавлен. @@ -2020,6 +2052,8 @@ settings.key_been_used=Идентичный ключ развёртывания settings.key_name_used=Ключ развёртывания с таким именем уже существует. settings.add_key_success=Новый ключ развёртывания '%s' успешно добавлен. settings.deploy_key_deletion=Удалить ключ развёртывания +settings.deploy_key_deletion_desc=Удаление ключа развёртывания сделает невозможным доступ к репозиторию с его помощью. Вы уверены? +settings.deploy_key_deletion_success=Ключ развёртывания был успешно удалён. settings.branches=Ветки settings.protected_branch=Защита веток settings.protected_branch.save_rule=Сохранить правило @@ -3035,11 +3069,20 @@ keywords=Ключевые слова details=Подробнее details.author=Автор details.project_site=Сайт проекта +details.repository_site=Сайт репозитория +details.documentation_site=Сайт документации details.license=Лицензия versions=Версии versions.view_all=Показать всё dependency.version=Версия +cargo.registry=Настройте этот реестр в файле конфигурации Cargo (например, ~/.cargo/config.toml): +cargo.install=Чтобы установить пакет с помощью Cargo, выполните следующую команду: +cargo.documentation=Для получения дополнительной информации о реестре Cargo смотрите документацию. +cargo.details.repository_site=Сайт репозитория +cargo.details.documentation_site=Сайт документации +chef.registry=Настройте этот реестр в своём файле ~/.chef/config.rb: chef.install=Чтобы установить пакет, выполните следующую команду: +chef.documentation=Для получения дополнительной информации о реестре Chef смотрите документацию. composer.registry=Настройте этот реестр в файле ~/.composer/config.json: composer.install=Чтобы установить пакет с помощью Composer, выполните следующую команду: composer.documentation=Для получения дополнительной информации о реестре Composer смотрите документацию. @@ -3048,6 +3091,11 @@ conan.details.repository=Репозиторий conan.registry=Настроить реестр из командной строки: conan.install=Чтобы установить пакет с помощью Conan, выполните следующую команду: conan.documentation=Для получения дополнительной информации о реестре Conan смотрите документацию. +conda.registry=Пропишите этот реестр в качестве репозитория Conda в своём файле .condarc: +conda.install=Чтобы установить пакет с помощью Conda, выполните следующую команду: +conda.documentation=Для получения дополнительной информации о реестре Conda смотрите документацию. +conda.details.repository_site=Сайт репозитория +conda.details.documentation_site=Сайт документации container.details.type=Тип образа container.details.platform=Платформа container.digest=Отпечаток: @@ -3061,6 +3109,7 @@ helm.registry=Настроить реестр из командной строк helm.install=Чтобы установить пакет, выполните следующую команду: helm.documentation=Для получения дополнительной информации о реестре Helm смотрите документацию. maven.install2=Выполнить через командную строку: +maven.download=Чтобы скачать зависимость, запустите в командной строке: nuget.registry=Настроить реестр из командной строки: nuget.install=Чтобы установить пакет с помощью NuGet, выполните следующую команду: nuget.documentation=Для получения дополнительной информации о реестре NuGet смотрите документацию. @@ -3071,6 +3120,8 @@ npm.dependencies=Зависимости npm.dependencies.peer=Одноранговые зависимости npm.dependencies.optional=Необязательные зависимости npm.details.tag=Тег +pub.install=Чтобы установить пакет с помощью Dart, выполните следующую команду: +pub.documentation=Для получения дополнительной информации о реестре Conda смотрите документацию. pypi.requires=Требуется Python pypi.documentation=Для получения дополнительной информации о реестре PyPI смотрите документацию. rubygems.install=Чтобы установить пакет с помощью gem, выполните следующую команду: diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 6dfbe25181a50..a2eb097eb4a6c 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -120,6 +120,8 @@ footer.software=關於軟體 footer.links=連結 [editor] +buttons.enable_monospace_font=啟用等寬字型 +buttons.disable_monospace_font=停用等寬字型 [filter] string.asc=A - Z @@ -1748,6 +1750,8 @@ wiki.create_first_page=建立第一個頁面 wiki.page=頁面 wiki.filter_page=過濾頁面 wiki.new_page=頁面 +wiki.page_title=頁面標題 +wiki.page_content=頁面內容 wiki.default_commit_message=關於此次頁面修改的說明(非必要)。 wiki.save_page=儲存頁面 wiki.last_commit_info=%s 於 %s 修改了此頁面 From b4e952545b95953056f2645b38aa17d15bcd58ab Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Sat, 15 Apr 2023 14:01:54 +0300 Subject: [PATCH 06/25] Remove untranslatable `on_date` key (#24106) - Follows #23988 - Fixes: #24074 by removing this key GitHub's `relative-time` elements allow us to force their rendering to `auto`, `past`, or `future` tense. We will never show an absolute date `on ...` in `TimeSince` ## Before ![image](https://user-images.githubusercontent.com/20454870/231735872-048c7bf3-6aa1-4113-929d-75a985c9922c.png) ## After ![image](https://user-images.githubusercontent.com/20454870/231736116-6ad47b63-77f4-4d3f-82a2-ee9a46ba2bd1.png) --------- Co-authored-by: wxiaoguang --- modules/timeutil/since.go | 20 ++++++++++++-- options/locale/locale_en-US.ini | 4 --- routers/web/devtest/devtest.go | 10 +++++++ templates/devtest/gitea-ui.tmpl | 49 +++++++++++++++++++++++++++++---- templates/package/view.tmpl | 6 ++-- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index bdde54c617540..e6a2519d2120b 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -114,11 +114,25 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string { return strings.TrimPrefix(timeStr, ", ") } +func timeSinceUnix(then, now time.Time, lang translation.Locale) template.HTML { + friendlyText := then.Format("2006-01-02 15:04:05 +07:00") + + // document: https://github.com/github/relative-time-element + attrs := `tense="past"` + isFuture := now.Before(then) + if isFuture { + attrs = `tense="future"` + } + + // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip + htm := fmt.Sprintf(`%s`, + attrs, then.Format(time.RFC3339), friendlyText) + return template.HTML(htm) +} + // TimeSince renders relative time HTML given a time.Time func TimeSince(then time.Time, lang translation.Locale) template.HTML { - timestamp := then.UTC().Format(time.RFC3339) - // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip - return template.HTML(fmt.Sprintf(`%s`, lang.Tr("on_date"), timestamp, timestamp)) + return timeSinceUnix(then, time.Now(), lang) } // TimeSinceUnix renders relative time HTML given a TimeStamp diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f71ea824e979b..c2c8f1e120c1f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -112,8 +112,6 @@ never = Never rss_feed = RSS Feed -on_date = on - [aria] navbar = Navigation Bar footer = Footer @@ -3129,8 +3127,6 @@ starred_repo = starred %[2]s watched_repo = started watching %[2]s [tool] -ago = %s ago -from_now = %s from now now = now future = future 1s = 1 second diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 784940909a612..48875e306d29e 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -7,6 +7,7 @@ import ( "net/http" "path" "strings" + "time" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -32,5 +33,14 @@ func List(ctx *context.Context) { } func Tmpl(ctx *context.Context) { + now := time.Now() + ctx.Data["TimeNow"] = now + ctx.Data["TimePast5s"] = now.Add(-5 * time.Second) + ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second) + ctx.Data["TimePast2m"] = now.Add(-2 * time.Minute) + ctx.Data["TimeFuture2m"] = now.Add(2 * time.Minute) + ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second) + ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second) + ctx.HTML(http.StatusOK, base.TplName("devtest"+path.Clean("/"+ctx.Params("sub")))) } diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl index c5ab863d00d04..1ab9ae7b7c7aa 100644 --- a/templates/devtest/gitea-ui.tmpl +++ b/templates/devtest/gitea-ui.tmpl @@ -1,12 +1,51 @@ {{template "base/head" .}} -
+
+
- - +

Tooltip

+
text with tooltip
+
text with interactive tooltip
+
- text with tooltip +

GiteaOriginUrl

+
+
- {{template "shared/combomarkdowneditor" .}} + +
+

LocaleNumber

+
{{LocaleNumber 1}}
+
{{LocaleNumber 12}}
+
{{LocaleNumber 123}}
+
{{LocaleNumber 1234}}
+
{{LocaleNumber 12345}}
+
{{LocaleNumber 123456}}
+
{{LocaleNumber 1234567}}
+
+ +
+

TimeSince

+
Now: {{TimeSince .TimeNow $.locale}}
+
5s past: {{TimeSince .TimePast5s $.locale}}
+
5s future: {{TimeSince .TimeFuture5s $.locale}}
+
2m past: {{TimeSince .TimePast2m $.locale}}
+
2m future: {{TimeSince .TimeFuture2m $.locale}}
+
1y past: {{TimeSince .TimePast1y $.locale}}
+
1y future: {{TimeSince .TimeFuture1y $.locale}}
+
+ +
+

ComboMarkdownEditor

+
ps: no JS code attached, so just a layout
+ {{template "shared/combomarkdowneditor" .}} +
+ +
{{template "base/footer" .}} diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index 9677b8eb09d9f..7c622a91b3eb7 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -84,9 +84,9 @@ {{.locale.Tr "packages.versions.view_all"}}
{{range .LatestVersions}} -
- {{.Version}} - {{$.locale.Tr "on_date"}} {{.CreatedUnix.FormatDate}} +
+ {{.Version}} + {{template "shared/datetime/short" (dict "Datetime" (.CreatedUnix.FormatDate) "Fallback" (.CreatedUnix.FormatDate))}}
{{end}}
From fa3495183bf12c2414e18a3d3454067663c7aaa9 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Sat, 15 Apr 2023 22:52:44 +0900 Subject: [PATCH 07/25] Add migration to fix external unit access mode of owner/admin team (#24117) Fix the incorrect migration in #23675 and #24012 External Unit (Tracker and Wiki) access mode should be `read` in owner/admin team. --- models/migrations/migrations.go | 2 ++ models/migrations/v1_20/v253.go | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 models/migrations/v1_20/v253.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 35a18fb7f2bbc..42806a808f791 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -483,6 +483,8 @@ var migrations = []Migration{ NewMigration("Fix incorrect owner team unit access mode", v1_20.FixIncorrectOwnerTeamUnitAccessMode), // v252 -> v253 NewMigration("Fix incorrect admin team unit access mode", v1_20.FixIncorrectAdminTeamUnitAccessMode), + // v253 -> v254 + NewMigration("Fix ExternalTracker and ExternalWiki accessMode in owner and admin team", v1_20.FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_20/v253.go b/models/migrations/v1_20/v253.go new file mode 100644 index 0000000000000..96c494bd8d903 --- /dev/null +++ b/models/migrations/v1_20/v253.go @@ -0,0 +1,49 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "code.gitea.io/gitea/modules/log" + + "xorm.io/xorm" +) + +func FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam(x *xorm.Engine) error { + type UnitType int + type AccessMode int + + type TeamUnit struct { + ID int64 `xorm:"pk autoincr"` + Type UnitType `xorm:"UNIQUE(s)"` + AccessMode AccessMode + } + + const ( + // AccessModeRead read access + AccessModeRead = 1 + + // Unit Type + TypeExternalWiki = 6 + TypeExternalTracker = 7 + ) + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + count, err := sess.Table("team_unit"). + Where("type IN (?, ?) AND access_mode > ?", TypeExternalWiki, TypeExternalTracker, AccessModeRead). + Update(&TeamUnit{ + AccessMode: AccessModeRead, + }) + if err != nil { + return err + } + log.Debug("Updated %d ExternalTracker and ExternalWiki access mode to belong to owner and admin", count) + + return sess.Commit() +} From 1af3dc6ee35b3c60e697bdb9160c03e6eb3700aa Mon Sep 17 00:00:00 2001 From: Jonathan Tran Date: Sun, 16 Apr 2023 03:27:23 -0400 Subject: [PATCH 08/25] Fix 2-dot direct compare to use the right base commit (#24133) For 2-dot direct compare, we should use the base commit in the title and templates, as is used elsewhere, not the common ancestor which is used for 3-dot compare. I believe that this change should have been included in #22949. --- routers/web/repo/compare.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index c49eb762d8a66..92abe0ce25917 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -551,7 +551,11 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.ServerError("GetCompareInfo", err) return nil } - ctx.Data["BeforeCommitID"] = ci.CompareInfo.MergeBase + if ci.DirectComparison { + ctx.Data["BeforeCommitID"] = ci.CompareInfo.BaseCommitID + } else { + ctx.Data["BeforeCommitID"] = ci.CompareInfo.MergeBase + } return ci } From 0e059846670190f0a2f4935fb74b2159e2d80181 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 16 Apr 2023 20:01:08 +0800 Subject: [PATCH 09/25] Set EasyMDE heading font-size to the same size as the resulting markdown (#24151) Fix #23816 According to my personal experience, the EasyMDE is still useful when writing a lot of contents, eg: the wiki page. It's not difficult to improve its heading styles, so let's make it. Before: image After: image --- web_src/css/editor-markdown.css | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/web_src/css/editor-markdown.css b/web_src/css/editor-markdown.css index 7d6c36635dbff..eb5c5d13b8c5f 100644 --- a/web_src/css/editor-markdown.css +++ b/web_src/css/editor-markdown.css @@ -44,6 +44,31 @@ max-height: calc(100vh - 200px); } +/* use the same styles as markup/content.css */ +.combo-markdown-editor .CodeMirror-scroll .cm-header-1 { + font-size: 2em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-2 { + font-size: 1.5em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-3 { + font-size: 1.25em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-4 { + font-size: 1em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-5 { + font-size: 0.875em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-6 { + font-size: 0.85em; +} + text-expander { display: block; position: relative; From 685b0ffa1969598a7b45b41af0a5566e5a74fabc Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 16 Apr 2023 21:58:22 +0800 Subject: [PATCH 10/25] Use 1.18's aria role for dropdown menus (#24144) According to erion's feedback, the 1.18 approach works with Safari (`role=menu` on the parent container), while the 1.19's approach doesn't work well with Safari+VoiceOver (although I tested it worked with Chrome a little better). I have tested this 1.18 approach could work for all Safari/Chrome+VoiceOver and Chrome+Talkback. Let's try to make it on try.gitea.io to see whether it helps Safari users. --- web_src/js/modules/aria/dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/js/modules/aria/dropdown.js b/web_src/js/modules/aria/dropdown.js index 26c135241682c..b1ff57ab85876 100644 --- a/web_src/js/modules/aria/dropdown.js +++ b/web_src/js/modules/aria/dropdown.js @@ -148,8 +148,8 @@ function attachInit($dropdown) { // Since #19861 we have prepared the "combobox" solution, but didn't get enough time to put it into practice and test before. const isComboBox = $dropdown.find('input').length > 0; - dropdown[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'button'; - dropdown[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : 'menu'; + dropdown[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'menu'; + dropdown[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : ''; dropdown[ariaPatchKey].listItemRole = isComboBox ? 'option' : 'menuitem'; attachDomEvents($dropdown, $focusable, $menu); From be7cd73439c2210bfd889915f74d66686e99dab6 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Mon, 17 Apr 2023 00:07:34 +0000 Subject: [PATCH 11/25] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 3 --- options/locale/locale_de-DE.ini | 3 --- options/locale/locale_el-GR.ini | 3 --- options/locale/locale_es-ES.ini | 3 --- options/locale/locale_fa-IR.ini | 3 --- options/locale/locale_fi-FI.ini | 3 --- options/locale/locale_fr-FR.ini | 3 --- options/locale/locale_hu-HU.ini | 3 --- options/locale/locale_id-ID.ini | 3 --- options/locale/locale_is-IS.ini | 2 -- options/locale/locale_it-IT.ini | 3 --- options/locale/locale_ja-JP.ini | 3 --- options/locale/locale_ko-KR.ini | 3 --- options/locale/locale_lv-LV.ini | 3 --- options/locale/locale_nl-NL.ini | 3 --- options/locale/locale_pl-PL.ini | 3 --- options/locale/locale_pt-BR.ini | 3 --- options/locale/locale_pt-PT.ini | 4 ---- options/locale/locale_ru-RU.ini | 3 --- options/locale/locale_si-LK.ini | 3 --- options/locale/locale_sk-SK.ini | 1 - options/locale/locale_sv-SE.ini | 3 --- options/locale/locale_tr-TR.ini | 3 --- options/locale/locale_uk-UA.ini | 3 --- options/locale/locale_zh-CN.ini | 3 --- options/locale/locale_zh-HK.ini | 3 --- options/locale/locale_zh-TW.ini | 3 --- 27 files changed, 79 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 5f6bfa76bca2d..2e720709f4f08 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -110,7 +110,6 @@ never=Nikdy rss_feed=RSS kanál - [aria] navbar=Navigační lišta footer=Patička @@ -3027,8 +3026,6 @@ starred_repo=si oblíbil/a %[2]s watched_repo=začal/a sledovat %[2]s [tool] -ago=před %s -from_now=od teď %s now=nyní future=budoucí 1s=1 sekundou diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 28fc247756724..5b2eaf316f915 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -107,7 +107,6 @@ never=Niemals rss_feed=RSS Feed - [aria] [editor] @@ -2956,8 +2955,6 @@ starred_repo=markiert %[2]s watched_repo=beobachtet %[2]s [tool] -ago=vor %s -from_now=in %s now=jetzt future=Zukunft 1s=1 Sekunde diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 8b7d03dac3292..e3fe183e5950e 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -110,7 +110,6 @@ never=Ποτέ rss_feed=Ροή RSS - [aria] navbar=Γραμμή Πλοήγησης footer=Υποσέλιδο @@ -3066,8 +3065,6 @@ starred_repo=έδωσε αστέρι στο %[2]s watched_repo=άρχισε να παρακολουθεί το %[2]s [tool] -ago=%s πριν -from_now=%s από τώρα now=τώρα future=μελλοντικό 1s=1 δευτερόλεπτο diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 719e4031fe3d8..00dcd046f64da 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -107,7 +107,6 @@ never=Nunca rss_feed=Fuentes RSS - [aria] [editor] @@ -2990,8 +2989,6 @@ starred_repo=destacó %[2]s watched_repo=comenzó a seguir %[2]s [tool] -ago=hace %s -from_now=desde ahora %s now=ahora future=futuro 1s=1 segundo diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index b9be06c23dc61..e4857e97db80c 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -92,7 +92,6 @@ error404=صفحه موردنظر شما یا وجود ندارد%[2]s watched_repo=شروع به تماشای %[2]s کرد [tool] -ago=%s پیش -from_now=%s از هم اکنون now=حالا future=آینده 1s=۱ ثانیه diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 0d05ffba6ccfa..ffb0896f9f97e 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -106,7 +106,6 @@ never=Ei koskaan rss_feed=RSS-syöte - [aria] [editor] @@ -1727,8 +1726,6 @@ compare_commits_general=Vertaa committeja create_branch=loi haaran %[3]s repossa %[4]s [tool] -ago=%s sitten -from_now=%s alkaen nyt now=nyt 1s=1 sekunti 1m=1 minuutti diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index fdda0d28918a8..689000709a38b 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -107,7 +107,6 @@ never=Jamais rss_feed=Flux RSS - [aria] [editor] @@ -2522,8 +2521,6 @@ publish_release=`a publié "%[4]s" à %[3] review_dismissed_reason=Raison : [tool] -ago=il y a %s -from_now=dans %s now=maintenant future=futur 1s=1 seconde diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 7e35825ad6d1d..5692f144246af 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -82,7 +82,6 @@ error404=Az elérni kívánt oldal vagy nem létezik, vagy %[3]s compare_commits=Bandingkan %d melakukan [tool] -ago=%s yang lalu -from_now=%s mulai sekarang now=sekarang future=masa depan 1s=1 detik diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index ade8d84efc52d..d4695364d4129 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -103,7 +103,6 @@ error404=Síðan sem þú ert að reyna að fá annað hvort er ekki til never=Aldrei - [aria] [editor] @@ -1319,7 +1318,6 @@ comment_pull=`gerði ummæli á sameiningarbeiðni %[3]s#%[2]s%[2]s watched_repo=ha iniziato a guardare %[2]s [tool] -ago=%s fa -from_now=%s da adesso now=ora future=futuro 1s=1 secondo diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 2976b2691f197..8bbc2a5a34feb 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -112,7 +112,6 @@ never=無し rss_feed=RSSフィード - [aria] navbar=ナビゲーションバー footer=フッター @@ -3105,8 +3104,6 @@ starred_repo=が %[2]s にスターをつけました watched_repo=が %[2]s のウォッチを開始しました [tool] -ago=%s前 -from_now=今から%s後 now=たった今 future=未来 1s=1秒 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 8d868257a3c6c..5b2bc78a0035c 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -75,7 +75,6 @@ loading=불러오는 중... - [aria] [editor] @@ -1583,8 +1582,6 @@ transfer_repo=%s에서 %s로 저장소가 전송 compare_commits=%d 커밋들 비교 [tool] -ago=%s 전 -from_now=%s 지금부터 now=현재 future=미래 1s=1 초 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index c649a2670e0ad..01b5c783e75e4 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -110,7 +110,6 @@ never=Nekad rss_feed=RSS barotne - [aria] navbar=Navigācijas josla footer=Kājene @@ -3060,8 +3059,6 @@ starred_repo=atzīmēja ar zvaigznīti %[2]s watched_repo=sāka sekot %[2]s [tool] -ago=pirms %s -from_now=pēc %s no šī brīža now=tagad future=nākotnē 1s=1 sekundes diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 068f67dc51ff6..2a1074034f046 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -107,7 +107,6 @@ never=Nooit rss_feed=RSS Feed - [aria] [editor] @@ -2753,8 +2752,6 @@ compare_commits=Vergelijk %d commits compare_commits_general=Vergelijk commits [tool] -ago=%s geleden -from_now=%s vanaf nu now=nu future=toekomst 1s=1 seconde diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 0bb732b159491..5d89b21827fc0 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -105,7 +105,6 @@ error404=Strona, do której próbujesz dotrzeć nie istnieje lu never=Nigdy - [aria] [editor] @@ -2641,8 +2640,6 @@ mirror_sync_delete=synchronizuje i usuwa odwołanie %[2]s w %[2]s watched_repo=começou a observar %[2]s [tool] -ago=%s atrás -from_now=%s a partir de agora now=agora future=futuro 1s=1 segundo diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 5649352254c81..85c243cabcd00 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -112,8 +112,6 @@ never=Nunca rss_feed=Fonte RSS -on_date=no dia - [aria] navbar=Barra de navegação footer=Rodapé @@ -3129,8 +3127,6 @@ starred_repo=juntou %[2]s aos favoritos watched_repo=começou a vigiar %[2]s [tool] -ago=há %s -from_now=daqui a %s now=agora future=futuro 1s=1 segundo diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index da5a739c5b650..3bd988d72f2e7 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -112,7 +112,6 @@ never=Никогда rss_feed=RSS-лента - [aria] navbar=Панель навигации footer=Подвал @@ -2996,8 +2995,6 @@ starred_repo=добавил(а) %[2]s в избранное watched_repo=начала(а) наблюдение за %[2]s [tool] -ago=%s назад -from_now=%s с этого момента now=сейчас future=в будущем 1s=1 секунду diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 99a43974ef1f7..6771d1181390c 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -92,7 +92,6 @@ error404=ඔබ ළඟා වීමට උත්සාහ කරන පිටු never=කිසි විටෙකත් - [aria] [editor] @@ -2678,8 +2677,6 @@ create_branch=නිර්මාණය කරන ලද ශාඛාව %[2]sනැරඹීමට පටන් ගත්තා [tool] -ago=%s කලින් -from_now=%s මෙතැන් සිට now=දැන් future=අනාගතය 1s=තත්පර 1 diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index 9e38834b7d7af..76cc71c940a94 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -106,7 +106,6 @@ never=Nikdy rss_feed=RSS kanál - [aria] [editor] diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 0d4a0036cf5db..cf3056ae3d569 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -81,7 +81,6 @@ error404=Sidan du försöker nå finns inte eller så h - [aria] [editor] @@ -2110,8 +2109,6 @@ compare_commits_general=Jämför commits mirror_sync_delete=synkade och raderade referens %[2]s%[3]s från spegel [tool] -ago=%s sedan -from_now=%s från och med nu now=nu future=framtiden 1s=1 sekund diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index a5e7061d49211..858fdea231710 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -110,7 +110,6 @@ never=Asla rss_feed=RSS Beslemesi - [aria] navbar=Gezinti Çubuğu footer=Alt Bilgi @@ -2988,8 +2987,6 @@ starred_repo=%[2]s deposuna yıldız bıraktı watched_repo=%[2]s deposunu izlemeye başladı [tool] -ago=%s önce -from_now=%s şu andan now=şimdi future=gelecek 1s=1 saniye diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 6b6aa67371d50..33a3eaeab8ec8 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -93,7 +93,6 @@ error404=Сторінка, до якої ви намагаєтеся зверн never=Ніколи - [aria] [editor] @@ -2750,8 +2749,6 @@ starred_repo=додав %[2]s у обране watched_repo=почав слідкувати за %[2] [tool] -ago=%s тому -from_now=%s з цього моменту now=зараз future=в майбутньому 1s=1 секунда diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 7806a195baf7f..c9adca186fcd5 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -112,7 +112,6 @@ never=从不 rss_feed=RSS 订阅源 - [aria] navbar=导航栏 footer=页脚 @@ -3095,8 +3094,6 @@ starred_repo=点赞了 %[2]s watched_repo=开始关注 %[2]s [tool] -ago=%s前 -from_now=%s 之后 now=现在 future=将来 1s=1 秒 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 53ead462a9a85..29530d9467ced 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -51,7 +51,6 @@ enabled=已啟用 - [aria] [editor] @@ -926,8 +925,6 @@ transfer_repo=將儲存庫 %s 轉移至 %s compare_commits=比較 %d 提交 [tool] -ago=%s之前 -from_now=%s之後 now=現在 future=未來 1s=1 秒 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index a2eb097eb4a6c..ca3990af83ca1 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -112,7 +112,6 @@ never=從來沒有 rss_feed=RSS 摘要 - [aria] navbar=導航列 footer=頁尾 @@ -3109,8 +3108,6 @@ starred_repo=為 %[2]s 加上星號 watched_repo=開始關注 %[2]s [tool] -ago=%s前 -from_now=%s之後 now=現在 future=未來 1s=1 秒 From 7681d582cdae42d9322309ddf732117e6d332776 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 17 Apr 2023 11:37:23 +0800 Subject: [PATCH 12/25] Refactor locale number (#24134) Before, the `GiteaLocaleNumber.js` was just written as a a drop-in replacement for old `js-pretty-number`. Actually, we can use Golang's `text` package to format. This PR partially completes the TODOs in `GiteaLocaleNumber.js`: > if we have complete backend locale support (eg: Golang "x/text" package), we can drop this component. > tooltip: only 2 usages of this, we can replace it with Golang's "x/text/number" package in the future. This PR also helps #24131 Screenshots:
![image](https://user-images.githubusercontent.com/2114189/232179420-b1b9974b-9d96-4408-b209-b80182c8b359.png) ![image](https://user-images.githubusercontent.com/2114189/232179416-14f36aa0-3f3e-4ac9-b366-7bd3a4464a11.png)
--- modules/charset/escape_test.go | 14 ++------- modules/csv/csv_test.go | 17 ++--------- modules/templates/helper.go | 7 ----- modules/test/context_tests.go | 17 ++--------- modules/translation/mock.go | 27 +++++++++++++++++ modules/translation/translation.go | 30 +++++++++++++++---- modules/translation/translation_test.go | 27 +++++++++++++++++ templates/devtest/gitea-ui.tmpl | 14 ++++----- templates/projects/list.tmpl | 8 ++--- templates/repo/issue/milestones.tmpl | 8 ++--- templates/repo/issue/openclose.tmpl | 4 +-- templates/repo/projects/list.tmpl | 8 ++--- templates/repo/release/list.tmpl | 4 +-- templates/repo/release/new.tmpl | 4 +-- templates/repo/sub_menu.tmpl | 2 +- templates/user/dashboard/issues.tmpl | 4 +-- templates/user/dashboard/milestones.tmpl | 8 ++--- web_src/js/webcomponents/GiteaLocaleNumber.js | 20 ------------- web_src/js/webcomponents/webcomponents.js | 1 - 19 files changed, 118 insertions(+), 106 deletions(-) create mode 100644 modules/translation/mock.go create mode 100644 modules/translation/translation_test.go delete mode 100644 web_src/js/webcomponents/GiteaLocaleNumber.js diff --git a/modules/charset/escape_test.go b/modules/charset/escape_test.go index 26e82bf13acf4..f63c5c5c52b32 100644 --- a/modules/charset/escape_test.go +++ b/modules/charset/escape_test.go @@ -132,18 +132,10 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`, }, } -type nullLocale struct{} - -func (nullLocale) Language() string { return "" } -func (nullLocale) Tr(key string, _ ...interface{}) string { return key } -func (nullLocale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string { return "" } - -var _ (translation.Locale) = nullLocale{} - func TestEscapeControlString(t *testing.T) { for _, tt := range escapeControlTests { t.Run(tt.name, func(t *testing.T) { - status, result := EscapeControlString(tt.text, nullLocale{}) + status, result := EscapeControlString(tt.text, &translation.MockLocale{}) if !reflect.DeepEqual(*status, tt.status) { t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status) } @@ -179,7 +171,7 @@ func TestEscapeControlReader(t *testing.T) { t.Run(tt.name, func(t *testing.T) { input := strings.NewReader(tt.text) output := &strings.Builder{} - status, err := EscapeControlReader(input, output, nullLocale{}) + status, err := EscapeControlReader(input, output, &translation.MockLocale{}) result := output.String() if err != nil { t.Errorf("EscapeControlReader(): err = %v", err) @@ -201,5 +193,5 @@ func TestEscapeControlReader_panic(t *testing.T) { for i := 0; i < 6826; i++ { bs = append(bs, []byte("—")...) } - _, _ = EscapeControlString(string(bs), nullLocale{}) + _, _ = EscapeControlString(string(bs), &translation.MockLocale{}) } diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go index 6b91a81fc4795..f6e782a5a460d 100644 --- a/modules/csv/csv_test.go +++ b/modules/csv/csv_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/translation" "github.com/stretchr/testify/assert" ) @@ -550,20 +551,6 @@ a|"he said, ""here I am"""`, } } -type mockLocale struct{} - -func (l mockLocale) Language() string { - return "en" -} - -func (l mockLocale) Tr(s string, _ ...interface{}) string { - return s -} - -func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string { - return key1 -} - func TestFormatError(t *testing.T) { cases := []struct { err error @@ -591,7 +578,7 @@ func TestFormatError(t *testing.T) { } for n, c := range cases { - message, err := FormatError(c.err, mockLocale{}) + message, err := FormatError(c.err, &translation.MockLocale{}) if c.expectsError { assert.Error(t, err, "case %d: expected an error to be returned", n) } else { diff --git a/modules/templates/helper.go b/modules/templates/helper.go index f93419fe873eb..27d6000daf24e 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -132,7 +132,6 @@ func NewFuncMap() []template.FuncMap { // ----------------------------------------------------------------- // time / number / format "FileSize": base.FileSize, - "LocaleNumber": LocaleNumber, "CountFmt": base.FormatNumberSI, "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, @@ -782,12 +781,6 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa return a } -// LocaleNumber renders a number with a Custom Element, browser will render it with a locale number -func LocaleNumber(v interface{}) template.HTML { - num, _ := util.ToInt64(v) - return template.HTML(fmt.Sprintf(`%d`, num, num)) -} - // Eval the expression and return the result, see the comment of eval.Expr for details. // To use this helper function in templates, pass each token as a separate parameter. // diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index 94dbd2f290d99..e558bf1b9f176 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -18,6 +18,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web/middleware" chi "github.com/go-chi/chi/v5" @@ -34,7 +35,7 @@ func MockContext(t *testing.T, path string) *context.Context { Values: make(url.Values), }, Resp: context.NewResponse(resp), - Locale: &mockLocale{}, + Locale: &translation.MockLocale{}, } defer ctx.Close() @@ -91,20 +92,6 @@ func LoadGitRepo(t *testing.T, ctx *context.Context) { assert.NoError(t, err) } -type mockLocale struct{} - -func (l mockLocale) Language() string { - return "en" -} - -func (l mockLocale) Tr(s string, _ ...interface{}) string { - return s -} - -func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string { - return key1 -} - type mockResponseWriter struct { httptest.ResponseRecorder size int diff --git a/modules/translation/mock.go b/modules/translation/mock.go new file mode 100644 index 0000000000000..6ce66166aa13d --- /dev/null +++ b/modules/translation/mock.go @@ -0,0 +1,27 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package translation + +import "fmt" + +// MockLocale provides a mocked locale without any translations +type MockLocale struct{} + +var _ Locale = (*MockLocale)(nil) + +func (l MockLocale) Language() string { + return "en" +} + +func (l MockLocale) Tr(s string, _ ...interface{}) string { + return s +} + +func (l MockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string { + return key1 +} + +func (l MockLocale) PrettyNumber(v any) string { + return fmt.Sprint(v) +} diff --git a/modules/translation/translation.go b/modules/translation/translation.go index 331da0f965c38..56cf1df2d438e 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -15,17 +15,20 @@ import ( "code.gitea.io/gitea/modules/translation/i18n" "golang.org/x/text/language" + "golang.org/x/text/message" + "golang.org/x/text/number" ) type contextKey struct{} -var ContextKey interface{} = &contextKey{} +var ContextKey any = &contextKey{} // Locale represents an interface to translation type Locale interface { Language() string - Tr(string, ...interface{}) string - TrN(cnt interface{}, key1, keyN string, args ...interface{}) string + Tr(string, ...any) string + TrN(cnt any, key1, keyN string, args ...any) string + PrettyNumber(v any) string } // LangType represents a lang type @@ -135,6 +138,7 @@ func Match(tags ...language.Tag) language.Tag { type locale struct { i18n.Locale Lang, LangName string // these fields are used directly in templates: .i18n.Lang + msgPrinter *message.Printer } // NewLocale return a locale @@ -147,13 +151,24 @@ func NewLocale(lang string) Locale { langName := "unknown" if l, ok := allLangMap[lang]; ok { langName = l.Name + } else if len(setting.Langs) > 0 { + lang = setting.Langs[0] + langName = setting.Names[0] } + i18nLocale, _ := i18n.GetLocale(lang) - return &locale{ + l := &locale{ Locale: i18nLocale, Lang: lang, LangName: langName, } + if langTag, err := language.Parse(lang); err != nil { + log.Error("Failed to parse language tag from name %q: %v", l.Lang, err) + l.msgPrinter = message.NewPrinter(language.English) + } else { + l.msgPrinter = message.NewPrinter(langTag) + } + return l } func (l *locale) Language() string { @@ -199,7 +214,7 @@ var trNLangRules = map[string]func(int64) int{ } // TrN returns translated message for plural text translation -func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string { +func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string { var c int64 if t, ok := cnt.(int); ok { c = int64(t) @@ -223,3 +238,8 @@ func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) st } return l.Tr(keyN, args...) } + +func (l *locale) PrettyNumber(v any) string { + // TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format + return l.msgPrinter.Sprintf("%v", number.Decimal(v)) +} diff --git a/modules/translation/translation_test.go b/modules/translation/translation_test.go new file mode 100644 index 0000000000000..83a40f145815a --- /dev/null +++ b/modules/translation/translation_test.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package translation + +import ( + "testing" + + "code.gitea.io/gitea/modules/translation/i18n" + + "github.com/stretchr/testify/assert" +) + +func TestPrettyNumber(t *testing.T) { + // TODO: make this package friendly to testing + + i18n.ResetDefaultLocales() + + allLangMap = make(map[string]*LangType) + allLangMap["id-ID"] = &LangType{Lang: "id-ID", Name: "Bahasa Indonesia"} + + l := NewLocale("id-ID") + assert.EqualValues(t, "1.000.000", l.PrettyNumber(1000000)) + + l = NewLocale("nosuch") + assert.EqualValues(t, "1,000,000", l.PrettyNumber(1000000)) +} diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl index 1ab9ae7b7c7aa..2fe478a07d8a0 100644 --- a/templates/devtest/gitea-ui.tmpl +++ b/templates/devtest/gitea-ui.tmpl @@ -15,13 +15,13 @@

LocaleNumber

-
{{LocaleNumber 1}}
-
{{LocaleNumber 12}}
-
{{LocaleNumber 123}}
-
{{LocaleNumber 1234}}
-
{{LocaleNumber 12345}}
-
{{LocaleNumber 123456}}
-
{{LocaleNumber 1234567}}
+
{{.locale.PrettyNumber 1}}
+
{{.locale.PrettyNumber 12}}
+
{{.locale.PrettyNumber 123}}
+
{{.locale.PrettyNumber 1234}}
+
{{.locale.PrettyNumber 12345}}
+
{{.locale.PrettyNumber 123456}}
+
{{.locale.PrettyNumber 1234567}}
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 213bab70b66e2..ae9e3a0d11870 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -13,11 +13,11 @@ @@ -46,9 +46,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{$.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{$.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}}
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}} diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index 602461400b928..e54a72714a5c1 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -18,11 +18,11 @@
@@ -84,9 +84,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{$.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{$.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} {{if .TotalTrackedTime}}{{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Time}}{{end}} {{if .UpdatedUnix}}{{svg "octicon-clock"}} {{$.locale.Tr "repo.milestones.update_ago" (TimeSinceUnix .UpdatedUnix $.locale) | Safe}}{{end}} diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl index 6eb26b36c5d6d..045f513974671 100644 --- a/templates/repo/issue/openclose.tmpl +++ b/templates/repo/issue/openclose.tmpl @@ -5,10 +5,10 @@ {{else}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} {{end}} - {{LocaleNumber .IssueStats.OpenCount}} {{.locale.Tr "repo.issues.open_title"}} + {{.locale.PrettyNumber .IssueStats.OpenCount}} {{.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .IssueStats.ClosedCount}} {{.locale.Tr "repo.issues.closed_title"}} + {{.locale.PrettyNumber .IssueStats.ClosedCount}} {{.locale.Tr "repo.issues.closed_title"}}
diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl index fb5bc4f48d91b..bbcc20dd7cbd2 100644 --- a/templates/repo/projects/list.tmpl +++ b/templates/repo/projects/list.tmpl @@ -15,11 +15,11 @@ @@ -48,9 +48,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} {{if and $.CanWriteProjects (not $.Repository.IsArchived)}} diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 12aaa0bd71723..8e1793a5bab7c 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -161,9 +161,9 @@
  • {{.Size | FileSize}} - + {{svg "octicon-info"}} - + {{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}} diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index 7a4e28cffa1be..ddedfd608690c 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -71,9 +71,9 @@ {{.Size | FileSize}} - + {{svg "octicon-info"}} - + {{end}} diff --git a/templates/repo/sub_menu.tmpl b/templates/repo/sub_menu.tmpl index 97fbabda412d4..9289295b1d733 100644 --- a/templates/repo/sub_menu.tmpl +++ b/templates/repo/sub_menu.tmpl @@ -4,7 +4,7 @@
    {{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo)}} diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index 99151597217ae..39eea2fc7517d 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -39,11 +39,11 @@
    @@ -104,9 +104,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} {{if .TotalTrackedTime}} {{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Time}} {{end}} diff --git a/web_src/js/webcomponents/GiteaLocaleNumber.js b/web_src/js/webcomponents/GiteaLocaleNumber.js deleted file mode 100644 index 613aa67359128..0000000000000 --- a/web_src/js/webcomponents/GiteaLocaleNumber.js +++ /dev/null @@ -1,20 +0,0 @@ -// Convert a number to a locale string by data-number attribute. -// Or add a tooltip by data-number-in-tooltip attribute. JSON: {message: "count: %s", number: 123} -window.customElements.define('gitea-locale-number', class extends HTMLElement { - connectedCallback() { - // ideally, the number locale formatting and plural processing should be done by backend with translation strings. - // if we have complete backend locale support (eg: Golang "x/text" package), we can drop this component. - const number = this.getAttribute('data-number'); - if (number) { - this.attachShadow({mode: 'open'}); - this.shadowRoot.textContent = new Intl.NumberFormat().format(Number(number)); - } - const numberInTooltip = this.getAttribute('data-number-in-tooltip'); - if (numberInTooltip) { - // TODO: only 2 usages of this, we can replace it with Golang's "x/text/number" package in the future - const {message, number} = JSON.parse(numberInTooltip); - const tooltipContent = message.replace(/%[ds]/, new Intl.NumberFormat().format(Number(number))); - this.setAttribute('data-tooltip-content', tooltipContent); - } - } -}); diff --git a/web_src/js/webcomponents/webcomponents.js b/web_src/js/webcomponents/webcomponents.js index 7e8135aa00ccf..123607282bc78 100644 --- a/web_src/js/webcomponents/webcomponents.js +++ b/web_src/js/webcomponents/webcomponents.js @@ -1,4 +1,3 @@ import '@webcomponents/custom-elements'; // polyfill for some browsers like Pale Moon import '@github/relative-time-element'; -import './GiteaLocaleNumber.js'; import './GiteaOriginUrl.js'; From dcde4701a5b31c88b3120722c3163af4214264d2 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 17 Apr 2023 12:10:22 +0200 Subject: [PATCH 13/25] Fix math and mermaid rendering bugs (#24049) 1. Fix multiple error display for math and mermaid: ![err](https://user-images.githubusercontent.com/115237/231126411-8a21a777-cd53-4b7e-ac67-5332623106e8.gif) 2. Fix height calculation of certain mermaid diagrams by reading the iframe inner height from it's document instead of parsing it from SVG: Before: Screenshot 2023-04-11 at 11 56 27 After: Screenshot 2023-04-11 at 11 56 35 3. Refactor error handling to a common function 4. Rename to `renderAsciicast` for consistency 5. Improve mermaid loading sequence Note: I did try `securityLevel: 'sandbox'` to make mermaid output a iframe directly, but that showed a bug in mermaid where the iframe style height was set incorrectly. Opened https://github.com/mermaid-js/mermaid/issues/4289 for this. --------- Co-authored-by: Giteabot --- web_src/css/base.css | 2 +- web_src/css/helpers.css | 3 +- web_src/css/markup/content.css | 2 +- web_src/js/markup/asciicast.js | 2 +- web_src/js/markup/common.js | 8 ++++++ web_src/js/markup/content.js | 4 +-- web_src/js/markup/math.js | 18 +++++------- web_src/js/markup/mermaid.js | 50 ++++++++++++++++------------------ 8 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 web_src/js/markup/common.js diff --git a/web_src/css/base.css b/web_src/css/base.css index c48a36c854764..bdf601951b717 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -9,7 +9,7 @@ /* non-color variables */ --border-radius: 0.28571429rem; --opacity-disabled: 0.55; - --height-loading: 12rem; + --height-loading: 16rem; /* base colors */ --color-primary: #4183c4; --color-primary-contrast: #ffffff; diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 8d64bd751b879..beb93e1e86d83 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -25,7 +25,8 @@ .gt-overflow-x-scroll { overflow-x: scroll !important; } .gt-cursor-default { cursor: default !important; } .gt-items-start { align-items: flex-start !important; } -.gt-whitespace-pre { white-space: pre !important } +.gt-whitespace-pre { white-space: pre !important; } +.gt-invisible { visibility: hidden !important; } .gt-mono { font-family: var(--fonts-monospace) !important; diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 90f8c7091e9a6..d0f11e8e76500 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -542,7 +542,7 @@ .markup-block-error { display: block !important; /* override fomantic .ui.form .error.message {display: none} */ - border: 1px solid var(--color-error-border) !important; + border: none !important; margin-bottom: 0 !important; border-bottom-left-radius: 0 !important; border-bottom-right-radius: 0 !important; diff --git a/web_src/js/markup/asciicast.js b/web_src/js/markup/asciicast.js index 902cfcb7316bc..97b18743a15d3 100644 --- a/web_src/js/markup/asciicast.js +++ b/web_src/js/markup/asciicast.js @@ -1,4 +1,4 @@ -export async function renderAsciinemaPlayer() { +export async function renderAsciicast() { const els = document.querySelectorAll('.asciinema-player-container'); if (!els.length) return; diff --git a/web_src/js/markup/common.js b/web_src/js/markup/common.js new file mode 100644 index 0000000000000..aff4a3242368e --- /dev/null +++ b/web_src/js/markup/common.js @@ -0,0 +1,8 @@ +export function displayError(el, err) { + el.classList.remove('is-loading'); + const errorNode = document.createElement('pre'); + errorNode.setAttribute('class', 'ui message error markup-block-error'); + errorNode.textContent = err.str || err.message || String(err); + el.before(errorNode); + el.setAttribute('data-render-done', 'true'); +} diff --git a/web_src/js/markup/content.js b/web_src/js/markup/content.js index e4ec3d0b4baa6..1d29dc07f29ad 100644 --- a/web_src/js/markup/content.js +++ b/web_src/js/markup/content.js @@ -1,7 +1,7 @@ import {renderMermaid} from './mermaid.js'; import {renderMath} from './math.js'; import {renderCodeCopy} from './codecopy.js'; -import {renderAsciinemaPlayer} from './asciicast.js'; +import {renderAsciicast} from './asciicast.js'; import {initMarkupTasklist} from './tasklist.js'; // code that runs for all markup content @@ -9,7 +9,7 @@ export function initMarkupContent() { renderMermaid(); renderMath(); renderCodeCopy(); - renderAsciinemaPlayer(); + renderAsciicast(); } // code that only runs for comments diff --git a/web_src/js/markup/math.js b/web_src/js/markup/math.js index dcc656ea82cee..8427637a0f3d4 100644 --- a/web_src/js/markup/math.js +++ b/web_src/js/markup/math.js @@ -1,14 +1,8 @@ -function displayError(el, err) { - const target = targetElement(el); - target.classList.remove('is-loading'); - const errorNode = document.createElement('div'); - errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono'); - errorNode.textContent = err.str || err.message || String(err); - target.before(errorNode); -} +import {displayError} from './common.js'; function targetElement(el) { - // The target element is either the current element if it has the `is-loading` class or the pre that contains it + // The target element is either the current element if it has the + // `is-loading` class or the pre that contains it return el.classList.contains('is-loading') ? el : el.closest('pre'); } @@ -22,6 +16,8 @@ export async function renderMath() { ]); for (const el of els) { + const target = targetElement(el); + if (target.hasAttribute('data-render-done')) continue; const source = el.textContent; const displayMode = el.classList.contains('display'); const nodeName = displayMode ? 'p' : 'span'; @@ -33,9 +29,9 @@ export async function renderMath() { maxExpand: 50, displayMode, }); - targetElement(el).replaceWith(tempEl); + target.replaceWith(tempEl); } catch (error) { - displayError(el, error); + displayError(target, error); } } } diff --git a/web_src/js/markup/mermaid.js b/web_src/js/markup/mermaid.js index b519e2dcdc352..865a414c93367 100644 --- a/web_src/js/markup/mermaid.js +++ b/web_src/js/markup/mermaid.js @@ -1,21 +1,12 @@ import {isDarkTheme} from '../utils.js'; import {makeCodeCopyButton} from './codecopy.js'; +import {displayError} from './common.js'; const {mermaidMaxSourceCharacters} = window.config; -const iframeCss = ` - :root {color-scheme: normal} - body {margin: 0; padding: 0; overflow: hidden} - #mermaid {display: block; margin: 0 auto} -`; - -function displayError(el, err) { - el.closest('pre').classList.remove('is-loading'); - const errorNode = document.createElement('div'); - errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono'); - errorNode.textContent = err.str || err.message || String(err); - el.closest('pre').before(errorNode); -} +const iframeCss = `:root {color-scheme: normal} +body {margin: 0; padding: 0; overflow: hidden} +#mermaid {display: block; margin: 0 auto}`; export async function renderMermaid() { const els = document.querySelectorAll('.markup code.language-mermaid'); @@ -30,18 +21,19 @@ export async function renderMermaid() { }); for (const el of els) { - const source = el.textContent; + const pre = el.closest('pre'); + if (pre.hasAttribute('data-render-done')) continue; + const source = el.textContent; if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) { - displayError(el, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); + displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); continue; } try { await mermaid.parse(source); } catch (err) { - displayError(el, err); - el.closest('pre').classList.remove('is-loading'); + displayError(pre, err); continue; } @@ -49,26 +41,32 @@ export async function renderMermaid() { // can't use bindFunctions here because we can't cross the iframe boundary. This // means js-based interactions won't work but they aren't intended to work either const {svg} = await mermaid.render('mermaid', source); - const heightStr = (svg.match(/viewBox="(.+?)"/) || ['', ''])[1].split(/\s+/)[3]; - if (!heightStr) return displayError(el, new Error('Could not determine chart height')); const iframe = document.createElement('iframe'); - iframe.classList.add('markup-render'); - iframe.sandbox = 'allow-scripts'; - iframe.style.height = `${Math.ceil(parseFloat(heightStr))}px`; + iframe.classList.add('markup-render', 'gt-invisible'); iframe.srcdoc = `${svg}`; const mermaidBlock = document.createElement('div'); - mermaidBlock.classList.add('mermaid-block'); + mermaidBlock.classList.add('mermaid-block', 'is-loading', 'gt-hidden'); mermaidBlock.append(iframe); const btn = makeCodeCopyButton(); btn.setAttribute('data-clipboard-text', source); - mermaidBlock.append(btn); - el.closest('pre').replaceWith(mermaidBlock); + + iframe.addEventListener('load', () => { + pre.replaceWith(mermaidBlock); + mermaidBlock.classList.remove('gt-hidden'); + iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`; + setTimeout(() => { // avoid flash of iframe background + mermaidBlock.classList.remove('is-loading'); + iframe.classList.remove('gt-invisible'); + }, 0); + }); + + document.body.append(mermaidBlock); } catch (err) { - displayError(el, err); + displayError(pre, err); } } } From cda84bec87f18fa5cbbbd62fc72bc3d997aba66c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 17 Apr 2023 21:22:10 +0800 Subject: [PATCH 14/25] Support converting varchar to nvarchar for mssql database (#24105) --- cmd/convert.go | 27 ++++++++++++++++----------- models/db/convert.go | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/cmd/convert.go b/cmd/convert.go index d9b89495c1bf0..8c7746fdf2970 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -17,7 +17,7 @@ import ( var CmdConvert = cli.Command{ Name: "convert", Usage: "Convert the database", - Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar", Action: runConvert, } @@ -35,17 +35,22 @@ func runConvert(ctx *cli.Context) error { log.Info("Log path: %s", setting.Log.RootPath) log.Info("Configuration file: %s", setting.CustomConf) - if !setting.Database.Type.IsMySQL() { - fmt.Println("This command can only be used with a MySQL database") - return nil + switch { + case setting.Database.Type.IsMySQL(): + if err := db.ConvertUtf8ToUtf8mb4(); err != nil { + log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) + return err + } + fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") + case setting.Database.Type.IsMSSQL(): + if err := db.ConvertVarcharToNVarchar(); err != nil { + log.Fatal("Failed to convert database from varchar to nvarchar: %v", err) + return err + } + fmt.Println("Converted successfully, please confirm your database's all columns character is NVARCHAR now") + default: + fmt.Println("This command can only be used with a MySQL or MSSQL database") } - if err := db.ConvertUtf8ToUtf8mb4(); err != nil { - log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) - return err - } - - fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") - return nil } diff --git a/models/db/convert.go b/models/db/convert.go index b17e68c87e591..112c8575ca2c7 100644 --- a/models/db/convert.go +++ b/models/db/convert.go @@ -42,6 +42,33 @@ func ConvertUtf8ToUtf8mb4() error { return nil } +// ConvertVarcharToNVarchar converts database and tables from varchar to nvarchar if it's mssql +func ConvertVarcharToNVarchar() error { + if x.Dialect().URI().DBType != schemas.MSSQL { + return nil + } + + sess := x.NewSession() + defer sess.Close() + res, err := sess.QuerySliceString(`SELECT 'ALTER TABLE ' + OBJECT_NAME(SC.object_id) + ' MODIFY SC.name NVARCHAR(' + CONVERT(VARCHAR(5),SC.max_length) + ')' +FROM SYS.columns SC +JOIN SYS.types ST +ON SC.system_type_id = ST.system_type_id +AND SC.user_type_id = ST.user_type_id +WHERE ST.name ='varchar'`) + if err != nil { + return err + } + for _, row := range res { + if len(row) == 1 { + if _, err = sess.Exec(row[0]); err != nil { + return err + } + } + } + return err +} + // Cell2Int64 converts a xorm.Cell type to int64, // and handles possible irregular cases. func Cell2Int64(val xorm.Cell) int64 { From 4b1c6cd8e5745a945df0f72233964f6aede8a3a6 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 17 Apr 2023 16:46:25 +0200 Subject: [PATCH 15/25] Make HAS_GO a simply expanded variable (#24169) Avoid recursive expansion on this variable and simplify the value. [Reference](https://www.gnu.org/software/make/manual/html_node/Setting.html). --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index eb48766194f18..59cc27ee8a102 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ IMPORT := code.gitea.io/gitea GO ?= go SHASUM ?= shasum -a 256 -HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) +HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) COMMA := , XGO_VERSION := go-1.20.x @@ -41,7 +41,7 @@ DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) -ifeq ($(HAS_GO), GO) +ifeq ($(HAS_GO), yes) GOPATH ?= $(shell $(GO) env GOPATH) export PATH := $(GOPATH)/bin:$(PATH) From f20057271def2240474d64c57eeba8b365642c08 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 17 Apr 2023 23:35:57 +0800 Subject: [PATCH 16/25] Fix Org edit page bugs: renaming detection, maxlength (#24161) ## Before * The renaming detection is wrong (eg: pasting a new name into the input doesn't trigger the detection) * The renaming prompt layout is not good * Some MaxSize/maxlength rules is missing ![image](https://user-images.githubusercontent.com/2114189/232379191-5d0f6d10-56ca-4cec-ac52-7f77b9cb4a8a.png) ![image](https://user-images.githubusercontent.com/2114189/232379234-3289373b-9ddb-4627-ae86-f4d74589fa0c.png) ## After * Fix these problems ![image](https://user-images.githubusercontent.com/2114189/232379098-31c6fa21-c210-4e7f-a337-b38b99670835.png) --- modules/structs/org.go | 6 +++--- templates/org/create.tmpl | 2 +- templates/org/settings/options.tmpl | 15 ++++++++------- web_src/js/features/common-organization.js | 19 +++++-------------- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/modules/structs/org.go b/modules/structs/org.go index b4c58623fd29c..7c83dcdee7e4d 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -30,8 +30,8 @@ type OrganizationPermissions struct { // CreateOrgOption options for creating an organization type CreateOrgOption struct { // required: true - UserName string `json:"username" binding:"Required"` - FullName string `json:"full_name"` + UserName string `json:"username" binding:"Required;Username;MaxSize(40)"` + FullName string `json:"full_name" binding:"MaxSize(100)"` Description string `json:"description" binding:"MaxSize(255)"` Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` @@ -45,7 +45,7 @@ type CreateOrgOption struct { // EditOrgOption options for editing an organization type EditOrgOption struct { - FullName string `json:"full_name"` + FullName string `json:"full_name" binding:"MaxSize(100)"` Description string `json:"description" binding:"MaxSize(255)"` Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` diff --git a/templates/org/create.tmpl b/templates/org/create.tmpl index f734a39a93724..7e988ba0c792a 100644 --- a/templates/org/create.tmpl +++ b/templates/org/create.tmpl @@ -11,7 +11,7 @@ {{template "base/alert" .}}
    - + {{.locale.Tr "org.org_name_helper"}}
    diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 833b97e347253..1caa4210e62ca 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -14,26 +14,27 @@ {{.CsrfTokenHtml}}
    - +
    - +
    - +
    - +
    - +
    diff --git a/web_src/js/features/common-organization.js b/web_src/js/features/common-organization.js index 1796efc6a8cfd..352e824b05be4 100644 --- a/web_src/js/features/common-organization.js +++ b/web_src/js/features/common-organization.js @@ -1,25 +1,16 @@ import $ from 'jquery'; import {initCompLabelEdit} from './comp/LabelEdit.js'; -import {hideElem, showElem} from '../utils/dom.js'; +import {toggleElem} from '../utils/dom.js'; export function initCommonOrganization() { if ($('.organization').length === 0) { return; } - if ($('.organization.settings.options').length > 0) { - $('#org_name').on('keyup', function () { - const $prompt = $('#org-name-change-prompt'); - const $prompt_redirect = $('#org-name-change-redirect-prompt'); - if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) { - showElem($prompt); - showElem($prompt_redirect); - } else { - hideElem($prompt); - hideElem($prompt_redirect); - } - }); - } + $('.organization.settings.options #org_name').on('input', function () { + const nameChanged = $(this).val().toLowerCase() !== $(this).attr('data-org-name').toLowerCase(); + toggleElem('#org-name-change-prompt', nameChanged); + }); // Labels initCompLabelEdit('.organization.settings.labels'); From 1819c4b59b81ba4db2a38d3b3dc81f29102fde51 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Mon, 17 Apr 2023 12:36:50 -0400 Subject: [PATCH 17/25] Add new user types `reserved`, `bot`, and `remote` (#24026) This allows for usernames, and emails connected to them to be reserved and not reused. Use case, I manage an instance with open registration, and sometimes when users are deleted for spam (or other purposes), their usernames are freed up and they sign up again with the same information. This could also be used to reserve usernames, and block them from being registered (in case an instance would like to block certain things without hardcoding the list in code and compiling from scratch). This is an MVP, that will allow for future work where you can set something as reserved via the interface. --------- Co-authored-by: delvh Co-authored-by: John Olheiser --- models/user/user.go | 16 +++++++++++++++- services/auth/source/db/authenticate.go | 8 ++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/models/user/user.go b/models/user/user.go index 5709ed7ff27e9..5f152780bff04 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -41,6 +41,18 @@ const ( // UserTypeOrganization defines an organization UserTypeOrganization + + // UserTypeReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on + UserTypeUserReserved + + // UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved + UserTypeOrganizationReserved + + // UserTypeBot defines a bot user + UserTypeBot + + // UserTypeRemoteUser defines a remote user for federated users + UserTypeRemoteUser ) const ( @@ -312,6 +324,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.user_id"). Where("follow.follow_id=?", u.ID). + And("`user`.type=?", UserTypeIndividual). And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { @@ -333,6 +346,7 @@ func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListO Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). + And("`user`.type=?", UserTypeIndividual). And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { @@ -959,7 +973,7 @@ func GetUserByName(ctx context.Context, name string) (*User, error) { if len(name) == 0 { return nil, ErrUserNotExist{0, name, 0} } - u := &User{LowerName: strings.ToLower(name)} + u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} has, err := db.GetEngine(ctx).Get(u) if err != nil { return nil, err diff --git a/services/auth/source/db/authenticate.go b/services/auth/source/db/authenticate.go index ec89984499862..76445e0d6d53d 100644 --- a/services/auth/source/db/authenticate.go +++ b/services/auth/source/db/authenticate.go @@ -40,5 +40,13 @@ func Authenticate(user *user_model.User, login, password string) (*user_model.Us } } + // attempting to login as a non-user account + if user.Type != user_model.UserTypeIndividual { + return nil, user_model.ErrUserProhibitLogin{ + UID: user.ID, + Name: user.Name, + } + } + return user, nil } From 4014200021a1997283c779a815fe9e5febf1fda1 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Mon, 17 Apr 2023 13:07:13 -0400 Subject: [PATCH 18/25] add CLI command to register runner tokens (#23762) This is a CLI command to generate new tokens for the runners to register with Fix https://github.com/go-gitea/gitea/issues/23643 --------- Co-authored-by: delvh --- cmd/actions.go | 56 ++++++++++++ .../doc/administration/command-line.en-us.md | 25 +++++ main.go | 1 + modules/private/actions.go | 27 ++++++ routers/private/actions.go | 91 +++++++++++++++++++ routers/private/internal.go | 1 + 6 files changed, 201 insertions(+) create mode 100644 cmd/actions.go create mode 100644 modules/private/actions.go create mode 100644 routers/private/actions.go diff --git a/cmd/actions.go b/cmd/actions.go new file mode 100644 index 0000000000000..66ad336da508e --- /dev/null +++ b/cmd/actions.go @@ -0,0 +1,56 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "fmt" + + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +var ( + // CmdActions represents the available actions sub-commands. + CmdActions = cli.Command{ + Name: "actions", + Usage: "", + Description: "Commands for managing Gitea Actions", + Subcommands: []cli.Command{ + subcmdActionsGenRunnerToken, + }, + } + + subcmdActionsGenRunnerToken = cli.Command{ + Name: "generate-runner-token", + Usage: "Generate a new token for a runner to use to register with the server", + Action: runGenerateActionsRunnerToken, + Aliases: []string{"grt"}, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "scope, s", + Value: "", + Usage: "{owner}[/{repo}] - leave empty for a global runner", + }, + }, + } +) + +func runGenerateActionsRunnerToken(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + setting.InitProviderFromExistingFile() + setting.LoadCommonSettings() + + scope := c.String("scope") + + respText, extra := private.GenerateActionsRunnerToken(ctx, scope) + if extra.HasError() { + return handleCliResponseExtra(extra) + } + _, _ = fmt.Printf("%s\n", respText) + return nil +} diff --git a/docs/content/doc/administration/command-line.en-us.md b/docs/content/doc/administration/command-line.en-us.md index d3362e573138b..4d01d6e640ef0 100644 --- a/docs/content/doc/administration/command-line.en-us.md +++ b/docs/content/doc/administration/command-line.en-us.md @@ -551,3 +551,28 @@ Restore-repo restore repository data from disk dir: - `--owner_name lunny`: Restore destination owner name - `--repo_name tango`: Restore destination repository name - `--units `: Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units. + +### actions generate-runner-token + +Generate a new token for a runner to use to register with the server + +- Options: + - `--scope {owner}[/{repo}]`, `-s {owner}[/{repo}]`: To limit the scope of the runner, no scope means the runner can be used for all repos, but you can also limit it to a specific repo or owner + +To register a global runner: + +``` +gitea actions generate-runner-token +``` + +To register a runner for a specific organization, in this case `org`: + +``` +gitea actions generate-runner-token -s org +``` + +To register a runner for a specific repo, in this case `username/test-repo`: + +``` +gitea actions generate-runner-token -s username/test-repo +``` diff --git a/main.go b/main.go index eeedf54c27419..1589fa97db434 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdDocs, cmd.CmdDumpRepository, cmd.CmdRestoreRepository, + cmd.CmdActions, } // Now adjust these commands to add our global configuration options diff --git a/modules/private/actions.go b/modules/private/actions.go new file mode 100644 index 0000000000000..be24e16d3ff3f --- /dev/null +++ b/modules/private/actions.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" +) + +// Email structure holds a data for sending general emails +type GenerateTokenRequest struct { + Scope string +} + +// GenerateActionsRunnerToken calls the internal GenerateActionsRunnerToken function +func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, ResponseExtra) { + reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token" + + req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{ + Scope: scope, + }) + + resp, extra := requestJSONResp(req, &responseText{}) + return resp.Text, extra +} diff --git a/routers/private/actions.go b/routers/private/actions.go new file mode 100644 index 0000000000000..b7e416f56a33b --- /dev/null +++ b/routers/private/actions.go @@ -0,0 +1,91 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "errors" + "fmt" + "net/http" + "strings" + + actions_model "code.gitea.io/gitea/models/actions" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/util" +) + +// GenerateActionsRunnerToken generates a new runner token for a given scope +func GenerateActionsRunnerToken(ctx *context.PrivateContext) { + var genRequest private.GenerateTokenRequest + rd := ctx.Req.Body + defer rd.Close() + + if err := json.NewDecoder(rd).Decode(&genRequest); err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + return + } + + owner, repo, err := parseScope(ctx, genRequest.Scope) + if err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + } + + token, err := actions_model.GetUnactivatedRunnerToken(ctx, owner, repo) + if errors.Is(err, util.ErrNotExist) { + token, err = actions_model.NewRunnerToken(ctx, owner, repo) + if err != nil { + err := fmt.Sprintf("error while creating runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + } else if err != nil { + err := fmt.Sprintf("could not get unactivated runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + + ctx.PlainText(http.StatusOK, token.Token) +} + +func parseScope(ctx *context.PrivateContext, scope string) (ownerID, repoID int64, err error) { + ownerID = 0 + repoID = 0 + if scope == "" { + return ownerID, repoID, nil + } + + ownerName, repoName, found := strings.Cut(scope, "/") + + u, err := user_model.GetUserByName(ctx, ownerName) + if err != nil { + return ownerID, repoID, err + } + + if !found { + return u.ID, repoID, nil + } + + r, err := repo_model.GetRepositoryByName(u.ID, repoName) + if err != nil { + return ownerID, repoID, err + } + repoID = r.ID + return ownerID, repoID, nil +} diff --git a/routers/private/internal.go b/routers/private/internal.go index 4acede33705d6..b4d32c37a6439 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -77,6 +77,7 @@ func Routes() *web.Route { r.Get("/manager/processes", Processes) r.Post("/mail/send", SendEmail) r.Post("/restore_repo", RestoreRepo) + r.Post("/actions/generate_actions_runner_token", GenerateActionsRunnerToken) return r } From 5eb4c6386709259a9280c5aad6e0488f381144c5 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Tue, 18 Apr 2023 01:49:47 +0800 Subject: [PATCH 19/25] Support triggering workflows by wiki related events (#24119) This PR is to support triggering workflows by wiki related events like creating, editing or deleting wiki pages. In GitHub, this event is called [gollum](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum) --- modules/actions/github.go | 5 +++++ modules/actions/workflows.go | 2 -- modules/actions/workflows_test.go | 7 +++++++ services/actions/notifier.go | 35 +++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/modules/actions/github.go b/modules/actions/github.go index 1148554139cd1..f3cb335da98c4 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -21,6 +21,7 @@ const ( githubEventIssueComment = "issue_comment" githubEventRelease = "release" githubEventPullRequestComment = "pull_request_comment" + githubEventGollum = "gollum" ) // canGithubEventMatch check if the input Github event can match any Gitea event. @@ -29,6 +30,10 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent case githubEventRegistryPackage: return triggedEvent == webhook_module.HookEventPackage + // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum + case githubEventGollum: + return triggedEvent == webhook_module.HookEventWiki + case githubEventIssues: switch triggedEvent { case webhook_module.HookEventIssues, diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index d21dc1d809c01..f37f4f2878af7 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -119,8 +119,6 @@ func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType webhook_module.HookEventCreate, webhook_module.HookEventDelete, webhook_module.HookEventFork, - // FIXME: `wiki` event should match `gollum` event - // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum webhook_module.HookEventWiki: if len(evt.Acts()) != 0 { log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts()) diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 6724abafd859e..6ef5d59942298 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -92,6 +92,13 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on:\n registry_package:\n types: [updated]", expected: false, }, + { + desc: "HookEventWiki(wiki) matches githubEventGollum(gollum)", + triggedEvent: webhook_module.HookEventWiki, + payload: nil, + yamlOn: "on: gollum", + expected: true, + }, } for _, tc := range testCases { diff --git a/services/actions/notifier.go b/services/actions/notifier.go index 6956c25cee2fd..4ac77276ffe2e 100644 --- a/services/actions/notifier.go +++ b/services/actions/notifier.go @@ -526,3 +526,38 @@ func (n *actionsNotifier) NotifyPullRequestChangeTargetBranch(ctx context.Contex WithPullRequest(pr). Notify(ctx) } + +func (n *actionsNotifier) NotifyNewWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) { + ctx = withMethod(ctx, "NotifyNewWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiCreated, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + Comment: comment, + }).Notify(ctx) +} + +func (n *actionsNotifier) NotifyEditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) { + ctx = withMethod(ctx, "NotifyEditWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiEdited, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + Comment: comment, + }).Notify(ctx) +} + +func (n *actionsNotifier) NotifyDeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) { + ctx = withMethod(ctx, "NotifyDeleteWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiDeleted, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + }).Notify(ctx) +} From f045e58cc7ba076a0ac4b0b5bf0702fa155eaa59 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Mon, 17 Apr 2023 21:26:01 +0300 Subject: [PATCH 20/25] Localize activity heatmap (except tooltip) (#24131) The calculation of the total sum is moved to the backend so a full HTML string could be sent. ![image](https://user-images.githubusercontent.com/20454870/232112381-c11d896b-ba47-40f8-b2a3-71cf4b3208de.png) - Closes #10669 - 2nd attempt (the first was in #21570) --------- Signed-off-by: Yarden Shoham Co-authored-by: Giteabot --- models/activities/user_heatmap.go | 9 +++++++++ options/locale/locale_en-US.ini | 6 ++++++ routers/web/user/home.go | 1 + routers/web/user/profile.go | 1 + templates/user/heatmap.tmpl | 8 +++++++- web_src/js/components/ActivityHeatmap.vue | 11 +---------- web_src/js/features/heatmap.js | 6 +++++- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go index d3f0f0db73b27..33207995267b6 100644 --- a/models/activities/user_heatmap.go +++ b/models/activities/user_heatmap.go @@ -69,3 +69,12 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us OrderBy("timestamp"). Find(&hdata) } + +// GetTotalContributionsInHeatmap returns the total number of contributions in a heatmap +func GetTotalContributionsInHeatmap(hdata []*UserHeatmapData) int64 { + var total int64 + for _, v := range hdata { + total += v.Contributions + } + return total +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c2c8f1e120c1f..2b0260a615e6f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -118,6 +118,12 @@ footer = Footer footer.software = About Software footer.links = Links +[heatmap] +number_of_contributions_in_the_last_12_months = %s contributions in the last 12 months +no_contributions = No contributions +less = Less +more = More + [editor] buttons.heading.tooltip = Add heading buttons.bold.tooltip = Add bold text diff --git a/routers/web/user/home.go b/routers/web/user/home.go index a0a5dc3c4b9ba..1f77379044afe 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -107,6 +107,7 @@ func Dashboard(ctx *context.Context) { return } ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } feeds, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index d690fa4d0113d..b39ba58f12455 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -74,6 +74,7 @@ func Profile(ctx *context.Context) { return } ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } if len(ctx.ContextUser.Description) != 0 { diff --git a/templates/user/heatmap.tmpl b/templates/user/heatmap.tmpl index 9d58bc8fc5e51..5d42a5435bb7b 100644 --- a/templates/user/heatmap.tmpl +++ b/templates/user/heatmap.tmpl @@ -1,5 +1,11 @@ {{if .HeatmapData}} -
    +
    {{.locale.Tr "user.heatmap.loading"}}
    diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index 98ffce44b5579..7834ebe82ccac 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -1,7 +1,7 @@