new user dahsboard issues
This commit is contained in:
parent
be90ea583a
commit
f808df5a7b
|
@ -124,6 +124,8 @@ collaborative_repos = Collaborative Repositories
|
||||||
my_orgs = My Organizations
|
my_orgs = My Organizations
|
||||||
my_mirrors = My Mirrors
|
my_mirrors = My Mirrors
|
||||||
|
|
||||||
|
issues.in_your_repos = In your repositories
|
||||||
|
|
||||||
[explore]
|
[explore]
|
||||||
repos = Repositories
|
repos = Repositories
|
||||||
|
|
||||||
|
|
|
@ -275,6 +275,26 @@
|
||||||
"strictMath": 0,
|
"strictMath": 0,
|
||||||
"strictUnits": 0
|
"strictUnits": 0
|
||||||
},
|
},
|
||||||
|
"\/public\/less\/_dashboard.less": {
|
||||||
|
"allowInsecureImports": 0,
|
||||||
|
"createSourceMap": 0,
|
||||||
|
"disableJavascript": 0,
|
||||||
|
"fileType": 1,
|
||||||
|
"ieCompatibility": 1,
|
||||||
|
"ignore": 1,
|
||||||
|
"ignoreWasSetByUser": 0,
|
||||||
|
"inputAbbreviatedPath": "\/public\/less\/_dashboard.less",
|
||||||
|
"outputAbbreviatedPath": "\/public\/css\/_dashboard.css",
|
||||||
|
"outputPathIsOutsideProject": 0,
|
||||||
|
"outputPathIsSetByUser": 0,
|
||||||
|
"outputStyle": 0,
|
||||||
|
"relativeURLS": 0,
|
||||||
|
"shouldRunAutoprefixer": 0,
|
||||||
|
"shouldRunBless": 0,
|
||||||
|
"strictImports": 0,
|
||||||
|
"strictMath": 0,
|
||||||
|
"strictUnits": 0
|
||||||
|
},
|
||||||
"\/public\/less\/_form.less": {
|
"\/public\/less\/_form.less": {
|
||||||
"allowInsecureImports": 0,
|
"allowInsecureImports": 0,
|
||||||
"createSourceMap": 0,
|
"createSourceMap": 0,
|
||||||
|
|
|
@ -641,9 +641,8 @@ func parseCountResult(results []map[string][]byte) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIssueStats returns issue statistic information by given conditions.
|
// GetIssueStats returns issue statistic information by given conditions.
|
||||||
func GetIssueStats(repoID, uid, labelID, milestoneID, assigneeID int64, isShowClosed bool, filterMode int) *IssueStats {
|
func GetIssueStats(repoID, uid, labelID, milestoneID, assigneeID int64, filterMode int) *IssueStats {
|
||||||
stats := &IssueStats{}
|
stats := &IssueStats{}
|
||||||
// issue := new(Issue)
|
|
||||||
|
|
||||||
queryStr := "SELECT COUNT(*) FROM `issue` "
|
queryStr := "SELECT COUNT(*) FROM `issue` "
|
||||||
if labelID > 0 {
|
if labelID > 0 {
|
||||||
|
@ -659,38 +658,75 @@ func GetIssueStats(repoID, uid, labelID, milestoneID, assigneeID int64, isShowCl
|
||||||
}
|
}
|
||||||
switch filterMode {
|
switch filterMode {
|
||||||
case FM_ALL, FM_ASSIGN:
|
case FM_ALL, FM_ASSIGN:
|
||||||
resutls, _ := x.Query(queryStr+baseCond, repoID, false)
|
results, _ := x.Query(queryStr+baseCond, repoID, false)
|
||||||
stats.OpenCount = parseCountResult(resutls)
|
stats.OpenCount = parseCountResult(results)
|
||||||
resutls, _ = x.Query(queryStr+baseCond, repoID, true)
|
results, _ = x.Query(queryStr+baseCond, repoID, true)
|
||||||
stats.ClosedCount = parseCountResult(resutls)
|
stats.ClosedCount = parseCountResult(results)
|
||||||
|
|
||||||
case FM_CREATE:
|
case FM_CREATE:
|
||||||
baseCond += " AND poster_id=?"
|
baseCond += " AND poster_id=?"
|
||||||
resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid)
|
results, _ := x.Query(queryStr+baseCond, repoID, false, uid)
|
||||||
stats.OpenCount = parseCountResult(resutls)
|
stats.OpenCount = parseCountResult(results)
|
||||||
resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid)
|
results, _ = x.Query(queryStr+baseCond, repoID, true, uid)
|
||||||
stats.ClosedCount = parseCountResult(resutls)
|
stats.ClosedCount = parseCountResult(results)
|
||||||
|
|
||||||
case FM_MENTION:
|
case FM_MENTION:
|
||||||
queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
|
queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
|
||||||
baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
|
baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
|
||||||
resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid, true)
|
results, _ := x.Query(queryStr+baseCond, repoID, false, uid, true)
|
||||||
stats.OpenCount = parseCountResult(resutls)
|
stats.OpenCount = parseCountResult(results)
|
||||||
resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid, true)
|
results, _ = x.Query(queryStr+baseCond, repoID, true, uid, true)
|
||||||
stats.ClosedCount = parseCountResult(resutls)
|
stats.ClosedCount = parseCountResult(results)
|
||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
|
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
|
||||||
func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
|
func GetUserIssueStats(repoID, uid int64, filterMode int) *IssueStats {
|
||||||
stats := &IssueStats{}
|
stats := &IssueStats{}
|
||||||
issue := new(Issue)
|
issue := new(Issue)
|
||||||
stats.AssignCount, _ = x.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
|
stats.AssignCount, _ = x.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
|
||||||
stats.CreateCount, _ = x.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
|
stats.CreateCount, _ = x.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
|
||||||
|
|
||||||
|
queryStr := "SELECT COUNT(*) FROM `issue` "
|
||||||
|
baseCond := " WHERE issue.is_closed=?"
|
||||||
|
if repoID > 0 {
|
||||||
|
baseCond += " AND issue.repo_id=" + com.ToStr(repoID)
|
||||||
|
}
|
||||||
|
switch filterMode {
|
||||||
|
case FM_ASSIGN:
|
||||||
|
baseCond += " AND assignee_id=" + com.ToStr(uid)
|
||||||
|
|
||||||
|
case FM_CREATE:
|
||||||
|
baseCond += " AND poster_id=" + com.ToStr(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
results, _ := x.Query(queryStr+baseCond, false)
|
||||||
|
stats.OpenCount = parseCountResult(results)
|
||||||
|
results, _ = x.Query(queryStr+baseCond, true)
|
||||||
|
stats.ClosedCount = parseCountResult(results)
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRepoIssueStats returns number of open and closed repository issues by given filter mode.
|
||||||
|
func GetRepoIssueStats(repoID, uid int64, filterMode int) (numOpen int64, numClosed int64) {
|
||||||
|
queryStr := "SELECT COUNT(*) FROM `issue` "
|
||||||
|
baseCond := " WHERE issue.repo_id=? AND issue.is_closed=?"
|
||||||
|
switch filterMode {
|
||||||
|
case FM_ASSIGN:
|
||||||
|
baseCond += " AND assignee_id=" + com.ToStr(uid)
|
||||||
|
|
||||||
|
case FM_CREATE:
|
||||||
|
baseCond += " AND poster_id=" + com.ToStr(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
results, _ := x.Query(queryStr+baseCond, repoID, false)
|
||||||
|
numOpen = parseCountResult(results)
|
||||||
|
results, _ = x.Query(queryStr+baseCond, repoID, true)
|
||||||
|
numClosed = parseCountResult(results)
|
||||||
|
return numOpen, numClosed
|
||||||
|
}
|
||||||
|
|
||||||
func updateIssue(e Engine, issue *Issue) error {
|
func updateIssue(e Engine, issue *Issue) error {
|
||||||
_, err := e.Id(issue.ID).AllCols().Update(issue)
|
_, err := e.Id(issue.ID).AllCols().Update(issue)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -221,6 +221,11 @@ func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error)
|
||||||
return GetRepoMilestoneByID(repo.ID, milestoneID)
|
return GetRepoMilestoneByID(repo.ID, milestoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IssueStats returns number of open and closed repository issues by given filter mode.
|
||||||
|
func (repo *Repository) IssueStats(uid int64, filterMode int) (int64, int64) {
|
||||||
|
return GetRepoIssueStats(repo.ID, uid, filterMode)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *Repository) GetMirror() (err error) {
|
func (repo *Repository) GetMirror() (err error) {
|
||||||
repo.Mirror, err = GetMirror(repo.ID)
|
repo.Mirror, err = GetMirror(repo.ID)
|
||||||
return err
|
return err
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,20 @@
|
||||||
|
.dashboard {
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: @footer-margin * 2;
|
||||||
|
|
||||||
|
&.issues {
|
||||||
|
.context.user.menu {
|
||||||
|
min-width: 200px;
|
||||||
|
.ui.header {
|
||||||
|
font-size: 1rem;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter.menu {
|
||||||
|
.item.active {
|
||||||
|
background-color: #4183c4;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,47 +79,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.buttons {
|
|
||||||
padding-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.issue.list {
|
|
||||||
list-style: none;
|
|
||||||
padding-top: 15px;
|
|
||||||
>.item {
|
|
||||||
padding-top: 15px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px dashed #AAA;
|
|
||||||
.title {
|
|
||||||
color: #444;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 6px;
|
|
||||||
&:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.comment {
|
|
||||||
padding-right: 10px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
.desc {
|
|
||||||
padding-top: 5px;
|
|
||||||
color: #999;
|
|
||||||
a.milestone {
|
|
||||||
padding-left: 5px;
|
|
||||||
color: #999!important;
|
|
||||||
&:hover {
|
|
||||||
color: #000!important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.assignee {
|
|
||||||
margin-top: -5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@comment-avatar-width: 3em;
|
@comment-avatar-width: 3em;
|
||||||
&.new.issue {
|
&.new.issue {
|
||||||
.comment.form {
|
.comment.form {
|
||||||
|
@ -607,6 +566,48 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.issue.list {
|
||||||
|
list-style: none;
|
||||||
|
padding-top: 15px;
|
||||||
|
>.item {
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px dashed #AAA;
|
||||||
|
.title {
|
||||||
|
color: #444;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 6px;
|
||||||
|
&:hover {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.comment {
|
||||||
|
padding-right: 10px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
padding-top: 5px;
|
||||||
|
color: #999;
|
||||||
|
a.milestone {
|
||||||
|
padding-left: 5px;
|
||||||
|
color: #999!important;
|
||||||
|
&:hover {
|
||||||
|
color: #000!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.assignee {
|
||||||
|
margin-top: -5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.buttons {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.ui.comments {
|
.ui.comments {
|
||||||
.dropzone {
|
.dropzone {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -6,4 +6,5 @@
|
||||||
@import "_form";
|
@import "_form";
|
||||||
@import "_repository";
|
@import "_repository";
|
||||||
@import "_user";
|
@import "_user";
|
||||||
|
@import "_dashboard";
|
||||||
@import "_admin";
|
@import "_admin";
|
|
@ -79,11 +79,11 @@ func Issues(ctx *middleware.Context) {
|
||||||
filterMode := models.FM_ALL
|
filterMode := models.FM_ALL
|
||||||
switch viewType {
|
switch viewType {
|
||||||
case "assigned":
|
case "assigned":
|
||||||
assigneeID = ctx.User.Id
|
|
||||||
filterMode = models.FM_ASSIGN
|
filterMode = models.FM_ASSIGN
|
||||||
|
assigneeID = ctx.User.Id
|
||||||
case "created_by":
|
case "created_by":
|
||||||
posterID = ctx.User.Id
|
|
||||||
filterMode = models.FM_CREATE
|
filterMode = models.FM_CREATE
|
||||||
|
posterID = ctx.User.Id
|
||||||
case "mentioned":
|
case "mentioned":
|
||||||
filterMode = models.FM_MENTION
|
filterMode = models.FM_MENTION
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ func Issues(ctx *middleware.Context) {
|
||||||
selectLabels := ctx.Query("labels")
|
selectLabels := ctx.Query("labels")
|
||||||
milestoneID := ctx.QueryInt64("milestone")
|
milestoneID := ctx.QueryInt64("milestone")
|
||||||
isShowClosed := ctx.Query("state") == "closed"
|
isShowClosed := ctx.Query("state") == "closed"
|
||||||
issueStats := models.GetIssueStats(repo.ID, uid, com.StrTo(selectLabels).MustInt64(), milestoneID, assigneeID, isShowClosed, filterMode)
|
issueStats := models.GetIssueStats(repo.ID, uid, com.StrTo(selectLabels).MustInt64(), milestoneID, assigneeID, filterMode)
|
||||||
|
|
||||||
page := ctx.QueryInt("page")
|
page := ctx.QueryInt("page")
|
||||||
if page <= 1 {
|
if page <= 1 {
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Unknwon/com"
|
"github.com/Unknwon/com"
|
||||||
|
"github.com/Unknwon/paginater"
|
||||||
|
|
||||||
"github.com/gogits/gogs/models"
|
"github.com/gogits/gogs/models"
|
||||||
"github.com/gogits/gogs/modules/base"
|
"github.com/gogits/gogs/modules/base"
|
||||||
"github.com/gogits/gogs/modules/log"
|
|
||||||
"github.com/gogits/gogs/modules/middleware"
|
"github.com/gogits/gogs/modules/middleware"
|
||||||
"github.com/gogits/gogs/modules/setting"
|
"github.com/gogits/gogs/modules/setting"
|
||||||
)
|
)
|
||||||
|
@ -21,18 +21,13 @@ import (
|
||||||
const (
|
const (
|
||||||
DASHBOARD base.TplName = "user/dashboard/dashboard"
|
DASHBOARD base.TplName = "user/dashboard/dashboard"
|
||||||
PULLS base.TplName = "user/dashboard/pulls"
|
PULLS base.TplName = "user/dashboard/pulls"
|
||||||
ISSUES base.TplName = "user/issues"
|
ISSUES base.TplName = "user/dashboard/issues"
|
||||||
STARS base.TplName = "user/stars"
|
STARS base.TplName = "user/stars"
|
||||||
PROFILE base.TplName = "user/profile"
|
PROFILE base.TplName = "user/profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Dashboard(ctx *middleware.Context) {
|
func getDashboardContextUser(ctx *middleware.Context) *models.User {
|
||||||
ctx.Data["Title"] = ctx.Tr("dashboard")
|
ctxUser := ctx.User
|
||||||
ctx.Data["PageIsDashboard"] = true
|
|
||||||
ctx.Data["PageIsNews"] = true
|
|
||||||
|
|
||||||
var ctxUser *models.User
|
|
||||||
// Check context type.
|
|
||||||
orgName := ctx.Params(":org")
|
orgName := ctx.Params(":org")
|
||||||
if len(orgName) > 0 {
|
if len(orgName) > 0 {
|
||||||
// Organization.
|
// Organization.
|
||||||
|
@ -43,10 +38,33 @@ func Dashboard(ctx *middleware.Context) {
|
||||||
} else {
|
} else {
|
||||||
ctx.Handle(500, "GetUserByName", err)
|
ctx.Handle(500, "GetUserByName", err)
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
ctxUser = org
|
ctxUser = org
|
||||||
} else {
|
}
|
||||||
|
ctx.Data["ContextUser"] = ctxUser
|
||||||
|
|
||||||
|
if err := ctx.User.GetOrganizations(); err != nil {
|
||||||
|
ctx.Handle(500, "GetOrganizations", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx.Data["Orgs"] = ctx.User.Orgs
|
||||||
|
|
||||||
|
return ctxUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func Dashboard(ctx *middleware.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("dashboard")
|
||||||
|
ctx.Data["PageIsDashboard"] = true
|
||||||
|
ctx.Data["PageIsNews"] = true
|
||||||
|
|
||||||
|
ctxUser := getDashboardContextUser(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check context type.
|
||||||
|
if !ctxUser.IsOrganization() {
|
||||||
// Normal user.
|
// Normal user.
|
||||||
ctxUser = ctx.User
|
ctxUser = ctx.User
|
||||||
collaborates, err := ctx.User.GetAccessibleRepositories()
|
collaborates, err := ctx.User.GetAccessibleRepositories()
|
||||||
|
@ -63,13 +81,6 @@ func Dashboard(ctx *middleware.Context) {
|
||||||
ctx.Data["CollaborateCount"] = len(repositories)
|
ctx.Data["CollaborateCount"] = len(repositories)
|
||||||
ctx.Data["CollaborativeRepos"] = repositories
|
ctx.Data["CollaborativeRepos"] = repositories
|
||||||
}
|
}
|
||||||
ctx.Data["ContextUser"] = ctxUser
|
|
||||||
|
|
||||||
if err := ctx.User.GetOrganizations(); err != nil {
|
|
||||||
ctx.Handle(500, "GetOrganizations", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["Orgs"] = ctx.User.Orgs
|
|
||||||
|
|
||||||
repos, err := models.GetRepositories(ctxUser.Id, true)
|
repos, err := models.GetRepositories(ctxUser.Id, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -142,6 +153,138 @@ func Pulls(ctx *middleware.Context) {
|
||||||
ctx.HTML(200, PULLS)
|
ctx.HTML(200, PULLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Issues(ctx *middleware.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("issues")
|
||||||
|
ctx.Data["PageIsIssues"] = true
|
||||||
|
|
||||||
|
ctxUser := getDashboardContextUser(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Organization does not have view type and filter mode.
|
||||||
|
var (
|
||||||
|
viewType string
|
||||||
|
filterMode = models.FM_ALL
|
||||||
|
assigneeID int64
|
||||||
|
posterID int64
|
||||||
|
)
|
||||||
|
if ctxUser.IsOrganization() {
|
||||||
|
viewType = "all"
|
||||||
|
} else {
|
||||||
|
viewType = ctx.Query("type")
|
||||||
|
types := []string{"assigned", "created_by"}
|
||||||
|
if !com.IsSliceContainsStr(types, viewType) {
|
||||||
|
viewType = "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch viewType {
|
||||||
|
case "assigned":
|
||||||
|
filterMode = models.FM_ASSIGN
|
||||||
|
assigneeID = ctxUser.Id
|
||||||
|
case "created_by":
|
||||||
|
filterMode = models.FM_CREATE
|
||||||
|
posterID = ctxUser.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repoID := ctx.QueryInt64("repo")
|
||||||
|
isShowClosed := ctx.Query("state") == "closed"
|
||||||
|
issueStats := models.GetUserIssueStats(repoID, ctxUser.Id, filterMode)
|
||||||
|
|
||||||
|
page := ctx.QueryInt("page")
|
||||||
|
if page <= 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int
|
||||||
|
if !isShowClosed {
|
||||||
|
total = int(issueStats.OpenCount)
|
||||||
|
} else {
|
||||||
|
total = int(issueStats.ClosedCount)
|
||||||
|
}
|
||||||
|
ctx.Data["Page"] = paginater.New(total, setting.IssuePagingNum, page, 5)
|
||||||
|
|
||||||
|
// Get repositories.
|
||||||
|
repos, err := models.GetRepositories(ctxUser.Id, true)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "GetRepositories", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repoIDs := make([]int64, 0, len(repos))
|
||||||
|
showRepos := make([]*models.Repository, 0, len(repos))
|
||||||
|
for _, repo := range repos {
|
||||||
|
if repo.NumIssues == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repoIDs = append(repoIDs, repo.ID)
|
||||||
|
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
|
||||||
|
issueStats.AllCount += int64(repo.NumOpenIssues)
|
||||||
|
|
||||||
|
if repo.ID == repoID {
|
||||||
|
repo.NumOpenIssues = int(issueStats.OpenCount)
|
||||||
|
repo.NumClosedIssues = int(issueStats.ClosedCount)
|
||||||
|
} else if filterMode != models.FM_ALL && repo.NumIssues > 0 {
|
||||||
|
// Calculate repository issue count with filter mode.
|
||||||
|
numOpen, numClosed := repo.IssueStats(ctxUser.Id, filterMode)
|
||||||
|
repo.NumOpenIssues, repo.NumClosedIssues = int(numOpen), int(numClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.ID == repoID ||
|
||||||
|
(isShowClosed && repo.NumClosedIssues > 0) ||
|
||||||
|
(!isShowClosed && repo.NumOpenIssues > 0) {
|
||||||
|
showRepos = append(showRepos, repo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Data["Repos"] = showRepos
|
||||||
|
|
||||||
|
if repoID > 0 {
|
||||||
|
repoIDs = []int64{repoID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get issues.
|
||||||
|
issues, err := models.Issues(ctxUser.Id, assigneeID, repoID, posterID, 0,
|
||||||
|
page, isShowClosed, false, "", "")
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "Issues: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get posters and repository.
|
||||||
|
for i := range issues {
|
||||||
|
issues[i].Repo, err = models.GetRepositoryByID(issues[i].RepoID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d]%v", issues[i].ID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = issues[i].Repo.GetOwner(); err != nil {
|
||||||
|
ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", issues[i].ID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = issues[i].GetPoster(); err != nil {
|
||||||
|
ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Data["Issues"] = issues
|
||||||
|
|
||||||
|
ctx.Data["IssueStats"] = issueStats
|
||||||
|
ctx.Data["ViewType"] = viewType
|
||||||
|
ctx.Data["RepoID"] = repoID
|
||||||
|
ctx.Data["IsShowClosed"] = isShowClosed
|
||||||
|
if isShowClosed {
|
||||||
|
ctx.Data["State"] = "closed"
|
||||||
|
} else {
|
||||||
|
ctx.Data["State"] = "open"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.HTML(200, ISSUES)
|
||||||
|
}
|
||||||
|
|
||||||
func ShowSSHKeys(ctx *middleware.Context, uid int64) {
|
func ShowSSHKeys(ctx *middleware.Context, uid int64) {
|
||||||
keys, err := models.ListPublicKeys(uid)
|
keys, err := models.ListPublicKeys(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -256,136 +399,3 @@ func Email2User(ctx *middleware.Context) {
|
||||||
}
|
}
|
||||||
ctx.Redirect(setting.AppSubUrl + "/user/" + u.Name)
|
ctx.Redirect(setting.AppSubUrl + "/user/" + u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Issues(ctx *middleware.Context) {
|
|
||||||
ctx.Data["Title"] = ctx.Tr("issues")
|
|
||||||
ctx.Data["PageIsDashboard"] = true
|
|
||||||
ctx.Data["PageIsIssues"] = true
|
|
||||||
|
|
||||||
viewType := ctx.Query("type")
|
|
||||||
types := []string{"assigned", "created_by"}
|
|
||||||
if !com.IsSliceContainsStr(types, viewType) {
|
|
||||||
viewType = "all"
|
|
||||||
}
|
|
||||||
|
|
||||||
isShowClosed := ctx.Query("state") == "closed"
|
|
||||||
|
|
||||||
var filterMode int
|
|
||||||
switch viewType {
|
|
||||||
case "assigned":
|
|
||||||
filterMode = models.FM_ASSIGN
|
|
||||||
case "created_by":
|
|
||||||
filterMode = models.FM_CREATE
|
|
||||||
}
|
|
||||||
|
|
||||||
repoId, _ := com.StrTo(ctx.Query("repoid")).Int64()
|
|
||||||
issueStats := models.GetUserIssueStats(ctx.User.Id, filterMode)
|
|
||||||
|
|
||||||
// Get all repositories.
|
|
||||||
repos, err := models.GetRepositories(ctx.User.Id, true)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Handle(500, "user.Issues(GetRepositories)", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
repoIds := make([]int64, 0, len(repos))
|
|
||||||
showRepos := make([]*models.Repository, 0, len(repos))
|
|
||||||
for _, repo := range repos {
|
|
||||||
if repo.NumIssues == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
repoIds = append(repoIds, repo.ID)
|
|
||||||
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
|
|
||||||
issueStats.AllCount += int64(repo.NumOpenIssues)
|
|
||||||
|
|
||||||
if isShowClosed {
|
|
||||||
if repo.NumClosedIssues > 0 {
|
|
||||||
if filterMode == models.FM_CREATE {
|
|
||||||
repo.NumClosedIssues = int(models.GetIssueCountByPoster(ctx.User.Id, repo.ID, isShowClosed))
|
|
||||||
}
|
|
||||||
showRepos = append(showRepos, repo)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if repo.NumOpenIssues > 0 {
|
|
||||||
if filterMode == models.FM_CREATE {
|
|
||||||
repo.NumOpenIssues = int(models.GetIssueCountByPoster(ctx.User.Id, repo.ID, isShowClosed))
|
|
||||||
}
|
|
||||||
showRepos = append(showRepos, repo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repoId > 0 {
|
|
||||||
repoIds = []int64{repoId}
|
|
||||||
}
|
|
||||||
|
|
||||||
page, _ := com.StrTo(ctx.Query("page")).Int()
|
|
||||||
|
|
||||||
// Get all issues.
|
|
||||||
var ius []*models.IssueUser
|
|
||||||
switch viewType {
|
|
||||||
case "assigned":
|
|
||||||
fallthrough
|
|
||||||
case "created_by":
|
|
||||||
ius, err = models.GetIssueUserPairsByMode(ctx.User.Id, repoId, isShowClosed, page, filterMode)
|
|
||||||
default:
|
|
||||||
ius, err = models.GetIssueUserPairsByRepoIds(repoIds, isShowClosed, page)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
ctx.Handle(500, "user.Issues(GetAllIssueUserPairs)", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
issues := make([]*models.Issue, len(ius))
|
|
||||||
for i := range ius {
|
|
||||||
issues[i], err = models.GetIssueByID(ius[i].IssueID)
|
|
||||||
if err != nil {
|
|
||||||
if models.IsErrIssueNotExist(err) {
|
|
||||||
log.Warn("user.Issues(GetIssueById #%d): issue not exist", ius[i].IssueID)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
ctx.Handle(500, fmt.Sprintf("user.Issues(GetIssueById #%d)", ius[i].IssueID), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
issues[i].Repo, err = models.GetRepositoryByID(issues[i].RepoID)
|
|
||||||
if err != nil {
|
|
||||||
if models.IsErrRepoNotExist(err) {
|
|
||||||
log.Warn("GetRepositoryById[%d]: repository not exist", issues[i].RepoID)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
ctx.Handle(500, fmt.Sprintf("GetRepositoryById[%d]", issues[i].RepoID), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = issues[i].Repo.GetOwner(); err != nil {
|
|
||||||
ctx.Handle(500, "user.Issues(GetOwner)", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = issues[i].GetPoster(); err != nil {
|
|
||||||
ctx.Handle(500, "user.Issues(GetUserById)", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["RepoId"] = repoId
|
|
||||||
ctx.Data["Repos"] = showRepos
|
|
||||||
ctx.Data["Issues"] = issues
|
|
||||||
ctx.Data["ViewType"] = viewType
|
|
||||||
ctx.Data["IssueStats"] = issueStats
|
|
||||||
ctx.Data["IsShowClosed"] = isShowClosed
|
|
||||||
if isShowClosed {
|
|
||||||
ctx.Data["State"] = "closed"
|
|
||||||
ctx.Data["ShowCount"] = issueStats.ClosedCount
|
|
||||||
} else {
|
|
||||||
ctx.Data["ShowCount"] = issueStats.OpenCount
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["ContextUser"] = ctx.User
|
|
||||||
|
|
||||||
ctx.HTML(200, ISSUES)
|
|
||||||
}
|
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
|
|
||||||
{{if .IsSigned}}
|
{{if .IsSigned}}
|
||||||
<a class="item{{if .PageIsDashboard}} active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a>
|
<a class="item{{if .PageIsDashboard}} active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a>
|
||||||
|
<a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a class="item{{if .PageIsHome}} active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a>
|
<a class="item{{if .PageIsHome}} active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -75,7 +76,6 @@
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
{{if .IsSigned}}
|
{{if .IsSigned}}
|
||||||
<a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
|
|
||||||
<div class="right menu">
|
<div class="right menu">
|
||||||
<div class="ui dropdown head link jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted">
|
<div class="ui dropdown head link jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted">
|
||||||
<span class="text">
|
<span class="text">
|
||||||
|
|
|
@ -10,9 +10,11 @@
|
||||||
<a class="{{if .PageIsSettingsHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks">
|
<a class="{{if .PageIsSettingsHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks">
|
||||||
{{.i18n.Tr "repo.settings.hooks"}}
|
{{.i18n.Tr "repo.settings.hooks"}}
|
||||||
</a>
|
</a>
|
||||||
|
{{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}}
|
||||||
<a class="{{if .PageIsSettingsGitHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks/git">
|
<a class="{{if .PageIsSettingsGitHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks/git">
|
||||||
{{.i18n.Tr "repo.settings.githooks"}}
|
{{.i18n.Tr "repo.settings.githooks"}}
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
<a class="{{if .PageIsSettingsKeys}}active{{end}} item" href="{{.RepoLink}}/settings/keys">
|
<a class="{{if .PageIsSettingsKeys}}active{{end}} item" href="{{.RepoLink}}/settings/keys">
|
||||||
{{.i18n.Tr "repo.settings.deploy_keys"}}
|
{{.i18n.Tr "repo.settings.deploy_keys"}}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div class="dashboard issues">
|
||||||
|
{{template "user/dashboard/navbar" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="four wide column">
|
||||||
|
<div class="ui secondary vertical filter menu">
|
||||||
|
<a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{.Link}}?repo={{.RepoID}}&state={{.State}}">
|
||||||
|
{{.i18n.Tr "home.issues.in_your_repos"}}
|
||||||
|
<strong class="ui right">{{.IssueStats.AllCount}}</strong>
|
||||||
|
</a>
|
||||||
|
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{.Link}}?type=assigned&repo={{.RepoID}}&state={{.State}}">
|
||||||
|
{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}
|
||||||
|
<strong class="ui right">{{.IssueStats.AssignCount}}</strong>
|
||||||
|
</a>
|
||||||
|
<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{.Link}}?type=created_by&repo={{.RepoID}}&state={{.State}}">
|
||||||
|
{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}
|
||||||
|
<strong class="ui right">{{.IssueStats.CreateCount}}</strong>
|
||||||
|
</a>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
{{range .Repos}}
|
||||||
|
<a class="{{if eq $.RepoID .ID}}active{{end}} item" href="{{$.Link}}?type={{$.ViewType}}{{if not (eq $.RepoID .ID)}}&repo={{.ID}}{{end}}&state={{$.State}}">{{$.SignedUser.Name}}/{{.Name}} <strong class="ui right">{{if $.IsShowClosed}}{{.NumClosedIssues}}{{else}}{{.NumOpenIssues}}{{end}}</strong></a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="twelve wide column content">
|
||||||
|
<div class="ui tiny buttons">
|
||||||
|
<a class="ui green basic button {{if not .IsShowClosed}}active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repo={{.RepoID}}&state=open">
|
||||||
|
<i class="octicon octicon-issue-opened"></i>
|
||||||
|
{{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}}
|
||||||
|
</a>
|
||||||
|
<a class="ui red basic button {{if .IsShowClosed}}active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repo={{.RepoID}}&state=closed">
|
||||||
|
<i class="octicon octicon-issue-closed"></i>
|
||||||
|
{{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="issue list">
|
||||||
|
{{range .Issues}}
|
||||||
|
{{ $timeStr:= TimeSince .Created $.Lang }}
|
||||||
|
<li class="item">
|
||||||
|
<div class="ui label">#{{.ID}}</div>
|
||||||
|
<a class="title" href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Name}}</a>
|
||||||
|
|
||||||
|
{{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 | Safe}}
|
||||||
|
{{if .Assignee}}
|
||||||
|
<a class="ui right assignee poping up" href="{{.Assignee.HomeLink}}" data-content="{{.Assignee.Name}}" data-variation="inverted" data-position="left center">
|
||||||
|
<img class="ui avatar image" src="{{.Assignee.AvatarLink}}">
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{with .Page}}
|
||||||
|
{{if gt .TotalPages 1}}
|
||||||
|
<div class="center page buttons">
|
||||||
|
<div class="ui borderless pagination menu">
|
||||||
|
<a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&page={{.Previous}}"{{end}}>
|
||||||
|
<i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}}
|
||||||
|
</a>
|
||||||
|
{{range .Pages}}
|
||||||
|
{{if eq .Num -1}}
|
||||||
|
<a class="disabled item">...</a>
|
||||||
|
{{else}}
|
||||||
|
<a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&page={{.Num}}"{{end}}>{{.Num}}</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&page={{.Next}}"{{end}}>
|
||||||
|
{{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui floating dropdown link jump">
|
||||||
|
<span class="text">
|
||||||
|
<img class="ui avatar image" src="{{.ContextUser.AvatarLink}}">
|
||||||
|
{{.ContextUser.Name}}
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
</span>
|
||||||
|
<div class="context user menu" tabindex="-1">
|
||||||
|
<div class="ui header">
|
||||||
|
{{.i18n.Tr "home.switch_dashboard_context"}}
|
||||||
|
</div>
|
||||||
|
<a class="{{if eq .ContextUser.Id .SignedUser.Id}}active selected{{end}} item" href="{{AppSubUrl}}/issues">
|
||||||
|
<img class="ui image" src="{{.SignedUser.AvatarLink}}">
|
||||||
|
{{.SignedUser.Name}}
|
||||||
|
</a>
|
||||||
|
{{range .Orgs}}
|
||||||
|
{{if .IsOwnedBy $.SignedUser.Id}}
|
||||||
|
<a class="{{if eq $.ContextUser.Id .Id}}active selected{{end}} item" href="{{AppSubUrl}}/org/{{.Name}}/issues">
|
||||||
|
<img class="ui image" src="{{.AvatarLink}}">
|
||||||
|
{{.Name}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
<a class="item" href="{{AppSubUrl}}/org/create">
|
||||||
|
<i class="octicon octicon-repo-create"></i> {{.i18n.Tr "new_org"}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
|
@ -1,44 +0,0 @@
|
||||||
{{template "ng/base/head" .}}
|
|
||||||
{{template "ng/base/header" .}}
|
|
||||||
{{template "user/dashboard/nav" .}}
|
|
||||||
<div id="dashboard-wrapper">
|
|
||||||
<div id="dashboard" class="container" data-page="user">
|
|
||||||
{{if .HasInfo}}<div class="alert alert-info">{{.InfoMsg}}</div>{{end}}
|
|
||||||
<div id="issue">
|
|
||||||
<div class="left grid-1-5 filter-list">
|
|
||||||
<ul class="list-unstyled menu menu-vertical">
|
|
||||||
<li><a href="{{AppSubUrl}}/issues?state={{.State}}&repoid={{.RepoId}}" class="radius{{if eq .ViewType "all"}} active{{end}}" >In your repositories <strong class="pull-right">{{.IssueStats.AllCount}}</strong></a></li>
|
|
||||||
<li><a href="{{AppSubUrl}}/issues?type=assigned&repoid={{.RepoId}}&state={{.State}}" class="radius{{if eq .ViewType "assigned"}} active{{end}}">Assigned to you <strong class="pull-right">{{.IssueStats.AssignCount}}</strong></a></li>
|
|
||||||
<li><a href="{{AppSubUrl}}/issues?type=created_by&repoid={{.RepoId}}&state={{.State}}" class="radius{{if eq .ViewType "created_by"}} active{{end}}">Created by you <strong class="pull-right">{{.IssueStats.CreateCount}}</strong></a></li>
|
|
||||||
<li><hr/></li>
|
|
||||||
{{range .Repos}}
|
|
||||||
<li><a href="{{AppSubUrl}}/issues?type={{$.ViewType}}{{if eq $.RepoId .ID}}{{else}}&repoid={{.ID}}{{end}}&state={{$.State}}" class="radius{{if eq $.RepoId .ID}} active{{end}}">{{$.SignedUser.Name}}/{{.Name}} <strong class="pull-right">{{if $.IsShowClosed}}{{.NumClosedIssues}}{{else}}{{.NumOpenIssues}}{{end}}</strong></a></li>
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right grid-3-4">
|
|
||||||
<div class="filter-option">
|
|
||||||
<div class="btn-group">
|
|
||||||
<a class="btn btn-white btn-small issue-open{{if not .IsShowClosed}} active{{end}}" href="{{AppSubUrl}}/issues?type={{.ViewType}}&repoid={{.RepoId}}">Open</a>
|
|
||||||
<a class="btn btn-white btn-small issue-close{{if .IsShowClosed}} active{{end}}" href="{{AppSubUrl}}/issues?type={{.ViewType}}&repoid={{.RepoId}}&state=closed">Closed</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="issues list-group">
|
|
||||||
{{range .Issues}}{{if .}}
|
|
||||||
<div class="list-group-item issue-item" id="issue-{{.ID}}" onclick="window.location.href='{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}'">
|
|
||||||
<span class="number pull-right">#{{.Index}}</span>
|
|
||||||
<h5 class="title"><a href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Name}}</a></h5>
|
|
||||||
<p class="info">
|
|
||||||
<span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/>
|
|
||||||
<a href="{{AppSubUrl}}/{{.Poster.Name}}">{{.Poster.Name}}</a></span>
|
|
||||||
<span class="time">{{TimeSince .Created $.Lang}}</span>
|
|
||||||
<span class="comment"><i class="fa fa-comments"></i> {{.NumComments}}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{{end}}{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{template "ng/base/footer" .}}
|
|
Loading…
Reference in New Issue