Browse Source

release: able to add attchments to release (#1614)

Added new config section '[release.attachment]’.
Unknwon 7 years ago
parent
commit
1df54ea0cd

+ 5 - 5
cmd/web.go

@@ -339,15 +339,15 @@ func runWeb(ctx *cli.Context) error {
 			defer fr.Close()
 
 			ctx.Header().Set("Cache-Control", "public,max-age=86400")
+			fmt.Println("attach.Name:", attach.Name)
 			ctx.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name))
-			// Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
-			// We must put the name in " manually.
-			if err = repo.ServeData(ctx, "\""+attach.Name+"\"", fr); err != nil {
+			if err = repo.ServeData(ctx, attach.Name, fr); err != nil {
 				ctx.Handle(500, "ServeData", err)
 				return
 			}
 		})
 		m.Post("/issues/attachments", repo.UploadIssueAttachment)
+		m.Post("/releases/attachments", repo.UploadReleaseAttachment)
 	}, ignSignIn)
 
 	m.Group("/:username", func() {
@@ -490,7 +490,7 @@ func runWeb(ctx *cli.Context) error {
 		// So they can apply their own enable/disable logic on routers.
 		m.Group("/issues", func() {
 			m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
-				Post(bindIgnErr(form.CreateIssue{}), repo.NewIssuePost)
+				Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost)
 
 			m.Group("/:index", func() {
 				m.Post("/label", repo.UpdateIssueLabel)
@@ -538,7 +538,7 @@ func runWeb(ctx *cli.Context) error {
 		// e.g. /org1/test-repo/compare/master...org1:develop
 		// which should be /org1/test-repo/compare/master...develop
 		m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest).
-			Post(bindIgnErr(form.CreateIssue{}), repo.CompareAndPullRequestPost)
+			Post(bindIgnErr(form.NewIssue{}), repo.CompareAndPullRequestPost)
 
 		m.Group("", func() {
 			m.Combo("/_edit/*").Get(repo.EditFile).

+ 16 - 2
conf/app.ini

@@ -114,6 +114,19 @@ FILE_MAX_SIZE = 3
 ; Maximum number of files per upload
 MAX_FILES = 5
 
+; Attachment settings for releases
+[release.attachment]
+; Whether attachments are enabled. Defaults to `true`
+ENABLED = true
+; Path for attachments. Defaults to `data/attachments`
+PATH = data/attachments
+; One or more allowed types, e.g. image/jpeg|image/png
+ALLOWED_TYPES = */*
+; Max size of each file. Defaults to 32MB
+MAX_SIZE = 32
+; Max number of files per upload. Defaults to 10
+MAX_FILES = 10
+
 [markdown]
 ; Enable hard line break extension
 ENABLE_HARD_LINE_BREAK = false
@@ -273,6 +286,7 @@ DISABLE_GRAVATAR = false
 ; This value will be forced to be false in offline mode or Gravatar is disbaled.
 ENABLE_FEDERATED_AVATAR = true
 
+; Attachment settings for issues
 [attachment]
 ; Whether attachments are enabled. Defaults to `true`
 ENABLE = true
@@ -280,9 +294,9 @@ ENABLE = true
 PATH = data/attachments
 ; One or more allowed types, e.g. image/jpeg|image/png
 ALLOWED_TYPES = image/jpeg|image/png
-; Max size of each file. Defaults to 32MB
+; Max size of each file. Defaults to 4MB
 MAX_SIZE = 4
-; Max number of files per upload. Defaults to 10
+; Max number of files per upload. Defaults to 5
 MAX_FILES = 5
 
 [time]

+ 1 - 1
gogs.go

@@ -16,7 +16,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.10.17.0313"
+const APP_VER = "0.10.18.0313"
 
 func init() {
 	setting.AppVer = APP_VER

+ 13 - 3
models/attachment.go

@@ -110,7 +110,7 @@ func GetAttachmentByUUID(uuid string) (*Attachment, error) {
 }
 
 func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) {
-	attachments := make([]*Attachment, 0, 10)
+	attachments := make([]*Attachment, 0, 5)
 	return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
 }
 
@@ -120,15 +120,25 @@ func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
 }
 
 func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) {
-	attachments := make([]*Attachment, 0, 10)
+	attachments := make([]*Attachment, 0, 5)
 	return attachments, e.Where("comment_id=?", commentID).Find(&attachments)
 }
 
-// GetAttachmentsByCommentID returns all attachments if comment by given ID.
+// GetAttachmentsByCommentID returns all attachments of a comment.
 func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
 	return getAttachmentsByCommentID(x, commentID)
 }
 
+func getAttachmentsByReleaseID(e Engine, releaseID int64) ([]*Attachment, error) {
+	attachments := make([]*Attachment, 0, 10)
+	return attachments, e.Where("release_id = ?", releaseID).Find(&attachments)
+}
+
+// GetAttachmentsByReleaseID returns all attachments of a release.
+func GetAttachmentsByReleaseID(releaseID int64) ([]*Attachment, error) {
+	return getAttachmentsByReleaseID(x, releaseID)
+}
+
 // DeleteAttachment deletes the given attachment and optionally the associated file.
 func DeleteAttachment(a *Attachment, remove bool) error {
 	_, err := DeleteAttachments([]*Attachment{a}, remove)

+ 1 - 1
models/issue.go

@@ -741,7 +741,7 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
 	return opts.Issue.loadAttributes(e)
 }
 
-// NewIssue creates new issue with labels for repository.
+// NewIssue creates new issue with labels and attachments for repository.
 func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
 	sess := x.NewSession()
 	defer sessionRelease(sess)

+ 52 - 5
models/release.go

@@ -39,6 +39,8 @@ type Release struct {
 
 	Created     time.Time `xorm:"-"`
 	CreatedUnix int64
+
+	Attachments []*Attachment `xorm:"-"`
 }
 
 func (r *Release) BeforeInsert() {
@@ -74,6 +76,13 @@ func (r *Release) loadAttributes(e Engine) (err error) {
 		}
 	}
 
+	if r.Attachments == nil {
+		r.Attachments, err = getAttachmentsByReleaseID(e, r.ID)
+		if err != nil {
+			return fmt.Errorf("getAttachmentsByReleaseID [%d]: %v", r.ID, err)
+		}
+	}
+
 	return nil
 }
 
@@ -150,8 +159,8 @@ func (r *Release) preparePublishWebhooks() {
 	}
 }
 
-// CreateRelease creates a new release of repository.
-func CreateRelease(gitRepo *git.Repository, r *Release) error {
+// NewRelease creates a new release with attachments for repository.
+func NewRelease(gitRepo *git.Repository, r *Release, uuids []string) error {
 	isExist, err := IsReleaseExist(r.RepoID, r.TagName)
 	if err != nil {
 		return err
@@ -163,10 +172,27 @@ func CreateRelease(gitRepo *git.Repository, r *Release) error {
 		return err
 	}
 	r.LowerTagName = strings.ToLower(r.TagName)
-	if _, err = x.Insert(r); err != nil {
+
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if _, err = sess.Insert(r); err != nil {
 		return fmt.Errorf("Insert: %v", err)
 	}
 
+	if len(uuids) > 0 {
+		if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil {
+			return fmt.Errorf("link attachments: %v", err)
+		}
+	}
+
+	if err = sess.Commit(); err != nil {
+		return fmt.Errorf("Commit: %v", err)
+	}
+
 	// Only send webhook when actually published, skip drafts
 	if r.IsDraft {
 		return nil
@@ -254,15 +280,36 @@ func SortReleases(rels []*Release) {
 }
 
 // UpdateRelease updates information of a release.
-func UpdateRelease(doer *User, gitRepo *git.Repository, r *Release, isPublish bool) (err error) {
+func UpdateRelease(doer *User, gitRepo *git.Repository, r *Release, isPublish bool, uuids []string) (err error) {
 	if err = createTag(gitRepo, r); err != nil {
 		return fmt.Errorf("createTag: %v", err)
 	}
 
 	r.PublisherID = doer.ID
-	if _, err = x.Id(r.ID).AllCols().Update(r); err != nil {
+
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
 		return err
 	}
+	if _, err = sess.Id(r.ID).AllCols().Update(r); err != nil {
+		return fmt.Errorf("Update: %v", err)
+	}
+
+	// Unlink all current attachments and link back later if still valid
+	if _, err = sess.Exec("UPDATE attachment SET release_id = 0 WHERE release_id = ?", r.ID); err != nil {
+		return fmt.Errorf("unlink current attachments: %v", err)
+	}
+
+	if len(uuids) > 0 {
+		if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil {
+			return fmt.Errorf("link attachments: %v", err)
+		}
+	}
+
+	if err = sess.Commit(); err != nil {
+		return fmt.Errorf("Commit: %v", err)
+	}
 
 	if !isPublish {
 		return nil

File diff suppressed because it is too large
+ 0 - 0
modules/bindata/bindata.go


+ 4 - 2
modules/form/repo.go

@@ -200,7 +200,7 @@ func (f *NewDiscordHook) Validate(ctx *macaron.Context, errs binding.Errors) bin
 // |___/____  >____  >____/  \___  >
 //          \/     \/            \/
 
-type CreateIssue struct {
+type NewIssue struct {
 	Title       string `binding:"Required;MaxSize(255)"`
 	LabelIDs    string `form:"label_ids"`
 	MilestoneID int64
@@ -209,7 +209,7 @@ type CreateIssue struct {
 	Files       []string
 }
 
-func (f *CreateIssue) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
+func (f *NewIssue) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 	return validate(errs, ctx.Data, f, ctx.Locale)
 }
 
@@ -279,6 +279,7 @@ type NewRelease struct {
 	Content    string
 	Draft      string
 	Prerelease bool
+	Files      []string
 }
 
 func (f *NewRelease) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -290,6 +291,7 @@ type EditRelease struct {
 	Content    string
 	Draft      string
 	Prerelease bool
+	Files      []string
 }
 
 func (f *EditRelease) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

+ 13 - 0
modules/setting/setting.go

@@ -146,6 +146,17 @@ var (
 		PagingNum      int
 	}
 
+	// Release settigns
+	Release struct {
+		Attachment struct {
+			Enabled      bool
+			TempPath     string
+			AllowedTypes []string `delim:"|"`
+			MaxSize      int64
+			MaxFiles     int
+		} `ini:"-"`
+	}
+
 	// Markdown sttings
 	Markdown struct {
 		EnableHardLineBreak bool
@@ -590,6 +601,8 @@ func NewContext() {
 		log.Fatal(2, "Fail to map HTTP settings: %v", err)
 	} else if err = Cfg.Section("webhook").MapTo(&Webhook); err != nil {
 		log.Fatal(2, "Fail to map Webhook settings: %v", err)
+	} else if err = Cfg.Section("release.attachment").MapTo(&Release.Attachment); err != nil {
+		log.Fatal(2, "Fail to map Release.Attachment settings: %v", err)
 	} else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
 		log.Fatal(2, "Fail to map Markdown settings: %v", err)
 	} else if err = Cfg.Section("smartypants").MapTo(&Smartypants); err != nil {

+ 2 - 2
routers/repo/download.go

@@ -23,7 +23,7 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error {
 
 	if !base.IsTextFile(buf) {
 		if !base.IsImageFile(buf) {
-			ctx.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+path.Base(ctx.Repo.TreePath)+"\"")
+			ctx.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"")
 			ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
 		}
 	} else if !ctx.QueryBool("render") {
@@ -40,7 +40,7 @@ func ServeBlob(ctx *context.Context, blob *git.Blob) error {
 		return err
 	}
 
-	return ServeData(ctx, ctx.Repo.TreePath, dataRc)
+	return ServeData(ctx, path.Base(ctx.Repo.TreePath), dataRc)
 }
 
 func SingleDownload(ctx *context.Context) {

+ 20 - 21
routers/repo/issue.go

@@ -348,7 +348,7 @@ func NewIssue(ctx *context.Context) {
 	ctx.HTML(200, ISSUE_NEW)
 }
 
-func ValidateRepoMetas(ctx *context.Context, f form.CreateIssue) ([]int64, int64, int64) {
+func ValidateRepoMetas(ctx *context.Context, f form.NewIssue) ([]int64, int64, int64) {
 	var (
 		repo = ctx.Repo.Repository
 		err  error
@@ -402,34 +402,30 @@ func ValidateRepoMetas(ctx *context.Context, f form.CreateIssue) ([]int64, int64
 	return labelIDs, milestoneID, assigneeID
 }
 
-func NewIssuePost(ctx *context.Context, f form.CreateIssue) {
+func NewIssuePost(ctx *context.Context, f form.NewIssue) {
 	ctx.Data["Title"] = ctx.Tr("repo.issues.new")
 	ctx.Data["PageIsIssueList"] = true
 	ctx.Data["RequireHighlightJS"] = true
 	ctx.Data["RequireSimpleMDE"] = true
 	renderAttachmentSettings(ctx)
 
-	var (
-		repo        = ctx.Repo.Repository
-		attachments []string
-	)
-
 	labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, f)
 	if ctx.Written() {
 		return
 	}
 
-	if setting.AttachmentEnabled {
-		attachments = f.Files
-	}
-
 	if ctx.HasError() {
 		ctx.HTML(200, ISSUE_NEW)
 		return
 	}
 
+	var attachments []string
+	if setting.AttachmentEnabled {
+		attachments = f.Files
+	}
+
 	issue := &models.Issue{
-		RepoID:      repo.ID,
+		RepoID:      ctx.Repo.Repository.ID,
 		Title:       f.Title,
 		PosterID:    ctx.User.ID,
 		Poster:      ctx.User,
@@ -437,21 +433,16 @@ func NewIssuePost(ctx *context.Context, f form.CreateIssue) {
 		AssigneeID:  assigneeID,
 		Content:     f.Content,
 	}
-	if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil {
+	if err := models.NewIssue(ctx.Repo.Repository, issue, labelIDs, attachments); err != nil {
 		ctx.Handle(500, "NewIssue", err)
 		return
 	}
 
-	log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
+	log.Trace("Issue created: %d/%d", ctx.Repo.Repository.ID, issue.ID)
 	ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
 }
 
-func UploadIssueAttachment(ctx *context.Context) {
-	if !setting.AttachmentEnabled {
-		ctx.Error(404, "attachment is not enabled")
-		return
-	}
-
+func uploadAttachment(ctx *context.Context, allowedTypes []string) {
 	file, header, err := ctx.Req.FormFile("file")
 	if err != nil {
 		ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
@@ -466,7 +457,6 @@ func UploadIssueAttachment(ctx *context.Context) {
 	}
 	fileType := http.DetectContentType(buf)
 
-	allowedTypes := strings.Split(setting.AttachmentAllowedTypes, ",")
 	allowed := false
 	for _, t := range allowedTypes {
 		t := strings.Trim(t, " ")
@@ -493,6 +483,15 @@ func UploadIssueAttachment(ctx *context.Context) {
 	})
 }
 
+func UploadIssueAttachment(ctx *context.Context) {
+	if !setting.AttachmentEnabled {
+		ctx.NotFound()
+		return
+	}
+
+	uploadAttachment(ctx, strings.Split(setting.AttachmentAllowedTypes, ","))
+}
+
 func ViewIssue(ctx *context.Context) {
 	ctx.Data["RequireHighlightJS"] = true
 	ctx.Data["RequireDropzone"] = true

+ 1 - 1
routers/repo/pull.go

@@ -634,7 +634,7 @@ func CompareAndPullRequest(ctx *context.Context) {
 	ctx.HTML(200, COMPARE_PULL)
 }
 
-func CompareAndPullRequestPost(ctx *context.Context, f form.CreateIssue) {
+func CompareAndPullRequestPost(ctx *context.Context, f form.NewIssue) {
 	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
 	ctx.Data["PageIsComparePull"] = true
 	ctx.Data["IsDiffCompare"] = true

+ 38 - 3
routers/repo/release.go

@@ -6,6 +6,7 @@ package repo
 
 import (
 	"fmt"
+	"strings"
 
 	log "gopkg.in/clog.v1"
 
@@ -14,6 +15,7 @@ import (
 	"github.com/gogits/gogs/modules/context"
 	"github.com/gogits/gogs/modules/form"
 	"github.com/gogits/gogs/modules/markdown"
+	"github.com/gogits/gogs/modules/setting"
 )
 
 const (
@@ -148,16 +150,26 @@ func Releases(ctx *context.Context) {
 	ctx.HTML(200, RELEASES)
 }
 
+func renderReleaseAttachmentSettings(ctx *context.Context) {
+	ctx.Data["RequireDropzone"] = true
+	ctx.Data["IsAttachmentEnabled"] = setting.Release.Attachment.Enabled
+	ctx.Data["AttachmentAllowedTypes"] = strings.Join(setting.Release.Attachment.AllowedTypes, ",")
+	ctx.Data["AttachmentMaxSize"] = setting.Release.Attachment.MaxSize
+	ctx.Data["AttachmentMaxFiles"] = setting.Release.Attachment.MaxFiles
+}
+
 func NewRelease(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
 	ctx.Data["PageIsReleaseList"] = true
 	ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
+	renderReleaseAttachmentSettings(ctx)
 	ctx.HTML(200, RELEASE_NEW)
 }
 
 func NewReleasePost(ctx *context.Context, f form.NewRelease) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
 	ctx.Data["PageIsReleaseList"] = true
+	renderReleaseAttachmentSettings(ctx)
 
 	if ctx.HasError() {
 		ctx.HTML(200, RELEASE_NEW)
@@ -169,6 +181,7 @@ func NewReleasePost(ctx *context.Context, f form.NewRelease) {
 		return
 	}
 
+	// Use current time if tag not yet exist, otherwise get time from Git
 	var tagCreatedUnix int64
 	tag, err := ctx.Repo.GitRepo.GetTag(f.TagName)
 	if err == nil {
@@ -190,6 +203,11 @@ func NewReleasePost(ctx *context.Context, f form.NewRelease) {
 		return
 	}
 
+	var attachments []string
+	if setting.Release.Attachment.Enabled {
+		attachments = f.Files
+	}
+
 	rel := &models.Release{
 		RepoID:       ctx.Repo.Repository.ID,
 		PublisherID:  ctx.User.ID,
@@ -203,7 +221,7 @@ func NewReleasePost(ctx *context.Context, f form.NewRelease) {
 		IsPrerelease: f.Prerelease,
 		CreatedUnix:  tagCreatedUnix,
 	}
-	if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil {
+	if err = models.NewRelease(ctx.Repo.GitRepo, rel, attachments); err != nil {
 		ctx.Data["Err_TagName"] = true
 		switch {
 		case models.IsErrReleaseAlreadyExist(err):
@@ -211,7 +229,7 @@ func NewReleasePost(ctx *context.Context, f form.NewRelease) {
 		case models.IsErrInvalidTagName(err):
 			ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), RELEASE_NEW, &f)
 		default:
-			ctx.Handle(500, "CreateRelease", err)
+			ctx.Handle(500, "NewRelease", err)
 		}
 		return
 	}
@@ -224,6 +242,7 @@ func EditRelease(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
 	ctx.Data["PageIsReleaseList"] = true
 	ctx.Data["PageIsEditRelease"] = true
+	renderReleaseAttachmentSettings(ctx)
 
 	tagName := ctx.Params("*")
 	rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName)
@@ -240,6 +259,7 @@ func EditRelease(ctx *context.Context) {
 	ctx.Data["tag_target"] = rel.Target
 	ctx.Data["title"] = rel.Title
 	ctx.Data["content"] = rel.Note
+	ctx.Data["attachments"] = rel.Attachments
 	ctx.Data["prerelease"] = rel.IsPrerelease
 	ctx.Data["IsDraft"] = rel.IsDraft
 
@@ -250,6 +270,7 @@ func EditReleasePost(ctx *context.Context, f form.EditRelease) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
 	ctx.Data["PageIsReleaseList"] = true
 	ctx.Data["PageIsEditRelease"] = true
+	renderReleaseAttachmentSettings(ctx)
 
 	tagName := ctx.Params("*")
 	rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName)
@@ -265,6 +286,7 @@ func EditReleasePost(ctx *context.Context, f form.EditRelease) {
 	ctx.Data["tag_target"] = rel.Target
 	ctx.Data["title"] = rel.Title
 	ctx.Data["content"] = rel.Note
+	ctx.Data["attachments"] = rel.Attachments
 	ctx.Data["prerelease"] = rel.IsPrerelease
 	ctx.Data["IsDraft"] = rel.IsDraft
 
@@ -273,18 +295,31 @@ func EditReleasePost(ctx *context.Context, f form.EditRelease) {
 		return
 	}
 
+	var attachments []string
+	if setting.Release.Attachment.Enabled {
+		attachments = f.Files
+	}
+
 	isPublish := rel.IsDraft && len(f.Draft) == 0
 	rel.Title = f.Title
 	rel.Note = f.Content
 	rel.IsDraft = len(f.Draft) > 0
 	rel.IsPrerelease = f.Prerelease
-	if err = models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, isPublish); err != nil {
+	if err = models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, isPublish, attachments); err != nil {
 		ctx.Handle(500, "UpdateRelease", err)
 		return
 	}
 	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
 }
 
+func UploadReleaseAttachment(ctx *context.Context) {
+	if !setting.Release.Attachment.Enabled {
+		ctx.NotFound()
+		return
+	}
+	uploadAttachment(ctx, setting.Release.Attachment.AllowedTypes)
+}
+
 func DeleteRelease(ctx *context.Context) {
 	if err := models.DeleteReleaseOfRepoByID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
 		ctx.Flash.Error("DeleteReleaseByID: " + err.Error())

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.10.17.0313
+0.10.18.0313

+ 13 - 6
templates/repo/release/list.tmpl

@@ -51,12 +51,19 @@
 							<div class="download">
 								<h2>{{$.i18n.Tr "repo.release.downloads"}}</h2>
 								<ul class="list">
-									<li>
-										<a href="{{$.RepoLink}}/archive/{{.TagName}}.zip" rel="nofollow"><i class="octicon octicon-file-zip"></i> {{$.i18n.Tr "repo.release.source_code"}} (ZIP)</a>
-									</li>
-									<li>
-										<a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz"><i class="octicon octicon-file-zip"></i> {{$.i18n.Tr "repo.release.source_code"}} (TAR.GZ)</a>
-									</li>
+									{{range .Attachments}}
+										<li>
+											<i class="octicon octicon-package"></i> <a href="{{AppSubUrl}}/attachments/{{.UUID}}" rel="nofollow">{{.Name}}</a>
+										</li>
+									{{end}}
+									{{if not .IsDraft}}
+										<li>
+											<i class="octicon octicon-file-zip"></i> <a href="{{$.RepoLink}}/archive/{{.TagName}}.zip" rel="nofollow">{{$.i18n.Tr "repo.release.source_code"}} (ZIP)</a>
+										</li>
+										<li>
+											<i class="octicon octicon-file-zip"></i> <a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz">{{$.i18n.Tr "repo.release.source_code"}} (TAR.GZ)</a>
+										</li>
+									{{end}}
 								</ul>
 							</div>
 						{{else}}

+ 59 - 32
templates/repo/release/new.tmpl

@@ -48,42 +48,60 @@
 					<label>{{.i18n.Tr "repo.release.content"}}</label>
 					<textarea name="content">{{.content}}</textarea>
 				</div>
+				{{if .attachments}}
+					<table class="ui table">
+						<thead></thead>
+						<tbody>
+							{{range .attachments}}
+								<tr>
+									<td>
+										<a target="_blank" href="{{AppSubUrl}}/attachments/{{.UUID}}" rel="nofollow">{{.Name}}</a>
+										<a class="ui text red right delete-attachment-button" href="#"><i class="octicon octicon-x" data-uuid="{{.UUID}}"></i></a>
+										<input name="files" type="hidden" value="{{.UUID}}">
+									</td>
+								</tr>
+							{{end}}
+						</tbody>
+					</table>
+				{{end}}
+				{{if .IsAttachmentEnabled}}
+					<div class="files"></div>
+					<div class="ui basic button dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/releases/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
+				{{end}}
 			</div>
 			<div class="ui container">
-				<div class="ui divider"></div>
-				<div class="ui text right">
-					<div class="prerelease field">
-						<div class="ui checkbox">
-							<input type="checkbox" name="prerelease" {{if .prerelease}}checked{{end}}>
-							<label><strong>{{.i18n.Tr "repo.release.prerelease_desc"}}</strong></label>
-						</div>
+				<div class="prerelease field">
+					<div class="ui checkbox">
+						<input type="checkbox" name="prerelease" {{if .prerelease}}checked{{end}}>
+						<label><strong>{{.i18n.Tr "repo.release.prerelease_desc"}}</strong></label>
 					</div>
-					<span class="help">{{.i18n.Tr "repo.release.prerelease_helper"}}</span>
-					<div class="field">
-						<a class="ui blue basic button" href="{{.RepoLink}}/releases">
-							{{.i18n.Tr "repo.release.cancel"}}
-						</a>
-						{{/* Release didn't save as draft at first time is not possible to be draft again because the Git tag had been created. */}}
-						{{if or (not .PageIsEditRelease) .IsDraft}}
-							<input class="ui grey button" type="submit" name="draft" value="{{.i18n.Tr "repo.release.save_draft"}}"/>
-						{{end}}
-						{{if .PageIsEditRelease}}
-							<button class="ui green button">
-								{{if .IsDraft}}
-									{{.i18n.Tr "repo.release.publish"}}
-								{{else}}
-									{{.i18n.Tr "repo.release.edit_release"}}
-								{{end}}
-							</button>
-							<a class="ui red button delete-button" data-url="{{$.RepoLink}}/releases/delete" data-id="{{.ID}}">
-								{{$.i18n.Tr "repo.release.delete_release"}}
-							</a>
-						{{else}}
-							<button class="ui green button">
+				</div>
+				<span class="help">{{.i18n.Tr "repo.release.prerelease_helper"}}</span>
+				<div class="ui divider"></div>
+				<div class="field">
+					{{if .PageIsEditRelease}}
+						<button class="ui green button">
+							{{if .IsDraft}}
 								{{.i18n.Tr "repo.release.publish"}}
-							</button>
-						{{end}}
-					</div>
+							{{else}}
+								{{.i18n.Tr "repo.release.edit_release"}}
+							{{end}}
+						</button>
+						<a class="ui red button delete-button" data-url="{{$.RepoLink}}/releases/delete" data-id="{{.ID}}">
+							{{$.i18n.Tr "repo.release.delete_release"}}
+						</a>
+					{{else}}
+						<button class="ui green button">
+							{{.i18n.Tr "repo.release.publish"}}
+						</button>
+					{{end}}
+					{{/* Release didn't save as draft at first time is not possible to be draft again because the Git tag had been created. */}}
+					{{if or (not .PageIsEditRelease) .IsDraft}}
+						<input class="ui grey button" type="submit" name="draft" value="{{.i18n.Tr "repo.release.save_draft"}}"/>
+					{{end}}
+					<a class="ui basic button" href="{{.RepoLink}}/releases">
+						{{.i18n.Tr "repo.release.cancel"}}
+					</a>
 				</div>
 			</div>
 		</form>
@@ -102,4 +120,13 @@
 		{{template "base/delete_modal_actions" .}}
 	</div>
 {{end}}
+
+<script>
+	// Delete attachment field
+	$('.delete-attachment-button').click(function (e) {
+		$(this).parentsUntil('tbody').remove();
+		e.preventDefault();
+	})
+</script>
+
 {{template "base/footer" .}}

Some files were not shown because too many files changed in this diff