new issue and label page

This commit is contained in:
Unknwon 2015-07-25 02:52:25 +08:00
parent 54b58e988d
commit 1ba837a055
11 changed files with 149 additions and 141 deletions

View file

@ -12,6 +12,10 @@ RUN_MODE = dev
ROOT = ROOT =
SCRIPT_TYPE = bash SCRIPT_TYPE = bash
[ui]
; Number of issues that are showed in one page
ISSUE_PAGING_NUM = 10
[server] [server]
PROTOCOL = http PROTOCOL = http
DOMAIN = localhost DOMAIN = localhost

View file

@ -370,6 +370,7 @@ issues.new_label_placeholder = Label name...
issues.open_tab = %d Open issues.open_tab = %d Open
issues.close_tab = %d Closed issues.close_tab = %d Closed
issues.filter_label = Label issues.filter_label = Label
issues.filter_label_no_select = No selected label
issues.filter_milestone = Milestone issues.filter_milestone = Milestone
issues.filter_assignee = Assignee issues.filter_assignee = Assignee
issues.filter_type = Type issues.filter_type = Type

View file

@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.6.2.0724 Beta" const APP_VER = "0.6.2.0725 Beta"
func init() { func init() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())

View file

@ -14,7 +14,6 @@ import (
"time" "time"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
@ -73,7 +72,7 @@ func (i *Issue) GetLabels() error {
strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$") strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
i.Labels = make([]*Label, 0, len(strIds)) i.Labels = make([]*Label, 0, len(strIds))
for _, strId := range strIds { for _, strId := range strIds {
id, _ := com.StrTo(strId).Int64() id := com.StrTo(strId).MustInt64()
if id > 0 { if id > 0 {
l, err := GetLabelById(id) l, err := GetLabelById(id)
if err != nil { if err != nil {
@ -186,29 +185,29 @@ func GetIssueById(id int64) (*Issue, error) {
} }
// GetIssues returns a list of issues by given conditions. // GetIssues returns a list of issues by given conditions.
func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) { func GetIssues(uid, assigneeID, repoID, posterID, milestoneID int64, page int, isClosed, isMention bool, labelIds, sortType string) ([]Issue, error) {
sess := x.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum) sess := x.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
if rid > 0 { if repoID > 0 {
sess.Where("repo_id=?", rid).And("is_closed=?", isClosed) sess.Where("issue.repo_id=?", repoID).And("issue.is_closed=?", isClosed)
} else { } else {
sess.Where("is_closed=?", isClosed) sess.Where("issue.is_closed=?", isClosed)
} }
if uid > 0 { if assigneeID > 0 {
sess.And("assignee_id=?", uid) sess.And("issue.assignee_id=?", assigneeID)
} else if pid > 0 { } else if posterID > 0 {
sess.And("poster_id=?", pid) sess.And("issue.poster_id=?", posterID)
} }
if mid > 0 { if milestoneID > 0 {
sess.And("milestone_id=?", mid) sess.And("issue.milestone_id=?", milestoneID)
} }
if len(labelIds) > 0 { if len(labelIds) > 0 {
for _, label := range strings.Split(labelIds, ",") { for _, label := range strings.Split(labelIds, ",") {
if com.StrTo(label).MustInt() > 0 { if com.StrTo(label).MustInt() > 0 {
sess.And("label_ids like ?", "'%$"+label+"|%'") sess.And("label_ids like ?", "%$"+label+"|%")
} }
} }
} }
@ -230,6 +229,14 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sort
sess.Desc("created") sess.Desc("created")
} }
if isMention {
queryStr := "issue.id == issue_user.issue_id AND issue_user.is_mentioned=1"
if uid > 0 {
queryStr += " AND issue_user.uid = " + com.ToStr(uid)
}
sess.Join("INNER", "issue_user", queryStr)
}
var issues []Issue var issues []Issue
return issues, sess.Find(&issues) return issues, sess.Find(&issues)
} }
@ -394,53 +401,42 @@ type IssueStats struct {
// Filter modes. // Filter modes.
const ( const (
FM_ASSIGN = iota + 1 FM_ALL = iota
FM_ASSIGN
FM_CREATE FM_CREATE
FM_MENTION FM_MENTION
) )
// GetIssueStats returns issue statistic information by given conditions. // GetIssueStats returns issue statistic information by given conditions.
func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats { func GetIssueStats(repoID, uid, labelID int64, isShowClosed bool, filterMode int) *IssueStats {
stats := &IssueStats{} stats := &IssueStats{}
issue := new(Issue) issue := new(Issue)
tmpSess := &xorm.Session{}
sess := x.Where("repo_id=?", rid) queryStr := "repo_id=? AND is_closed=?"
*tmpSess = *sess
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
*tmpSess = *sess
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
if isShowClosed {
stats.AllCount = stats.ClosedCount
} else {
stats.AllCount = stats.OpenCount
}
if filterMode != FM_MENTION {
sess = x.Where("repo_id=?", rid)
switch filterMode { switch filterMode {
case FM_ALL:
stats.OpenCount, _ = x.Where(queryStr, repoID, false).Count(issue)
stats.ClosedCount, _ = x.Where(queryStr, repoID, true).Count(issue)
return stats
case FM_ASSIGN: case FM_ASSIGN:
sess.And("assignee_id=?", uid) queryStr += " AND assignee_id=?"
stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid).Count(issue)
stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid).Count(issue)
return stats
case FM_CREATE: case FM_CREATE:
sess.And("poster_id=?", uid) queryStr += " AND poster_id=?"
default: stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid).Count(issue)
goto nofilter stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid).Count(issue)
return stats
case FM_MENTION:
queryStr += " AND uid=? AND is_mentioned=?"
stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid, true).Count(new(IssueUser))
stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid, true).Count(new(IssueUser))
return stats
} }
*tmpSess = *sess
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
*tmpSess = *sess
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
} else {
sess := x.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
*tmpSess = *sess
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
*tmpSess = *sess
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
}
nofilter:
stats.AssignCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
stats.CreateCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
stats.MentionCount, _ = x.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
return stats return stats
} }
@ -894,7 +890,7 @@ type Comment struct {
// CreateComment creates comment of issue or commit. // CreateComment creates comment of issue or commit.
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) { func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sessionRelease(sess)
if err := sess.Begin(); err != nil { if err := sess.Begin(); err != nil {
return nil, err return nil, err
} }
@ -903,7 +899,6 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
CommitId: commitId, Line: line, Content: content} CommitId: commitId, Line: line, Content: content}
if _, err := sess.Insert(comment); err != nil { if _, err := sess.Insert(comment); err != nil {
sess.Rollback()
return nil, err return nil, err
} }
@ -912,7 +907,6 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
case COMMENT_TYPE_COMMENT: case COMMENT_TYPE_COMMENT:
rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?" rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, issueId); err != nil { if _, err := sess.Exec(rawSql, issueId); err != nil {
sess.Rollback()
return nil, err return nil, err
} }
@ -926,20 +920,17 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
} }
if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil { if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil {
sess.Rollback()
return nil, err return nil, err
} }
} }
case COMMENT_TYPE_REOPEN: case COMMENT_TYPE_REOPEN:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?" rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil { if _, err := sess.Exec(rawSql, repoId); err != nil {
sess.Rollback()
return nil, err return nil, err
} }
case COMMENT_TYPE_CLOSE: case COMMENT_TYPE_CLOSE:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?" rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil { if _, err := sess.Exec(rawSql, repoId); err != nil {
sess.Rollback()
return nil, err return nil, err
} }
} }

