186 changes: 123 additions & 63 deletions routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,34 +654,66 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {

// repoReviewerSelection items to bee shown
type repoReviewerSelection struct {
IsTeam bool
Team *organization.Team
User *user_model.User
Review *issues_model.Review
CanChange bool
Checked bool
ItemID int64
IsTeam bool
Team *organization.Team
User *user_model.User
Review *issues_model.Review
CanBeDismissed bool
CanChange bool
Requested bool
ItemID int64
}

// RetrieveRepoReviewers find all reviewers of a repository
type issueSidebarReviewersData struct {
Repository *repo_model.Repository
RepoOwnerName string
RepoLink string
IssueID int64
CanChooseReviewer bool
OriginalReviews issues_model.ReviewList
TeamReviewers []*repoReviewerSelection
Reviewers []*repoReviewerSelection
CurrentPullReviewers []*repoReviewerSelection
}

// RetrieveRepoReviewers find all reviewers of a repository. If issue is nil, it means the doer is creating a new PR.
func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, issue *issues_model.Issue, canChooseReviewer bool) {
ctx.Data["CanChooseReviewer"] = canChooseReviewer
data := &issueSidebarReviewersData{}
data.RepoLink = ctx.Repo.RepoLink
data.Repository = repo
data.RepoOwnerName = repo.OwnerName
data.CanChooseReviewer = canChooseReviewer

originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, issue.ID)
if err != nil {
ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err)
return
}
ctx.Data["OriginalReviews"] = originalAuthorReviews
var posterID int64
var isClosed bool
var reviews issues_model.ReviewList

reviews, err := issues_model.GetReviewsByIssueID(ctx, issue.ID)
if err != nil {
ctx.ServerError("GetReviewersByIssueID", err)
return
}
if issue == nil {
posterID = ctx.Doer.ID
} else {
posterID = issue.PosterID
if issue.OriginalAuthorID > 0 {
posterID = 0 // for migrated PRs, no poster ID
}

if len(reviews) == 0 && !canChooseReviewer {
return
data.IssueID = issue.ID
isClosed = issue.IsClosed || issue.PullRequest.HasMerged

originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, issue.ID)
if err != nil {
ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err)
return
}
data.OriginalReviews = originalAuthorReviews

reviews, err = issues_model.GetReviewsByIssueID(ctx, issue.ID)
if err != nil {
ctx.ServerError("GetReviewersByIssueID", err)
return
}
if len(reviews) == 0 && !canChooseReviewer {
return
}
}

var (
Expand All @@ -693,11 +725,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
)

if canChooseReviewer {
posterID := issue.PosterID
if issue.OriginalAuthorID > 0 {
posterID = 0
}

var err error
reviewers, err = repo_model.GetReviewers(ctx, repo, ctx.Doer.ID, posterID)
if err != nil {
ctx.ServerError("GetReviewers", err)
Expand All @@ -723,9 +751,9 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is

for _, review := range reviews {
tmp := &repoReviewerSelection{
Checked: review.Type == issues_model.ReviewTypeRequest,
Review: review,
ItemID: review.ReviewerID,
Requested: review.Type == issues_model.ReviewTypeRequest,
Review: review,
ItemID: review.ReviewerID,
}
if review.ReviewerTeamID > 0 {
tmp.IsTeam = true
Expand Down Expand Up @@ -756,7 +784,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
currentPullReviewers := make([]*repoReviewerSelection, 0, len(pullReviews))
for _, item := range pullReviews {
if item.Review.ReviewerID > 0 {
if err = item.Review.LoadReviewer(ctx); err != nil {
if err := item.Review.LoadReviewer(ctx); err != nil {
if user_model.IsErrUserNotExist(err) {
continue
}
Expand All @@ -765,7 +793,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
}
item.User = item.Review.Reviewer
} else if item.Review.ReviewerTeamID > 0 {
if err = item.Review.LoadReviewerTeam(ctx); err != nil {
if err := item.Review.LoadReviewerTeam(ctx); err != nil {
if organization.IsErrTeamNotExist(err) {
continue
}
Expand All @@ -776,10 +804,11 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
} else {
continue
}

item.CanBeDismissed = ctx.Repo.Permission.IsAdmin() && !isClosed &&
(item.Review.Type == issues_model.ReviewTypeApprove || item.Review.Type == issues_model.ReviewTypeReject)
currentPullReviewers = append(currentPullReviewers, item)
}
ctx.Data["PullReviewers"] = currentPullReviewers
data.CurrentPullReviewers = currentPullReviewers
}

if canChooseReviewer && reviewersResult != nil {
Expand Down Expand Up @@ -807,7 +836,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
})
}

ctx.Data["Reviewers"] = reviewersResult
data.Reviewers = reviewersResult
}

if canChooseReviewer && teamReviewersResult != nil {
Expand Down Expand Up @@ -835,8 +864,10 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
})
}

ctx.Data["TeamReviewers"] = teamReviewersResult
data.TeamReviewers = teamReviewersResult
}

