mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-11 07:55:55 +01:00
Merge branch 'master' into fork-and-pullreq
Conflicts: src/main/scala/app/CreateRepositoryController.scala src/main/scala/util/JGitUtil.scala
This commit is contained in:
@@ -8,3 +8,17 @@ ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK0 FOREIGN KEY (GROUP_
|
|||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK1 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK1 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ADD COLUMN GROUP_ACCOUNT BOOLEAN NOT NULL DEFAULT FALSE;
|
ALTER TABLE ACCOUNT ADD COLUMN GROUP_ACCOUNT BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
||||||
|
SELECT
|
||||||
|
A.USER_NAME,
|
||||||
|
A.REPOSITORY_NAME,
|
||||||
|
A.ISSUE_ID,
|
||||||
|
NVL(B.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
||||||
|
FROM ISSUE A
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
||||||
|
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
||||||
|
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||||
|
) B
|
||||||
|
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
context.mount(new SearchController, "/")
|
context.mount(new SearchController, "/")
|
||||||
context.mount(new FileUploadController, "/upload")
|
context.mount(new FileUploadController, "/upload")
|
||||||
context.mount(new SignInController, "/*")
|
context.mount(new SignInController, "/*")
|
||||||
|
context.mount(new DashboardController, "/*")
|
||||||
context.mount(new UserManagementController, "/*")
|
context.mount(new UserManagementController, "/*")
|
||||||
context.mount(new SystemSettingsController, "/*")
|
context.mount(new SystemSettingsController, "/*")
|
||||||
context.mount(new CreateRepositoryController, "/*")
|
context.mount(new CreateRepositoryController, "/*")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import _root_.util.Directory._
|
import _root_.util.Directory._
|
||||||
import _root_.util.{FileUtil, Validations}
|
import _root_.util.{StringUtil, FileUtil, Validations}
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.json._
|
import org.scalatra.json._
|
||||||
import org.json4s._
|
import org.json4s._
|
||||||
@@ -10,7 +10,7 @@ import org.apache.commons.io.FileUtils
|
|||||||
import model.Account
|
import model.Account
|
||||||
import scala.Some
|
import scala.Some
|
||||||
import service.AccountService
|
import service.AccountService
|
||||||
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
import javax.servlet.http.{HttpServletResponse, HttpSession, HttpServletRequest}
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||||
|
|
||||||
@@ -24,15 +24,27 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
|
|
||||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
||||||
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
||||||
val path = httpRequest.getRequestURI.substring(request.getServletContext.getContextPath.length)
|
val httpResponse = response.asInstanceOf[HttpServletResponse]
|
||||||
|
val context = request.getServletContext.getContextPath
|
||||||
|
val path = httpRequest.getRequestURI.substring(context.length)
|
||||||
|
|
||||||
if(path.startsWith("/console/")){
|
if(path.startsWith("/console/")){
|
||||||
Option(httpRequest.getSession.getAttribute("LOGIN_ACCOUNT").asInstanceOf[Account]).collect {
|
val account = httpRequest.getSession.getAttribute("LOGIN_ACCOUNT").asInstanceOf[Account]
|
||||||
case account if(account.isAdmin) => chain.doFilter(request, response)
|
if(account == null){
|
||||||
}
|
// Redirect to login form
|
||||||
} else if(path.startsWith("/git/")){
|
httpResponse.sendRedirect(context + "/signin?" + path)
|
||||||
|
} else if(account.isAdmin){
|
||||||
|
// H2 Console (administrators only)
|
||||||
chain.doFilter(request, response)
|
chain.doFilter(request, response)
|
||||||
} else {
|
} else {
|
||||||
|
// Redirect to dashboard
|
||||||
|
httpResponse.sendRedirect(context + "/")
|
||||||
|
}
|
||||||
|
} else if(path.startsWith("/git/")){
|
||||||
|
// Git repository
|
||||||
|
chain.doFilter(request, response)
|
||||||
|
} else {
|
||||||
|
// Scalatra actions
|
||||||
super.doFilter(request, response, chain)
|
super.doFilter(request, response, chain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/main/scala/app/DashboardController.scala
Normal file
49
src/main/scala/app/DashboardController.scala
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import service._
|
||||||
|
import util.UsersAuthenticator
|
||||||
|
|
||||||
|
class DashboardController extends DashboardControllerBase
|
||||||
|
with IssuesService with RepositoryService with AccountService
|
||||||
|
with UsersAuthenticator
|
||||||
|
|
||||||
|
trait DashboardControllerBase extends ControllerBase {
|
||||||
|
self: IssuesService with RepositoryService with UsersAuthenticator =>
|
||||||
|
|
||||||
|
get("/dashboard/issues/repos")(usersOnly {
|
||||||
|
searchIssues("all")
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/dashboard/issues/assigned")(usersOnly {
|
||||||
|
searchIssues("assigned")
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/dashboard/issues/created_by")(usersOnly {
|
||||||
|
searchIssues("created_by")
|
||||||
|
})
|
||||||
|
|
||||||
|
private def searchIssues(filter: String) = {
|
||||||
|
import IssuesService._
|
||||||
|
|
||||||
|
// condition
|
||||||
|
val sessionKey = "dashboard/issues"
|
||||||
|
val condition = if(request.getQueryString == null)
|
||||||
|
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
||||||
|
else IssueSearchCondition(request)
|
||||||
|
|
||||||
|
session.put(sessionKey, condition)
|
||||||
|
|
||||||
|
val repositories = getAccessibleRepositories(context.loginAccount, baseUrl)
|
||||||
|
//
|
||||||
|
dashboard.html.issues(
|
||||||
|
issues.html.listparts(Nil, 0, 0, 0, condition),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
repositories,
|
||||||
|
condition,
|
||||||
|
filter)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,11 @@ object IssueId extends Table[(String, String, Int)]("ISSUE_ID") with IssueTempla
|
|||||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object IssueOutline extends Table[(String, String, Int, Int)]("ISSUE_OUTLINE_VIEW") with IssueTemplate {
|
||||||
|
def commentCount = column[Int]("COMMENT_COUNT")
|
||||||
|
def * = userName ~ repositoryName ~ issueId ~ commentCount
|
||||||
|
}
|
||||||
|
|
||||||
object Issues extends Table[Issue]("ISSUE") with IssueTemplate with MilestoneTemplate {
|
object Issues extends Table[Issue]("ISSUE") with IssueTemplate with MilestoneTemplate {
|
||||||
def openedUserName = column[String]("OPENED_USER_NAME")
|
def openedUserName = column[String]("OPENED_USER_NAME")
|
||||||
def assignedUserName = column[String]("ASSIGNED_USER_NAME")
|
def assignedUserName = column[String]("ASSIGNED_USER_NAME")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Q.interpolation
|
|||||||
|
|
||||||
import model._
|
import model._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
import util.StringUtil
|
import util.StringUtil._
|
||||||
|
|
||||||
trait IssuesService {
|
trait IssuesService {
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
@@ -99,19 +99,19 @@ trait IssuesService {
|
|||||||
def searchIssue(owner: String, repository: String, condition: IssueSearchCondition,
|
def searchIssue(owner: String, repository: String, condition: IssueSearchCondition,
|
||||||
filter: String, userName: Option[String], offset: Int, limit: Int): List[(Issue, List[Label], Int)] = {
|
filter: String, userName: Option[String], offset: Int, limit: Int): List[(Issue, List[Label], Int)] = {
|
||||||
|
|
||||||
// get issues and comment count
|
// get issues and comment count and labels
|
||||||
val issues = searchIssueQuery(owner, repository, condition, filter, userName)
|
searchIssueQuery(owner, repository, condition, filter, userName)
|
||||||
.leftJoin(Query(IssueComments)
|
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
|
||||||
.filter { t =>
|
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
|
||||||
(t.byRepository(owner, repository)) &&
|
.leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
|
||||||
(t.action inSetBind Seq("comment", "close_comment", "reopen_comment"))
|
.map { case (((t1, t2), t3), t4) =>
|
||||||
|
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
|
||||||
}
|
}
|
||||||
.groupBy { _.issueId }
|
.sortBy(_._4) // labelName
|
||||||
.map { case (issueId, t) => issueId ~ t.length }).on((t1, t2) => t1.issueId is t2._1)
|
.sortBy { case (t1, commentCount, _,_,_) =>
|
||||||
.sortBy { case (t1, t2) =>
|
|
||||||
(condition.sort match {
|
(condition.sort match {
|
||||||
case "created" => t1.registeredDate
|
case "created" => t1.registeredDate
|
||||||
case "comments" => t2._2
|
case "comments" => commentCount
|
||||||
case "updated" => t1.updatedDate
|
case "updated" => t1.updatedDate
|
||||||
}) match {
|
}) match {
|
||||||
case sort => condition.direction match {
|
case sort => condition.direction match {
|
||||||
@@ -120,26 +120,17 @@ trait IssuesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { case (t1, t2) => (t1, t2._2.ifNull(0)) }
|
|
||||||
.drop(offset).take(limit)
|
.drop(offset).take(limit)
|
||||||
.list
|
.list
|
||||||
|
.splitWith(_._1.issueId == _._1.issueId)
|
||||||
// get labels
|
.map { issues => issues.head match {
|
||||||
val labels = Query(IssueLabels)
|
case (issue, commentCount, _,_,_) =>
|
||||||
.innerJoin(Labels).on { (t1, t2) =>
|
(issue,
|
||||||
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
|
issues.flatMap { t => t._3.map (
|
||||||
}
|
Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
|
||||||
.filter { case (t1, t2) =>
|
)} toList,
|
||||||
(t1.byRepository(owner, repository)) &&
|
commentCount)
|
||||||
(t1.issueId inSetBind (issues.map(_._1.issueId)))
|
}} toList
|
||||||
}
|
|
||||||
.sortBy { case (t1, t2) => t1.issueId ~ t2.labelName }
|
|
||||||
.map { case (t1, t2) => (t1.issueId, t2) }
|
|
||||||
.list
|
|
||||||
|
|
||||||
issues.map { case (issue, commentCount) =>
|
|
||||||
(issue, labels.collect { case (issueId, labels) if(issueId == issue.issueId) => labels }, commentCount)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,42 +238,47 @@ trait IssuesService {
|
|||||||
*/
|
*/
|
||||||
def searchIssuesByKeyword(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
|
def searchIssuesByKeyword(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
|
||||||
import scala.slick.driver.H2Driver.likeEncode
|
import scala.slick.driver.H2Driver.likeEncode
|
||||||
val keywords = StringUtil.splitWords(query.toLowerCase)
|
val keywords = splitWords(query.toLowerCase)
|
||||||
|
|
||||||
// Search Issue
|
// Search Issue
|
||||||
val issues = Query(Issues).filter { t =>
|
val issues = Issues
|
||||||
|
.innerJoin(IssueOutline).on { case (t1, t2) =>
|
||||||
|
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||||
|
}
|
||||||
|
.filter { case (t1, t2) =>
|
||||||
keywords.map { keyword =>
|
keywords.map { keyword =>
|
||||||
(t.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
|
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
|
||||||
(t.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
|
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
|
||||||
} .reduceLeft(_ && _)
|
} .reduceLeft(_ && _)
|
||||||
}.map { t => (t, 0, t.content.?) }
|
}
|
||||||
|
.map { case (t1, t2) =>
|
||||||
|
(t1, 0, t1.content.?, t2.commentCount)
|
||||||
|
}
|
||||||
|
|
||||||
// Search IssueComment
|
// Search IssueComment
|
||||||
val comments = Query(IssueComments).innerJoin(Issues).on { case (t1, t2) =>
|
val comments = IssueComments
|
||||||
|
.innerJoin(Issues).on { case (t1, t2) =>
|
||||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||||
}.filter { case (t1, t2) =>
|
}
|
||||||
|
.innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
|
||||||
|
t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
|
||||||
|
}
|
||||||
|
.filter { case ((t1, t2), t3) =>
|
||||||
keywords.map { query =>
|
keywords.map { query =>
|
||||||
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
|
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
|
||||||
}.reduceLeft(_ && _)
|
}.reduceLeft(_ && _)
|
||||||
}.map { case (t1, t2) => (t2, t1.commentId, t1.content.?) }
|
|
||||||
|
|
||||||
def getCommentCount(issue: Issue): Int = {
|
|
||||||
Query(IssueComments)
|
|
||||||
.filter { t =>
|
|
||||||
t.byIssue(issue.userName, issue.repositoryName, issue.issueId) &&
|
|
||||||
(t.action inSetBind Seq("comment", "close_comment", "reopen_comment"))
|
|
||||||
}
|
}
|
||||||
.map(_.issueId)
|
.map { case ((t1, t2), t3) =>
|
||||||
.list.length
|
(t2, t1.commentId, t1.content.?, t3.commentCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
issues.union(comments).sortBy { case (issue, commentId, _) =>
|
issues.union(comments).sortBy { case (issue, commentId, _, _) =>
|
||||||
issue.issueId ~ commentId
|
issue.issueId ~ commentId
|
||||||
}.list.splitWith { case ((issue1, _, _), (issue2, _, _)) =>
|
}.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
|
||||||
issue1.issueId == issue2.issueId
|
issue1.issueId == issue2.issueId
|
||||||
}.map { result =>
|
}.map { _.head match {
|
||||||
val (issue, _, content) = result.head
|
case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
|
||||||
(issue, getCommentCount(issue) , content.getOrElse(""))
|
}
|
||||||
}.toList
|
}.toList
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,13 +286,13 @@ trait IssuesService {
|
|||||||
|
|
||||||
object IssuesService {
|
object IssuesService {
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import util.StringUtil._
|
|
||||||
|
|
||||||
val IssueLimit = 30
|
val IssueLimit = 30
|
||||||
|
|
||||||
case class IssueSearchCondition(
|
case class IssueSearchCondition(
|
||||||
labels: Set[String] = Set.empty,
|
labels: Set[String] = Set.empty,
|
||||||
milestoneId: Option[Option[Int]] = None,
|
milestoneId: Option[Option[Int]] = None,
|
||||||
|
repo: Option[String] = None,
|
||||||
state: String = "open",
|
state: String = "open",
|
||||||
sort: String = "created",
|
sort: String = "created",
|
||||||
direction: String = "desc"){
|
direction: String = "desc"){
|
||||||
@@ -308,6 +304,7 @@ object IssuesService {
|
|||||||
case Some(x) => x.toString
|
case Some(x) => x.toString
|
||||||
case None => "none"
|
case None => "none"
|
||||||
})},
|
})},
|
||||||
|
repo.map("for=" + urlEncode(_)),
|
||||||
Some("state=" + urlEncode(state)),
|
Some("state=" + urlEncode(state)),
|
||||||
Some("sort=" + urlEncode(sort)),
|
Some("sort=" + urlEncode(sort)),
|
||||||
Some("direction=" + urlEncode(direction))).flatten.mkString("&")
|
Some("direction=" + urlEncode(direction))).flatten.mkString("&")
|
||||||
@@ -328,6 +325,7 @@ object IssuesService {
|
|||||||
case "none" => None
|
case "none" => None
|
||||||
case x => Some(x.toInt)
|
case x => Some(x.toInt)
|
||||||
}),
|
}),
|
||||||
|
param(request, "for"),
|
||||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
|
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
|
||||||
|
|||||||
@@ -29,11 +29,14 @@ trait RepositorySearchService { self: IssuesService =>
|
|||||||
|
|
||||||
def countFiles(owner: String, repository: String, query: String): Int =
|
def countFiles(owner: String, repository: String, query: String): Int =
|
||||||
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
||||||
searchRepositoryFiles(git, query).length
|
if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
||||||
}
|
}
|
||||||
|
|
||||||
def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] =
|
def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] =
|
||||||
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
||||||
|
if(JGitUtil.isEmpty(git)){
|
||||||
|
Nil
|
||||||
|
} else {
|
||||||
val files = searchRepositoryFiles(git, query)
|
val files = searchRepositoryFiles(git, query)
|
||||||
val commits = JGitUtil.getLatestCommitFromPaths(git, files.toList.map(_._1), "HEAD")
|
val commits = JGitUtil.getLatestCommitFromPaths(git, files.toList.map(_._1), "HEAD")
|
||||||
files.map { case (path, text) =>
|
files.map { case (path, text) =>
|
||||||
@@ -45,6 +48,7 @@ trait RepositorySearchService { self: IssuesService =>
|
|||||||
lineNumber)
|
lineNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
|
private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
val revWalk = new RevWalk(git.getRepository)
|
||||||
|
|||||||
@@ -53,14 +53,11 @@ trait WikiService {
|
|||||||
*/
|
*/
|
||||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||||
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
||||||
try {
|
if(!JGitUtil.isEmpty(git)){
|
||||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||||
WikiPageInfo(file.name, new String(git.getRepository.open(file.id).getBytes, "UTF-8"), file.committer, file.time)
|
WikiPageInfo(file.name, new String(git.getRepository.open(file.id).getBytes, "UTF-8"), file.committer, file.time)
|
||||||
}
|
}
|
||||||
} catch {
|
} else None
|
||||||
// TODO no commit, but it should not judge by exception.
|
|
||||||
case e: NullPointerException => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +66,7 @@ trait WikiService {
|
|||||||
*/
|
*/
|
||||||
def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] = {
|
def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] = {
|
||||||
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
||||||
try {
|
if(!JGitUtil.isEmpty(git)){
|
||||||
val index = path.lastIndexOf('/')
|
val index = path.lastIndexOf('/')
|
||||||
val parentPath = if(index < 0) "." else path.substring(0, index)
|
val parentPath = if(index < 0) "." else path.substring(0, index)
|
||||||
val fileName = if(index < 0) path else path.substring(index + 1)
|
val fileName = if(index < 0) path else path.substring(index + 1)
|
||||||
@@ -77,10 +74,7 @@ trait WikiService {
|
|||||||
JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
|
JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
|
||||||
git.getRepository.open(file.id).getBytes
|
git.getRepository.open(file.id).getBytes
|
||||||
}
|
}
|
||||||
} catch {
|
} else None
|
||||||
// TODO no commit, but it should not judge by exception.
|
|
||||||
case e: NullPointerException => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -537,6 +537,8 @@ object JGitUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
||||||
|
|
||||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = {
|
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = {
|
||||||
val config = repository.getConfig
|
val config = repository.getConfig
|
||||||
config.setBoolean("http", null, "receivepack", true)
|
config.setBoolean("http", null, "receivepack", true)
|
||||||
|
|||||||
48
src/main/twirl/dashboard/issues.scala.html
Normal file
48
src/main/twirl/dashboard/issues.scala.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@(listparts: twirl.api.Html,
|
||||||
|
allCount: Int,
|
||||||
|
assignedCount: Int,
|
||||||
|
createdByCount: Int,
|
||||||
|
repositories: List[service.RepositoryService.RepositoryInfo],
|
||||||
|
condition: service.IssuesService.IssueSearchCondition,
|
||||||
|
filter: String)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@html.main("Your Issues"){
|
||||||
|
@dashboard.html.tab("issues")
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span3">
|
||||||
|
<ul class="nav nav-pills nav-stacked">
|
||||||
|
<li@if(filter == "all"){ class="active"}>
|
||||||
|
<a href="/dashboard/issues/repos@condition.toURL">
|
||||||
|
<span class="count-right">@allCount</span>
|
||||||
|
In your repositories
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li@if(filter == "assigned"){ class="active"}>
|
||||||
|
<a href="/dashboard/issues/assigned@condition.toURL">
|
||||||
|
<span class="count-right">@assignedCount</span>
|
||||||
|
Assigned to you
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li@if(filter == "created_by"){ class="active"}>
|
||||||
|
<a href="/dashboard/issues/created_by@condition.toURL">
|
||||||
|
<span class="count-right">@createdByCount</span>
|
||||||
|
Created by you
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<hr/>
|
||||||
|
<ul class="nav nav-pills nav-stacked small">
|
||||||
|
@repositories.map { repository =>
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(repo = Some(repository.owner + "/" + repository.name)).toURL">
|
||||||
|
<span class="count-right">0</span>
|
||||||
|
@repository.owner/@repository.name
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@listparts
|
||||||
|
</div>
|
||||||
|
}
|
||||||
8
src/main/twirl/dashboard/tab.scala.html
Normal file
8
src/main/twirl/dashboard/tab.scala.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
@(active: String = "")(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li@if(active == ""){ class="active"}><a href="/">News Feed</a></li>
|
||||||
|
@if(loginAccount.isDefined){
|
||||||
|
<li@if(active == "issues"){ class="active"}><a href="/dashboard/issues/repos">Issues</a></li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@main("GitBucket"){
|
@main("GitBucket"){
|
||||||
|
@dashboard.html.tab()
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span8">
|
<div class="span8">
|
||||||
<h3>News Feed</h3>
|
|
||||||
@helper.html.activities(activities)
|
@helper.html.activities(activities)
|
||||||
</div>
|
</div>
|
||||||
<div class="span4">
|
<div class="span4">
|
||||||
|
|||||||
@@ -131,158 +131,8 @@
|
|||||||
@_root_.issues.labels.html.edit(None, repository)
|
@_root_.issues.labels.html.edit(None, repository)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="span9">
|
@***** show issue list *****@
|
||||||
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
@listparts(issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission)
|
||||||
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL" id="clear-filter">
|
|
||||||
<i class="icon-remove-circle"></i> Clear milestone and label filters
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
<div class="pull-right">
|
|
||||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 7, condition.toURL)
|
|
||||||
</div>
|
|
||||||
<div class="btn-group">
|
|
||||||
<a class="btn@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
|
||||||
<a class="btn@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
|
||||||
</div>
|
|
||||||
<div class="btn-group">
|
|
||||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
|
||||||
Sort:
|
|
||||||
<strong>
|
|
||||||
@if(condition.sort == "created" && condition.direction == "desc"){ Newest }
|
|
||||||
@if(condition.sort == "created" && condition.direction == "asc" ){ Oldest }
|
|
||||||
@if(condition.sort == "comments" && condition.direction == "desc"){ Most commented }
|
|
||||||
@if(condition.sort == "comments" && condition.direction == "asc" ){ Least commented }
|
|
||||||
@if(condition.sort == "updated" && condition.direction == "desc"){ Recently updated }
|
|
||||||
@if(condition.sort == "updated" && condition.direction == "asc" ){ Least recently updated }
|
|
||||||
</strong>
|
|
||||||
<span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
|
||||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
|
||||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
|
||||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
|
||||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<table class="table table-bordered table-hover table-issues">
|
|
||||||
@if(issues.isEmpty){
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
|
||||||
No issues to show.
|
|
||||||
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
|
||||||
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL">Clear active filters.</a>
|
|
||||||
} else {
|
|
||||||
<a href="@url(repository)/issues/new">Create a new issue.</a>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} else {
|
|
||||||
@if(hasWritePermission){
|
|
||||||
<tr>
|
|
||||||
<td style="background-color: #eee;">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button class="btn btn-mini" id="state"><strong>@{if(condition.state == "open") "Close" else "Reopen"}</strong></button>
|
|
||||||
</div>
|
|
||||||
@helper.html.dropdown("Label") {
|
|
||||||
@labels.map { label =>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId">
|
|
||||||
<i class="icon-white"></i>
|
|
||||||
<span class="label" style="background-color: #@label.color;"> </span>
|
|
||||||
@label.labelName
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@helper.html.dropdown("Assignee") {
|
|
||||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
|
|
||||||
@collaborators.map { collaborator =>
|
|
||||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="icon-white"></i>@avatar(collaborator, 20) @collaborator</a></li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@helper.html.dropdown("Milestone") {
|
|
||||||
<li><a href="javascript:void(0);" class="toggle-milestone" data-id=""><i class="icon-remove-circle"></i> Clear this milestone</a></li>
|
|
||||||
@milestones.map { milestone =>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0);" class="toggle-milestone" data-id="@milestone.milestoneId">
|
|
||||||
<i class="icon-white"></i> @milestone.title
|
|
||||||
<div class="small" style="padding-left: 20px;">
|
|
||||||
@milestone.dueDate.map { dueDate =>
|
|
||||||
@if(isPast(dueDate)){
|
|
||||||
<img src="@assets/common/images/alert_mono.png"/>Due in @date(dueDate)
|
|
||||||
} else {
|
|
||||||
<span class="muted">Due in @date(dueDate)</span>
|
|
||||||
}
|
|
||||||
}.getOrElse {
|
|
||||||
<span class="muted">No due date</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@issues.map { case (issue, labels, commentCount) =>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
@if(hasWritePermission){
|
|
||||||
<label class="checkbox" style="cursor: default;">
|
|
||||||
<input type="checkbox" value="@issue.issueId"/>
|
|
||||||
}
|
|
||||||
<a href="@url(repository)/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
|
||||||
@labels.map { label =>
|
|
||||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
|
||||||
}
|
|
||||||
<span class="pull-right muted">
|
|
||||||
@issue.assignedUserName.map { userName =>
|
|
||||||
<a href="@url(userName)">@avatar(userName, 20, tooltip = true)</a>
|
|
||||||
}
|
|
||||||
#@issue.issueId
|
|
||||||
</span>
|
|
||||||
<div class="small muted">
|
|
||||||
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a> @datetime(issue.registeredDate)
|
|
||||||
@if(commentCount > 0){
|
|
||||||
<i class="icon-comment"></i><a href="@url(repository)/issues/@issue.issueId" class="issue-comment-count">@commentCount @plural(commentCount, "comment")</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
<div class="pull-right">
|
|
||||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@if(hasWritePermission){
|
@if(hasWritePermission){
|
||||||
<form id="batcheditForm" method="POST">
|
<form id="batcheditForm" method="POST">
|
||||||
|
|||||||
176
src/main/twirl/issues/listparts.scala.html
Normal file
176
src/main/twirl/issues/listparts.scala.html
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
@(issues: List[(model.Issue, List[model.Label], Int)],
|
||||||
|
page: Int,
|
||||||
|
openCount: Int,
|
||||||
|
closedCount: Int,
|
||||||
|
condition: service.IssuesService.IssueSearchCondition,
|
||||||
|
collaborators: List[String] = Nil,
|
||||||
|
milestones: List[model.Milestone] = Nil,
|
||||||
|
labels: List[model.Label] = Nil,
|
||||||
|
repository: Option[service.RepositoryService.RepositoryInfo] = None,
|
||||||
|
hasWritePermission: Boolean = false)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
|
||||||
|
<div class="span9">
|
||||||
|
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
||||||
|
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL" id="clear-filter">
|
||||||
|
<i class="icon-remove-circle"></i> Clear milestone and label filters
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if(condition.repo.isDefined){
|
||||||
|
<a href="@condition.copy(repo = None).toURL" id="clear-filter">
|
||||||
|
<i class="icon-remove-circle"></i> Clear filter on @condition.repo
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<div class="pull-right">
|
||||||
|
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 7, condition.toURL)
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a class="btn@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
||||||
|
<a class="btn@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
||||||
|
Sort:
|
||||||
|
<strong>
|
||||||
|
@if(condition.sort == "created" && condition.direction == "desc"){ Newest }
|
||||||
|
@if(condition.sort == "created" && condition.direction == "asc" ){ Oldest }
|
||||||
|
@if(condition.sort == "comments" && condition.direction == "desc"){ Most commented }
|
||||||
|
@if(condition.sort == "comments" && condition.direction == "asc" ){ Least commented }
|
||||||
|
@if(condition.sort == "updated" && condition.direction == "desc"){ Recently updated }
|
||||||
|
@if(condition.sort == "updated" && condition.direction == "asc" ){ Least recently updated }
|
||||||
|
</strong>
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||||
|
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||||
|
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||||
|
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||||
|
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||||
|
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||||
|
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<table class="table table-bordered table-hover table-issues">
|
||||||
|
@if(issues.isEmpty){
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||||
|
No issues to show.
|
||||||
|
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
||||||
|
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL">Clear active filters.</a>
|
||||||
|
} else {
|
||||||
|
@if(repository.isDefined){
|
||||||
|
<a href="@url(repository.get)/issues/new">Create a new issue.</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
} else {
|
||||||
|
@if(hasWritePermission){
|
||||||
|
<tr>
|
||||||
|
<td style="background-color: #eee;">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-mini" id="state"><strong>@{if(condition.state == "open") "Close" else "Reopen"}</strong></button>
|
||||||
|
</div>
|
||||||
|
@helper.html.dropdown("Label") {
|
||||||
|
@labels.map { label =>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId">
|
||||||
|
<i class="icon-white"></i>
|
||||||
|
<span class="label" style="background-color: #@label.color;"> </span>
|
||||||
|
@label.labelName
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@helper.html.dropdown("Assignee") {
|
||||||
|
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
|
||||||
|
@collaborators.map { collaborator =>
|
||||||
|
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="icon-white"></i>@avatar(collaborator, 20) @collaborator</a></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@helper.html.dropdown("Milestone") {
|
||||||
|
<li><a href="javascript:void(0);" class="toggle-milestone" data-id=""><i class="icon-remove-circle"></i> Clear this milestone</a></li>
|
||||||
|
@milestones.map { milestone =>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:void(0);" class="toggle-milestone" data-id="@milestone.milestoneId">
|
||||||
|
<i class="icon-white"></i> @milestone.title
|
||||||
|
<div class="small" style="padding-left: 20px;">
|
||||||
|
@milestone.dueDate.map { dueDate =>
|
||||||
|
@if(isPast(dueDate)){
|
||||||
|
<img src="@assets/common/images/alert_mono.png"/>Due in @date(dueDate)
|
||||||
|
} else {
|
||||||
|
<span class="muted">Due in @date(dueDate)</span>
|
||||||
|
}
|
||||||
|
}.getOrElse {
|
||||||
|
<span class="muted">No due date</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@issues.map { case (issue, labels, commentCount) =>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
@if(hasWritePermission){
|
||||||
|
<label class="checkbox" style="cursor: default;">
|
||||||
|
<input type="checkbox" value="@issue.issueId"/>
|
||||||
|
}
|
||||||
|
@if(repository.isEmpty){
|
||||||
|
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a> ・
|
||||||
|
}
|
||||||
|
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||||
|
@labels.map { label =>
|
||||||
|
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||||
|
}
|
||||||
|
<span class="pull-right muted">
|
||||||
|
@issue.assignedUserName.map { userName =>
|
||||||
|
@avatar(userName, 20, tooltip = true)
|
||||||
|
}
|
||||||
|
#@issue.issueId
|
||||||
|
</span>
|
||||||
|
<div class="small muted">
|
||||||
|
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a> @datetime(issue.registeredDate)
|
||||||
|
@if(commentCount > 0){
|
||||||
|
<i class="icon-comment"></i><a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">@commentCount @plural(commentCount, "comment")</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
<div class="pull-right">
|
||||||
|
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<li><a href="javascript:void(0);" data-name="@groupName"><i class="icon-white"></i> <span>@avatar(groupName, 20) @groupName</span></a></li>
|
<li><a href="javascript:void(0);" data-name="@groupName"><i class="icon-white"></i> <span>@avatar(groupName, 20) @groupName</span></a></li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<input type="hidden" name="owner" id="owner" />
|
<input type="hidden" name="owner" id="owner" value="@loginAccount.get.userName"/>
|
||||||
</div>
|
</div>
|
||||||
/
|
/
|
||||||
<input type="text" name="name" id="name" />
|
<input type="text" name="name" id="name" />
|
||||||
|
|||||||
Reference in New Issue
Block a user