From edd8ea0b0d07f7fee0f8cf506975c8f3bec690ca Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Sun, 21 May 2023 12:03:20 +0300 Subject: [PATCH 01/24] Fix topics deleted via API not being deleted in org page (#24825) The topics are saved in the `repo_topic` table. They are also saved directly in the `repository` table. Before this PR, only `AddTopic` and `SaveTopics` made sure the `topics` field in the `repository` table was synced with the `repo_topic` table. This PR makes sure `GenerateTopics` and `DeleteTopic` also sync the `topics` in the repository table. `RemoveTopicsFromRepo` doesn't need to sync the data as it is only used to delete a repository. Fixes #24820 --- models/repo/topic.go | 45 ++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/models/repo/topic.go b/models/repo/topic.go index 88fe532be9..ec3de869d5 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -253,16 +253,7 @@ func AddTopic(repoID int64, topicName string) (*Topic, error) { return nil, err } - topicNames := make([]string, 0, 25) - if err := sess.Select("name").Table("topic"). - Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id"). - Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil { - return nil, err - } - - if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{ - Topics: topicNames, - }); err != nil { + if err = syncTopicsInRepository(sess, repoID); err != nil { return nil, err } @@ -281,6 +272,11 @@ func DeleteTopic(repoID int64, topicName string) (*Topic, error) { } err = removeTopicFromRepo(db.DefaultContext, repoID, topic) + if err != nil { + return nil, err + } + + err = syncTopicsInRepository(db.GetEngine(db.DefaultContext), repoID) return topic, err } @@ -347,16 +343,7 @@ func SaveTopics(repoID int64, topicNames ...string) error { } } - topicNames = make([]string, 0, 25) - if err := sess.Table("topic").Cols("name"). - Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id"). - Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil { - return err - } - - if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{ - Topics: topicNames, - }); err != nil { + if err := syncTopicsInRepository(sess, repoID); err != nil { return err } @@ -370,5 +357,23 @@ func GenerateTopics(ctx context.Context, templateRepo, generateRepo *Repository) return err } } + + return syncTopicsInRepository(db.GetEngine(ctx), generateRepo.ID) +} + +// syncTopicsInRepository makes sure topics in the topics table are copied into the topics field of the repository +func syncTopicsInRepository(sess db.Engine, repoID int64) error { + topicNames := make([]string, 0, 25) + if err := sess.Table("topic").Cols("name"). + Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id"). + Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil { + return err + } + + if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{ + Topics: topicNames, + }); err != nil { + return err + } return nil } From 64f6a5d113da0d5d187752c9398d6e8d22d24b79 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 21 May 2023 20:48:28 +0800 Subject: [PATCH 02/24] Use `CommentList` instead of `[]*Comment` (#24828) As title. --- models/issues/comment.go | 2 +- models/issues/comment_code.go | 4 ++-- models/issues/issue.go | 4 ++-- routers/api/v1/repo/issue_comment.go | 16 ++++++++-------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/models/issues/comment.go b/models/issues/comment.go index c7e815c8b6..bf2bbfa414 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -1048,7 +1048,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond { } // FindComments returns all comments according options -func FindComments(ctx context.Context, opts *FindCommentsOptions) ([]*Comment, error) { +func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) { comments := make([]*Comment, 0, 10) sess := db.GetEngine(ctx).Where(opts.ToConds()) if opts.RepoID > 0 { diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 8475da1a8c..304ac4569f 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -48,7 +48,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u } func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review) ([]*Comment, error) { - var comments []*Comment + var comments CommentList if review == nil { review = &Review{ID: 0} } @@ -68,7 +68,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu return nil, err } - if err := CommentList(comments).LoadPosters(ctx); err != nil { + if err := comments.LoadPosters(ctx); err != nil { return nil, err } diff --git a/models/issues/issue.go b/models/issues/issue.go index bf41a7ec28..fc046d273c 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -124,7 +124,7 @@ type Issue struct { ClosedUnix timeutil.TimeStamp `xorm:"INDEX"` Attachments []*repo_model.Attachment `xorm:"-"` - Comments []*Comment `xorm:"-"` + Comments CommentList `xorm:"-"` Reactions ReactionList `xorm:"-"` TotalTrackedTime int64 `xorm:"-"` Assignees []*user_model.User `xorm:"-"` @@ -353,7 +353,7 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) { return err } - if err = CommentList(issue.Comments).loadAttributes(ctx); err != nil { + if err = issue.Comments.loadAttributes(ctx); err != nil { return err } if issue.IsTimetrackerEnabled(ctx) { diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 7c8f30f116..5616e255ad 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -90,12 +90,12 @@ func ListIssueComments(ctx *context.APIContext) { return } - if err := issues_model.CommentList(comments).LoadPosters(ctx); err != nil { + if err := comments.LoadPosters(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadPosters", err) return } - if err := issues_model.CommentList(comments).LoadAttachments(ctx); err != nil { + if err := comments.LoadAttachments(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadAttachments", err) return } @@ -182,7 +182,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) { return } - if err := issues_model.CommentList(comments).LoadPosters(ctx); err != nil { + if err := comments.LoadPosters(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadPosters", err) return } @@ -285,25 +285,25 @@ func ListRepoIssueComments(ctx *context.APIContext) { return } - if err = issues_model.CommentList(comments).LoadPosters(ctx); err != nil { + if err = comments.LoadPosters(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadPosters", err) return } apiComments := make([]*api.Comment, len(comments)) - if err := issues_model.CommentList(comments).LoadIssues(ctx); err != nil { + if err := comments.LoadIssues(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadIssues", err) return } - if err := issues_model.CommentList(comments).LoadPosters(ctx); err != nil { + if err := comments.LoadPosters(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadPosters", err) return } - if err := issues_model.CommentList(comments).LoadAttachments(ctx); err != nil { + if err := comments.LoadAttachments(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadAttachments", err) return } - if _, err := issues_model.CommentList(comments).Issues().LoadRepositories(ctx); err != nil { + if _, err := comments.Issues().LoadRepositories(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadRepositories", err) return } From c59a057297c782f44a81a3e630b5094a58099edb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 21 May 2023 23:13:47 +0800 Subject: [PATCH 03/24] Refactor rename user and rename organization (#24052) This PR is a refactor at the beginning. And now it did 4 things. - [x] Move renaming organizaiton and user logics into services layer and merged as one function - [x] Support rename a user capitalization only. For example, rename the user from `Lunny` to `lunny`. We just need to change one table `user` and others should not be touched. - [x] Before this PR, some renaming were missed like `agit` - [x] Fix bug the API reutrned from `http.StatusNoContent` to `http.StatusOK` --- models/repo/repo.go | 8 ++ models/user/error.go | 38 ++++++-- models/user/user.go | 45 ---------- options/locale/locale_en-US.ini | 1 + routers/api/v1/admin/user.go | 14 +-- routers/web/org/setting.go | 39 ++++---- routers/web/user/setting/profile.go | 15 ++-- services/org/org.go | 17 ++-- services/user/avatar.go | 62 +++++++++++++ services/user/rename.go | 41 --------- services/user/user.go | 134 +++++++++++++++++----------- tests/integration/api_admin_test.go | 41 +++++++++ 12 files changed, 267 insertions(+), 188 deletions(-) create mode 100644 services/user/avatar.go delete mode 100644 services/user/rename.go diff --git a/models/repo/repo.go b/models/repo/repo.go index 00e875407c..bcf8e5bbe8 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -832,3 +832,11 @@ func FixNullArchivedRepository(ctx context.Context) (int64, error) { IsArchived: false, }) } + +// UpdateRepositoryOwnerName updates the owner name of all repositories owned by the user +func UpdateRepositoryOwnerName(ctx context.Context, oldUserName, newUserName string) error { + if _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil { + return fmt.Errorf("change repo owner name: %w", err) + } + return nil +} diff --git a/models/user/error.go b/models/user/error.go index 306b9ee9d9..f512994169 100644 --- a/models/user/error.go +++ b/models/user/error.go @@ -9,13 +9,6 @@ import ( "code.gitea.io/gitea/modules/util" ) -// ____ ___ -// | | \______ ___________ -// | | / ___// __ \_ __ \ -// | | /\___ \\ ___/| | \/ -// |______//____ >\___ >__| -// \/ \/ - // ErrUserAlreadyExist represents a "user already exists" error. type ErrUserAlreadyExist struct { Name string @@ -99,3 +92,34 @@ func (err ErrUserInactive) Error() string { func (err ErrUserInactive) Unwrap() error { return util.ErrPermissionDenied } + +// ErrUserIsNotLocal represents a "ErrUserIsNotLocal" kind of error. +type ErrUserIsNotLocal struct { + UID int64 + Name string +} + +func (err ErrUserIsNotLocal) Error() string { + return fmt.Sprintf("user is not local type [uid: %d, name: %s]", err.UID, err.Name) +} + +// IsErrUserIsNotLocal +func IsErrUserIsNotLocal(err error) bool { + _, ok := err.(ErrUserIsNotLocal) + return ok +} + +type ErrUsernameNotChanged struct { + UID int64 + Name string +} + +func (err ErrUsernameNotChanged) Error() string { + return fmt.Sprintf("username hasn't been changed[uid: %d, name: %s]", err.UID, err.Name) +} + +// IsErrUsernameNotChanged +func IsErrUsernameNotChanged(err error) bool { + _, ok := err.(ErrUsernameNotChanged) + return ok +} diff --git a/models/user/user.go b/models/user/user.go index 46c4440e5f..7428e51065 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -9,7 +9,6 @@ import ( "encoding/hex" "fmt" "net/url" - "os" "path/filepath" "strings" "time" @@ -756,50 +755,6 @@ func VerifyUserActiveCode(code string) (user *User) { return nil } -// ChangeUserName changes all corresponding setting from old user name to new one. -func ChangeUserName(ctx context.Context, u *User, newUserName string) (err error) { - oldUserName := u.Name - if err = IsUsableUsername(newUserName); err != nil { - return err - } - - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - isExist, err := IsUserExist(ctx, 0, newUserName) - if err != nil { - return err - } else if isExist { - return ErrUserAlreadyExist{newUserName} - } - - if _, err = db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil { - return fmt.Errorf("Change repo owner name: %w", err) - } - - // Do not fail if directory does not exist - if err = util.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("Rename user directory: %w", err) - } - - if err = NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil { - return err - } - - if err = committer.Commit(); err != nil { - if err2 := util.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) { - log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2) - return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2) - } - return err - } - - return nil -} - // checkDupEmail checks whether there are the same email with the user func checkDupEmail(ctx context.Context, u *User) error { u.Email = strings.ToLower(u.Email) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6f85bc4d2d..e092d3722c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -520,6 +520,7 @@ lang_select_error = Select a language from the list. username_been_taken = The username is already taken. username_change_not_local_user = Non-local users are not allowed to change their username. +username_has_not_been_changed = Username has not been changed repo_name_been_taken = The repository name is already used. repository_force_private = Force Private is enabled: private repositories cannot be made public. repository_files_already_exist = Files already exist for this repository. Contact the system administrator. diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 8afa83aa94..c3af5dc90a 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -502,17 +502,15 @@ func RenameUser(ctx *context.APIContext) { return } + oldName := ctx.ContextUser.Name newName := web.GetForm(ctx).(*api.RenameUserOption).NewName - if strings.EqualFold(newName, ctx.ContextUser.Name) { - // Noop as username is not changed - ctx.Status(http.StatusNoContent) - return - } - // Check if user name has been changed if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil { switch { + case user_model.IsErrUsernameNotChanged(err): + // Noop as username is not changed + ctx.Status(http.StatusNoContent) case user_model.IsErrUserAlreadyExist(err): ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken")) case db.IsErrNameReserved(err): @@ -526,5 +524,7 @@ func RenameUser(ctx *context.APIContext) { } return } - ctx.Status(http.StatusNoContent) + + log.Trace("User name changed: %s -> %s", oldName, newName) + ctx.Status(http.StatusOK) } diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index a8ad1daece..2c4a6b93e3 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -22,8 +22,7 @@ import ( "code.gitea.io/gitea/modules/web" user_setting "code.gitea.io/gitea/routers/web/user/setting" "code.gitea.io/gitea/services/forms" - "code.gitea.io/gitea/services/org" - container_service "code.gitea.io/gitea/services/packages/container" + org_service "code.gitea.io/gitea/services/org" repo_service "code.gitea.io/gitea/services/repository" user_service "code.gitea.io/gitea/services/user" ) @@ -67,31 +66,23 @@ func SettingsPost(ctx *context.Context) { nameChanged := org.Name != form.Name // Check if organization name has been changed. - if org.LowerName != strings.ToLower(form.Name) { - isExist, err := user_model.IsUserExist(ctx, org.ID, form.Name) - if err != nil { - ctx.ServerError("IsUserExist", err) - return - } else if isExist { + if nameChanged { + err := org_service.RenameOrganization(ctx, org, form.Name) + switch { + case user_model.IsErrUserAlreadyExist(err): ctx.Data["OrgName"] = true ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form) return - } else if err = user_model.ChangeUserName(ctx, org.AsUser(), form.Name); err != nil { - switch { - case db.IsErrNameReserved(err): - ctx.Data["OrgName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) - case db.IsErrNamePatternNotAllowed(err): - ctx.Data["OrgName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) - default: - ctx.ServerError("ChangeUserName", err) - } + case db.IsErrNameReserved(err): + ctx.Data["OrgName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) return - } - - if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), form.Name); err != nil { - ctx.ServerError("UpdateRepositoryNames", err) + case db.IsErrNamePatternNotAllowed(err): + ctx.Data["OrgName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) + return + case err != nil: + ctx.ServerError("org_service.RenameOrganization", err) return } @@ -186,7 +177,7 @@ func SettingsDelete(ctx *context.Context) { return } - if err := org.DeleteOrganization(ctx.Org.Organization); err != nil { + if err := org_service.DeleteOrganization(ctx.Org.Organization); err != nil { if models.IsErrUserOwnRepos(err) { ctx.Flash.Error(ctx.Tr("form.org_still_own_repo")) ctx.Redirect(ctx.Org.OrgLink + "/settings/delete") diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 0a8a5e6280..47066d5e38 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -49,15 +49,16 @@ func Profile(ctx *context.Context) { // HandleUsernameChange handle username changes from user settings and admin interface func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName string) error { - // Non-local users are not allowed to change their username. - if !user.IsLocal() { - ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user")) - return fmt.Errorf(ctx.Tr("form.username_change_not_local_user")) - } - + oldName := user.Name // rename user if err := user_service.RenameUser(ctx, user, newName); err != nil { switch { + // Noop as username is not changed + case user_model.IsErrUsernameNotChanged(err): + ctx.Flash.Error(ctx.Tr("form.username_has_not_been_changed")) + // Non-local users are not allowed to change their username. + case user_model.IsErrUserIsNotLocal(err): + ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user")) case user_model.IsErrUserAlreadyExist(err): ctx.Flash.Error(ctx.Tr("form.username_been_taken")) case user_model.IsErrEmailAlreadyUsed(err): @@ -73,7 +74,7 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s } return err } - + log.Trace("User name changed: %s -> %s", oldName, newName) return nil } diff --git a/services/org/org.go b/services/org/org.go index e45fb305de..a62e5b6fc8 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -4,20 +4,22 @@ package org import ( + "context" "fmt" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/organization" + org_model "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" + user_service "code.gitea.io/gitea/services/user" ) // DeleteOrganization completely and permanently deletes everything of organization. -func DeleteOrganization(org *organization.Organization) error { +func DeleteOrganization(org *org_model.Organization) error { ctx, commiter, err := db.TxContext(db.DefaultContext) if err != nil { return err @@ -39,7 +41,7 @@ func DeleteOrganization(org *organization.Organization) error { return models.ErrUserOwnPackages{UID: org.ID} } - if err := organization.DeleteOrganization(ctx, org); err != nil { + if err := org_model.DeleteOrganization(ctx, org); err != nil { return fmt.Errorf("DeleteOrganization: %w", err) } @@ -53,15 +55,20 @@ func DeleteOrganization(org *organization.Organization) error { path := user_model.UserPath(org.Name) if err := util.RemoveAll(path); err != nil { - return fmt.Errorf("Failed to RemoveAll %s: %w", path, err) + return fmt.Errorf("failed to RemoveAll %s: %w", path, err) } if len(org.Avatar) > 0 { avatarPath := org.CustomAvatarRelativePath() if err := storage.Avatars.Delete(avatarPath); err != nil { - return fmt.Errorf("Failed to remove %s: %w", avatarPath, err) + return fmt.Errorf("failed to remove %s: %w", avatarPath, err) } } return nil } + +// RenameOrganization renames an organization. +func RenameOrganization(ctx context.Context, org *org_model.Organization, newName string) error { + return user_service.RenameUser(ctx, org.AsUser(), newName) +} diff --git a/services/user/avatar.go b/services/user/avatar.go new file mode 100644 index 0000000000..26c100abdb --- /dev/null +++ b/services/user/avatar.go @@ -0,0 +1,62 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "fmt" + "io" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/avatar" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/storage" +) + +// UploadAvatar saves custom avatar for user. +func UploadAvatar(u *user_model.User, data []byte) error { + avatarData, err := avatar.ProcessAvatarImage(data) + if err != nil { + return err + } + + ctx, committer, err := db.TxContext(db.DefaultContext) + if err != nil { + return err + } + defer committer.Close() + + u.UseCustomAvatar = true + u.Avatar = avatar.HashAvatar(u.ID, data) + if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil { + return fmt.Errorf("updateUser: %w", err) + } + + if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { + _, err := w.Write(avatarData) + return err + }); err != nil { + return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err) + } + + return committer.Commit() +} + +// DeleteAvatar deletes the user's custom avatar. +func DeleteAvatar(u *user_model.User) error { + aPath := u.CustomAvatarRelativePath() + log.Trace("DeleteAvatar[%d]: %s", u.ID, aPath) + if len(u.Avatar) > 0 { + if err := storage.Avatars.Delete(aPath); err != nil { + return fmt.Errorf("Failed to remove %s: %w", aPath, err) + } + } + + u.UseCustomAvatar = false + u.Avatar = "" + if _, err := db.GetEngine(db.DefaultContext).ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil { + return fmt.Errorf("UpdateUser: %w", err) + } + return nil +} diff --git a/services/user/rename.go b/services/user/rename.go deleted file mode 100644 index af195d7d76..0000000000 --- a/services/user/rename.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package user - -import ( - "context" - "fmt" - "strings" - - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/services/agit" - container_service "code.gitea.io/gitea/services/packages/container" -) - -func renameUser(ctx context.Context, u *user_model.User, newUserName string) error { - if u.IsOrganization() { - return fmt.Errorf("cannot rename organization") - } - - if err := user_model.ChangeUserName(ctx, u, newUserName); err != nil { - return err - } - - if err := agit.UserNameChanged(ctx, u, newUserName); err != nil { - return err - } - if err := container_service.UpdateRepositoryNames(ctx, u, newUserName); err != nil { - return err - } - - u.Name = newUserName - u.LowerName = strings.ToLower(newUserName) - if err := user_model.UpdateUser(ctx, u, false); err != nil { - return err - } - - log.Trace("User name changed: %s -> %s", u.Name, newUserName) - return nil -} diff --git a/services/user/user.go b/services/user/user.go index 5148f2168d..e0815bd860 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -6,7 +6,8 @@ package user import ( "context" "fmt" - "io" + "os" + "strings" "time" "code.gitea.io/gitea/models" @@ -17,29 +18,105 @@ import ( repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/agit" "code.gitea.io/gitea/services/packages" + container_service "code.gitea.io/gitea/services/packages/container" ) // RenameUser renames a user func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error { + // Non-local users are not allowed to change their username. + if !u.IsOrganization() && !u.IsLocal() { + return user_model.ErrUserIsNotLocal{ + UID: u.ID, + Name: u.Name, + } + } + + if newUserName == u.Name { + return user_model.ErrUsernameNotChanged{ + UID: u.ID, + Name: u.Name, + } + } + + if err := user_model.IsUsableUsername(newUserName); err != nil { + return err + } + + onlyCapitalization := strings.EqualFold(newUserName, u.Name) + oldUserName := u.Name + + if onlyCapitalization { + u.Name = newUserName + if err := user_model.UpdateUserCols(ctx, u, "name"); err != nil { + u.Name = oldUserName + return err + } + return nil + } + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - if err := renameUser(ctx, u, newUserName); err != nil { + + isExist, err := user_model.IsUserExist(ctx, u.ID, newUserName) + if err != nil { return err } - if err := committer.Commit(); err != nil { + if isExist { + return user_model.ErrUserAlreadyExist{ + Name: newUserName, + } + } + + if err = repo_model.UpdateRepositoryOwnerName(ctx, oldUserName, newUserName); err != nil { return err } - return err + + if err = user_model.NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil { + return err + } + + if err := agit.UserNameChanged(ctx, u, newUserName); err != nil { + return err + } + if err := container_service.UpdateRepositoryNames(ctx, u, newUserName); err != nil { + return err + } + + u.Name = newUserName + u.LowerName = strings.ToLower(newUserName) + if err := user_model.UpdateUserCols(ctx, u, "name", "lower_name"); err != nil { + u.Name = oldUserName + u.LowerName = strings.ToLower(oldUserName) + return err + } + + // Do not fail if directory does not exist + if err = util.Rename(user_model.UserPath(oldUserName), user_model.UserPath(newUserName)); err != nil && !os.IsNotExist(err) { + u.Name = oldUserName + u.LowerName = strings.ToLower(oldUserName) + return fmt.Errorf("rename user directory: %w", err) + } + + if err = committer.Commit(); err != nil { + u.Name = oldUserName + u.LowerName = strings.ToLower(oldUserName) + if err2 := util.Rename(user_model.UserPath(newUserName), user_model.UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) { + log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2) + return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2) + } + return err + } + return nil } // DeleteUser completely and permanently deletes everything of a user, @@ -240,50 +317,3 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { return user_model.DeleteInactiveEmailAddresses(ctx) } - -// UploadAvatar saves custom avatar for user. -func UploadAvatar(u *user_model.User, data []byte) error { - avatarData, err := avatar.ProcessAvatarImage(data) - if err != nil { - return err - } - - ctx, committer, err := db.TxContext(db.DefaultContext) - if err != nil { - return err - } - defer committer.Close() - - u.UseCustomAvatar = true - u.Avatar = avatar.HashAvatar(u.ID, data) - if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil { - return fmt.Errorf("updateUser: %w", err) - } - - if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { - _, err := w.Write(avatarData) - return err - }); err != nil { - return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err) - } - - return committer.Commit() -} - -// DeleteAvatar deletes the user's custom avatar. -func DeleteAvatar(u *user_model.User) error { - aPath := u.CustomAvatarRelativePath() - log.Trace("DeleteAvatar[%d]: %s", u.ID, aPath) - if len(u.Avatar) > 0 { - if err := storage.Avatars.Delete(aPath); err != nil { - return fmt.Errorf("Failed to remove %s: %w", aPath, err) - } - } - - u.UseCustomAvatar = false - u.Avatar = "" - if _, err := db.GetEngine(db.DefaultContext).ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil { - return fmt.Errorf("UpdateUser: %w", err) - } - return nil -} diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 53e4849aa5..7cfc3276ee 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -241,3 +241,44 @@ func TestAPICreateRepoForUser(t *testing.T) { ) MakeRequest(t, req, http.StatusCreated) } + +func TestAPIRenameUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "user2", token) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + // required + "new_name": "User2", + }) + MakeRequest(t, req, http.StatusOK) + + urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2", token) + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + // required + "new_name": "User2-2-2", + }) + MakeRequest(t, req, http.StatusOK) + + urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2", token) + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + // required + "new_name": "user1", + }) + // the old user name still be used by with a redirect + MakeRequest(t, req, http.StatusTemporaryRedirect) + + urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2-2-2", token) + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + // required + "new_name": "user1", + }) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "User2-2-2", token) + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + // required + "new_name": "user2", + }) + MakeRequest(t, req, http.StatusOK) +} From e95b42e187cde9ac4bd541cd714bdb4f5c1fd8bc Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 21 May 2023 22:47:41 +0200 Subject: [PATCH 04/24] Improve accessibility when (re-)viewing files (#24817) Visually, nothing should have changed. Changes include - Convert most `` to ` + @@ -52,7 +52,7 @@ {{if $.EscapeStatus.Escaped}} {{if $row.EscapeStatus.Escaped}} - + {{end}} {{end}} diff --git a/templates/repo/diff/blob_excerpt.tmpl b/templates/repo/diff/blob_excerpt.tmpl index 7e11b33ba6..ab7abdbd7b 100644 --- a/templates/repo/diff/blob_excerpt.tmpl +++ b/templates/repo/diff/blob_excerpt.tmpl @@ -3,28 +3,30 @@ {{if eq .GetType 4}} +
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} - + {{end}} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} - + {{end}} {{if eq $line.GetExpandDirection 2}} - + {{end}} +
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line $.locale}}{{/* */}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.locale}} {{else}} {{$inlineDiff := $.section.GetComputedInlineDiffFor $line $.locale}} - {{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}{{end}} + {{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}{{end}} {{if $line.LeftIdx}}{{end}} {{/* */}}{{if $line.LeftIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.locale}}{{else}}{{/* @@ -32,7 +34,7 @@ */}}{{end}}{{/* */}} - {{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}{{end}} + {{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}{{end}} {{if $line.RightIdx}}{{end}} {{/* */}}{{if $line.RightIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.locale}}{{else}}{{/* @@ -47,28 +49,30 @@ {{if eq .GetType 4}} - {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} - - {{svg "octicon-fold-down"}} - - {{end}} - {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} - - {{svg "octicon-fold-up"}} - - {{end}} - {{if eq $line.GetExpandDirection 2}} - - {{svg "octicon-fold"}} - - {{end}} +
+ {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} + + {{end}} + {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} + + {{end}} + {{if eq $line.GetExpandDirection 2}} + + {{end}} +
{{else}} {{end}} {{$inlineDiff := $.section.GetComputedInlineDiffFor $line $.locale}} - {{if $inlineDiff.EscapeStatus.Escaped}}{{end}} + {{if $inlineDiff.EscapeStatus.Escaped}}{{end}} {{$inlineDiff.Content}} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 26b863acea..740b39dbd5 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -90,13 +90,13 @@

- +
{{if $file.IsBin}} @@ -125,8 +125,8 @@ {{$.locale.Tr "repo.diff.protected"}} {{end}} {{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}} - {{$.locale.Tr "repo.unescape_control_characters"}} - {{$.locale.Tr "repo.escape_control_characters"}} + + {{end}} {{if and (not $file.IsSubmodule) (not $.PageIsWiki)}} {{if $file.IsDeleted}} diff --git a/templates/repo/diff/section_split.tmpl b/templates/repo/diff/section_split.tmpl index 42aed85084..cb96d64056 100644 --- a/templates/repo/diff/section_split.tmpl +++ b/templates/repo/diff/section_split.tmpl @@ -16,23 +16,25 @@ {{if eq .GetType 4}} +
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} - + {{end}} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} - + {{end}} {{if eq $line.GetExpandDirection 2}} - + {{end}} +
{{$inlineDiff := $section.GetComputedInlineDiffFor $line $.root.locale}} - {{if $inlineDiff.EscapeStatus.Escaped}}{{end}} + {{if $inlineDiff.EscapeStatus.Escaped}}{{end}} {{/* */}}{{template "repo/diff/section_code" dict "diff" $inlineDiff "locale" $.root.locale}}{{/* */}} @@ -41,7 +43,7 @@ {{- $leftDiff := ""}}{{if $line.LeftIdx}}{{$leftDiff = $section.GetComputedInlineDiffFor $line $.root.locale}}{{end}} {{- $rightDiff := ""}}{{if $match.RightIdx}}{{$rightDiff = $section.GetComputedInlineDiffFor $match $.root.locale}}{{end}} - {{if $line.LeftIdx}}{{if $leftDiff.EscapeStatus.Escaped}}{{end}}{{end}} + {{if $line.LeftIdx}}{{if $leftDiff.EscapeStatus.Escaped}}{{end}}{{end}} {{if $match.RightIdx}}{{end}} {{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/* @@ -73,7 +75,7 @@ {{else}} {{$inlineDiff := $section.GetComputedInlineDiffFor $line $.root.locale}} - {{if $line.LeftIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}{{end}}{{end}} + {{if $line.LeftIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}{{end}}{{end}} {{if $line.LeftIdx}}{{end}} {{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2))}}{{/* @@ -88,7 +90,7 @@ */}}{{end}}{{/* */}} - {{if $line.RightIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}{{end}}{{end}} + {{if $line.RightIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}{{end}}{{end}} {{if $line.RightIdx}}{{end}} {{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3))}}{{/* diff --git a/templates/repo/diff/section_unified.tmpl b/templates/repo/diff/section_unified.tmpl index 8bf331f6db..d970853159 100644 --- a/templates/repo/diff/section_unified.tmpl +++ b/templates/repo/diff/section_unified.tmpl @@ -12,21 +12,23 @@ {{if eq .GetType 4}} {{if $.root.AfterCommitID}} - {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} - - {{svg "octicon-fold-down"}} - - {{end}} - {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} - - {{svg "octicon-fold-up"}} - - {{end}} - {{if eq $line.GetExpandDirection 2}} - - {{svg "octicon-fold"}} - - {{end}} +
+ {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5)}} + + {{end}} + {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4)}} + + {{end}} + {{if eq $line.GetExpandDirection 2}} + + {{end}} +
{{else}} {{/* for code file preview page or comment diffs on pull comment pages, do not show the expansion arrows */}} @@ -37,7 +39,11 @@ {{end}} {{$inlineDiff := $section.GetComputedInlineDiffFor $line $.root.locale -}} - {{if $inlineDiff.EscapeStatus.Escaped}}{{end}} + + {{- if $inlineDiff.EscapeStatus.Escaped -}} + + {{- end -}} + {{if eq .GetType 4}} {{/* diff --git a/templates/repo/issue/labels/labels_selector_field.tmpl b/templates/repo/issue/labels/labels_selector_field.tmpl index 00d852bba9..350cf631e8 100644 --- a/templates/repo/issue/labels/labels_selector_field.tmpl +++ b/templates/repo/issue/labels/labels_selector_field.tmpl @@ -1,10 +1,10 @@

@@ -107,7 +107,7 @@ {{if $.EscapeStatus.Escaped}} - {{if (index $.LineEscapeStatus $idx).Escaped}}{{end}} + {{if (index $.LineEscapeStatus $idx).Escaped}}{{end}} {{end}} {{$code | Safe}} diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 92fbf5814c..dc1bbc84d5 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -125,7 +125,7 @@
- {{$.locale.Tr "settings.delete_current_avatar"}} +
diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 73096a6434..ddcc258be9 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -2943,7 +2943,7 @@ tbody.commit-list { padding-top: 0 !important; } -td.blob-excerpt { +.blob-excerpt { background-color: var(--color-secondary-alpha-30); } diff --git a/web_src/css/review.css b/web_src/css/review.css index d57fcc85df..d44e98584e 100644 --- a/web_src/css/review.css +++ b/web_src/css/review.css @@ -21,7 +21,7 @@ transform: scale(1.1); } -.lines-escape a.toggle-escape-button::before { +.lines-escape .toggle-escape-button::before { visibility: visible; content: "⚠️"; font-family: var(--fonts-emoji); @@ -198,7 +198,8 @@ color: var(--color-text); } -a.blob-excerpt { +.code-expander-button { + border: none; color: var(--color-text-light); height: 28px; display: flex; @@ -206,9 +207,10 @@ a.blob-excerpt { align-items: center; width: 100%; background: var(--color-expand-button); + flex: 1; } -a.blob-excerpt:hover { +.code-expander-button:hover { background: var(--color-primary); color: var(--color-primary-contrast); } diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 8245aa57f8..d4d955601e 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -56,9 +56,8 @@ export function initGlobalEnterQuickSubmit() { } export function initGlobalButtonClickOnEnter() { - $(document).on('keypress', '.ui.button', (e) => { - if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar - if (e.target.nodeName === 'BUTTON') return; // button already handles space&enter correctly + $(document).on('keypress', 'div.ui.button,span.ui.button', (e) => { + if (e.code === ' ' || e.code === 'Enter') { $(e.target).trigger('click'); e.preventDefault(); } diff --git a/web_src/js/features/pull-view-file.js b/web_src/js/features/pull-view-file.js index e57663736a..daa520ea7e 100644 --- a/web_src/js/features/pull-view-file.js +++ b/web_src/js/features/pull-view-file.js @@ -38,7 +38,7 @@ export function initViewedCheckboxListenerFor() { // The checkbox consists of a div containing the real checkbox with its label and the CSRF token, // hence the actual checkbox first has to be found const checkbox = form.querySelector('input[type=checkbox]'); - checkbox.addEventListener('change', function() { + checkbox.addEventListener('input', function() { // Mark the file as viewed visually - will especially change the background if (this.checked) { form.classList.add(viewedStyleClass); diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index 9b504068f6..73eccaf193 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -181,7 +181,7 @@ export function initRepoCodeView() { $(document).on('click', '.fold-file', ({currentTarget}) => { invertFileFolding(currentTarget.closest('.file-content'), currentTarget); }); - $(document).on('click', '.blob-excerpt', async ({currentTarget}) => { + $(document).on('click', '.code-expander-button', async ({currentTarget}) => { const url = currentTarget.getAttribute('data-url'); const query = currentTarget.getAttribute('data-query'); const anchor = currentTarget.getAttribute('data-anchor'); diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 5a9f5ba0a2..d0622254bf 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -62,9 +62,9 @@ function initRepoDiffConversationForm() { $form.closest('.conversation-holder').replaceWith($newConversationHolder); if ($form.closest('tr').data('line-type') === 'same') { - $(`[data-path="${path}"] a.add-code-comment[data-idx="${idx}"]`).addClass('invisible'); + $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('invisible'); } else { - $(`[data-path="${path}"] a.add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('invisible'); + $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('invisible'); } $newConversationHolder.find('.dropdown').dropdown(); initCompReactionSelector($newConversationHolder); diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 3723e0f627..cf6e09472e 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -177,9 +177,9 @@ export function initRepoIssueCommentDelete() { const idx = $conversationHolder.data('idx'); const lineType = $conversationHolder.closest('tr').data('line-type'); if (lineType === 'same') { - $(`[data-path="${path}"] a.add-code-comment[data-idx="${idx}"]`).removeClass('invisible'); + $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('invisible'); } else { - $(`[data-path="${path}"] a.add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('invisible'); + $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('invisible'); } $conversationHolder.remove(); } @@ -488,7 +488,7 @@ export function initRepoPullRequestReview() { }); } - $(document).on('click', 'a.add-code-comment', async function (e) { + $(document).on('click', '.add-code-comment', async function (e) { if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745 e.preventDefault(); diff --git a/web_src/js/features/repo-unicode-escape.js b/web_src/js/features/repo-unicode-escape.js index 67f2df1d9a..6a201ec4d1 100644 --- a/web_src/js/features/repo-unicode-escape.js +++ b/web_src/js/features/repo-unicode-escape.js @@ -2,30 +2,30 @@ import $ from 'jquery'; import {hideElem, showElem} from '../utils/dom.js'; export function initUnicodeEscapeButton() { - $(document).on('click', 'a.escape-button', (e) => { + $(document).on('click', '.escape-button', (e) => { e.preventDefault(); $(e.target).parents('.file-content, .non-diff-file-content').find('.file-code, .file-view').addClass('unicode-escaped'); hideElem($(e.target)); - showElem($(e.target).siblings('a.unescape-button')); + showElem($(e.target).siblings('.unescape-button')); }); - $(document).on('click', 'a.unescape-button', (e) => { + $(document).on('click', '.unescape-button', (e) => { e.preventDefault(); $(e.target).parents('.file-content, .non-diff-file-content').find('.file-code, .file-view').removeClass('unicode-escaped'); hideElem($(e.target)); - showElem($(e.target).siblings('a.escape-button')); + showElem($(e.target).siblings('.escape-button')); }); - $(document).on('click', 'a.toggle-escape-button', (e) => { + $(document).on('click', '.toggle-escape-button', (e) => { e.preventDefault(); const fileContent = $(e.target).parents('.file-content, .non-diff-file-content'); const fileView = fileContent.find('.file-code, .file-view'); if (fileView.hasClass('unicode-escaped')) { fileView.removeClass('unicode-escaped'); - hideElem(fileContent.find('a.unescape-button')); - showElem(fileContent.find('a.escape-button')); + hideElem(fileContent.find('.unescape-button')); + showElem(fileContent.find('.escape-button')); } else { fileView.addClass('unicode-escaped'); - showElem(fileContent.find('a.unescape-button')); - hideElem(fileContent.find('a.escape-button')); + showElem(fileContent.find('.unescape-button')); + hideElem(fileContent.find('.escape-button')); } }); } From 268d121f4bf9bd7c0b601937c9232a3e2b233cb6 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sun, 21 May 2023 23:19:37 +0200 Subject: [PATCH 05/24] Fix video width overflow in markdown, and other changes to match img (#24834) This change makes the CSS for `