ctx.Data["IssueSidebarReviewersData"] = data
}

// RetrieveRepoMetas find all the meta information of a repository
Expand Down Expand Up @@ -1117,15 +1148,22 @@ func DeleteIssue(ctx *context.Context) {
}

// ValidateRepoMetas check and returns repository's meta information
func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull bool) ([]int64, []int64, int64, int64) {
func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull bool) (ret struct {
LabelIDs, AssigneeIDs []int64
MilestoneID, ProjectID int64

Reviewers []*user_model.User
TeamReviewers []*organization.Team
},
) {
var (
repo = ctx.Repo.Repository
err error
)

labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository, isPull)
if ctx.Written() {
return nil, nil, 0, 0
return ret
}

var labelIDs []int64
Expand All @@ -1134,7 +1172,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
if len(form.LabelIDs) > 0 {
labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
if err != nil {
return nil, nil, 0, 0
return ret
}
labelIDMark := make(container.Set[int64])
labelIDMark.AddMultiple(labelIDs...)
Expand All @@ -1157,11 +1195,11 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
if err != nil {
ctx.ServerError("GetMilestoneByID", err)
return nil, nil, 0, 0
return ret
}
if milestone.RepoID != repo.ID {
ctx.ServerError("GetMilestoneByID", err)
return nil, nil, 0, 0
return ret
}
ctx.Data["Milestone"] = milestone
ctx.Data["milestone_id"] = milestoneID
Expand All @@ -1171,11 +1209,11 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
p, err := project_model.GetProjectByID(ctx, form.ProjectID)
if err != nil {
ctx.ServerError("GetProjectByID", err)
return nil, nil, 0, 0
return ret
}
if p.RepoID != ctx.Repo.Repository.ID && p.OwnerID != ctx.Repo.Repository.OwnerID {
ctx.NotFound("", nil)
return nil, nil, 0, 0
return ret
}

ctx.Data["Project"] = p
Expand All @@ -1187,26 +1225,26 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
if len(form.AssigneeIDs) > 0 {
assigneeIDs, err = base.StringsToInt64s(strings.Split(form.AssigneeIDs, ","))
if err != nil {
return nil, nil, 0, 0
return ret
}

// Check if the passed assignees actually exists and is assignable
for _, aID := range assigneeIDs {
assignee, err := user_model.GetUserByID(ctx, aID)
if err != nil {
ctx.ServerError("GetUserByID", err)
return nil, nil, 0, 0
return ret
}

valid, err := access_model.CanBeAssigned(ctx, assignee, repo, isPull)
if err != nil {
ctx.ServerError("CanBeAssigned", err)
return nil, nil, 0, 0
return ret
}

if !valid {
ctx.ServerError("canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
return nil, nil, 0, 0
return ret
}
}
}
Expand All @@ -1216,7 +1254,39 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
assigneeIDs = append(assigneeIDs, form.AssigneeID)
}

return labelIDs, assigneeIDs, milestoneID, form.ProjectID
// Check reviewers
var reviewers []*user_model.User
var teamReviewers []*organization.Team
if isPull && len(form.ReviewerIDs) > 0 {
reviewerIDs, err := base.StringsToInt64s(strings.Split(form.ReviewerIDs, ","))
if err != nil {
return ret
}
// Check if the passed reviewers (user/team) actually exist
for _, rID := range reviewerIDs {
// negative reviewIDs represent team requests
if rID < 0 {
teamReviewer, err := organization.GetTeamByID(ctx, -rID)
if err != nil {
ctx.ServerError("GetTeamByID", err)
return ret
}
teamReviewers = append(teamReviewers, teamReviewer)
continue
}

reviewer, err := user_model.GetUserByID(ctx, rID)
if err != nil {
ctx.ServerError("GetUserByID", err)
return ret
}
reviewers = append(reviewers, reviewer)
}
}

ret.LabelIDs, ret.AssigneeIDs, ret.MilestoneID, ret.ProjectID = labelIDs, assigneeIDs, milestoneID, form.ProjectID
ret.Reviewers, ret.TeamReviewers = reviewers, teamReviewers
return ret
}

// NewIssuePost response for creating new issue
Expand All @@ -1234,11 +1304,13 @@ func NewIssuePost(ctx *context.Context) {
attachments []string
)

labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, false)
validateRet := ValidateRepoMetas(ctx, *form, false)
if ctx.Written() {
return
}

labelIDs, assigneeIDs, milestoneID, projectID := validateRet.LabelIDs, validateRet.AssigneeIDs, validateRet.MilestoneID, validateRet.ProjectID