File diff suppressed because one or more lines are too long

View file

@ -84,7 +84,9 @@ var (
// Repository settings. // Repository settings.
RepoRootPath string RepoRootPath string
ScriptType string ScriptType string
IssuePagingNum int = 10
// UI settings.
IssuePagingNum int
// Picture settings. // Picture settings.
PictureService string PictureService string
@ -311,6 +313,9 @@ func NewConfigContext() {
} }
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash") ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
// UI settings.
IssuePagingNum = Cfg.Section("ui").Key("ISSUE_PAGING_NUM").MustInt(10)
sec = Cfg.Section("picture") sec = Cfg.Section("picture")
PictureService = sec.Key("SERVICE").In("server", []string{"server"}) PictureService = sec.Key("SERVICE").In("server", []string{"server"})
AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString("data/avatars") AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString("data/avatars")

File diff suppressed because one or more lines are too long

View file

@ -78,9 +78,21 @@
} }
} }
} }
.filter.menu .label.color { .filter.menu {
.label.color {
margin-left: 17px;
padding: 0 8px; padding: 0 8px;
} }
.octicon {
float: left;
margin-left: -5px;
margin-right: -7px;
}
.menu {
max-height: 300px;
overflow-x: auto;
}
}
.type.item .menu { .type.item .menu {
right: 0!important; right: 0!important;
left: auto!important; left: auto!important;

View file

@ -74,32 +74,26 @@ func Issues(ctx *middleware.Context) {
return return
} }
var assigneeId, posterId int64 var assigneeID, posterID int64
var filterMode int filterMode := models.FM_ALL
switch viewType { switch viewType {
case "assigned": case "assigned":
assigneeId = ctx.User.Id assigneeID = ctx.User.Id
filterMode = models.FM_ASSIGN filterMode = models.FM_ASSIGN
case "created_by": case "created_by":
posterId = ctx.User.Id posterID = ctx.User.Id
filterMode = models.FM_CREATE filterMode = models.FM_CREATE
case "mentioned": case "mentioned":
filterMode = models.FM_MENTION filterMode = models.FM_MENTION
} }
var uid int64 = -1
if ctx.IsSigned {
uid = ctx.User.Id
}
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
milestoneID := ctx.QueryInt64("milestone")
var mid int64
midx := ctx.QueryInt64("milestone")
if midx > 0 {
mile, err := models.GetMilestoneByIndex(repo.Id, midx)
if err != nil {
ctx.Handle(500, "GetMilestoneByIndex: %v", err)
return
}
mid = mile.Id
}
page := ctx.QueryInt("page") page := ctx.QueryInt("page")
if page <= 1 { if page <= 1 {
page = 1 page = 1
@ -114,15 +108,15 @@ func Issues(ctx *middleware.Context) {
selectLabels := ctx.Query("labels") selectLabels := ctx.Query("labels")
// Get issues. // Get issues.
issues, err := models.GetIssues(assigneeId, repo.Id, posterId, mid, page, issues, err := models.GetIssues(uid, assigneeID, repo.Id, posterID, milestoneID,
isShowClosed, selectLabels, ctx.Query("sortType")) page, isShowClosed, filterMode == models.FM_MENTION, selectLabels, ctx.Query("sortType"))
if err != nil { if err != nil {
ctx.Handle(500, "GetIssues: %v", err) ctx.Handle(500, "GetIssues: %v", err)
return return
} }
// Get issue-user pairs. // Get issue-user pairs.
pairs, err := models.GetIssueUserPairs(repo.Id, posterId, isShowClosed) pairs, err := models.GetIssueUserPairs(repo.Id, posterID, isShowClosed)
if err != nil { if err != nil {
ctx.Handle(500, "GetIssueUserPairs: %v", err) ctx.Handle(500, "GetIssueUserPairs: %v", err)
return return
@ -130,18 +124,23 @@ func Issues(ctx *middleware.Context) {
// Get posters. // Get posters.
for i := range issues { for i := range issues {
if err = issues[i].GetPoster(); err != nil {
ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
if err = issues[i].GetLabels(); err != nil { if err = issues[i].GetLabels(); err != nil {
ctx.Handle(500, "GetLabels", fmt.Errorf("[#%d]%v", issues[i].ID, err)) ctx.Handle(500, "GetLabels", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return return
} }
if ctx.IsSigned { if !ctx.IsSigned {
idx := models.PairsContains(pairs, issues[i].ID, ctx.User.Id) issues[i].IsRead = true
if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) {
continue continue
} }
// Check read status.
idx := models.PairsContains(pairs, issues[i].ID, ctx.User.Id)
if idx > -1 { if idx > -1 {
issues[i].IsRead = pairs[idx].IsRead issues[i].IsRead = pairs[idx].IsRead
} else { } else {
@ -149,17 +148,7 @@ func Issues(ctx *middleware.Context) {
} }
} }
if err = issues[i].GetPoster(); err != nil { issueStats := models.GetIssueStats(repo.Id, uid, com.StrTo(selectLabels).MustInt64(), isShowClosed, filterMode)
ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
}
var uid int64 = -1
if ctx.User != nil {
uid = ctx.User.Id
}
issueStats := models.GetIssueStats(repo.Id, uid, isShowClosed, filterMode)
ctx.Data["IssueStats"] = issueStats ctx.Data["IssueStats"] = issueStats
ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64() ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
ctx.Data["ViewType"] = viewType ctx.Data["ViewType"] = viewType
@ -169,8 +158,10 @@ func Issues(ctx *middleware.Context) {
ctx.Data["State"] = "closed" ctx.Data["State"] = "closed"
ctx.Data["ShowCount"] = issueStats.ClosedCount ctx.Data["ShowCount"] = issueStats.ClosedCount
} else { } else {
ctx.Data["State"] = "open"
ctx.Data["ShowCount"] = issueStats.OpenCount ctx.Data["ShowCount"] = issueStats.OpenCount
} }
ctx.HTML(200, ISSUES) ctx.HTML(200, ISSUES)
} }

View file

@ -1 +1 @@
0.6.2.0724 Beta 0.6.2.0725 Beta

View file

@ -4,20 +4,18 @@
<div class="ui middle page grid body"> <div class="ui middle page grid body">
<div class="navbar"> <div class="navbar">
{{template "repo/issue/navbar" .}} {{template "repo/issue/navbar" .}}
{{if .IsRepositoryAdmin}}
<div class="ui right floated secondary menu"> <div class="ui right floated secondary menu">
<a class="ui green button" href="{{$.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a> <a class="ui green button" href="{{$.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a>
</div> </div>
{{end}}
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui left"> <div class="ui left">
<div class="ui tiny buttons"> <div class="ui tiny buttons">
<a class="ui green basic button {{if not .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}"> <a class="ui green basic button {{if not .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{$.ViewType}}&state=open&labels={{.SelectLabels}}">
<i class="octicon octicon-issue-opened"></i> <i class="octicon octicon-issue-opened"></i>
{{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}} {{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}}
</a> </a>
<a class="ui red basic button {{if .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}&state=closed"> <a class="ui red basic button {{if .IsShowClosed}}active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}&state=closed&labels={{.SelectLabels}}">
<i class="octicon octicon-issue-closed"></i> <i class="octicon octicon-issue-closed"></i>
{{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}} {{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}}
</a> </a>
@ -30,12 +28,13 @@
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
</span> </span>
<div class="menu"> <div class="menu">
<a class="item" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}} {{range .Labels}}
<a class="item" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a> <a class="item" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}"><span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
{{end}} {{end}}
</div> </div>
</div> </div>
<div class="ui {{if not .Milestones}}disabled{{end}} pointing dropdown jump item"> <!-- <div class="ui {{if not .Milestones}}disabled{{end}} pointing dropdown jump item">
<span class="text"> <span class="text">
{{.i18n.Tr "repo.issues.filter_milestone"}} {{.i18n.Tr "repo.issues.filter_milestone"}}
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
@ -45,8 +44,8 @@
<a class="item" href="{{$.RepoLink}}/issues">{{.Name}}</a> <a class="item" href="{{$.RepoLink}}/issues">{{.Name}}</a>
{{end}} {{end}}
</div> </div>
</div> </div> -->
<div class="ui {{if not .Assignees}}disabled{{end}} pointing dropdown jump item"> <!-- <div class="ui {{if not .Assignees}}disabled{{end}} pointing dropdown jump item">
<span class="text"> <span class="text">
{{.i18n.Tr "repo.issues.filter_assignee"}} {{.i18n.Tr "repo.issues.filter_assignee"}}
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
@ -56,17 +55,17 @@
<a class="item" href="{{$.RepoLink}}/issues">{{.Name}}</a> <a class="item" href="{{$.RepoLink}}/issues">{{.Name}}</a>
{{end}} {{end}}
</div> </div>
</div> </div> -->
<div class="ui pointing dropdown type jump item"> <div class="ui pointing dropdown type jump item">
<span class="text"> <span class="text">
{{.i18n.Tr "repo.issues.filter_type"}} {{.i18n.Tr "repo.issues.filter_type"}}
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
</span> </span>
<div class="menu"> <div class="menu">
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a> <a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=all&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a>
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a> <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=assigned&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a> <a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=created_by&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a>
<a class="item" href="{{$.RepoLink}}/issues">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a> <a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="{{$.RepoLink}}/issues?type=mentioned&state={{$.State}}&labels={{.SelectLabels}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a>
</div> </div>
</div> </div>
</div> </div>
@ -77,6 +76,11 @@
<li class="item"> <li class="item">
<div class="ui {{if .IsRead}}black{{else}}green{{end}} label">#{{.Index}}</div> <div class="ui {{if .IsRead}}black{{else}}green{{end}} label">#{{.Index}}</div>
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Name}}</a> <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Name}}</a>
{{range .Labels}}
<a class="ui label" href="{{$.RepoLink}}/issues?type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}" style="background-color: {{.Color}}">{{.Name}}</a>
{{end}}
{{if .NumComments}}<span class="comment ui right"><i class="octicon octicon-comment"></i> {{.NumComments}}</span>{{end}} {{if .NumComments}}<span class="comment ui right"><i class="octicon octicon-comment"></i> {{.NumComments}}</span>{{end}}
<p class="desc">{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.Name|Str2html}}</p> <p class="desc">{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.Name|Str2html}}</p>
</li> </li>