mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-10 05:24:18 +09:00
Adds per-event watch options, so a user can choose which repository notifications they receive. The watch button is now a menu with four modes: participating and mentions (the unwatched state, as on GitHub), all activity, ignore, and custom, which opens a dialog to pick issues, pull requests and releases. The same dialog is reachable from the gear icon on the watched repository list. Options apply to UI notifications and to mail. The watcher, star and fork counts moved inside their buttons, so all three share one shape, and the links to those lists moved to the repository sidebar. Assignees and requested reviewers now also receive UI notifications without having commented first. Buttons and dropdown: <img width="688" height="398" alt="image" src="https://github.com/user-attachments/assets/889b567d-086f-4a06-ac67-3a4205fc63b2" /> Modal: <img width="841" height="327" alt="Screenshot 2026-08-07 at 21 10 59" src="https://github.com/user-attachments/assets/3bedbff5-9011-4f1b-91a9-e8f5c212fb5d" /> New entries in sidebar: <img width="170" height="207" alt="Screenshot 2026-08-07 at 21 13 28" src="https://github.com/user-attachments/assets/5f5150cd-f42d-4543-bd0b-7334cf4aab0e" /> Fixes: https://github.com/go-gitea/gitea/issues/17238 Ref: https://github.com/go-gitea/gitea/issues/35492 --------- Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
139 lines
4.4 KiB
Go
139 lines
4.4 KiB
Go
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package issues
|
|
|
|
import (
|
|
"context"
|
|
|
|
"gitea.dev/models/db"
|
|
repo_model "gitea.dev/models/repo"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/timeutil"
|
|
"gitea.dev/modules/util"
|
|
)
|
|
|
|
// IssueWatch is connection request for receiving issue notification.
|
|
type IssueWatch struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
UserID int64 `xorm:"UNIQUE(watch) NOT NULL"`
|
|
IssueID int64 `xorm:"UNIQUE(watch) NOT NULL"`
|
|
IsWatching bool `xorm:"NOT NULL"`
|
|
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
|
|
}
|
|
|
|
func init() {
|
|
db.RegisterModel(new(IssueWatch))
|
|
}
|
|
|
|
// IssueWatchList contains IssueWatch
|
|
type IssueWatchList []*IssueWatch
|
|
|
|
// CreateOrUpdateIssueWatch set watching for a user and issue
|
|
func CreateOrUpdateIssueWatch(ctx context.Context, userID, issueID int64, isWatching bool) error {
|
|
iw, exists, err := GetIssueWatch(ctx, userID, issueID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !exists {
|
|
iw = &IssueWatch{
|
|
UserID: userID,
|
|
IssueID: issueID,
|
|
IsWatching: isWatching,
|
|
}
|
|
|
|
if _, err := db.GetEngine(ctx).Insert(iw); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
iw.IsWatching = isWatching
|
|
|
|
if _, err := db.GetEngine(ctx).ID(iw.ID).Cols("is_watching", "updated_unix").Update(iw); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetIssueWatch returns all IssueWatch objects from db by user and issue
|
|
// the current Web-UI need iw object for watchers AND explicit non-watchers
|
|
func GetIssueWatch(ctx context.Context, userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
|
|
iw = new(IssueWatch)
|
|
exists, err = db.GetEngine(ctx).
|
|
Where("user_id = ?", userID).
|
|
And("issue_id = ?", issueID).
|
|
Get(iw)
|
|
return iw, exists, err
|
|
}
|
|
|
|
// CheckIssueWatch check if a user is watching an issue
|
|
// it takes participants and repo watch into account
|
|
func CheckIssueWatch(ctx context.Context, user *user_model.User, issue *Issue) (bool, error) {
|
|
iw, exist, err := GetIssueWatch(ctx, user.ID, issue.ID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if exist {
|
|
return iw.IsWatching, nil
|
|
}
|
|
w, err := repo_model.GetWatch(ctx, user.ID, issue.RepoID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if repo_model.IsWatchMode(w.Mode) && util.Iif(issue.IsPull, w.PullRequests, w.Issues) {
|
|
return true, nil
|
|
}
|
|
return IsUserParticipantsOfIssue(ctx, user, issue), nil
|
|
}
|
|
|
|
// GetIssueWatchersIDs returns IDs of subscribers or explicit unsubscribers to a given issue id
|
|
// but avoids joining with `user` for performance reasons
|
|
// User permissions must be verified elsewhere if required
|
|
func GetIssueWatchersIDs(ctx context.Context, issueID int64, watching bool) ([]int64, error) {
|
|
ids := make([]int64, 0, 64)
|
|
return ids, db.GetEngine(ctx).Table("issue_watch").
|
|
Where("issue_id=?", issueID).
|
|
And("is_watching = ?", watching).
|
|
Select("user_id").
|
|
Find(&ids)
|
|
}
|
|
|
|
// GetIssueWatchers returns watchers/unwatchers of a given issue
|
|
func GetIssueWatchers(ctx context.Context, issueID int64, listOptions db.ListOptions) (IssueWatchList, error) {
|
|
sess := db.GetEngine(ctx).
|
|
Where("`issue_watch`.issue_id = ?", issueID).
|
|
And("`issue_watch`.is_watching = ?", true).
|
|
And("`user`.is_active = ?", true).
|
|
And("`user`.prohibit_login = ?", false).
|
|
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id")
|
|
|
|
if listOptions.Page > 0 {
|
|
db.SetSessionPagination(sess, &listOptions)
|
|
watches := make([]*IssueWatch, 0, listOptions.PageSize)
|
|
return watches, sess.Find(&watches)
|
|
}
|
|
watches := make([]*IssueWatch, 0, 8)
|
|
return watches, sess.Find(&watches)
|
|
}
|
|
|
|
// CountIssueWatchers count watchers/unwatchers of a given issue
|
|
func CountIssueWatchers(ctx context.Context, issueID int64) (int64, error) {
|
|
return db.GetEngine(ctx).
|
|
Where("`issue_watch`.issue_id = ?", issueID).
|
|
And("`issue_watch`.is_watching = ?", true).
|
|
And("`user`.is_active = ?", true).
|
|
And("`user`.prohibit_login = ?", false).
|
|
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id").Count(new(IssueWatch))
|
|
}
|
|
|
|
// RemoveIssueWatchersByRepoID remove issue watchers by repoID
|
|
func RemoveIssueWatchersByRepoID(ctx context.Context, userID, repoID int64) error {
|
|
_, err := db.GetEngine(ctx).
|
|
Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", repoID).
|
|
Where("`issue_watch`.user_id = ?", userID).
|
|
Delete(new(IssueWatch))
|
|
return err
|
|
}
|