Files

651 lines
18 KiB
Go
Raw Permalink Normal View History

2014-03-22 13:50:50 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2014-03-22 13:50:50 -04:00
package repo
2014-07-26 02:28:04 -04:00
import (
"errors"
"fmt"
"html/template"
"net/http"
2017-03-14 21:10:35 -04:00
"strconv"
2014-07-26 02:28:04 -04:00
"strings"
"code.gitea.io/gitea/models/db"
2022-03-31 17:20:39 +08:00
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
2024-11-24 16:18:57 +08:00
"code.gitea.io/gitea/models/renderhelper"
2021-11-19 21:39:57 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 03:57:58 +08:00
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
2024-03-01 15:11:51 +08:00
"code.gitea.io/gitea/modules/templates"
2017-01-24 21:43:02 -05:00
"code.gitea.io/gitea/modules/util"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/context"
2022-12-29 03:57:15 +01:00
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
issue_service "code.gitea.io/gitea/services/issue"
2014-07-26 02:28:04 -04:00
)
const (
tplAttachment templates.TplName = "repo/issue/view_content/attachments"
tplIssues templates.TplName = "repo/issue/list"
tplIssueNew templates.TplName = "repo/issue/new"
tplIssueChoose templates.TplName = "repo/issue/choose"
tplIssueView templates.TplName = "repo/issue/view"
2014-07-26 02:28:04 -04:00
tplPullMergeBox templates.TplName = "repo/issue/view_content/pull_merge_box"
tplReactions templates.TplName = "repo/issue/view_content/reactions"
2020-09-11 09:48:39 -05:00
issueTemplateKey = "IssueTemplate"
issueTemplateTitleKey = "IssueTemplateTitle"
2014-07-26 02:28:04 -04:00
)
2022-01-20 18:46:10 +01:00
// IssueTemplateCandidates issue templates
var IssueTemplateCandidates = []string{
"ISSUE_TEMPLATE.md",
2022-09-02 15:58:49 +08:00
"ISSUE_TEMPLATE.yaml",
"ISSUE_TEMPLATE.yml",
2022-01-20 18:46:10 +01:00
"issue_template.md",
2022-09-02 15:58:49 +08:00
"issue_template.yaml",
"issue_template.yml",
2022-01-20 18:46:10 +01:00
".gitea/ISSUE_TEMPLATE.md",
2022-09-02 15:58:49 +08:00
".gitea/ISSUE_TEMPLATE.yaml",
".gitea/ISSUE_TEMPLATE.yml",
".gitea/issue_template.md",
".gitea/issue_template.yaml",
2022-09-09 11:22:33 +08:00
".gitea/issue_template.yml",
2022-01-20 18:46:10 +01:00
".github/ISSUE_TEMPLATE.md",
2022-09-02 15:58:49 +08:00
".github/ISSUE_TEMPLATE.yaml",
".github/ISSUE_TEMPLATE.yml",
2022-01-20 18:46:10 +01:00
".github/issue_template.md",
2022-09-02 15:58:49 +08:00
".github/issue_template.yaml",
".github/issue_template.yml",
2022-01-20 18:46:10 +01:00
}
2014-07-26 02:28:04 -04:00
// MustAllowUserComment checks to make sure if an issue is locked.
// If locked and user has permissions to write to the repository,
// then the comment is allowed, else it is blocked
func MustAllowUserComment(ctx *context.Context) {
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
2022-03-22 08:03:22 +01:00
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
ctx.Redirect(issue.Link())
return
}
}
2016-11-24 15:04:31 +08:00
// MustEnableIssues check if repository enable internal issues
2016-03-11 11:56:52 -05:00
func MustEnableIssues(ctx *context.Context) {
2021-11-10 03:57:58 +08:00
if !ctx.Repo.CanRead(unit.TypeIssues) &&
!ctx.Repo.CanRead(unit.TypeExternalTracker) {
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
2016-03-06 23:57:46 -05:00
return
2015-12-04 21:30:33 -05:00
}
2022-12-10 10:46:31 +08:00
unit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalTracker)
if err == nil {
ctx.Redirect(unit.ExternalTrackerConfig().ExternalTrackerURL)
return
}
2015-12-04 21:30:33 -05:00
}
// MustAllowPulls check if repository enable pull requests and user have right to do that
2016-03-11 11:56:52 -05:00
func MustAllowPulls(ctx *context.Context) {
2021-11-10 03:57:58 +08:00
if !ctx.Repo.Repository.CanEnablePulls() || !ctx.Repo.CanRead(unit.TypePullRequests) {
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
2016-03-06 23:57:46 -05:00
return
2015-12-04 21:30:33 -05:00
}
}
func retrieveProjectsInternal(ctx *context.Context, repo *repo_model.Repository) (open, closed []*project_model.Project) {
// Distinguish whether the owner of the repository
// is an individual or an organization
repoOwnerType := project_model.TypeIndividual
if repo.Owner.IsOrganization() {
repoOwnerType = project_model.TypeOrganization
}
2023-01-20 19:42:33 +08:00
projectsUnit := repo.MustGetUnit(ctx, unit.TypeProjects)
2020-08-17 04:07:38 +01:00
var openProjects []*project_model.Project
var closedProjects []*project_model.Project
var err error
if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) {
openProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
RepoID: repo.ID,
IsClosed: optional.Some(false),
Type: project_model.TypeRepository,
})
if err != nil {
ctx.ServerError("GetProjects", err)
return nil, nil
}
closedProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
RepoID: repo.ID,
IsClosed: optional.Some(true),
Type: project_model.TypeRepository,
})
if err != nil {
ctx.ServerError("GetProjects", err)
return nil, nil
}
2020-08-17 04:07:38 +01:00
}
if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeOwner) {
openProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
OwnerID: repo.OwnerID,
IsClosed: optional.Some(false),
Type: repoOwnerType,
})
if err != nil {
ctx.ServerError("GetProjects", err)
return nil, nil
}
openProjects = append(openProjects, openProjects2...)
closedProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
OwnerID: repo.OwnerID,
IsClosed: optional.Some(true),
Type: repoOwnerType,
})
if err != nil {
ctx.ServerError("GetProjects", err)
return nil, nil
}
closedProjects = append(closedProjects, closedProjects2...)
2023-01-20 19:42:33 +08:00
}
return openProjects, closedProjects
2020-08-17 04:07:38 +01:00
}
// GetActionIssue will return the issue which is used in the context.
func GetActionIssue(ctx *context.Context) *issues_model.Issue {
2024-12-24 21:47:45 +08:00
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.NotFoundOrServerError("GetIssueByIndex", issues_model.IsErrIssueNotExist, err)
return nil
}
issue.Repo = ctx.Repo.Repository
checkIssueRights(ctx, issue)
if ctx.Written() {
return nil
}
if err = issue.LoadAttributes(ctx); err != nil {
ctx.ServerError("LoadAttributes", err)
return nil
}
return issue
}
func checkIssueRights(ctx *context.Context, issue *issues_model.Issue) {
if issue.IsPull && !ctx.Repo.CanRead(unit.TypePullRequests) ||
!issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) {
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
}
}
func getActionIssues(ctx *context.Context) issues_model.IssueList {
commaSeparatedIssueIDs := ctx.FormString("issue_ids")
if len(commaSeparatedIssueIDs) == 0 {
return nil
}
issueIDs := make([]int64, 0, 10)
2025-06-18 03:48:09 +02:00
for stringIssueID := range strings.SplitSeq(commaSeparatedIssueIDs, ",") {
issueID, err := strconv.ParseInt(stringIssueID, 10, 64)
if err != nil {
ctx.ServerError("ParseInt", err)
return nil
}
issueIDs = append(issueIDs, issueID)
}
issues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
if err != nil {
ctx.ServerError("GetIssuesByIDs", err)
return nil
}
// Check access rights for all issues
issueUnitEnabled := ctx.Repo.CanRead(unit.TypeIssues)
prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests)
for _, issue := range issues {
if issue.RepoID != ctx.Repo.Repository.ID {
2025-02-17 14:13:17 +08:00
ctx.NotFound(errors.New("some issue's RepoID is incorrect"))
return nil
}
if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled {
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
return nil
}
if err = issue.LoadAttributes(ctx); err != nil {
ctx.ServerError("LoadAttributes", err)
return nil
}
}
return issues
}
// GetIssueInfo get an issue of a repository
func GetIssueInfo(ctx *context.Context) {
2024-12-24 21:47:45 +08:00
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusNotFound)
} else {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusInternalServerError, "GetIssueByIndex", err.Error())
}
return
}
if issue.IsPull {
// Need to check if Pulls are enabled and we can read Pulls
if !ctx.Repo.Repository.CanEnablePulls() || !ctx.Repo.CanRead(unit.TypePullRequests) {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusNotFound)
return
}
} else {
// Need to check if Issues are enabled and we can read Issues
if !ctx.Repo.CanRead(unit.TypeIssues) {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusNotFound)
return
}
}
ctx.JSON(http.StatusOK, map[string]any{
"convertedIssue": convert.ToIssue(ctx, ctx.Doer, issue),
"renderedLabels": templates.NewRenderUtils(ctx).RenderLabels(issue.Labels, ctx.Repo.RepoLink, issue),
})
}
// UpdateIssueTitle change issue's title
func UpdateIssueTitle(ctx *context.Context) {
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusForbidden)
2014-07-26 02:28:04 -04:00
return
}
title := ctx.FormTrim("title")
if len(title) == 0 {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusNoContent)
2017-03-14 21:10:35 -04:00
return
}
if err := issue_service.ChangeTitle(ctx, issue, ctx.Doer, title); err != nil {
ctx.ServerError("ChangeTitle", err)
return
}
ctx.JSON(http.StatusOK, map[string]any{
"title": issue.Title,
})
2014-07-26 02:28:04 -04:00
}
// UpdateIssueRef change issue's ref (branch)
func UpdateIssueRef(ctx *context.Context) {
issue := GetActionIssue(ctx)
if ctx.Written() {
2014-07-26 02:28:04 -04:00
return
}
if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) || issue.IsPull {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusForbidden)
return
}
ref := ctx.FormTrim("ref")
2014-07-26 02:28:04 -04:00
if err := issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, ref); err != nil {
ctx.ServerError("ChangeRef", err)
2014-07-26 02:28:04 -04:00
return
}
ctx.JSON(http.StatusOK, map[string]any{
"ref": ref,
})
2014-07-26 02:28:04 -04:00
}
// UpdateIssueContent change issue's content
func UpdateIssueContent(ctx *context.Context) {
issue := GetActionIssue(ctx)
if ctx.Written() {
2023-11-26 01:21:21 +08:00
return
}
if !ctx.IsSigned || (ctx.Doer.ID != issue.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusForbidden)
2015-08-20 04:31:28 +08:00
return
2022-01-19 01:28:38 +08:00
}
if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, ctx.Req.FormValue("content"), ctx.FormInt("content_version")); err != nil {
2024-03-04 09:16:03 +01:00
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.edit.blocked_user"))
} else if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
if issue.IsPull {
ctx.JSONError(ctx.Tr("repo.pulls.edit.already_changed"))
} else {
ctx.JSONError(ctx.Tr("repo.issues.edit.already_changed"))
}
2024-03-04 09:16:03 +01:00
} else {
ctx.ServerError("ChangeContent", err)
2024-03-04 09:16:03 +01:00
}
2015-08-20 04:31:28 +08:00
return
}
// when update the request doesn't intend to update attachments (eg: change checkbox state), ignore attachment updates
if !ctx.FormBool("ignore_attachments") {
if err := updateAttachments(ctx, issue, ctx.FormStrings("files[]")); err != nil {
ctx.ServerError("UpdateAttachments", err)
return
}
2021-04-20 06:25:08 +08:00
}
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{
FootnoteContextID: "0",
})
2024-11-24 16:18:57 +08:00
content, err := markdown.RenderString(rctx, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
2023-07-04 20:36:08 +02:00
ctx.JSON(http.StatusOK, map[string]any{
"content": commentContentHTML(ctx, content),
"contentVersion": issue.ContentVersion,
"attachments": attachmentsHTML(ctx, issue.Attachments, issue.Content),
2015-08-20 04:31:28 +08:00
})
}
// UpdateIssueDeadline updates an issue deadline
func UpdateIssueDeadline(ctx *context.Context) {
2024-12-24 21:47:45 +08:00
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
2016-07-26 02:48:17 +08:00
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
2025-02-17 14:13:17 +08:00
ctx.NotFound(err)
} else {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusInternalServerError, "GetIssueByIndex", err.Error())
}
2016-07-26 02:48:17 +08:00
return
}
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusForbidden, "", "Not repo writer")
return
}
deadlineUnix, _ := common.ParseDeadlineDateToEndOfDay(ctx.FormString("deadline"))
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error())
2023-11-26 01:21:21 +08:00
return
}
ctx.JSONRedirect("")
}
// UpdateIssueMilestone change issue's milestone
func UpdateIssueMilestone(ctx *context.Context) {
issues := getActionIssues(ctx)
if ctx.Written() {
2016-07-26 02:48:17 +08:00
return
}
milestoneID := ctx.FormInt64("id")
for _, issue := range issues {
oldMilestoneID := issue.MilestoneID
if oldMilestoneID == milestoneID {
continue
}
issue.MilestoneID = milestoneID
if milestoneID > 0 {
var err error
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
if err != nil {
ctx.ServerError("GetMilestoneByRepoID", err)
return
}
} else {
issue.Milestone = nil
}
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
ctx.ServerError("ChangeMilestoneAssign", err)
return
}
}
ctx.JSONOK()
}
// UpdateIssueAssignee change issue's or pull's assignee
func UpdateIssueAssignee(ctx *context.Context) {
issues := getActionIssues(ctx)
if ctx.Written() {
2016-07-26 02:48:17 +08:00
return
}
assigneeID := ctx.FormInt64("id")
action := ctx.FormString("action")
for _, issue := range issues {
switch action {
case "clear":
if err := issue_service.DeleteNotPassedAssignee(ctx, issue, ctx.Doer, []*user_model.User{}); err != nil {
ctx.ServerError("ClearAssignees", err)
return
}
default:
assignee, err := user_model.GetUserByID(ctx, assigneeID)
if err != nil {
ctx.ServerError("GetUserByID", err)
return
}
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
if err != nil {
ctx.ServerError("canBeAssigned", err)
return
}
if !valid {
ctx.ServerError("canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name})
return
}
_, _, err = issue_service.ToggleAssigneeWithNotify(ctx, issue, ctx.Doer, assigneeID)
if err != nil {
ctx.ServerError("ToggleAssignee", err)
return
}
}
}
ctx.JSONOK()
2016-07-26 02:48:17 +08:00
}
// ChangeIssueReaction create a reaction for issue
2021-01-26 23:36:53 +08:00
func ChangeIssueReaction(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.ReactionForm)
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
2022-03-22 08:03:22 +01:00
if !ctx.IsSigned || (ctx.Doer.ID != issue.PosterID && !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull)) {
if log.IsTrace() {
if ctx.IsSigned {
issueType := "issues"
if issue.IsPull {
issueType = "pulls"
}
log.Trace("Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n"+
"User in Repo has Permissions: %-+v",
2022-03-22 08:03:22 +01:00
ctx.Doer,
2023-05-22 06:35:11 +08:00
issue.PosterID,
issueType,
ctx.Repo.Repository,
ctx.Repo.Permission)
} else {
log.Trace("Permission Denied: Not logged in")
}
}
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusForbidden)
return
}
if ctx.HasError() {
2018-01-10 22:34:17 +01:00
ctx.ServerError("ChangeIssueReaction", errors.New(ctx.GetErrMsg()))
return
}
2024-12-24 21:47:45 +08:00
switch ctx.PathParam("action") {
case "react":
2024-03-04 09:16:03 +01:00
reaction, err := issue_service.CreateIssueReaction(ctx, ctx.Doer, issue, form.Content)
if err != nil {
2024-03-04 09:16:03 +01:00
if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) {
2019-12-07 23:04:19 +01:00
ctx.ServerError("ChangeIssueReaction", err)
return
}
log.Info("CreateIssueReaction: %s", err)
break
}
// Reload new reactions
issue.Reactions = nil
if err = issue.LoadAttributes(ctx); err != nil {
log.Info("issue.LoadAttributes: %s", err)
break
}
log.Trace("Reaction for issue created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, reaction.ID)
case "unreact":
if err := issues_model.DeleteIssueReaction(ctx, ctx.Doer.ID, issue.ID, form.Content); err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("DeleteIssueReaction", err)
return
}
// Reload new reactions
issue.Reactions = nil
if err := issue.LoadAttributes(ctx); err != nil {
log.Info("issue.LoadAttributes: %s", err)
break
}
log.Trace("Reaction for issue removed: %d/%d", ctx.Repo.Repository.ID, issue.ID)
default:
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
return
}
if len(issue.Reactions) == 0 {
2023-07-04 20:36:08 +02:00
ctx.JSON(http.StatusOK, map[string]any{
"empty": true,
"html": "",
})
return
}
html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index),
"Reactions": issue.Reactions.GroupByType(),
})
if err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("ChangeIssueReaction.HTMLString", err)
return
}
2023-07-04 20:36:08 +02:00
ctx.JSON(http.StatusOK, map[string]any{
"html": html,
})
}
// GetIssueAttachments returns attachments for the issue
func GetIssueAttachments(ctx *context.Context) {
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
2022-01-20 18:46:10 +01:00
attachments := make([]*api.Attachment, len(issue.Attachments))
for i := 0; i < len(issue.Attachments); i++ {
2023-07-10 17:31:19 +08:00
attachments[i] = convert.ToAttachment(ctx.Repo.Repository, issue.Attachments[i])
}
ctx.JSON(http.StatusOK, attachments)
}
2023-07-04 20:36:08 +02:00
func updateAttachments(ctx *context.Context, item any, files []string) error {
2021-11-19 21:39:57 +08:00
var attachments []*repo_model.Attachment
switch content := item.(type) {
case *issues_model.Issue:
attachments = content.Attachments
case *issues_model.Comment:
attachments = content.Attachments
default:
2022-02-26 12:15:32 +00:00
return fmt.Errorf("unknown Type: %T", content)
}
for i := 0; i < len(attachments); i++ {
2023-01-11 13:31:16 +08:00
if util.SliceContainsString(files, attachments[i].UUID) {
continue
}
if err := repo_model.DeleteAttachment(ctx, attachments[i], true); err != nil {
return err
}
}
var err error
if len(files) > 0 {
switch content := item.(type) {
case *issues_model.Issue:
2023-09-29 14:12:54 +02:00
err = issues_model.UpdateIssueAttachments(ctx, content.ID, files)
case *issues_model.Comment:
err = issues_model.UpdateCommentAttachments(ctx, content, files)
default:
2022-02-26 12:15:32 +00:00
return fmt.Errorf("unknown Type: %T", content)
}
if err != nil {
return err
}
}
switch content := item.(type) {
case *issues_model.Issue:
content.Attachments, err = repo_model.GetAttachmentsByIssueID(ctx, content.ID)
case *issues_model.Comment:
content.Attachments, err = repo_model.GetAttachmentsByCommentID(ctx, content.ID)
default:
2022-02-26 12:15:32 +00:00
return fmt.Errorf("unknown Type: %T", content)
}
return err
}
func commentContentHTML(ctx *context.Context, content template.HTML) template.HTML {
if strings.TrimSpace(string(content)) == "" {
return htmlutil.HTMLFormat(`<span class="no-content">%s</span>`, ctx.Tr("repo.issues.no_content"))
}
return content
}
func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) template.HTML {
attachHTML, err := ctx.RenderToHTML(tplAttachment, map[string]any{
2023-03-03 01:44:06 +08:00
"ctxData": ctx.Data,
"Attachments": attachments,
"Content": content,
})
if err != nil {
ctx.ServerError("attachmentsHTML.HTMLString", err)
return ""
}
return attachHTML
}