if projectID > 0 {
if !ctx.Repo.CanRead(unit.TypeProjects) {
// User must also be able to see the project.
Expand Down Expand Up @@ -2479,7 +2551,7 @@ func UpdatePullReviewRequest(ctx *context.Context) {
return
}

err = issue_service.IsValidTeamReviewRequest(ctx, team, ctx.Doer, action == "attach", issue)
_, err = issue_service.TeamReviewRequest(ctx, issue, ctx.Doer, team, action == "attach")
if err != nil {
if issues_model.IsErrNotValidReviewRequest(err) {
log.Warn(
Expand All @@ -2490,12 +2562,6 @@ func UpdatePullReviewRequest(ctx *context.Context) {
ctx.Status(http.StatusForbidden)
return
}
ctx.ServerError("IsValidTeamReviewRequest", err)
return
}

_, err = issue_service.TeamReviewRequest(ctx, issue, ctx.Doer, team, action == "attach")
if err != nil {
ctx.ServerError("TeamReviewRequest", err)
return
}
Expand All @@ -2517,7 +2583,7 @@ func UpdatePullReviewRequest(ctx *context.Context) {
return
}

err = issue_service.IsValidReviewRequest(ctx, reviewer, ctx.Doer, action == "attach", issue, nil)
_, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, &ctx.Repo.Permission, reviewer, action == "attach")
if err != nil {
if issues_model.IsErrNotValidReviewRequest(err) {
log.Warn(
Expand All @@ -2528,12 +2594,6 @@ func UpdatePullReviewRequest(ctx *context.Context) {
ctx.Status(http.StatusForbidden)
return
}
ctx.ServerError("isValidReviewRequest", err)
return
}

_, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach")
if err != nil {
if issues_model.IsErrReviewRequestOnClosedPR(err) {
ctx.Status(http.StatusForbidden)
return
Expand Down
17 changes: 14 additions & 3 deletions routers/web/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -1269,11 +1269,13 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}

labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, true)
validateRet := ValidateRepoMetas(ctx, *form, true)
if ctx.Written() {
return
}

labelIDs, assigneeIDs, milestoneID, projectID := validateRet.LabelIDs, validateRet.AssigneeIDs, validateRet.MilestoneID, validateRet.ProjectID

if setting.Attachment.Enabled {
attachments = form.Files
}
Expand Down Expand Up @@ -1318,8 +1320,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
}
// FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
// instead of 500.

if err := pull_service.NewPullRequest(ctx, repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil {
prOpts := &pull_service.NewPullRequestOptions{
Repo: repo,
Issue: pullIssue,
LabelIDs: labelIDs,
AttachmentUUIDs: attachments,
PullRequest: pullRequest,
AssigneeIDs: assigneeIDs,
Reviewers: validateRet.Reviewers,
TeamReviewers: validateRet.TeamReviewers,
}
if err := pull_service.NewPullRequest(ctx, prOpts); err != nil {
switch {
case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
Expand Down
8 changes: 6 additions & 2 deletions services/agit/agit.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,12 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
Type: issues_model.PullRequestGitea,
Flow: issues_model.PullRequestFlowAGit,
}

if err := pull_service.NewPullRequest(ctx, repo, prIssue, []int64{}, []string{}, pr, []int64{}); err != nil {
prOpts := &pull_service.NewPullRequestOptions{
Repo: repo,
Issue: prIssue,
PullRequest: pr,
}
if err := pull_service.NewPullRequest(ctx, prOpts); err != nil {
return nil, err
}

Expand Down
1 change: 1 addition & 0 deletions services/forms/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ type CreateIssueForm struct {
Title string `binding:"Required;MaxSize(255)"`
LabelIDs string `form:"label_ids"`
AssigneeIDs string `form:"assignee_ids"`
ReviewerIDs string `form:"reviewer_ids"`
Ref string `form:"ref"`
MilestoneID int64
ProjectID int64
Expand Down
28 changes: 20 additions & 8 deletions services/issue/assignee.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, do
}

// ReviewRequest add or remove a review request from a user for this PR, and make comment for it.
func ReviewRequest(ctx context.Context, 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 *user_model.User, permDoer *access_model.Permission, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) {
err = isValidReviewRequest(ctx, reviewer, doer, isAdd, issue, permDoer)
if err != nil {
return nil, err
}

if isAdd {
comment, err = issues_model.AddReviewRequest(ctx, issue, reviewer, doer)
} else {
Expand All @@ -79,8 +84,8 @@ func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewe
return comment, err
}

// IsValidReviewRequest Check permission for ReviewRequest
func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *issues_model.Issue, permDoer *access_model.Permission) error {
// isValidReviewRequest Check permission for ReviewRequest
func isValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *issues_model.Issue, permDoer *access_model.Permission) error {
if reviewer.IsOrganization() {
return issues_model.ErrNotValidReviewRequest{
Reason: "Organization can't be added as reviewer",
Expand Down Expand Up @@ -109,7 +114,7 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
}
}

lastreview, err := issues_model.GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
lastReview, err := issues_model.GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
if err != nil && !issues_model.IsErrReviewNotExist(err) {
return err
}
Expand Down Expand Up @@ -137,7 +142,7 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
return nil
}

if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != issues_model.ReviewTypeRequest {
if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastReview != nil && lastReview.Type != issues_model.ReviewTypeRequest {
return nil
}

Expand All @@ -152,7 +157,7 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
return nil
}

if lastreview != nil && lastreview.Type == issues_model.ReviewTypeRequest && lastreview.ReviewerID == doer.ID {
if lastReview != nil && lastReview.Type == issues_model.ReviewTypeRequest && lastReview.ReviewerID == doer.ID {
return nil
}

Expand All @@ -163,8 +168,8 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
}
}

// IsValidTeamReviewRequest Check permission for ReviewRequest Team
func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *issues_model.Issue) error {
// isValidTeamReviewRequest Check permission for ReviewRequest Team
func isValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *issues_model.Issue) error {
if doer.IsOrganization() {
return issues_model.ErrNotValidReviewRequest{
Reason: "Organization can't be doer to add reviewer",
Expand Down Expand Up @@ -212,6 +217,10 @@ 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(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) {
err = isValidTeamReviewRequest(ctx, reviewer, doer, isAdd, issue)
if err != nil {
return nil, err
}
if isAdd {
comment, err = issues_model.AddTeamReviewRequest(ctx, issue, reviewer, doer)
} else {
Expand Down Expand Up @@ -268,6 +277,9 @@ func teamReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doe

// CanDoerChangeReviewRequests returns if the doer can add/remove review requests of a PR
func CanDoerChangeReviewRequests(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue) bool {
if repo.IsArchived {
return false
}
// The poster of the PR can change the reviewers
if doer.ID == issue.PosterID {
return true
Expand Down
2 changes: 1 addition & 1 deletion services/mailer/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ func (s *dummySender) Send(from string, to []string, msg io.WriterTo) error {
if _, err := msg.WriteTo(&buf); err != nil {
return err
}
log.Info("Mail From: %s To: %v Body: %s", from, to, buf.String())
log.Debug("Mail From: %s To: %v Body: %s", from, to, buf.String())
return nil
}

Expand Down
27 changes: 25 additions & 2 deletions services/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
Expand All @@ -41,8 +42,20 @@ func getPullWorkingLockKey(prID int64) string {
return fmt.Sprintf("pull_working_%d", prID)
}

type NewPullRequestOptions struct {
Repo *repo_model.Repository
Issue *issues_model.Issue
LabelIDs []int64
AttachmentUUIDs []string
PullRequest *issues_model.PullRequest
AssigneeIDs []int64
Reviewers []*user_model.User
TeamReviewers []*organization.Team
}

// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
repo, issue, labelIDs, uuids, pr, assigneeIDs := opts.Repo, opts.Issue, opts.LabelIDs, opts.AttachmentUUIDs, opts.PullRequest, opts.AssigneeIDs
if err := issue.LoadPoster(ctx); err != nil {
return err
}
Expand Down Expand Up @@ -197,7 +210,17 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
}
notify_service.IssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID])
}

permDoer, err := access_model.GetUserRepoPermission(ctx, repo, issue.Poster)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like these changes are duplicated as line 193 issue_service.ReviewRequestNotify

for _, reviewer := range opts.Reviewers {
if _, err = issue_service.ReviewRequest(ctx, pr.Issue, issue.Poster, &permDoer, reviewer, true); err != nil {
return err
}
}
for _, teamReviewer := range opts.TeamReviewers {
if _, err = issue_service.TeamReviewRequest(ctx, pr.Issue, issue.Poster, teamReviewer, true); err != nil {
return err
}
}
return nil
}

Expand Down
6 changes: 5 additions & 1 deletion templates/repo/issue/new_form.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@
</div>

<div class="issue-content-right ui segment">
{{template "repo/issue/branch_selector_field" .}}
{{template "repo/issue/branch_selector_field" $}}
{{if .PageIsComparePull}}
{{template "repo/issue/sidebar/reviewer_list" dict "IssueSidebarReviewersData" $.IssueSidebarReviewersData}}
<div class="divider"></div>
{{end}}

<input id="label_ids" name="label_ids" type="hidden" value="{{.label_ids}}">
{{template "repo/issue/labels/labels_selector_field" .}}
Expand Down
174 changes: 92 additions & 82 deletions templates/repo/issue/sidebar/reviewer_list.tmpl
Original file line number Diff line number Diff line change
@@ -1,116 +1,126 @@
<input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}">
<div class="ui {{if or (and (not .Reviewers) (not .TeamReviewers)) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown">
<a class="text tw-flex tw-items-center muted">
<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong>
{{if and .CanChooseReviewer (not .Repository.IsArchived)}}
{{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</a>
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/request_review">
{{if .Reviewers}}
<div class="ui icon search input">
<i class="icon">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
</div>
{{end}}
{{if .Reviewers}}
{{range .Reviewers}}
{{$data := .IssueSidebarReviewersData}}
{{$hasCandidates := or $data.Reviewers $data.TeamReviewers}}
<div class="issue-sidebar-combo" data-sidebar-combo-for="reviewers"
{{if $data.IssueID}}data-update-url="{{$data.RepoLink}}/issues/request_review?issue_ids={{$data.IssueID}}"{{end}}
>
<input type="hidden" class="combo-value" name="reviewer_ids">{{/* match CreateIssueForm */}}
<div class="ui dropdown custom {{if or (not $hasCandidates) (not $data.CanChooseReviewer)}}disabled{{end}}">
<a class="muted text">
<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong> {{if and $data.CanChooseReviewer}}{{svg "octicon-gear"}}{{end}}
</a>
<div class="menu flex-items-menu">
{{if $hasCandidates}}
<div class="ui icon search input">
<i class="icon">{{svg "octicon-search"}}</i>
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
</div>
{{end}}
{{range $data.Reviewers}}
{{if .User}}
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
<span class="text">
{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}{{template "repo/search_name" .User}}
</span>
<a class="item muted {{if .Requested}}checked{{end}}" href="{{.User.HomeLink}}" data-value="{{.ItemID}}" data-can-change="{{.CanChange}}"
{{if not .CanChange}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
{{svg "octicon-check"}} {{ctx.AvatarUtils.Avatar .User 20}} {{template "repo/search_name" .User}}
</a>
{{end}}
{{end}}
{{end}}
{{if .TeamReviewers}}
{{if .Reviewers}}
<div class="divider"></div>
{{end}}
{{range .TeamReviewers}}
{{if .Team}}
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check" 16}}</span>
<span class="text">
{{svg "octicon-people" 16 "tw-ml-4 tw-mr-1"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
</span>
</a>
{{if $data.TeamReviewers}}
{{if $data.Reviewers}}<div class="divider"></div>{{end}}
{{range $data.TeamReviewers}}
{{if .Team}}
<a class="item muted {{if .Requested}}checked{{end}}" href="#" data-value="{{.ItemID}}" data-can-change="{{.CanChange}}"
{{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
{{svg "octicon-check"}} {{svg "octicon-people" 20}} {{$data.RepoOwnerName}}/{{.Team.Name}}
</a>
{{end}}
{{end}}
{{end}}
{{end}}
</div>
</div>
</div>

<div class="ui assignees list">
<span class="no-select item {{if or .OriginalReviews .PullReviewers}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}</span>
<div class="selected">
{{range .PullReviewers}}
<div class="item tw-flex tw-items-center tw-py-2">
<div class="tw-flex tw-items-center tw-flex-1">
<div class="ui relaxed list flex-items-block tw-my-4">
<span class="item empty-list {{if or $data.OriginalReviews $data.CurrentPullReviewers}}tw-hidden{{end}}">
{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}
</span>
{{range $data.CurrentPullReviewers}}
<div class="item">
<div class="flex-text-inline tw-flex-1">
{{if .User}}
<a class="muted sidebar-item-link" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20 "tw-mr-2"}}{{.User.GetDisplayName}}</a>
<a class="muted flex-text-inline" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20}} {{.User.GetDisplayName}}</a>
{{else if .Team}}
<span class="text">{{svg "octicon-people" 20 "tw-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}</span>
{{svg "octicon-people" 20}} {{$data.RepoOwnerName}}/{{.Team.Name}}
{{end}}
</div>
<div class="tw-flex tw-items-center tw-gap-2">
{{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged))}}
<a href="#" class="ui muted icon tw-flex tw-items-center show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}" data-modal="#dismiss-review-modal-{{.Review.ID}}">
<div class="flex-text-inline">
{{if .CanBeDismissed}}
<a href="#" class="ui muted icon show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}"
data-modal="#issue-sidebar-dismiss-review-modal" data-modal-reviewer-id="{{.Review.ID}}">
{{svg "octicon-x" 20}}
</a>
<div class="ui small modal" id="dismiss-review-modal-{{.Review.ID}}">
<div class="header">
{{ctx.Locale.Tr "repo.issues.dismiss_review"}}
</div>
<div class="content">
<div class="ui warning message">
{{ctx.Locale.Tr "repo.issues.dismiss_review_warning"}}
</div>
<form class="ui form dismiss-review-form" id="dismiss-review-{{.Review.ID}}" action="{{$.RepoLink}}/issues/dismiss_review" method="post">
{{$.CsrfTokenHtml}}
<input type="hidden" name="review_id" value="{{.Review.ID}}">
<div class="field">
<label for="message">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</label>
<input id="message" name="message">
</div>
<div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button" type="submit">{{ctx.Locale.Tr "ok"}}</button>
</div>
</form>
</div>
</div>
{{end}}
{{if .Review.Stale}}
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.is_stale"}}">
{{svg "octicon-hourglass" 16}}
</span>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.is_stale"}}">{{svg "octicon-hourglass" 16}}</span>
{{end}}
{{if and .CanChange (or .Checked (and (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged)))}}
<a href="#" class="ui muted icon re-request-review{{if .Checked}} checked{{end}}" data-tooltip-content="{{if .Checked}}{{ctx.Locale.Tr "repo.issues.remove_request_review"}}{{else}}{{ctx.Locale.Tr "repo.issues.re_request_review"}}{{end}}" data-issue-id="{{$.Issue.ID}}" data-id="{{.ItemID}}" data-update-url="{{$.RepoLink}}/issues/request_review">{{svg (Iif .Checked "octicon-trash" "octicon-sync")}}</a>
{{if and .CanChange $data.CanChooseReviewer}}
{{if .Requested}}
<a href="#" class="ui muted icon link-action"
data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review"}}"
data-url="{{$data.RepoLink}}/issues/request_review?action=detach&issue_ids={{$data.IssueID}}&id={{.ItemID}}">
{{svg "octicon-trash"}}
</a>
{{else}}
<a href="#" class="ui muted icon link-action"
data-tooltip-content="{{ctx.Locale.Tr "repo.issues.re_request_review"}}"
data-url="{{$data.RepoLink}}/issues/request_review?action=attach&issue_ids={{$data.IssueID}}&id={{.ItemID}}">
{{svg "octicon-sync"}}
</a>
{{end}}
{{end}}
<span {{if .Review.TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .Review.TooltipContent}}"{{end}}>
{{svg (printf "octicon-%s" .Review.Type.Icon) 16 (printf "text %s" (.Review.HTMLTypeColorName))}}
</span>
</div>
</div>
{{end}}
{{range .OriginalReviews}}
<div class="item tw-flex tw-items-center tw-py-2">
<div class="tw-flex tw-items-center tw-flex-1">
<a class="muted" href="{{$.Repository.OriginalURL}}" data-tooltip-content="{{ctx.Locale.Tr "repo.migrated_from_fake" $.Repository.GetOriginalURLHostname}}">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname) 20 "tw-mr-2"}}
{{.OriginalAuthor}}
{{range $data.OriginalReviews}}
<div class="item">
<div class="flex-text-inline tw-flex-1">
{{$originalURLHostname := $data.Repository.GetOriginalURLHostname}}
{{$originalURL := $data.Repository.OriginalURL}}
<a class="muted flex-text-inline" href="{{$originalURL}}" data-tooltip-content="{{ctx.Locale.Tr "repo.migrated_from_fake" $originalURLHostname}}">
{{svg (MigrationIcon $originalURLHostname) 20}} {{.OriginalAuthor}}
</a>
</div>
<div class="tw-flex tw-items-center tw-gap-2">
<div class="flex-text-inline">
<span {{if .TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .TooltipContent}}"{{end}}>
{{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}}
</span>
</div>
</div>
{{end}}
</div>

{{if $data.CurrentPullReviewers}}
<div class="ui small modal" id="issue-sidebar-dismiss-review-modal">
<div class="header">
{{ctx.Locale.Tr "repo.issues.dismiss_review"}}
</div>
<div class="content">
<div class="ui warning message">
{{ctx.Locale.Tr "repo.issues.dismiss_review_warning"}}
</div>
<form class="ui form" action="{{$data.RepoLink}}/issues/dismiss_review" method="post">
{{ctx.RootData.CsrfTokenHtml}}
<input type="hidden" class="reviewer-id" name="review_id">
<div class="field">
<label for="issue-sidebar-dismiss-review-message">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</label>
<input id="issue-sidebar-dismiss-review-message" name="message">
</div>
<div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button" type="submit">{{ctx.Locale.Tr "ok"}}</button>
</div>
</form>
</div>
</div>
{{end}}
</div>
2 changes: 1 addition & 1 deletion templates/repo/issue/sidebar/wip_switch.tmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .HasMerged) (not .Issue.IsClosed) (not .IsPullWorkInProgress)}}
<div class="toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefix="{{index .PullRequestWorkInProgressPrefixes 0}}" data-update-url="{{.Issue.Link}}/title">
<div class="toggle-wip tw-mt-2" data-title="{{.Issue.Title}}" data-wip-prefix="{{index .PullRequestWorkInProgressPrefixes 0}}" data-update-url="{{.Issue.Link}}/title">
<a class="muted">
{{ctx.Locale.Tr "repo.pulls.still_in_progress"}} {{ctx.Locale.Tr "repo.pulls.add_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
</a>
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/view_content/sidebar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{{template "repo/issue/branch_selector_field" $}}

{{if .Issue.IsPull}}
{{template "repo/issue/sidebar/reviewer_list" $}}
{{template "repo/issue/sidebar/reviewer_list" dict "IssueSidebarReviewersData" $.IssueSidebarReviewersData}}
{{template "repo/issue/sidebar/wip_switch" $}}
<div class="divider"></div>
{{end}}
Expand Down
14 changes: 14 additions & 0 deletions templates/swagger/v1_json.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions tests/integration/actions_trigger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ func TestPullRequestTargetEvent(t *testing.T) {
BaseRepo: baseRepo,
Type: issues_model.PullRequestGitea,
}
err = pull_service.NewPullRequest(git.DefaultContext, baseRepo, pullIssue, nil, nil, pullRequest, nil)
prOpts := &pull_service.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest}
err = pull_service.NewPullRequest(git.DefaultContext, prOpts)
assert.NoError(t, err)

// load and compare ActionRun
Expand Down Expand Up @@ -199,7 +200,8 @@ func TestPullRequestTargetEvent(t *testing.T) {
BaseRepo: baseRepo,
Type: issues_model.PullRequestGitea,
}
err = pull_service.NewPullRequest(git.DefaultContext, baseRepo, pullIssue, nil, nil, pullRequest, nil)
prOpts = &pull_service.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest}
err = pull_service.NewPullRequest(git.DefaultContext, prOpts)
assert.NoError(t, err)

// the new pull request cannot trigger actions, so there is still only 1 record
Expand Down
5 changes: 4 additions & 1 deletion tests/integration/api_pull_review_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
Expand Down Expand Up @@ -422,7 +423,9 @@ func TestAPIPullReviewStayDismissed(t *testing.T) {
pullIssue.ID, user8.ID, 1, 1, 2, false)

// user8 dismiss review
_, err = issue_service.ReviewRequest(db.DefaultContext, pullIssue, user8, user8, false)
permUser8, err := access_model.GetUserRepoPermission(db.DefaultContext, pullIssue.Repo, user8)
assert.NoError(t, err)
_, err = issue_service.ReviewRequest(db.DefaultContext, pullIssue, user8, &permUser8, user8, false)
assert.NoError(t, err)

reviewsCountCheck(t,
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/pull_merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,8 @@ func TestConflictChecking(t *testing.T) {
BaseRepo: baseRepo,
Type: issues_model.PullRequestGitea,
}
err = pull.NewPullRequest(git.DefaultContext, baseRepo, pullIssue, nil, nil, pullRequest, nil)
prOpts := &pull.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest}
err = pull.NewPullRequest(git.DefaultContext, prOpts)
assert.NoError(t, err)

issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: "PR with conflict!"})
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/pull_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_mod
BaseRepo: baseRepo,
Type: issues_model.PullRequestGitea,
}
err = pull_service.NewPullRequest(git.DefaultContext, baseRepo, pullIssue, nil, nil, pullRequest, nil)
prOpts := &pull_service.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest}
err = pull_service.NewPullRequest(git.DefaultContext, prOpts)
assert.NoError(t, err)

issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: "Test Pull -to-update-"})
Expand Down
1 change: 1 addition & 0 deletions web_src/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -1381,6 +1381,7 @@ table th[data-sortt-desc] .svg {
align-items: stretch;
}

.ui.list.flex-items-block > .item,
.flex-items-block > .item,
.flex-text-block {
display: flex;
Expand Down
9 changes: 9 additions & 0 deletions web_src/css/repo.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
width: 300px;
}

.issue-sidebar-combo .ui.dropdown .item:not(.checked) svg.octicon-check {
visibility: hidden;
}
/* ideally, we should move these styles to ".ui.dropdown .menu.flex-items-menu > .item ...", could be done later */
.issue-sidebar-combo .ui.dropdown .menu > .item > img,
.issue-sidebar-combo .ui.dropdown .menu > .item > svg {
margin: 0;
}

.issue-content-right .dropdown > .menu {
max-width: 270px;
min-width: 0;
Expand Down
89 changes: 89 additions & 0 deletions web_src/js/features/repo-issue-sidebar-combolist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {POST} from '../modules/fetch.ts';
import {queryElemChildren, toggleElem} from '../utils/dom.ts';

// if there are draft comments, confirm before reloading, to avoid losing comments
export function issueSidebarReloadConfirmDraftComment() {
const commentTextareas = [
document.querySelector<HTMLTextAreaElement>('.edit-content-zone:not(.tw-hidden) textarea'),
document.querySelector<HTMLTextAreaElement>('#comment-form textarea'),
];
for (const textarea of commentTextareas) {
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
if (textarea && textarea.value.trim().length > 10) {
textarea.parentElement.scrollIntoView();
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
return;
}
break;
}
}
window.location.reload();
}

