Enable Uploading/Removing Attachments When Editing an Issue/Comment (#8426)
parent
d7d348ea86
commit
8c909820a9
|
@ -855,6 +855,26 @@ func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branc
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAttachments update attachments by UUIDs for the issue
|
||||||
|
func (issue *Issue) UpdateAttachments(uuids []string) (err error) {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attachments, err := getAttachmentsByUUIDs(sess, uuids)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(attachments); i++ {
|
||||||
|
attachments[i].IssueID = issue.ID
|
||||||
|
if err := updateAttachment(sess, attachments[i]); err != nil {
|
||||||
|
return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
// ChangeContent changes issue content, as the given user.
|
// ChangeContent changes issue content, as the given user.
|
||||||
func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
|
func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
|
||||||
oldContent := issue.Content
|
oldContent := issue.Content
|
||||||
|
|
|
@ -357,6 +357,27 @@ func (c *Comment) LoadAttachments() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAttachments update attachments by UUIDs for the comment
|
||||||
|
func (c *Comment) UpdateAttachments(uuids []string) error {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attachments, err := getAttachmentsByUUIDs(sess, uuids)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(attachments); i++ {
|
||||||
|
attachments[i].IssueID = c.IssueID
|
||||||
|
attachments[i].CommentID = c.ID
|
||||||
|
if err := updateAttachment(sess, attachments[i]); err != nil {
|
||||||
|
return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
// LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees
|
// LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees
|
||||||
func (c *Comment) LoadAssigneeUser() error {
|
func (c *Comment) LoadAssigneeUser() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -35,6 +35,16 @@ func ExistsInSlice(target string, slice []string) bool {
|
||||||
return i < len(slice)
|
return i < len(slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsStringInSlice sequential searches if string exists in slice.
|
||||||
|
func IsStringInSlice(target string, slice []string) bool {
|
||||||
|
for i := 0; i < len(slice); i++ {
|
||||||
|
if slice[i] == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// IsEqualSlice returns true if slices are equal.
|
// IsEqualSlice returns true if slices are equal.
|
||||||
func IsEqualSlice(target []string, source []string) bool {
|
func IsEqualSlice(target []string, source []string) bool {
|
||||||
if len(target) != len(source) {
|
if len(target) != len(source) {
|
||||||
|
|
|
@ -865,6 +865,73 @@ function initRepository() {
|
||||||
issuesTribute.attach($textarea.get());
|
issuesTribute.attach($textarea.get());
|
||||||
emojiTribute.attach($textarea.get());
|
emojiTribute.attach($textarea.get());
|
||||||
|
|
||||||
|
const $dropzone = $editContentZone.find('.dropzone');
|
||||||
|
$dropzone.data("saved", false);
|
||||||
|
const $files = $editContentZone.find('.comment-files');
|
||||||
|
if ($dropzone.length > 0) {
|
||||||
|
const filenameDict = {};
|
||||||
|
$dropzone.dropzone({
|
||||||
|
url: $dropzone.data('upload-url'),
|
||||||
|
headers: {"X-Csrf-Token": csrf},
|
||||||
|
maxFiles: $dropzone.data('max-file'),
|
||||||
|
maxFilesize: $dropzone.data('max-size'),
|
||||||
|
acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'),
|
||||||
|
addRemoveLinks: true,
|
||||||
|
dictDefaultMessage: $dropzone.data('default-message'),
|
||||||
|
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
||||||
|
dictFileTooBig: $dropzone.data('file-too-big'),
|
||||||
|
dictRemoveFile: $dropzone.data('remove-file'),
|
||||||
|
init: function () {
|
||||||
|
this.on("success", function (file, data) {
|
||||||
|
filenameDict[file.name] = {
|
||||||
|
"uuid": data.uuid,
|
||||||
|
"submitted": false
|
||||||
|
}
|
||||||
|
const input = $('<input id="' + data.uuid + '" name="files" type="hidden">').val(data.uuid);
|
||||||
|
$files.append(input);
|
||||||
|
});
|
||||||
|
this.on("removedfile", function (file) {
|
||||||
|
if (!(file.name in filenameDict)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#' + filenameDict[file.name].uuid).remove();
|
||||||
|
if ($dropzone.data('remove-url') && $dropzone.data('csrf') && !filenameDict[file.name].submitted) {
|
||||||
|
$.post($dropzone.data('remove-url'), {
|
||||||
|
file: filenameDict[file.name].uuid,
|
||||||
|
_csrf: $dropzone.data('csrf')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.on("submit", function () {
|
||||||
|
$.each(filenameDict, function(name){
|
||||||
|
filenameDict[name].submitted = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.on("reload", function (){
|
||||||
|
$.getJSON($editContentZone.data('attachment-url'), function(data){
|
||||||
|
const drop = $dropzone.get(0).dropzone;
|
||||||
|
drop.removeAllFiles(true);
|
||||||
|
$files.empty();
|
||||||
|
$.each(data, function(){
|
||||||
|
const imgSrc = $dropzone.data('upload-url') + "/" + this.uuid;
|
||||||
|
drop.emit("addedfile", this);
|
||||||
|
drop.emit("thumbnail", this, imgSrc);
|
||||||
|
drop.emit("complete", this);
|
||||||
|
drop.files.push(this);
|
||||||
|
filenameDict[this.name] = {
|
||||||
|
"submitted": true,
|
||||||
|
"uuid": this.uuid
|
||||||
|
}
|
||||||
|
$dropzone.find("img[src='" + imgSrc + "']").css("max-width", "100%");
|
||||||
|
const input = $('<input id="' + this.uuid + '" name="files" type="hidden">').val(this.uuid);
|
||||||
|
$files.append(input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$dropzone.get(0).dropzone.emit("reload");
|
||||||
|
}
|
||||||
// Give new write/preview data-tab name to distinguish from others
|
// Give new write/preview data-tab name to distinguish from others
|
||||||
const $editContentForm = $editContentZone.find('.ui.comment.form');
|
const $editContentForm = $editContentZone.find('.ui.comment.form');
|
||||||
const $tabMenu = $editContentForm.find('.tabular.menu');
|
const $tabMenu = $editContentForm.find('.tabular.menu');
|
||||||
|
@ -880,27 +947,49 @@ function initRepository() {
|
||||||
$editContentZone.find('.cancel.button').click(function () {
|
$editContentZone.find('.cancel.button').click(function () {
|
||||||
$renderContent.show();
|
$renderContent.show();
|
||||||
$editContentZone.hide();
|
$editContentZone.hide();
|
||||||
|
$dropzone.get(0).dropzone.emit("reload");
|
||||||
});
|
});
|
||||||
$editContentZone.find('.save.button').click(function () {
|
$editContentZone.find('.save.button').click(function () {
|
||||||
$renderContent.show();
|
$renderContent.show();
|
||||||
$editContentZone.hide();
|
$editContentZone.hide();
|
||||||
|
const $attachments = $files.find("[name=files]").map(function(){
|
||||||
|
return $(this).val();
|
||||||
|
}).get();
|
||||||
$.post($editContentZone.data('update-url'), {
|
$.post($editContentZone.data('update-url'), {
|
||||||
"_csrf": csrf,
|
"_csrf": csrf,
|
||||||
"content": $textarea.val(),
|
"content": $textarea.val(),
|
||||||
"context": $editContentZone.data('context')
|
"context": $editContentZone.data('context'),
|
||||||
},
|
"files": $attachments
|
||||||
function (data) {
|
},
|
||||||
if (data.length == 0) {
|
function (data) {
|
||||||
$renderContent.html($('#no-content').html());
|
if (data.length == 0) {
|
||||||
} else {
|
$renderContent.html($('#no-content').html());
|
||||||
$renderContent.html(data.content);
|
} else {
|
||||||
emojify.run($renderContent[0]);
|
$renderContent.html(data.content);
|
||||||
$('pre code', $renderContent[0]).each(function () {
|
emojify.run($renderContent[0]);
|
||||||
hljs.highlightBlock(this);
|
$('pre code', $renderContent[0]).each(function () {
|
||||||
});
|
hljs.highlightBlock(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const $content = $segment.parent();
|
||||||
|
if(!$content.find(".ui.small.images").length){
|
||||||
|
if(data.attachments != ""){
|
||||||
|
$content.append(
|
||||||
|
'<div class="ui bottom attached segment">' +
|
||||||
|
' <div class="ui small images">' +
|
||||||
|
' </div>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
$content.find(".ui.small.images").html(data.attachments);
|
||||||
}
|
}
|
||||||
});
|
} else if (data.attachments == "") {
|
||||||
|
$content.find(".ui.small.images").parent().remove();
|
||||||
|
} else {
|
||||||
|
$content.find(".ui.small.images").html(data.attachments);
|
||||||
|
}
|
||||||
|
$dropzone.get(0).dropzone.emit("submit");
|
||||||
|
$dropzone.get(0).dropzone.emit("reload");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$textarea = $segment.find('textarea');
|
$textarea = $segment.find('textarea');
|
||||||
|
|
|
@ -63,3 +63,25 @@ func UploadAttachment(ctx *context.Context) {
|
||||||
"uuid": attach.UUID,
|
"uuid": attach.UUID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteAttachment response for deleting issue's attachment
|
||||||
|
func DeleteAttachment(ctx *context.Context) {
|
||||||
|
file := ctx.Query("file")
|
||||||
|
attach, err := models.GetAttachmentByUUID(file)
|
||||||
|
if !ctx.IsSigned || (ctx.User.ID != attach.UploaderID) {
|
||||||
|
ctx.Error(403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(400, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = models.DeleteAttachment(attach, true)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(200, map[string]string{
|
||||||
|
"uuid": attach.UUID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
tplAttachment base.TplName = "repo/issue/view_content/attachments"
|
||||||
|
|
||||||
tplIssues base.TplName = "repo/issue/list"
|
tplIssues base.TplName = "repo/issue/list"
|
||||||
tplIssueNew base.TplName = "repo/issue/new"
|
tplIssueNew base.TplName = "repo/issue/new"
|
||||||
tplIssueView base.TplName = "repo/issue/view"
|
tplIssueView base.TplName = "repo/issue/view"
|
||||||
|
@ -1074,8 +1076,14 @@ func UpdateIssueContent(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
files := ctx.QueryStrings("files[]")
|
||||||
|
if err := updateAttachments(issue, files); err != nil {
|
||||||
|
ctx.ServerError("UpdateAttachments", err)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(200, map[string]interface{}{
|
ctx.JSON(200, map[string]interface{}{
|
||||||
"content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
"content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
||||||
|
"attachments": attachmentsHTML(ctx, issue.Attachments),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1325,6 +1333,13 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if comment.Type == models.CommentTypeComment {
|
||||||
|
if err := comment.LoadAttachments(); err != nil {
|
||||||
|
ctx.ServerError("LoadAttachments", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||||
ctx.Error(403)
|
ctx.Error(403)
|
||||||
return
|
return
|
||||||
|
@ -1346,10 +1361,16 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
files := ctx.QueryStrings("files[]")
|
||||||
|
if err := updateAttachments(comment, files); err != nil {
|
||||||
|
ctx.ServerError("UpdateAttachments", err)
|
||||||
|
}
|
||||||
|
|
||||||
notification.NotifyUpdateComment(ctx.User, comment, oldContent)
|
notification.NotifyUpdateComment(ctx.User, comment, oldContent)
|
||||||
|
|
||||||
ctx.JSON(200, map[string]interface{}{
|
ctx.JSON(200, map[string]interface{}{
|
||||||
"content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
"content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
||||||
|
"attachments": attachmentsHTML(ctx, comment.Attachments),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1603,3 +1624,88 @@ func filterXRefComments(ctx *context.Context, issue *models.Issue) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIssueAttachments returns attachments for the issue
|
||||||
|
func GetIssueAttachments(ctx *context.Context) {
|
||||||
|
issue := GetActionIssue(ctx)
|
||||||
|
var attachments = make([]*api.Attachment, len(issue.Attachments))
|
||||||
|
for i := 0; i < len(issue.Attachments); i++ {
|
||||||
|
attachments[i] = issue.Attachments[i].APIFormat()
|
||||||
|
}
|
||||||
|
ctx.JSON(200, attachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommentAttachments returns attachments for the comment
|
||||||
|
func GetCommentAttachments(ctx *context.Context) {
|
||||||
|
comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var attachments = make([]*api.Attachment, 0)
|
||||||
|
if comment.Type == models.CommentTypeComment {
|
||||||
|
if err := comment.LoadAttachments(); err != nil {
|
||||||
|
ctx.ServerError("LoadAttachments", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(comment.Attachments); i++ {
|
||||||
|
attachments = append(attachments, comment.Attachments[i].APIFormat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.JSON(200, attachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAttachments(item interface{}, files []string) error {
|
||||||
|
var attachments []*models.Attachment
|
||||||
|
switch content := item.(type) {
|
||||||
|
case *models.Issue:
|
||||||
|
attachments = content.Attachments
|
||||||
|
case *models.Comment:
|
||||||
|
attachments = content.Attachments
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknow Type")
|
||||||
|
}
|
||||||
|
for i := 0; i < len(attachments); i++ {
|
||||||
|
if util.IsStringInSlice(attachments[i].UUID, files) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := models.DeleteAttachment(attachments[i], true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if len(files) > 0 {
|
||||||
|
switch content := item.(type) {
|
||||||
|
case *models.Issue:
|
||||||
|
err = content.UpdateAttachments(files)
|
||||||
|
case *models.Comment:
|
||||||
|
err = content.UpdateAttachments(files)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknow Type")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch content := item.(type) {
|
||||||
|
case *models.Issue:
|
||||||
|
content.Attachments, err = models.GetAttachmentsByIssueID(content.ID)
|
||||||
|
case *models.Comment:
|
||||||
|
content.Attachments, err = models.GetAttachmentsByCommentID(content.ID)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknow Type")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachmentsHTML(ctx *context.Context, attachments []*models.Attachment) string {
|
||||||
|
attachHTML, err := ctx.HTMLString(string(tplAttachment), map[string]interface{}{
|
||||||
|
"ctx": ctx.Data,
|
||||||
|
"Attachments": attachments,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("attachmentsHTML.HTMLString", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return attachHTML
|
||||||
|
}
|
||||||
|
|
|
@ -513,8 +513,9 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
})
|
})
|
||||||
}, ignSignIn)
|
}, ignSignIn)
|
||||||
|
|
||||||
m.Group("", func() {
|
m.Group("/attachments", func() {
|
||||||
m.Post("/attachments", repo.UploadAttachment)
|
m.Post("", repo.UploadAttachment)
|
||||||
|
m.Post("/delete", repo.DeleteAttachment)
|
||||||
}, reqSignIn)
|
}, reqSignIn)
|
||||||
|
|
||||||
m.Group("/:username", func() {
|
m.Group("/:username", func() {
|
||||||
|
@ -710,6 +711,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
|
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
|
||||||
m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue)
|
m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue)
|
||||||
m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
|
m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
|
||||||
|
m.Get("/attachments", repo.GetIssueAttachments)
|
||||||
}, context.RepoMustNotBeArchived())
|
}, context.RepoMustNotBeArchived())
|
||||||
|
|
||||||
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
|
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
|
||||||
|
@ -721,6 +723,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Post("", repo.UpdateCommentContent)
|
m.Post("", repo.UpdateCommentContent)
|
||||||
m.Post("/delete", repo.DeleteComment)
|
m.Post("/delete", repo.DeleteComment)
|
||||||
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
|
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
|
||||||
|
m.Get("/attachments", repo.GetCommentAttachments)
|
||||||
}, context.RepoMustNotBeArchived())
|
}, context.RepoMustNotBeArchived())
|
||||||
m.Group("/labels", func() {
|
m.Group("/labels", func() {
|
||||||
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="raw-content hide">{{.Issue.Content}}</div>
|
<div class="raw-content hide">{{.Issue.Content}}</div>
|
||||||
<div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}"></div>
|
<div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}" data-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/attachments" data-view-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/view-attachments"></div>
|
||||||
</div>
|
</div>
|
||||||
{{$reactions := .Issue.Reactions.GroupByType}}
|
{{$reactions := .Issue.Reactions.GroupByType}}
|
||||||
{{if $reactions}}
|
{{if $reactions}}
|
||||||
|
@ -57,15 +57,7 @@
|
||||||
{{if .Issue.Attachments}}
|
{{if .Issue.Attachments}}
|
||||||
<div class="ui bottom attached segment">
|
<div class="ui bottom attached segment">
|
||||||
<div class="ui small images">
|
<div class="ui small images">
|
||||||
{{range .Issue.Attachments}}
|
{{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Issue.Attachments}}
|
||||||
<a target="_blank" rel="noopener noreferrer" href="{{AppSubUrl}}/attachments/{{.UUID}}">
|
|
||||||
{{if FilenameIsImage .Name}}
|
|
||||||
<img class="ui image" src="{{AppSubUrl}}/attachments/{{.UUID}}" title='{{$.i18n.Tr "repo.issues.attachment.open_tab" .Name}}'>
|
|
||||||
{{else}}
|
|
||||||
<span class="ui image octicon octicon-desktop-download" title='{{$.i18n.Tr "repo.issues.attachment.download" .Name}}'></span>
|
|
||||||
{{end}}
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -182,6 +174,19 @@
|
||||||
<div class="ui bottom attached tab preview segment markdown">
|
<div class="ui bottom attached tab preview segment markdown">
|
||||||
{{$.i18n.Tr "loading"}}
|
{{$.i18n.Tr "loading"}}
|
||||||
</div>
|
</div>
|
||||||
|
{{if .IsAttachmentEnabled}}
|
||||||
|
<div class="comment-files"></div>
|
||||||
|
<div class="ui basic button dropzone" id="comment-dropzone"
|
||||||
|
data-upload-url="{{AppSubUrl}}/attachments"
|
||||||
|
data-remove-url="{{AppSubUrl}}/attachments/delete"
|
||||||
|
data-csrf="{{.CsrfToken}}" 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 class="text right edit buttons">
|
<div class="text right edit buttons">
|
||||||
<div class="ui basic blue cancel button" tabindex="3">{{.i18n.Tr "repo.issues.cancel"}}</div>
|
<div class="ui basic blue cancel button" tabindex="3">{{.i18n.Tr "repo.issues.cancel"}}</div>
|
||||||
<div class="ui green save button" tabindex="2">{{.i18n.Tr "repo.issues.save"}}</div>
|
<div class="ui green save button" tabindex="2">{{.i18n.Tr "repo.issues.save"}}</div>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{{range .Attachments}}
|
||||||
|
<a target="_blank" rel="noopener noreferrer" href="{{AppSubUrl}}/attachments/{{.UUID}}">
|
||||||
|
{{if FilenameIsImage .Name}}
|
||||||
|
<img class="ui image" src="{{AppSubUrl}}/attachments/{{.UUID}}" title='{{$.ctx.i18n.Tr "repo.issues.attachment.open_tab" .Name}}'>
|
||||||
|
{{else}}
|
||||||
|
<span class="ui image octicon octicon-desktop-download" title='{{$.ctx.i18n.Tr "repo.issues.attachment.download" .Name}}'></span>
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
|
@ -55,7 +55,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="raw-content hide">{{.Content}}</div>
|
<div class="raw-content hide">{{.Content}}</div>
|
||||||
<div class="edit-content-zone hide" data-write="issuecomment-{{.ID}}-write" data-preview="issuecomment-{{.ID}}-preview" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}"></div>
|
<div class="edit-content-zone hide" data-write="issuecomment-{{.ID}}-write" data-preview="issuecomment-{{.ID}}-preview" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
|
||||||
</div>
|
</div>
|
||||||
{{$reactions := .Reactions.GroupByType}}
|
{{$reactions := .Reactions.GroupByType}}
|
||||||
{{if $reactions}}
|
{{if $reactions}}
|
||||||
|
@ -66,15 +66,7 @@
|
||||||
{{if .Attachments}}
|
{{if .Attachments}}
|
||||||
<div class="ui bottom attached segment">
|
<div class="ui bottom attached segment">
|
||||||
<div class="ui small images">
|
<div class="ui small images">
|
||||||
{{range .Attachments}}
|
{{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Attachments}}
|
||||||
<a target="_blank" rel="noopener noreferrer" href="{{AppSubUrl}}/attachments/{{.UUID}}">
|
|
||||||
{{if FilenameIsImage .Name}}
|
|
||||||
<img class="ui image" src="{{AppSubUrl}}/attachments/{{.UUID}}" title='{{$.i18n.Tr "repo.issues.attachment.open_tab" .Name}}'>
|
|
||||||
{{else}}
|
|
||||||
<span class="ui image octicon octicon-desktop-download" title='{{$.i18n.Tr "repo.issues.attachment.download" .Name}}'></span>
|
|
||||||
{{end}}
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
Loading…
Reference in New Issue