diff --git a/models/issue_comment.go b/models/issue_comment.go index bf36881a0..34541dc3c 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -99,7 +99,7 @@ const ( // 28 merge pull request CommentTypeMergePull // 29 push to PR head branch - CommentTypePullPush + CommentTypePullRequestPush // 30 Project changed CommentTypeProject // 31 Project board changed @@ -725,7 +725,7 @@ func (c *Comment) CodeCommentURL() string { // LoadPushCommits Load push commits func (c *Comment) LoadPushCommits(ctx context.Context) (err error) { - if c.Content == "" || c.Commits != nil || c.Type != CommentTypePullPush { + if c.Content == "" || c.Commits != nil || c.Type != CommentTypePullRequestPush { return nil } @@ -1325,7 +1325,7 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *Pul } ops := &CreateCommentOptions{ - Type: CommentTypePullPush, + Type: CommentTypePullRequestPush, Doer: pusher, Repo: pr.BaseRepo, } diff --git a/models/user/setting.go b/models/user/setting.go index 5ff18f826..fbb6fbab3 100644 --- a/models/user/setting.go +++ b/models/user/setting.go @@ -31,8 +31,8 @@ func init() { db.RegisterModel(new(Setting)) } -// GetSettings returns specific settings from user -func GetSettings(uid int64, keys []string) (map[string]*Setting, error) { +// GetUserSettings returns specific settings from user +func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) { settings := make([]*Setting, 0, len(keys)) if err := db.GetEngine(db.DefaultContext). Where("user_id=?", uid). @@ -62,21 +62,53 @@ func GetUserAllSettings(uid int64) (map[string]*Setting, error) { return settingsMap, nil } -// DeleteSetting deletes a specific setting for a user -func DeleteSetting(setting *Setting) error { - _, err := db.GetEngine(db.DefaultContext).Delete(setting) +func validateUserSettingKey(key string) error { + if len(key) == 0 { + return fmt.Errorf("setting key must be set") + } + if strings.ToLower(key) != key { + return fmt.Errorf("setting key should be lowercase") + } + return nil +} + +// GetUserSetting gets a specific setting for a user +func GetUserSetting(userID int64, key string, def ...string) (string, error) { + if err := validateUserSettingKey(key); err != nil { + return "", err + } + setting := &Setting{UserID: userID, SettingKey: key} + has, err := db.GetEngine(db.DefaultContext).Get(setting) + if err != nil { + return "", err + } + if !has { + if len(def) == 1 { + return def[0], nil + } + return "", nil + } + return setting.SettingValue, nil +} + +// DeleteUserSetting deletes a specific setting for a user +func DeleteUserSetting(userID int64, key string) error { + if err := validateUserSettingKey(key); err != nil { + return err + } + _, err := db.GetEngine(db.DefaultContext).Delete(&Setting{UserID: userID, SettingKey: key}) return err } -// SetSetting updates a users' setting for a specific key -func SetSetting(setting *Setting) error { - if strings.ToLower(setting.SettingKey) != setting.SettingKey { - return fmt.Errorf("setting key should be lowercase") +// SetUserSetting updates a users' setting for a specific key +func SetUserSetting(userID int64, key, value string) error { + if err := validateUserSettingKey(key); err != nil { + return err } - return upsertSettingValue(setting.UserID, setting.SettingKey, setting.SettingValue) + return upsertUserSettingValue(userID, key, value) } -func upsertSettingValue(userID int64, key, value string) error { +func upsertUserSettingValue(userID int64, key, value string) error { return db.WithTx(func(ctx context.Context) error { e := db.GetEngine(ctx) diff --git a/models/user/setting_keys.go b/models/user/setting_keys.go new file mode 100644 index 000000000..458b78e35 --- /dev/null +++ b/models/user/setting_keys.go @@ -0,0 +1,10 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +const ( + // SettingsKeyHiddenCommentTypes is the settings key for hidden comment types + SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types" +) diff --git a/models/user/setting_test.go b/models/user/setting_test.go index 81445a9f6..0b42e0fe2 100644 --- a/models/user/setting_test.go +++ b/models/user/setting_test.go @@ -19,21 +19,29 @@ func TestSettings(t *testing.T) { newSetting := &Setting{UserID: 99, SettingKey: keyName, SettingValue: "Gitea User Setting Test"} // create setting - err := SetSetting(newSetting) + err := SetUserSetting(newSetting.UserID, newSetting.SettingKey, newSetting.SettingValue) assert.NoError(t, err) // test about saving unchanged values - err = SetSetting(newSetting) + err = SetUserSetting(newSetting.UserID, newSetting.SettingKey, newSetting.SettingValue) assert.NoError(t, err) // get specific setting - settings, err := GetSettings(99, []string{keyName}) + settings, err := GetUserSettings(99, []string{keyName}) assert.NoError(t, err) assert.Len(t, settings, 1) assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue) + settingValue, err := GetUserSetting(99, keyName) + assert.NoError(t, err) + assert.EqualValues(t, newSetting.SettingValue, settingValue) + + settingValue, err = GetUserSetting(99, "no_such") + assert.NoError(t, err) + assert.EqualValues(t, "", settingValue) + // updated setting updatedSetting := &Setting{UserID: 99, SettingKey: keyName, SettingValue: "Updated"} - err = SetSetting(updatedSetting) + err = SetUserSetting(updatedSetting.UserID, updatedSetting.SettingKey, updatedSetting.SettingValue) assert.NoError(t, err) // get all settings @@ -43,7 +51,7 @@ func TestSettings(t *testing.T) { assert.EqualValues(t, updatedSetting.SettingValue, settings[updatedSetting.SettingKey].SettingValue) // delete setting - err = DeleteSetting(&Setting{UserID: 99, SettingKey: keyName}) + err = DeleteUserSetting(99, keyName) assert.NoError(t, err) settings, err = GetUserAllSettings(99) assert.NoError(t, err) diff --git a/modules/context/form.go b/modules/context/form.go index 8d1859097..4f48d746b 100644 --- a/modules/context/form.go +++ b/modules/context/form.go @@ -46,9 +46,11 @@ func (ctx *Context) FormInt64(key string) int64 { return v } -// FormBool returns true if the value for the provided key in the form is "1" or "true" +// FormBool returns true if the value for the provided key in the form is "1", "true" or "on" func (ctx *Context) FormBool(key string) bool { - v, _ := strconv.ParseBool(ctx.Req.FormValue(key)) + s := ctx.Req.FormValue(key) + v, _ := strconv.ParseBool(s) + v = v || strings.EqualFold(s, "on") return v } @@ -59,6 +61,8 @@ func (ctx *Context) FormOptionalBool(key string) util.OptionalBool { if len(value) == 0 { return util.OptionalBoolNone } - v, _ := strconv.ParseBool(ctx.Req.FormValue(key)) + s := ctx.Req.FormValue(key) + v, _ := strconv.ParseBool(s) + v = v || strings.EqualFold(s, "on") return util.OptionalBoolOf(v) } diff --git a/modules/notification/mail/mail.go b/modules/notification/mail/mail.go index 94ee16ff8..21709d945 100644 --- a/modules/notification/mail/mail.go +++ b/modules/notification/mail/mail.go @@ -42,7 +42,7 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *rep act = models.ActionCommentIssue } else if comment.Type == models.CommentTypeCode { act = models.ActionCommentIssue - } else if comment.Type == models.CommentTypePullPush { + } else if comment.Type == models.CommentTypePullRequestPush { act = 0 } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d8398f6d9..301bd4f66 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -549,6 +549,22 @@ continue = Continue cancel = Cancel language = Language ui = Theme +hidden_comment_types = Hidden comment types +comment_type_group_reference = Reference +comment_type_group_label = Label +comment_type_group_milestone = Milestone +comment_type_group_assignee = Assignee +comment_type_group_title = Title +comment_type_group_branch = Branch +comment_type_group_time_tracking = Time Tracking +comment_type_group_deadline = Deadline +comment_type_group_dependency = Dependency +comment_type_group_lock = Lock Status +comment_type_group_review_request = Review request +comment_type_group_pull_request_push = Added commits +comment_type_group_project = Project +comment_type_group_issue_ref = Issue reference +saved_successfully = Your settings were saved successfully. privacy = Privacy keep_activity_private = Hide the activity from the profile page keep_activity_private_popup = Makes the activity visible only for you and the admins diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index aff5fa849..4f2716763 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "math/big" "net/http" "net/url" "path" @@ -1465,7 +1466,7 @@ func ViewIssue(ctx *context.Context) { ctx.ServerError("LoadResolveDoer", err) return } - } else if comment.Type == models.CommentTypePullPush { + } else if comment.Type == models.CommentTypePullRequestPush { participants = addParticipant(comment.Poster, participants) if err = comment.LoadPushCommits(ctx); err != nil { ctx.ServerError("LoadPushCommits", err) @@ -1650,6 +1651,20 @@ func ViewIssue(ctx *context.Context) { ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin) ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons ctx.Data["RefEndName"] = git.RefEndName(issue.Ref) + + var hiddenCommentTypes *big.Int + if ctx.IsSigned { + val, err := user_model.GetUserSetting(ctx.User.ID, user_model.SettingsKeyHiddenCommentTypes) + if err != nil { + ctx.ServerError("GetUserSetting", err) + return + } + hiddenCommentTypes, _ = new(big.Int).SetString(val, 10) // we can safely ignore the failed conversion here + } + ctx.Data["ShouldShowCommentType"] = func(commentType models.CommentType) bool { + return hiddenCommentTypes == nil || hiddenCommentTypes.Bit(int(commentType)) == 0 + } + ctx.HTML(http.StatusOK, tplIssueView) } diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 3a61f2f92..e77e02348 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "math/big" "net/http" "os" "path/filepath" @@ -358,6 +359,18 @@ func Appearance(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAppearance"] = true + var hiddenCommentTypes *big.Int + val, err := user_model.GetUserSetting(ctx.User.ID, user_model.SettingsKeyHiddenCommentTypes) + if err != nil { + ctx.ServerError("GetUserSetting", err) + return + } + hiddenCommentTypes, _ = new(big.Int).SetString(val, 10) // we can safely ignore the failed conversion here + + ctx.Data["IsCommentTypeGroupChecked"] = func(commentTypeGroup string) bool { + return forms.IsUserHiddenCommentTypeGroupChecked(commentTypeGroup, hiddenCommentTypes) + } + ctx.HTML(http.StatusOK, tplSettingsAppearance) } @@ -416,3 +429,16 @@ func UpdateUserLang(ctx *context.Context) { ctx.Flash.Success(i18n.Tr(ctx.User.Language, "settings.update_language_success")) ctx.Redirect(setting.AppSubURL + "/user/settings/appearance") } + +// UpdateUserHiddenComments update a user's shown comment types +func UpdateUserHiddenComments(ctx *context.Context) { + err := user_model.SetUserSetting(ctx.User.ID, user_model.SettingsKeyHiddenCommentTypes, forms.UserHiddenCommentTypesFromRequest(ctx).String()) + if err != nil { + ctx.ServerError("SetUserSetting", err) + return + } + + log.Trace("User settings updated: %s", ctx.User.Name) + ctx.Flash.Success(ctx.Tr("settings.saved_successfully")) + ctx.Redirect(setting.AppSubURL + "/user/settings/appearance") +} diff --git a/routers/web/web.go b/routers/web/web.go index 698f91b8c..6415788e4 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -323,6 +323,7 @@ func RegisterRoutes(m *web.Route) { m.Group("/appearance", func() { m.Get("", user_setting.Appearance) m.Post("/language", bindIgnErr(forms.UpdateLanguageForm{}), user_setting.UpdateUserLang) + m.Post("/hidden_comments", user_setting.UpdateUserHiddenComments) m.Post("/theme", bindIgnErr(forms.UpdateThemeForm{}), user_setting.UpdateUIThemePost) }) m.Group("/security", func() { diff --git a/services/forms/user_form_hidden_comments.go b/services/forms/user_form_hidden_comments.go new file mode 100644 index 000000000..e0c26e8dd --- /dev/null +++ b/services/forms/user_form_hidden_comments.go @@ -0,0 +1,105 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package forms + +import ( + "math/big" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" +) + +type hiddenCommentTypeGroupsType map[string][]models.CommentType + +// hiddenCommentTypeGroups maps the group names to comment types, these group names comes from the Web UI (appearance.tmpl) +var hiddenCommentTypeGroups = hiddenCommentTypeGroupsType{ + "reference": { + /*3*/ models.CommentTypeIssueRef, + /*4*/ models.CommentTypeCommitRef, + /*5*/ models.CommentTypeCommentRef, + /*6*/ models.CommentTypePullRef, + }, + "label": { + /*7*/ models.CommentTypeLabel, + }, + "milestone": { + /*8*/ models.CommentTypeMilestone, + }, + "assignee": { + /*9*/ models.CommentTypeAssignees, + }, + "title": { + /*10*/ models.CommentTypeChangeTitle, + }, + "branch": { + /*11*/ models.CommentTypeDeleteBranch, + /*25*/ models.CommentTypeChangeTargetBranch, + }, + "time_tracking": { + /*12*/ models.CommentTypeStartTracking, + /*13*/ models.CommentTypeStopTracking, + /*14*/ models.CommentTypeAddTimeManual, + /*15*/ models.CommentTypeCancelTracking, + /*26*/ models.CommentTypeDeleteTimeManual, + }, + "deadline": { + /*16*/ models.CommentTypeAddedDeadline, + /*17*/ models.CommentTypeModifiedDeadline, + /*18*/ models.CommentTypeRemovedDeadline, + }, + "dependency": { + /*19*/ models.CommentTypeAddDependency, + /*20*/ models.CommentTypeRemoveDependency, + }, + "lock": { + /*23*/ models.CommentTypeLock, + /*24*/ models.CommentTypeUnlock, + }, + "review_request": { + /*27*/ models.CommentTypeReviewRequest, + }, + "pull_request_push": { + /*29*/ models.CommentTypePullRequestPush, + }, + "project": { + /*30*/ models.CommentTypeProject, + /*31*/ models.CommentTypeProjectBoard, + }, + "issue_ref": { + /*33*/ models.CommentTypeChangeIssueRef, + }, +} + +// UserHiddenCommentTypesFromRequest parse the form to hidden comment types bitset +func UserHiddenCommentTypesFromRequest(ctx *context.Context) *big.Int { + bitset := new(big.Int) + for group, commentTypes := range hiddenCommentTypeGroups { + if ctx.FormBool(group) { + for _, commentType := range commentTypes { + bitset = bitset.SetBit(bitset, int(commentType), 1) + } + } + } + return bitset +} + +// IsUserHiddenCommentTypeGroupChecked check whether a hidden comment type group is "enabled" (checked on UI) +func IsUserHiddenCommentTypeGroupChecked(group string, hiddenCommentTypes *big.Int) (ret bool) { + commentTypes, ok := hiddenCommentTypeGroups[group] + if !ok { + log.Critical("the group map for hidden comment types is out of sync, unknown group: %v", group) + return + } + if hiddenCommentTypes == nil { + return false + } + for _, commentType := range commentTypes { + if hiddenCommentTypes.Bit(int(commentType)) == 1 { + return true + } + } + return false +} diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 5244cd043..e5aa25008 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -449,7 +449,7 @@ func actionToTemplate(issue *models.Issue, actionType models.ActionType, name = "code" case models.CommentTypeAssignees: name = "assigned" - case models.CommentTypePullPush: + case models.CommentTypePullRequestPush: name = "push" default: name = "default" diff --git a/services/mailer/mail_comment.go b/services/mailer/mail_comment.go index a42458b2c..baecd2a10 100644 --- a/services/mailer/mail_comment.go +++ b/services/mailer/mail_comment.go @@ -21,7 +21,7 @@ func MailParticipantsComment(ctx context.Context, c *models.Comment, opType mode } content := c.Content - if c.Type == models.CommentTypePullPush { + if c.Type == models.CommentTypePullRequestPush { content = "" } if err := mailIssueCommentToParticipants( diff --git a/services/pull/pull.go b/services/pull/pull.go index 4f691c0eb..10fbc124a 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -108,7 +108,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *mode } ops := &models.CreateCommentOptions{ - Type: models.CommentTypePullPush, + Type: models.CommentTypePullRequestPush, Doer: pull.Poster, Repo: repo, Issue: pr.Issue, diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 73057248c..e7c161a3f 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -1,835 +1,837 @@ {{ template "base/alert" }} {{range .Issue.Comments}} - {{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} + {{if call $.ShouldShowCommentType .Type}} + {{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} - - {{if eq .Type 0}} -
{{end}} - - {{.Poster.GetDisplayName}} - {{$.i18n.Tr $refTr (.EventTag|Escape) $createdStr (.RefCommentHTMLURL|Escape) $refFrom | Safe}} - - {{if eq .RefAction 3}}{{end}} - -{{end}} + + {{.Poster.GetDisplayName}} + {{$.i18n.Tr $refTr (.EventTag|Escape) $createdStr (.RefCommentHTMLURL|Escape) $refFrom | Safe}} + + {{if eq .RefAction 3}}{{end}} + ++ {{.i18n.Tr "settings.hidden_comment_types"}} +
+