@@ -144,8 +144,8 @@ func (i *Issue) AfterDelete() { | |||
} | |||
} | |||
// CreateIssue creates new issue for repository. | |||
func NewIssue(issue *Issue) (err error) { | |||
// CreateIssue creates new issue with labels for repository. | |||
func NewIssue(issue *Issue, labelIDs []int64) (err error) { | |||
sess := x.NewSession() | |||
defer sessionRelease(sess) | |||
if err = sess.Begin(); err != nil { | |||
@@ -158,6 +158,12 @@ func NewIssue(issue *Issue) (err error) { | |||
return err | |||
} | |||
for _, id := range labelIDs { | |||
if err = issue.addLabel(sess, id); err != nil { | |||
return fmt.Errorf("addLabel: %v", err) | |||
} | |||
} | |||
if err = sess.Commit(); err != nil { | |||
return err | |||
} | |||
@@ -688,6 +694,10 @@ func HasIssueLabel(issueID, labelID int64) bool { | |||
} | |||
func newIssueLabel(e Engine, issueID, labelID int64) error { | |||
if issueID == 0 || labelID == 0 { | |||
return nil | |||
} | |||
_, err := e.Insert(&IssueLabel{ | |||
IssueID: issueID, | |||
LabelID: labelID, |
@@ -98,8 +98,8 @@ func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) b | |||
// \/ \/ \/ | |||
type CreateIssueForm struct { | |||
Title string `binding:"Required;MaxSize(255)"` | |||
LabelIDs []int64 `form:"label_id"` | |||
Title string `binding:"Required;MaxSize(255)"` | |||
LabelIDs string `form:"label_ids"` | |||
MilestoneID int64 | |||
AssigneeID int64 | |||
Content string |
@@ -420,3 +420,21 @@ func Subtract(left interface{}, right interface{}) interface{} { | |||
return fleft + float64(rleft) - (fright + float64(rright)) | |||
} | |||
} | |||
// StringsToInt64s converts a slice of string to a slice of int64. | |||
func StringsToInt64s(strs []string) []int64 { | |||
ints := make([]int64, len(strs)) | |||
for i := range strs { | |||
ints[i] = com.StrTo(strs[i]).MustInt64() | |||
} | |||
return ints | |||
} | |||
// Int64sToMap converts a slice of int64 to a int64 map. | |||
func Int64sToMap(ints []int64) map[int64]bool { | |||
m := make(map[int64]bool) | |||
for _, i := range ints { | |||
m[i] = true | |||
} | |||
return m | |||
} |
@@ -134,24 +134,65 @@ $(document).ready(function () { | |||
$('.poping.up').popup(); | |||
// Comment form | |||
$('.comment.form .tabular.menu .item').tab(); | |||
$('.comment.form .tabular.menu .item[data-tab="preview"]').click(function () { | |||
var $this = $(this); | |||
console.log($('.comment.form .tab.segment[data-tab="write"] textarea').val()) | |||
console.log($('.comment.form .tab.segment[data-tab="preview"]').html()) | |||
$.post($this.data('url'), { | |||
"_csrf": csrf, | |||
"mode": "gfm", | |||
"context": $this.data('context'), | |||
"text": $('.comment.form .tab.segment[data-tab="write"] textarea').val() | |||
}, | |||
function (data) { | |||
console.log(data) | |||
$('.comment.form .tab.segment[data-tab="preview"]').html(data); | |||
if ($('.comment.form').length > 0) { | |||
var $form = $(this); | |||
$form.find('.tabular.menu .item').tab(); | |||
$form.find('.tabular.menu .item[data-tab="preview"]').click(function () { | |||
var $this = $(this); | |||
$.post($this.data('url'), { | |||
"_csrf": csrf, | |||
"mode": "gfm", | |||
"context": $this.data('context'), | |||
"text": $form.find('.tab.segment[data-tab="write"] textarea').val() | |||
}, | |||
function (data) { | |||
$form.find('.tab.segment[data-tab="preview"]').html(data); | |||
} | |||
); | |||
}); | |||
// Labels | |||
var $list = $('.ui.labels.list'); | |||
var $no_select = $list.find('.no-select'); | |||
$('.select-label .item:not(.no-select)').click(function () { | |||
if ($(this).hasClass('checked')) { | |||
$(this).removeClass('checked') | |||
$(this).find('.octicon').removeClass('octicon-check') | |||
} else { | |||
$(this).addClass('checked') | |||
$(this).find('.octicon').addClass('octicon-check') | |||
} | |||
) | |||
; | |||
}) | |||
var label_ids = ""; | |||
$(this).parent().find('.item').each(function () { | |||
if ($(this).hasClass('checked')) { | |||
label_ids += $(this).data('id') + ","; | |||
$($(this).data('id-selector')).removeClass('hide'); | |||
} else { | |||
$($(this).data('id-selector')).addClass('hide'); | |||
} | |||
}); | |||
if (label_ids.length == 0) { | |||
$no_select.removeClass('hide'); | |||
} else { | |||
$no_select.addClass('hide'); | |||
} | |||
$($(this).parent().data('id')).val(label_ids); | |||
return false; | |||
}); | |||
$('.select-label .no-select.item').click(function () { | |||
$(this).parent().find('.item').each(function () { | |||
$(this).removeClass('checked'); | |||
$(this).find('.octicon').removeClass('octicon-check'); | |||
}); | |||
$list.find('.item').each(function () { | |||
$(this).addClass('hide'); | |||
}); | |||
$no_select.removeClass('hide'); | |||
$($(this).parent().data('id')).val(''); | |||
}); | |||
} | |||
// Helpers. | |||
$('.delete-button').click(function () { |
@@ -102,7 +102,7 @@ footer { | |||
} | |||
.hide { | |||
display: none; | |||
display: none!important; | |||
} | |||
.center { | |||
text-align: center; |
@@ -29,6 +29,22 @@ | |||
font-weight: bold; | |||
} | |||
} | |||
.metas .ui.list { | |||
.label.color { | |||
padding: 0 8px; | |||
margin-right: 5px; | |||
} | |||
a { | |||
padding-top: 5px; | |||
padding-right: 10px; | |||
.text { | |||
color: #444; | |||
&:hover { | |||
color: #000; | |||
} | |||
} | |||
} | |||
} | |||
.filter.menu { | |||
.label.color { | |||
margin-left: 15px; | |||
@@ -91,6 +107,10 @@ | |||
.comment.form { | |||
.metas { | |||
min-width: 220px; | |||
.filter.menu { | |||
max-height: 300px; | |||
overflow-x: auto; | |||
} | |||
} | |||
} | |||
} |
@@ -180,14 +180,16 @@ func NewIssue(ctx *middleware.Context) { | |||
ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled | |||
ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes | |||
var ( | |||
repo = ctx.Repo.Repository | |||
err error | |||
) | |||
ctx.Data["Labels"], err = models.GetLabelsByRepoID(repo.ID) | |||
if err != nil { | |||
ctx.Handle(500, "GetLabelsByRepoID: %v", err) | |||
return | |||
if ctx.User.IsAdmin { | |||
var ( | |||
repo = ctx.Repo.Repository | |||
err error | |||
) | |||
ctx.Data["Labels"], err = models.GetLabelsByRepoID(repo.ID) | |||
if err != nil { | |||
ctx.Handle(500, "GetLabelsByRepoID: %v", err) | |||
return | |||
} | |||
} | |||
// // Get all milestones. | |||
@@ -219,6 +221,31 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||
ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled | |||
ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes | |||
var ( | |||
repo = ctx.Repo.Repository | |||
labelIDs []int64 | |||
) | |||
if ctx.User.IsAdmin { | |||
// Check labels. | |||
labelIDs = base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) | |||
labelIDMark := base.Int64sToMap(labelIDs) | |||
labels, err := models.GetLabelsByRepoID(repo.ID) | |||
if err != nil { | |||
ctx.Handle(500, "GetLabelsByRepoID: %v", err) | |||
return | |||
} | |||
hasSelected := false | |||
for i := range labels { | |||
if labelIDMark[labels[i].ID] { | |||
labels[i].IsChecked = true | |||
hasSelected = true | |||
} | |||
} | |||
ctx.Data["HasSelectedLabel"] = hasSelected | |||
ctx.Data["label_ids"] = form.LabelIDs | |||
ctx.Data["Labels"] = labels | |||
} | |||
if ctx.HasError() { | |||
ctx.HTML(200, ISSUE_NEW) | |||
return | |||
@@ -226,18 +253,17 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||
issue := &models.Issue{ | |||
RepoID: ctx.Repo.Repository.ID, | |||
Index: int64(ctx.Repo.Repository.NumIssues) + 1, | |||
Index: int64(repo.NumIssues) + 1, | |||
Name: form.Title, | |||
PosterID: ctx.User.Id, | |||
// MilestoneID: form.MilestoneID, | |||
// AssigneeID: form.AssigneeID, | |||
// LabelIDs: "$" + strings.Join(form.LabelIDs, "|$") + "|", | |||
Content: form.Content, | |||
} | |||
if err := models.NewIssue(issue); err != nil { | |||
if err := models.NewIssue(issue, labelIDs); err != nil { | |||
ctx.Handle(500, "NewIssue", err) | |||
return | |||
} else if err := models.NewIssueUserPairs(ctx.Repo.Repository, issue); err != nil { | |||
} else if err := models.NewIssueUserPairs(repo, issue); err != nil { | |||
ctx.Handle(500, "NewIssue", err) | |||
return | |||
} |
@@ -38,22 +38,26 @@ | |||
</div> | |||
{{if .IsRepositoryAdmin}} | |||
<input id="label_ids" name="label_ids" type="hidden" value="{{.label_ids}}"> | |||
<div class="four wide column"> | |||
<div class="ui segment metas"> | |||
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item"> | |||
<div class="ui {{if not .Labels}}disabled{{end}} jump select-label dropdown"> | |||
<span class="text"> | |||
<strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong> | |||
<span class="octicon octicon-gear"></span> | |||
</span> | |||
<div class="filter menu"> | |||
<a class="item" href="#">{{.i18n.Tr "repo.issues.new.clear_labels"}}</a> | |||
<div class="filter menu" data-id="#label_ids"> | |||
<a class="no-select item" href="#">{{.i18n.Tr "repo.issues.new.clear_labels"}}</a> | |||
{{range .Labels}} | |||
<a class="item" href="#"><span class="octicon {{if .IsChecked}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a> | |||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon {{if .IsChecked}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a> | |||
{{end}} | |||
</div> | |||
</div> | |||
<div class="ui list"> | |||
<span class="item {{if .SelectedLabels}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span> | |||
<div class="ui labels list"> | |||
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span> | |||
{{range .Labels}} | |||
<a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name}}</span></a> | |||
{{end}} | |||
</div> | |||
<!-- <div class="ui divider"></div> | |||
<div class="ui {{if .Labels}}disabled{{end}} dropdown jump item"> |