function collectCheckedValues(elDropdown: HTMLElement) {
return Array.from(elDropdown.querySelectorAll('.menu > .item.checked'), (el) => el.getAttribute('data-value'));
}

export function initIssueSidebarComboList(container: HTMLElement) {
if (!container) return;

const updateUrl = container.getAttribute('data-update-url');
const elDropdown = container.querySelector<HTMLElement>(':scope > .ui.dropdown');
const elList = container.querySelector<HTMLElement>(':scope > .ui.list');
const elComboValue = container.querySelector<HTMLInputElement>(':scope > .combo-value');
const initialValues = collectCheckedValues(elDropdown);

elDropdown.addEventListener('click', (e) => {
const elItem = (e.target as HTMLElement).closest('.item');
if (!elItem) return;
e.preventDefault();
if (elItem.getAttribute('data-can-change') !== 'true') return;
elItem.classList.toggle('checked');
elComboValue.value = collectCheckedValues(elDropdown).join(',');
});

const updateToBackend = async (changedValues) => {
let changed = false;
for (const value of initialValues) {
if (!changedValues.includes(value)) {
await POST(updateUrl, {data: new URLSearchParams({action: 'detach', id: value})});
changed = true;
}
}
for (const value of changedValues) {
if (!initialValues.includes(value)) {
await POST(updateUrl, {data: new URLSearchParams({action: 'attach', id: value})});
changed = true;
}
}
if (changed) issueSidebarReloadConfirmDraftComment();
};

const syncList = (changedValues) => {
const elEmptyTip = elList.querySelector('.item.empty-list');
queryElemChildren(elList, '.item:not(.empty-list)', (el) => el.remove());
for (const value of changedValues) {
const el = elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${value}"]`);
const listItem = el.cloneNode(true) as HTMLElement;
listItem.querySelector('svg.octicon-check')?.remove();
elList.append(listItem);
}
const hasItems = Boolean(elList.querySelector('.item:not(.empty-list)'));
toggleElem(elEmptyTip, !hasItems);
};

fomanticQuery(elDropdown).dropdown({
action: 'nothing', // do not hide the menu if user presses Enter
fullTextSearch: 'exact',
async onHide() {
const changedValues = collectCheckedValues(elDropdown);
if (updateUrl) {
await updateToBackend(changedValues); // send requests to backend and reload the page
} else {
syncList(changedValues); // only update the list in the sidebar
}
},
});
}
41 changes: 11 additions & 30 deletions web_src/js/features/repo-issue-sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,7 @@ import {updateIssuesMeta} from './repo-common.ts';
import {svg} from '../svg.ts';
import {htmlEscape} from 'escape-goat';
import {toggleElem} from '../utils/dom.ts';

// if there are draft comments, confirm before reloading, to avoid losing comments
function reloadConfirmDraftComment() {
const commentTextareas = [
document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'),
document.querySelector('#comment-form textarea'),
];
for (const textarea of commentTextareas) {
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
if (textarea && textarea.value.trim().length > 10) {
textarea.parentElement.scrollIntoView();
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
return;
}
break;
}
}
window.location.reload();
}
import {initIssueSidebarComboList, issueSidebarReloadConfirmDraftComment} from './repo-issue-sidebar-combolist.ts';

function initBranchSelector() {
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
Expand Down Expand Up @@ -78,7 +59,7 @@ function initListSubmits(selector, outerSelector) {
);
}
if (itemEntries.length) {
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
}
}
},
Expand Down Expand Up @@ -142,7 +123,7 @@ function initListSubmits(selector, outerSelector) {

// TODO: Which thing should be done for choosing review requests
// to make chosen items be shown on time here?
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
if (selector === 'select-assignees-modify') {
return false;
}

Expand Down Expand Up @@ -173,7 +154,7 @@ function initListSubmits(selector, outerSelector) {
$listMenu.data('issue-id'),
'',
);
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
})();
}

Expand All @@ -182,7 +163,7 @@ function initListSubmits(selector, outerSelector) {
$(this).find('.octicon-check').addClass('tw-invisible');
});

if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
if (selector === 'select-assignees-modify') {
return false;
}

Expand Down Expand Up @@ -213,7 +194,7 @@ function selectItem(select_id, input_id) {
$menu.data('issue-id'),
$(this).data('id'),
);
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
})();
}

Expand Down Expand Up @@ -249,7 +230,7 @@ function selectItem(select_id, input_id) {
$menu.data('issue-id'),
$(this).data('id'),
);
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
})();
}

Expand All @@ -276,14 +257,14 @@ export function initRepoIssueSidebar() {
initBranchSelector();
initRepoIssueDue();

// Init labels and assignees
// TODO: refactor the legacy initListSubmits&selectItem to initIssueSidebarComboList
initListSubmits('select-label', 'labels');
initListSubmits('select-assignees', 'assignees');
initListSubmits('select-assignees-modify', 'assignees');
initListSubmits('select-reviewers-modify', 'assignees');

// Milestone, Assignee, Project
selectItem('.select-project', '#project_id');
selectItem('.select-milestone', '#milestone_id');
selectItem('.select-assignee', '#assignee_id');

// init the combo list: a dropdown for selecting reviewers, and a list for showing selected reviewers and related actions
initIssueSidebarComboList(document.querySelector('.issue-sidebar-combo[data-sidebar-combo-for="reviewers"]'));
}
12 changes: 0 additions & 12 deletions web_src/js/features/repo-issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {parseIssuePageInfo, toAbsoluteUrl} from '../utils.ts';
import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
import {updateIssuesMeta} from './repo-common.ts';

const {appSubUrl} = window.config;

Expand Down Expand Up @@ -326,17 +325,6 @@ export function initRepoIssueWipTitle() {
export function initRepoIssueComments() {
if (!$('.repository.view.issue .timeline').length) return;

$('.re-request-review').on('click', async function (e) {
e.preventDefault();
const url = this.getAttribute('data-update-url');
const issueId = this.getAttribute('data-issue-id');
const id = this.getAttribute('data-id');
const isChecked = this.classList.contains('checked');

await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);
window.location.reload();
});

document.addEventListener('click', (e) => {
const urlTarget = document.querySelector(':target');
if (!urlTarget) return;
Expand Down